API Reference
All /api/vpn/* endpoints share these conventions:
- Auth: Bearer token (
Authorization: Bearer zsm_...) or signed session cookie. Write endpoints require ownership of the machine they act on. - Rate limit:
60/minon handshake/terminate,240/minon reads. - Time: Every response includes
zeqond(integer) andphase(0..1, 4 decimals). Bit-exact againstlib/zeq-kernel-constants.ts. - Errors:
{ error, code }. HTTP status reflects failure class (400 invalid, 401 unauthorized, 403 not-party, 404 not-found, 409 invalid-state, 402 insufficient-balance).
POST /api/vpn/tunnel/handshake-request
Open a pending tunnel row. Requires the requester to own requester_slug
and to hold enough ZEQ to cover 100 Zeqonds at the chosen rate.
// Request
{
"requester_slug": "ZEQ07000000001",
"target_slug": "ZEQ07000000007",
"requested_lifespan_zeqonds": 1000, // [10, 86_400]
"cover_traffic": false,
"multi_hop": false,
"pricing_unit": "gb" // "gb" | "zeqond"
}
// Response — 200 OK
{
"ok": true,
"tunnel_id": "8f7e6d5c-…",
"state": "pending",
"key_purpose_id": "vpn.tunnel.8f7e6d5c-….key",
"rate_zeq_per_unit": 50,
"min_required_zeq": 5000,
"balance_zeq": 12345,
"subscribers_notified": 3,
"zeqond": 2293617891,
"phase": 0.4117,
"pulse_hz": 1.2870016419610952
}
Error codes: INVALID_TARGET, INVALID_PRICING_UNIT, INVALID_LIFESPAN,
TARGET_NOT_FOUND, TARGET_NOT_RELAY, TARGET_FULL, INSUFFICIENT_BALANCE,
UNAUTHORIZED, NOT_OWNER.
POST /api/vpn/tunnel/handshake-accept
// Request — auth must own `accepter_slug`
{ "tunnel_id": "8f7e…", "accepter_slug": "ZEQ07000000007" }
// Response — 200 OK
{
"ok": true,
"tunnel_id": "8f7e…",
"state": "active",
"key_purpose_id": "vpn.tunnel.8f7e….key",
"started_at_zeqond": 2293617900,
"encrypted_key_fetch_url": "/api/zsc/get/vpn.tunnel.8f7e%E2%80%A6.key"
}
The shared key is derived as
HMAC_SHA256( ZEQ_MESH_SECRET, requesterMachineId | accepterMachineId | nonce | zeqond )
and parked in zsc_secrets under key_purpose_id. Both peers fetch it with
their existing ZSC-permission gate (the requester's ZID is added to the
secret's permissions array at write time).
POST /api/vpn/tunnel/:id/terminate
Either party may terminate. Settlement math:
per-GB: ceil(cumulative_bytes / 1e9) × price_per_gb_zeq
per-Zeqond: cumulative_zeqonds × price_per_zeqond_zeq
// Request — auth must own one of (requester_slug, accepter_slug)
{
"terminator_slug": "ZEQ07000000001",
"cumulative_bytes": 734003200,
"cumulative_zeqonds": 472
}
// Response
{
"ok": true,
"tunnel_id": "8f7e…",
"state": "terminated",
"cumulative_bytes": 734003200,
"cumulative_zeqonds": 472,
"total_zeq_charged": 50, // ceil(0.734 GB) × 50 ZEQ/GB
"ended_at_zeqond": 2293618372
}
GET /api/vpn/tunnels?slug=<X>
Owner's last 100 tunnels (active + recent), most recent first. Auth required;
caller must own slug.
GET /api/vpn/marketplace
Anonymous-readable. Filterable by:
country(e.g.EU,US) — matchesaccept_country_classesorANY.max_zeq_per_gb— price ceiling.
Returns only peers with an active=true row in state_machine_relay_offers
and whose underlying state machine is is_public=true + status=active.
Private machines are invisible.
POST /api/vpn/marketplace/list
Owner opts a machine into the public marketplace. Idempotent
(onConflictDoUpdate).
{
"slug": "ZEQ07000000007",
"price_per_gb_zeq": 50,
"price_per_zeqond_zeq": 1,
"max_concurrent_tunnels": 8,
"accept_country_classes": ["EU", "ANY"],
"active": true // false = unlist
}