Overview

The Tala WTE HTTP API is the same one the web console uses; everything you can do in the browser, you can drive with a request. There is no separate or undocumented surface. It is built for a local, isolated training lab and is not meant to be exposed to the internet.

The API has three layers:

  • The PocketBase Collections API under /api/collections/... for stored data (networks, portals, captures, and so on). These are locked to superusers only.
  • The Tala WTE app API under /api/wte/... for actions (start a network, run a capture, provision LDAP, drive a pack member, update the box). Most of these require a superuser; a handful accept a pack leader's agent key, and a few are public by design.
  • The first-boot setup endpoints, which exist only until the admin account is created.

For what the routes do in context, see Networks, Captive Portals, Packet Captures, RADIUS 802.1X, Client Mode, The Pack, and Settings. To get a box to the point where the API is reachable, see Installation and System Requirements.

Base URL and TLS

The API is served over HTTPS on port 8443:

https://<host>:8443

The certificate is self-signed (a built-in CA issues it on first boot), so clients must skip CA verification or trust the box's CA. Port 80 exists only to redirect to 8443. PocketBase itself binds to 127.0.0.1:8090 behind the TLS proxy and is not meant to be reached directly.

This is a lab tool. Do not put it on the public internet.

Authentication model

There are three ways a request is authorized, and each endpoint uses exactly one.

Superuser token (most endpoints). Authenticate against the PocketBase superusers collection and send the returned token as the Authorization header.

POST /api/collections/_superusers/auth-with-password
Content-Type: application/json

{ "identity": "admin@example.com", "password": "..." }

The response carries a token field. Send it on later requests:

Authorization: <token>

Endpoints marked superuser below reject anything that is not an authenticated superuser with HTTP 403 ({"error":"superuser authentication required"}). Note that being any authenticated record is not enough; the wrapper checks for superuser specifically.

