PORTAL v2

Portal Agent

portal agent is the long-lived version of portal expose. It runs one local agent process, reads a TOML config file, and keeps every declared tunnel registered with the selected relays.

Use the agent when tunnels should survive terminal closes, login sessions, or manual restarts. Use portal expose for one-off development sessions.

What The Agent Owns

The agent owns:

  • one config.toml
  • one local loopback control API
  • one OS service when run in managed mode
  • one or more tunnel runtimes declared under [[tunnels]]
  • tunnel identities stored under the agent state directory unless overridden

Each tunnel still uses the normal Portal SDK path internally: it registers a lease, opens reverse sessions, renews the lease, and proxies traffic to the configured local target.

Create A Config

portal agent run requires an existing config file. The installer does not create one.

Default config paths:

OSConfig path
Linux user$XDG_CONFIG_HOME/portal-tunnel/agent/config.toml or ~/.config/portal-tunnel/agent/config.toml
Linux root/etc/portal-tunnel/agent/config.toml
macOS user~/Library/Application Support/Portal Tunnel/Agent/config.toml
macOS root/Library/Application Support/Portal Tunnel/Agent/config.toml
Windows%ProgramData%\Portal Tunnel\Agent\config.toml

Minimal config:

[agent]
control_addr = "127.0.0.1:4018"
service_name = "portal-agent"

[[tunnels]]
id = "web"
name = "myapp"
target = "127.0.0.1:3000"
relays = ["https://portal.example.com"]
discovery = false
description = "Managed web tunnel"
tags = ["web"]
auth = "siwe"
auth_allowed_wallets = ["0x1234..."]
auth_identity_headers = true

Static site config:

[[tunnels]]
id = "site"
name = "my-site"
serve = "./dist"

serve accepts a directory containing index.html or an HTML file such as ./dist/main.html. Relative paths resolve from the directory containing config.toml. The file’s parent directory is served when a file is selected; unknown request paths fall back to the entry file, as with portal expose --serve. The entry file must exist when the tunnel starts. Edit serve in TOML and restart the tunnel or agent to change the site path.

Set auth = "credential" on a target, routed HTTP, or static tunnel for Portal-native credentials, or use auth = "siwe". With SIWE, auth_allowed_wallets optionally restricts login to listed Ethereum addresses. auth_identity_headers = true injects the verified wallet address and siwe for SIWE, or the credential subject and credential for credential auth, as X-Portal-User and X-Portal-Auth; Portal always strips client-supplied copies first.

Routed HTTP config:

[agent]
control_addr = "127.0.0.1:4018"
service_name = "portal-agent"

[[tunnels]]
id = "frontend"
name = "myapp"
relays = ["https://portal.example.com"]
discovery = false
x402_pay_to = "0x..."
x402_testnet = true

[[tunnels.http_routes]]
prefix = "/api"
upstream = "http://127.0.0.1:3001"
methods = ["GET"]
amount = "0.01"

[[tunnels.http_routes]]
prefix = "/"
upstream = "http://127.0.0.1:5173"

For Casper, replace the Sui payment fields with:

x402_network = "casper:casper-test"
x402_asset = "hash-..."
x402_pay_to = "account-hash-..."
x402_endpoints = ["https://x402-facilitator.cspr.cloud"]

Set CSPR_CLOUD_API_KEY in the agent service environment for the hosted CSPR.cloud facilitator. If the service cannot receive that environment variable, set x402_facilitator_token in this tunnel’s TOML instead. Custom facilitators that do not require authentication can omit both.

If a route has amount, the tunnel serves /x402/client.js and /x402/prepare on the public tunnel origin for the Sui wallet flow. Casper clients consume the protected route’s 402 requirements, sign with an external Casper x402 SDK, and retry with PAYMENT-SIGNATURE or X-PAYMENT. The tunnel still verifies and settles payment before proxying the paid route.

Relative paths in the config are resolved from the config file directory.

Run The Agent

Run as a managed OS service:

portal agent run

Run in the current terminal:

portal agent run --config config.toml --foreground

Open the local dashboard:

portal agent dashboard

Restart or stop:

portal agent restart
portal agent stop

portal agent run, stop, and restart load the config so they can find the state directory and service name. portal agent dashboard can attach with only the default state directory or an explicit --state-dir.

portal agent run --service is the internal service entrypoint installed by portal agent run. Operators normally do not run it directly.

Docker Compose TUI

See the Portal CLI Docker Compose instructions for the single maintained setup, including persistent agent state and host directory permissions.

Dashboard

The dashboard is a local terminal UI. It polls agent status every two seconds and edits the same TOML config file that the service uses.

Dashboard panes:

PanePurpose
TunnelsAdd, select, and delete tunnels
SettingsEdit max active relays and public metadata
RelaysConnect or disconnect relays for the selected tunnel

Keyboard controls:

KeyAction
left / rightSwitch panes
up / downMove within the active pane
enterApply the active action
deleteDelete the selected tunnel or disconnect the selected relay
cConnect the selected relay in the Relays pane
dDisconnect the selected relay in the Relays pane
oOpen the selected public tunnel URL
escCancel input or return to the Tunnels pane
ctrl+cExit the dashboard

The Add Tunnel action opens a form. Fill either Target for a simple loopback tunnel or Routes for routed HTTP. Routes use this syntax:

