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:
| OS | Config 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:
| Pane | Purpose |
|---|---|
| Tunnels | Add, select, and delete tunnels |
| Settings | Edit max active relays and public metadata |
| Relays | Connect or disconnect relays for the selected tunnel |
Keyboard controls:
| Key | Action |
|---|---|
left / right | Switch panes |
up / down | Move within the active pane |
enter | Apply the active action |
delete | Delete the selected tunnel or disconnect the selected relay |
c | Connect the selected relay in the Relays pane |
d | Disconnect the selected relay in the Relays pane |
o | Open the selected public tunnel URL |
esc | Cancel input or return to the Tunnels pane |
ctrl+c | Exit 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:
| Field | Description |
|---|---|
id | Stable local tunnel ID used by the dashboard and control API |
name | Public lease name, used as the subdomain label |
target | Local TCP target, equivalent to portal expose <target> |
http_routes | Routed HTTP mappings; cannot be combined with target, serve, tcp, or udp |
serve | Static site directory or HTML file; relative to the config file’s directory |
relays | Explicit relay API URLs |
discovery | Include registry and relay discovery expansion |
max_active_relays | Maximum auto-selected relays kept connected; explicit relays are always included |
overlay | Prefer an IVNP overlay path when available; defaults to direct and retains direct fallback |
identity_path | Tunnel identity JSON path |
identity_json | In-memory identity JSON; takes precedence over identity_path without reading or writing that file |
udp, udp_addr | UDP transport settings |
tcp | Dedicated raw TCP port setting |
ban_mitm | Ban relays when the TLS self-probe detects termination; defaults to warning-only |
description, tags, owner, thumbnail, hide | Public relay metadata |
auth | Application login provider: siwe or credential |
auth_allowed_wallets | Optional allowed Ethereum wallet array; empty allows any valid wallet |
auth_identity_headers | Inject verified Portal identity headers into upstream requests |
x402_pay_to | Payment recipient for paid HTTP routes |
x402_testnet | Use Sui testnet when x402_network is omitted |
x402_network | Optional Sui or Casper CAIP-2 network |
x402_asset | wCSPR CEP-18 contract hash required by Casper |
x402_endpoints | Optional Sui RPC endpoints or Casper facilitator URL |
x402_facilitator_token | Casper facilitator token; falls back to CSPR_CLOUD_API_KEY |
http_routes[].amount | Optional human payment amount, such as 0.01, for one HTTP route prefix |
http_routes[].methods | Optional 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:
targetcannot be combined withhttp_routes.servecannot be combined withtarget,http_routes,tcp, orudp.http_routescannot be combined withtcporudp.- Application auth cannot be combined with
tcporudp; wallet allowlists require the SIWE provider and identity headers require application auth. http_routes[].amountrequiresx402_pay_to.http_routes[].methodsrequireshttp_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:
| Method | Path | Auth | Purpose |
|---|---|---|---|
GET | /agent/status | Bearer token or wallet session | Read agent and tunnel status |
POST | /agent/shutdown | Bearer token | Ask the agent to stop |
POST | /agent/tunnels | Bearer token | Add a tunnel |
PATCH | /agent/tunnels/{id} | Bearer token | Update metadata or max active relays |
DELETE | /agent/tunnels/{id} | Bearer token | Delete a tunnel |
POST | /agent/tunnels/{id}/relays | Bearer token | Connect a relay |
DELETE | /agent/tunnels/{id}/relays | Bearer token | Disconnect 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
- Configuration Reference: every agent config field
- Wallet and ENS: admin tokens, wallet auth, and ENS gasless behavior
- CLI Reference: command flags and examples