Skip to content

Overview & Auth

Ghost exposes approximately 168 REST endpoints under /api/v1/ plus WebSocket connections for real-time events. The API is built on chi/v5, a lightweight Go HTTP router. All endpoints return JSON responses and follow consistent conventions for authentication, error handling, pagination, and body size limits.

The API is designed for local use — it listens on localhost only and requires a bearer token for authentication. The frontend communicates with this API through standard HTTP requests and a persistent WebSocket connection for real-time traffic updates.

http://localhost:{api_port}/api/v1

The API port defaults to 5565 (DefaultAPIPort in config). In production (Tauri sidecar mode), the port is dynamically assigned by the OS and reported to the Tauri shell via a JSON line on stdout. During frontend development, a fixed port of 9090 is used so the Vite dev server can proxy /api requests to a known address.

All endpoints under /api/v1/* require authentication. Two methods are supported:

Bearer token header (preferred):

Authorization: Bearer <token>

Query parameter fallback (for WebSocket connections):

GET /ws?token=<token>

The token is a 64-character hex string generated from 32 bytes of crypto/rand randomness:

b := make([]byte, 32) // 32 bytes of cryptographic randomness
rand.Read(b) // from crypto/rand
hex.EncodeToString(b) // → 64-character hex string

The token is auto-generated on first run and stored in ~/.ghost/config.toml under [api] as bearer_token. In memory, it’s held in an atomic.Value for lock-free concurrent reads across goroutines.

Token comparison uses crypto/subtle.ConstantTimeCompare — a constant-time comparison function that takes the same amount of CPU time regardless of how many characters match. This prevents timing attacks, where an attacker could figure out the correct token one character at a time by measuring how long each comparison takes (a regular == comparison returns faster when the first character doesn’t match than when only the last character differs).

POST /api/v1/settings/rotate-token

Generates a new token, updates the in-memory atomic.Value first, then persists to the config file. If persistence fails, the in-memory token is reverted to the old value (preventing a split where the running server expects one token but the config file has another).

Returns:

{
"token": "<new_64_char_hex_token>",
"message": "Token rotated successfully. Update your clients with the new token."
}

A small number of endpoints work without authentication, either because they serve public resources or because they’re called by contexts that don’t have the token:

EndpointPurposeWhy Unauthenticated
GET /healthHealth check returning {"status": "ok"}Monitoring tools and load balancers need to check health without credentials
GET /proxy.pacPAC file for system proxy auto-configurationThe OS proxy subsystem fetches this URL automatically and can’t include auth headers
GET /api/v1/mobile/profileiOS mobileconfig certificate profileAccessed from device Safari during mobile setup
POST /api/v1/script/fetchScript injection HTTP bridgeCalled by injected page scripts running in the browser — they don’t have the token
POST /api/v1/script/analyzeScript injection AI analysis bridgeSame reason — injected scripts can’t access the token
POST /api/v1/telemetry/errorExtension error relay to SentryRate-limited (10/minute per source), localhost-only
GET /wsWebSocket connectionAuthenticates via ?token= query parameter (constant-time comparison)
GET /ws/extensionBrowser extension WebSocketSame auth as /ws

Five middleware layers are applied to every request, in this exact order:

#MiddlewareWhat It Does
1Request IDChecks for an incoming X-Request-ID header. If present, propagates it; if absent, generates a new ULID (Universally Unique Lexicographically Sortable Identifier). Sets the X-Request-ID response header and stores it in the request context so handlers can include it in log messages and error reports.
2Real IPExtracts the real client IP address from X-Forwarded-For or X-Real-IP headers (chi’s built-in RealIP middleware). This ensures log entries show the actual client IP, not a proxy IP.
3Request LoggerLogs every request at DEBUG level with: HTTP method, URL path, response status code, response duration, and request ID. This creates a complete audit trail of all API activity.
4Panic RecoveryCatches any Go panics (unexpected crashes) that occur during request handling. Logs the panic with a full stack trace, reports it to Sentry (if configured), and returns a clean 500 Internal Server Error to the client instead of crashing the server.
5CORSHandles Cross-Origin Resource Sharing headers so the frontend (which may be served from a different port during development) can make API requests.

The auth middleware is NOT in the global chain — it’s applied only to the /api/v1 route group, so the unauthenticated endpoints listed above are accessible without a token.

SettingValueWhy
Allowed Origins* (all origins)The API runs on localhost only — restricting origins would just prevent legitimate use without adding security
Allowed MethodsGET, POST, PUT, PATCH, DELETE, OPTIONSStandard REST methods
Allowed HeadersAccept, Authorization, Content-Type, X-Request-IDHeaders the frontend sends
Exposed HeadersX-Request-IDLets the frontend read the request ID from responses
Allow CredentialsfalseNo cookies used — auth is via Bearer token
Max Age3600 seconds (1 hour)Browsers cache the CORS preflight response for 1 hour before re-checking

All request and response bodies are JSON (application/json; charset=utf-8) unless otherwise noted. Exceptions include:

  • File downloads (HAR, JSON, CSV exports) use appropriate content types with Content-Disposition headers
  • SSE streams (agent chat) use text/event-stream
  • Binary body responses use the original content type
  • The PAC file uses application/x-ns-proxy-autoconfig
  • The HTML report uses text/html

List endpoints accept limit and offset query parameters:

GET /api/v1/flows?limit=50&offset=0

Responses include the items array and a total count:

{
"flows": [...],
"total": 1234
}

The total field represents the complete count matching the query, regardless of limit and offset, so the frontend can show “showing 50 of 1,234 flows” and calculate pagination.

All errors return a consistent JSON format:

{
"error": "human-readable error message"
}

HTTP status codes follow standard conventions:

CodeMeaningWhen Used
400Bad RequestValidation errors, malformed JSON, invalid parameters
401UnauthorizedMissing or invalid bearer token
404Not FoundResource doesn’t exist (flow, session, addon, etc.)
409ConflictDuplicate resource (e.g., same-session comparison rejected)
413Payload Too LargeRequest body exceeds the size limit for that endpoint
429Too Many RequestsRate limit exceeded (telemetry error relay)
500Internal Server ErrorUnexpected server error (captured to Sentry)
502Bad GatewayUpstream proxy error (target server unreachable)
503Service UnavailableRequired service not configured (e.g., no LLM API key for agent)

The respondError helper automatically captures 5xx errors to Sentry.

Simple success operations return:

{
"ok": true
}

Every endpoint enforces a maximum request body size to prevent memory exhaustion. The limits are tuned per-endpoint based on expected payload sizes:

ContextLimitEndpoints
Default1 MBMost JSON request bodies
Agent chat64 KBPOST /agent/chat
Agent file upload512 KBPOST /agent/upload (with per-type sub-limits: .txt/.md 100 KB, .json/.yaml 200 KB)
Script fetch64 KB request, 5 MB response, 1 MB textPOST /script/fetch
Script analyze256 KB request, 32 KB data, 1 KB promptPOST /script/analyze
Injection script64 KBInjection rule script content
Frida script256 KBFrida script CRUD
Frida attach/spawn512 KBFrida attach/spawn (embeds script)
HAR/JSON import256 MBPOST /sessions/{id}/import/*
Compose response1 MB response body, 2 MB request JSONPOST /compose
Replay response10 MBPOST /flows/{id}/replay inline body
Security finding64 KBPATCH /findings/{id}/status
Telemetry error4 KBPOST /telemetry/error
Device commands1–4 KBTap, input, simulator commands
WebSocket inbound512 bytesWebSocket client messages (only pings expected)

The API is organized into 22 named route groups under /api/v1:

GroupPrefixEndpoint CountPurpose
Flows/flows14Flow CRUD, stats, host/app/device aggregation, replay, tags, notes, body streaming, WebSocket frames
Sessions/sessions16Session CRUD, activate, compare, export (HAR/JSON/CSV/Postman/report/PoC), import (HAR/JSON)
Proxy/proxy12Proxy start/stop/status, system proxy enable/disable/status, throttling, SSL bypass
Cert/cert4CA download, install, status check
Settings/settings7Settings CRUD, import/export, LLM validation, TestRail validation, token rotation
Setup/setup2First-run wizard status and completion
Monitor/monitor1Database size and memory stats
Addons/addons5Addon CRUD (hot-reload on update)
Agent/agent9SSE chat, stop, steer, file upload, conversations CRUD, workspace files
Breakpoints/breakpoints7Breakpoint rule CRUD, toggle, clear, resume held flows
Map Rules/rules6Map rule CRUD, toggle
Injection Rules/injection-rules6Injection rule CRUD, toggle
Compose/compose2Manual request composition, cURL import
Network/network1Network interface information
Mobile/mobile9Platform info, simulators, emulators, cert install, proxy setup, verify
Artifacts/artifacts4Bug report and export artifact CRUD, download
Inspector/inspector/devices16Device CRUD, screenshot, hierarchy, element at point, selectors, correlation, interactions, bug report, tap, input, WebView detection
Findings/findings5Security finding CRUD, stats, status update
Security Tools/security/tools3External scanner list, install, toggle
Frida/frida14Status, devices, apps, processes, attach/spawn/detach, sessions, console output, saved scripts CRUD
Attacker/attacker4Request fuzzer run, stop, status, wordlists
Extension/extension4Extension status/info, browser actions, inject
Interactions/interactions2Browser interaction CRUD
Journeys/journeys11Journey recording CRUD, steps, start/stop recording, active check, complete, export (8 formats: Cypress UI/API, Playwright UI/API, k6, Postman, cURL, HAR), replay (SSE)
TimeoutValuePurpose
Read30 secondsMaximum time to read the entire request (headers + body). Prevents slow-client attacks.
Write60 secondsMaximum time to write the response. Set higher than Read because some responses (SSE streams, large exports) take longer to generate.
Idle120 secondsMaximum time a keep-alive connection can sit idle between requests.
Shutdown10 secondsMaximum time to gracefully drain active connections during server shutdown. After 10 seconds, remaining connections are forcibly closed.