Agent key (pack control endpoints). A pack member exposes a long random agent key. A pack leader presents it in the X-Agent-Key header to drive that member remotely over its self-signed HTTPS. Endpoints marked agent-key accept either a local superuser (the member's own console) or a matching agent key; anything else gets HTTP 403 ({"error":"agent key or superuser auth required"}). The member's own console retrieves and rotates this key through the client endpoints below.

Public (no auth). A few endpoints serve content that must be reachable before an admin exists or that an iframe cannot send headers for. They are marked public below and serve only non-sensitive content.

The in-browser terminal is a special case: WebSockets cannot send the Authorization header, so it takes the superuser token as the ?token= query parameter instead.

Responses and errors

App endpoints (/api/wte/...) return indented JSON. Errors use the shape {"error":"message"} with an appropriate HTTP status. Successful action endpoints typically return a small status object such as {"status":"started","id":"..."}. The Collections API uses PocketBase's own response and error formats.

Authentication & Setup

First-boot account setup runs entirely in the browser wizard. No superuser is auto-created and no credentials are printed. The wizard endpoints are unauthenticated because no admin exists yet, and /complete hard-rejects once a real admin is present.

Method Path Auth Purpose
GET /api/wte/setup/status public Report whether first-boot setup is still needed.
POST /api/wte/setup/complete public Create the first superuser from the wizard.

GET /api/wte/setup/status returns {"needs_setup": <bool>}.

POST /api/wte/setup/complete body:

{
  "email": "admin@example.com",
  "password": "at-least-10-chars",
  "license_ack": true
}

The password must be at least 10 characters, the email must contain @, and license_ack must be true (the license gate is enforced server-side). On success it returns an auth token shaped like a normal PocketBase login so the browser can persist it:

{
  "token": "<auth token>",
  "record": { "id": "...", "email": "...", "collectionName": "_superusers" }
}

Once a real admin exists this endpoint returns HTTP 409 and you log in through the normal auth-with-password flow instead.

Networks

Networks are AP definitions. The records live in the networks collection (see the Collections API); these endpoints act on a saved network by its record id. See Networks.

Method Path Auth Purpose
POST /api/wte/networks/{id}/start superuser Bring a network online (hostapd + DHCP + optional portal).
POST /api/wte/networks/{id}/stop superuser Stop a running network and tear down its resources.
GET /api/wte/networks/{id}/status superuser Report whether the network is running, stopped, or errored.
GET /api/wte/networks/{id}/clients superuser List clients currently associated to the network.
GET /api/wte/networks/{id}/logs superuser Return the per-network activity log lines.
GET /api/wte/networks/{id}/client-config superuser Download a client connection profile (.json) for this network.

start accepts an optional JSON body; all fields are optional:

{ "auto_provision": false, "interface": "wlan1", "band": "5" }

interface and band override the saved adapter and band (used when the radio-management prompt confirms a substitute adapter). auto_provision applies only to enterprise SSIDs: if the enterprise preflight fails and this is true, the box runs the enterprise auto-provision before starting. Responses include {"status":"started","id":...} on success, {"status":"already_running"} if it was already up, HTTP 412 with a preflight report when an enterprise SSID is not ready and auto_provision was not set, and HTTP 500 with provision and preflight reports if auto-provision fails.

stop and status return {"status":...,"id":...}. clients returns {"clients":[...]}. logs returns {"lines":[...]}. client-config streams a downloadable JSON profile (the same shape a Tala WTE client imports) as a file attachment.

Captive Portals & Credentials

Portal templates and generated credential sets live in the portals and portal_credentials collections; these endpoints manage the catalog and the credential generator. See Captive Portals.

Method Path Auth Purpose
GET /api/wte/portals/auth-types superuser List captive-portal auth types and the fields each collects.
POST /api/wte/portals/credentials/generate superuser Generate a validatable credential set for an auth type.
GET /api/wte/portals/templates superuser List the built-in template gallery.
POST /api/wte/portals/restore superuser Re-seed built-in templates (recreate deleted, reset changed).
POST /api/wte/portals/upload superuser Upload a portal as a single .html file or a .zip bundle.
POST /api/wte/portals/scrape superuser Clone a live page by URL into a custom portal (SSRF-guarded).
GET /api/wte/portals/{id}/preview public Render a portal for in-UI iframe preview (same-origin framed).
GET /api/wte/portals/{id}/preview/{path...} public Serve an asset inside a multi-file portal bundle for preview.

auth-types returns {"auth_types":[...]}. templates returns {"templates":[...]}.

credentials/generate body:

{ "name": "Hotel set", "auth_type": "hotel", "count": 25 }

The auth type must be one that uses credentials. count defaults to 25 and is capped at 1000. It saves a portal_credentials record and returns {"id":...,"name":...,"count":...}.

upload is a multipart form with a file field (a .html/.htm document, or a .zip bundle that contains an index.html at its root) and an optional name field; max 25 MB. It returns {"id":...,"name":...}.

scrape body:

{ "url": "https://example.com/login", "name": "Example portal" }

url is required; name defaults to the page host. It returns {"id":...,"name":...}.

restore returns {"restored":<n>,"reset":<n>}. The preview endpoints are public because an iframe cannot attach the auth header; they serve only same-origin-framed, non-sensitive portal content.

Captures

Packet captures run against a network and are recorded in the captures collection; the record id doubles as the capture session id. See Packet Captures.

Method Path Auth Purpose
POST /api/wte/captures/start superuser Start a packet capture on an interface for a network.
POST /api/wte/captures/{id}/stop superuser Stop a capture and finalize the pcap and packet count.
GET /api/wte/captures/{id}/download superuser Download the finished pcapng file.
GET /api/wte/captures/{id}/analyze superuser Return a structured analysis of the capture.
GET /api/wte/captures/{id}/packets superuser Return the packet list, optionally filtered.
GET /api/wte/captures/{id}/packet/{n} superuser Return the full dissection of one frame.

start body:

{ "network_id": "...", "interface": "wlan1", "layer": "network", "filter": "" }

network_id and interface are required. layer is network (default) or wireless. filter is an optional BPF capture filter (validated). It returns {"status":"started","id":...,"file":...}.

stop returns {"status":"stopped","id":...,"packet_count":<n>}. download streams the .pcapng (content type application/vnd.tcpdump.pcap) as an attachment. analyze returns the analysis object (protocol mix, top talkers, DNS, HTTP, cleartext credentials). packets accepts ?filter=<wireshark display filter> and ?limit=<n> and returns {"packets":[...],"truncated":<bool>,"count":<n>}. packet/{n} returns {"detail":"..."} for frame number n.

LDAP

The embedded OpenLDAP directory backs WPA-Enterprise auth. These endpoints manage users, groups, and the directory as a whole. See RADIUS 802.1X.

Method Path Auth Purpose
GET /api/wte/ldap/users superuser List directory users.
POST /api/wte/ldap/users superuser Create a user.
PUT /api/wte/ldap/users/{uid} superuser Update a user's attributes.
DELETE /api/wte/ldap/users/{uid} superuser Delete a user.
POST /api/wte/ldap/users/{uid}/password superuser Set a user's password.
GET /api/wte/ldap/groups superuser List groups.
POST /api/wte/ldap/groups superuser Create a group.
DELETE /api/wte/ldap/groups/{cn} superuser Delete a group.
POST /api/wte/ldap/groups/{cn}/members superuser Add a user to a group.
DELETE /api/wte/ldap/groups/{cn}/members/{uid} superuser Remove a user from a group.
POST /api/wte/ldap/test-auth superuser Test an LDAP bind for a user.
GET /api/wte/ldap/status superuser Report slapd status and base DN.
POST /api/wte/ldap/provision superuser Wipe and reprovision the directory from a company template.
POST /api/wte/ldap/provision/random superuser Wipe and reprovision with a random company and user count.

GET /users returns {"users":[...]}, where each user carries uid, cn, sn, given_name, mail, optional title and department, groups, and dn. POST /users body: uid, cn, sn, given_name, mail, password; returns {"status":"created","dn":...}. PUT /users/{uid} body is a map of attribute updates (limited to cn, sn, givenName, mail); returns {"status":"updated","uid":...}. DELETE /users/{uid} returns {"status":"deleted","uid":...}. POST /users/{uid}/password body {"password":"..."}; returns {"status":"password_set","uid":...}.

GET /groups returns {"groups":[...]} with cn, members, dn per group. POST /groups body {"cn":"...","members":[...]} (members optional); returns {"status":"created","cn":...}. DELETE /groups/{cn} returns {"status":"deleted","cn":...}. POST /groups/{cn}/members body {"uid":"..."}; returns {"status":"member_added","cn":...,"uid":...}. DELETE /groups/{cn}/members/{uid} returns {"status":"member_removed","cn":...,"uid":...}.

test-auth body {"uid":"...","password":"..."}; returns {"success":true,"dn":...} on a successful bind or {"success":false,"message":...} otherwise. status returns {"running":<bool>,"base_dn":"..."}.

provision body:

{ "company_name": "ACME Corp", "domain": "acme.example", "user_count": 25, "random_passwords": true }

Both provision endpoints wipe the directory and reprovision it, returning {"status":"provisioned","company_name":...,"domain":...,"users":[...]} where each user carries uid, cn, mail, password, department, and title. provision/random takes no body and picks a random company and user count.

RADIUS

FreeRADIUS configuration for enterprise SSIDs. The per-network RADIUS settings live in the radius_config collection; this endpoint applies the global server settings. See RADIUS 802.1X.

Method Path Auth Purpose
POST /api/wte/radius/config superuser Save and apply FreeRADIUS settings, then restart the service.

Body:

{ "eap_type": "peap", "inner_auth": "mschapv2", "shared_secret": "..." }

All fields are optional (only the supplied ones are applied). The shared secret is character-validated, written to clients.conf, and the EAP method is applied to the running FreeRADIUS before it is restarted. Returns {"status":"saved","eap_type":...,"inner_auth":...}.

Certificates

The internal PKI issues the CA and the certificates enterprise networks use. The on-disk PKI is mirrored into the certificates collection for the UI. See RADIUS 802.1X.

Method Path Auth Purpose
POST /api/wte/certs/ca superuser Initialize a new internal CA.
POST /api/wte/certs/server superuser Issue a CA-signed server certificate.
POST /api/wte/certs/client superuser Issue a CA-signed client certificate (EAP-TLS).

These read parameters from the query string, not a body. ca returns {"status":"ca_created","pki_dir":...}. server accepts ?name= (defaults to radius-server) and returns {"status":"server_cert_created","name":...}. client requires ?uid= and returns {"status":"client_cert_created","uid":...}. Names and uids must be alphanumeric plus dash and underscore. After any of these, the certificates collection is reconciled so the new material shows up immediately.

Enterprise

Readiness checks and one-shot provisioning that bring every WPA-Enterprise dependency (LDAP, CA, server cert, FreeRADIUS modules and config) to a known-good state. See RADIUS 802.1X.

Method Path Auth Purpose
GET /api/wte/enterprise/preflight superuser Return the enterprise readiness checklist (no mutations).
POST /api/wte/enterprise/provision superuser Run the auto-provision and return the per-step report.

preflight returns {"ok":<bool>,"checks":[{"id","label","ok","detail","auto_fixable"}, ...]}. provision takes no body and returns {"ok":<bool>,"steps":[{"id","label","status","detail"}, ...]} and, when LDAP was provisioned, a users array.

Client mode

When a box is switched to client mode it joins a target AP and generates traffic. These endpoints connect, drive traffic, and report status. They are agent-key endpoints: the member's own superuser console or a pack leader's agent key may call them, so a leader can drive a member remotely. The agent-key retrieval and rotation endpoints are superuser-only (the member sets up its own key). See Client Mode.

Method Path Auth Purpose
POST /api/wte/client/connect agent-key Connect to an imported network profile.
POST /api/wte/client/start agent-key Start the selected traffic generators.
POST /api/wte/client/stop agent-key Stop traffic but keep the connection up.
POST /api/wte/client/disconnect agent-key Stop traffic and tear down the Wi-Fi connection.
GET /api/wte/client/status agent-key Return live client status.
POST /api/wte/client/reconnect agent-key Toggle reconnect cycling (handshake capture).
GET /api/wte/client/logs agent-key Return the client's activity log.
GET /api/wte/client/agent-key superuser Return this member's agent key (creating one if needed).
POST /api/wte/client/agent-key/regenerate superuser Rotate the agent key.

connect takes a client config profile (the JSON exported from a network's client-config endpoint, with at least an ssid); the join runs in the background and the response is {"status":"connecting","ssid":...}. start takes traffic options (the generator toggles) and returns the live status; stop, disconnect, and status return the live status object. reconnect body {"enabled":<bool>,"frequency_seconds":<n>,"jitter_seconds":<n>} controls periodic deauth/reassociate for WPA handshake capture. logs returns {"lines":[...]}. agent-key and agent-key/regenerate return {"key":"..."}.

Pack

A pack leader (an AP box) drives registered client members. Members are stored in the pack_members collection; the leader reaches each over its pinned, self-signed channel using the member's agent key. See The Pack.

Method Path Auth Purpose
POST /api/wte/pack/{id}/deploy superuser Assign a member to a network and bring it online.
POST /api/wte/pack/{id}/stop superuser Stop traffic, disconnect the member, clear its assignment.
GET /api/wte/pack/{id}/status superuser Proxy the member's live status through the leader.
POST /api/wte/pack/update superuser Update every pack member from the leader.
GET /api/wte/pack/discovered superuser Browse the LAN over mDNS for other Tala WTE instances.

{id} is the pack_members record id. deploy body:

{ "network_id": "...", "traffic": { ... }, "reconnect": { "enabled": true, "frequency_seconds": 60, "jitter_seconds": 10 } }

network_id is required; traffic is the generator mix (defaults to a standard web/DNS/ping/local/internet mix) and reconnect is the optional handshake-cycling schedule. The leader pushes the network's profile to the member, then starts traffic in the background once it associates; the response is {"status":"deploying"}. stop returns {"status":"stopped"}. status returns {"reachable":<bool>,"status":{...}} (or {"reachable":false,"error":...} if the member cannot be reached). update downloads each needed CPU architecture once and pushes the matching, checksum-verified binary to each member (falling back to a member pulling from GitHub), returning {"results":[{"name","ok","detail"}, ...]}. discovered returns {"peers":[...]}, filtering out this instance.

System & Settings

Host status, interfaces, role swap, settings, the in-browser terminal, and the self-update. See Settings.

Method Path Auth Purpose
GET /api/wte/system/status public Aggregate system status (used by the login/setup screen).
GET /api/wte/system/interfaces superuser List wireless adapters (free, in-use, unsupported).
POST /api/wte/system/interfaces/heal superuser Run a USB-reset recovery on a wedged adapter.
POST /api/wte/system/mode superuser Swap the instance between AP and client roles.
GET /api/wte/system/version superuser Report running version and whether an update exists.
POST /api/wte/system/update agent-key Pull and apply the latest release, then restart.
POST /api/wte/system/apply agent-key Apply a binary pushed by a pack leader, then restart.
GET /api/wte/system/settings superuser Read uplink interface, regulatory domain, AP subnet.
POST /api/wte/system/settings superuser Save those settings.
GET /api/wte/terminal/ws superuser PTY shell over a WebSocket (token via ?token=).

system/status is public so the login and setup screens can render before an admin exists; it returns {"status":"ok","mode":...,"needs_setup":<bool>,"radius_running":<bool>,"ldap_running":<bool>,"interface_count":<n>,"real_adapter_count":<n>}.

system/interfaces returns {"interfaces":[...],"in_use":{iface:ssid},"in_use_adapters":[...],"unsupported":[...]}. interfaces/heal body {"interface":"wlan1"} or {"usb_path":"..."} (one is required); returns {"ok":true,"message":...}.

system/mode body {"mode":"ap"} or {"mode":"client"}; it persists the role and restarts the service in the background, returning {"status":"switching","mode":...} first. system/version returns the version status object: current, latest, update_available, notes, release_url, is_dev, and an optional error.

system/update and system/apply are agent-key so a pack leader can update a member with its agent key. update pulls the latest release from GitHub, verifies the checksum, swaps the binary, and schedules a restart, returning {"status":"updating","version":...,"restarting":true,"message":...}. apply receives a binary streamed by a leader (with X-Update-SHA256, X-Update-Version, and X-Update-Arch headers; the body limit on this route is raised to 256 MB), verifies it, and restarts.

GET /system/settings returns {"uplink_iface":...,"country_code":...,"ap_subnet":...}. POST /system/settings body:

{ "uplink_iface": "enp0s5", "country_code": "US", "ap_subnet": "10.0.0.0/24" }

All fields are optional. The country code must be two letters and is applied to the live radio's regulatory domain; the subnet must be CIDR. Returns {"status":"saved", ...} echoing the saved values.

terminal/ws upgrades to a WebSocket carrying a full PTY login shell. Because WebSockets cannot send the Authorization header, the superuser token is passed as ?token=; origin is restricted to same-origin. Binary frames are shell output; a JSON text frame {"type":"resize","cols":...,"rows":...} resizes the PTY.

License

Method Path Auth Purpose
GET /api/wte/license public Serve the Tala WTE license as plain text.

This is public because the license must be readable before an admin account is created (the setup wizard shows it). The related legal pages /legal/terms, /legal/aup, and /legal/privacy are also served publicly so portal-preview links resolve.

Collections API

Stored data is reached through PocketBase's standard records API. Each managed collection supports the usual operations:

GET    /api/collections/<name>/records          list + filter + sort + paginate
GET    /api/collections/<name>/records/<id>     view one
POST   /api/collections/<name>/records          create
PATCH  /api/collections/<name>/records/<id>     update
DELETE /api/collections/<name>/records/<id>     delete

Every managed collection has its API rules locked, so only an authenticated superuser can read or write them; send the superuser token in the Authorization header. The managed collections are:

Collection Holds
networks AP/SSID definitions (protocol, band, channel, portal binding, status).
portals Captive-portal templates (built-in and custom). Built-ins are read-only; an edit request is rejected, so clone one to customize it.
portal_credentials Generated credential sets a portal validates submissions against.
portal_submissions Credentials/data captured from clients at a portal.
captures Packet-capture sessions (interface, filter, status, packet count).
certificates Mirror of the on-disk PKI (CA, server, client) for the UI.
clients Clients seen associated to networks.
radius_config Per-network RADIUS settings (EAP type, inner auth, cert relations).
traffic_datasets Named URL/domain/IP target sets for traffic generation.
pack_members Registered client members a leader drives (address, agent key, pinned fingerprint).
client_configs Saved client connection profiles (used in client mode).
settings Internal key/value store (agent key, persisted settings).

For login itself, the superusers collection uses the auth endpoint shown earlier: POST /api/collections/_superusers/auth-with-password. The default public users collection that PocketBase ships with is removed (or locked) at boot, so there is no public self-registration.

(c) 2026 VTEM Labs, Inc. All rights reserved. | vtemlabs.com