/paid=3001 GET:0.01; /=5173

Each entry is PATH=UPSTREAM [METHOD[,METHOD...]:PAYMENT_AMOUNT]. Fill X402 Pay To when any route has an amount. Sui uses X402 Testnet; Casper additionally uses X402 Network, X402 Asset, and optionally its facilitator in X402 Endpoints. The form also accepts explicit Relays, Discovery, and Max Relays; max relays caps auto-selected discovery relays while explicit relays are still included.

After creation, routed HTTP paths, x402 payment amounts, payment network, and discovery mode are read-only in the Settings pane. To change routes, payment amounts, payment network, or discovery mode, edit http_routes, x402_pay_to, x402_testnet, x402_network, x402_asset, x402_endpoints, and discovery in config.toml, then restart the agent or tunnel. Other advanced options such as UDP, TCP, or custom identity JSON are also configured in config.toml.

Tunnel Config Fields

Common fields:

FieldDescription
idStable local tunnel ID used by the dashboard and control API
namePublic lease name, used as the subdomain label
targetLocal TCP target, equivalent to portal expose <target>
http_routesRouted HTTP mappings; cannot be combined with target, serve, tcp, or udp
serveStatic site directory or HTML file; relative to the config file’s directory
relaysExplicit relay API URLs
discoveryInclude registry and relay discovery expansion
max_active_relaysMaximum auto-selected relays kept connected; explicit relays are always included
overlayPrefer an IVNP overlay path when available; defaults to direct and retains direct fallback
identity_pathTunnel identity JSON path
identity_jsonIn-memory identity JSON; takes precedence over identity_path without reading or writing that file
udp, udp_addrUDP transport settings
tcpDedicated raw TCP port setting
ban_mitmBan relays when the TLS self-probe detects termination; defaults to warning-only
description, tags, owner, thumbnail, hidePublic relay metadata
authApplication login provider: siwe or credential
auth_allowed_walletsOptional allowed Ethereum wallet array; empty allows any valid wallet
auth_identity_headersInject verified Portal identity headers into upstream requests
x402_pay_toPayment recipient for paid HTTP routes
x402_testnetUse Sui testnet when x402_network is omitted
x402_networkOptional Sui or Casper CAIP-2 network
x402_assetwCSPR CEP-18 contract hash required by Casper
x402_endpointsOptional Sui RPC endpoints or Casper facilitator URL
x402_facilitator_tokenCasper facilitator token; falls back to CSPR_CLOUD_API_KEY
http_routes[].amountOptional human payment amount, such as 0.01, for one HTTP route prefix
http_routes[].methodsOptional HTTP methods that require payment on that route; empty means every method

The agent supports target, routed HTTP, and static site modes. The CLI relay cache options cache and cache_ttl are not supported in agent TOML.

Constraints:

  • target cannot be combined with http_routes.
  • serve cannot be combined with target, http_routes, tcp, or udp.
  • http_routes cannot be combined with tcp or udp.
  • Application auth cannot be combined with tcp or udp; wallet allowlists require the SIWE provider and identity headers require application auth.
  • http_routes[].amount requires x402_pay_to.
  • http_routes[].methods requires http_routes[].amount.

Identity Layout

If identity_path is omitted:

  • a single tunnel uses <state_dir>/identity.json
  • multiple tunnels use <state_dir>/<tunnel-id>/identity.json

An existing identity file or identity_json supplies its saved name and key; tunnels.name is used only when creating a new identity.

Reusing an identity keeps the same tunnel address and lease identity across restarts. Use separate identity paths when two tunnels should have separate lease identities.

Local Control API

The agent writes this file while running:

<state_dir>/agent-endpoint.json

It contains the loopback control address and a random bearer token. CLI commands read this file and send Authorization: Bearer <token> to the local control API.

The agent refuses non-loopback control_addr values. Use 127.0.0.1, localhost, or another loopback address.

Control endpoints:

MethodPathAuthPurpose
GET/agent/statusBearer token or wallet sessionRead agent and tunnel status
POST/agent/shutdownBearer tokenAsk the agent to stop
POST/agent/tunnelsBearer tokenAdd a tunnel
PATCH/agent/tunnels/{id}Bearer tokenUpdate metadata or max active relays
DELETE/agent/tunnels/{id}Bearer tokenDelete a tunnel
POST/agent/tunnels/{id}/relaysBearer tokenConnect a relay
DELETE/agent/tunnels/{id}/relaysBearer tokenDisconnect a relay

Wallet auth endpoints also exist under /agent/auth/*. Wallet-authenticated requests are read-only and can only call /agent/status; mutating operations use the local bearer token from the state directory.

Agent Wallet Access

Set agent.allowed_wallets to restrict wallet-authenticated status access:

[agent]
allowed_wallets = ["0x1234567890abcdef1234567890abcdef12345678"]

When allowed_wallets is empty, any wallet can sign in to the loopback agent auth endpoint. This does not grant mutation rights; the bearer token still owns config and tunnel changes.

Troubleshooting

If the dashboard says the agent is unavailable, start it explicitly:

portal agent run --config config.toml

If the OS service manager is unavailable:

portal agent run --config config.toml --foreground

If a tunnel is stuck in error, check the selected tunnel row in the dashboard. Common causes are an invalid local target, a relay URL that cannot be reached, or a transport disabled on the relay.

Next Steps