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.
Base URL
Section titled “Base URL”http://localhost:{api_port}/api/v1The 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.
Authentication
Section titled “Authentication”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>Token Format
Section titled “Token Format”The token is a 64-character hex string generated from 32 bytes of crypto/rand randomness:
b := make([]byte, 32) // 32 bytes of cryptographic randomnessrand.Read(b) // from crypto/randhex.EncodeToString(b) // → 64-character hex stringThe 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
Section titled “Token Comparison”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).
Token Rotation
Section titled “Token Rotation”POST /api/v1/settings/rotate-tokenGenerates 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."}Unauthenticated Endpoints
Section titled “Unauthenticated Endpoints”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:
| Endpoint | Purpose | Why Unauthenticated |
|---|---|---|
GET /health | Health check returning {"status": "ok"} | Monitoring tools and load balancers need to check health without credentials |
GET /proxy.pac | PAC file for system proxy auto-configuration | The OS proxy subsystem fetches this URL automatically and can’t include auth headers |
GET /api/v1/mobile/profile | iOS mobileconfig certificate profile | Accessed from device Safari during mobile setup |
POST /api/v1/script/fetch | Script injection HTTP bridge | Called by injected page scripts running in the browser — they don’t have the token |
POST /api/v1/script/analyze | Script injection AI analysis bridge | Same reason — injected scripts can’t access the token |
POST /api/v1/telemetry/error | Extension error relay to Sentry | Rate-limited (10/minute per source), localhost-only |
GET /ws | WebSocket connection | Authenticates via ?token= query parameter (constant-time comparison) |
GET /ws/extension | Browser extension WebSocket | Same auth as /ws |
Middleware Chain
Section titled “Middleware Chain”Five middleware layers are applied to every request, in this exact order:
| # | Middleware | What It Does |
|---|---|---|
| 1 | Request ID | Checks 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. |
| 2 | Real IP | Extracts 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. |
| 3 | Request Logger | Logs 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. |
| 4 | Panic Recovery | Catches 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. |
| 5 | CORS | Handles 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.
CORS Configuration
Section titled “CORS Configuration”| Setting | Value | Why |
|---|---|---|
| Allowed Origins | * (all origins) | The API runs on localhost only — restricting origins would just prevent legitimate use without adding security |
| Allowed Methods | GET, POST, PUT, PATCH, DELETE, OPTIONS | Standard REST methods |
| Allowed Headers | Accept, Authorization, Content-Type, X-Request-ID | Headers the frontend sends |
| Exposed Headers | X-Request-ID | Lets the frontend read the request ID from responses |
| Allow Credentials | false | No cookies used — auth is via Bearer token |
| Max Age | 3600 seconds (1 hour) | Browsers cache the CORS preflight response for 1 hour before re-checking |
Request/Response Conventions
Section titled “Request/Response Conventions”Content Type
Section titled “Content Type”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-Dispositionheaders - 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
Pagination
Section titled “Pagination”List endpoints accept limit and offset query parameters:
GET /api/v1/flows?limit=50&offset=0Responses 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.
Error Responses
Section titled “Error Responses”All errors return a consistent JSON format:
{ "error": "human-readable error message"}HTTP status codes follow standard conventions:
| Code | Meaning | When Used |
|---|---|---|
| 400 | Bad Request | Validation errors, malformed JSON, invalid parameters |
| 401 | Unauthorized | Missing or invalid bearer token |
| 404 | Not Found | Resource doesn’t exist (flow, session, addon, etc.) |
| 409 | Conflict | Duplicate resource (e.g., same-session comparison rejected) |
| 413 | Payload Too Large | Request body exceeds the size limit for that endpoint |
| 429 | Too Many Requests | Rate limit exceeded (telemetry error relay) |
| 500 | Internal Server Error | Unexpected server error (captured to Sentry) |
| 502 | Bad Gateway | Upstream proxy error (target server unreachable) |
| 503 | Service Unavailable | Required service not configured (e.g., no LLM API key for agent) |
The respondError helper automatically captures 5xx errors to Sentry.
Success Responses
Section titled “Success Responses”Simple success operations return:
{ "ok": true}Body Size Limits
Section titled “Body Size Limits”Every endpoint enforces a maximum request body size to prevent memory exhaustion. The limits are tuned per-endpoint based on expected payload sizes:
| Context | Limit | Endpoints |
|---|---|---|
| Default | 1 MB | Most JSON request bodies |
| Agent chat | 64 KB | POST /agent/chat |
| Agent file upload | 512 KB | POST /agent/upload (with per-type sub-limits: .txt/.md 100 KB, .json/.yaml 200 KB) |
| Script fetch | 64 KB request, 5 MB response, 1 MB text | POST /script/fetch |
| Script analyze | 256 KB request, 32 KB data, 1 KB prompt | POST /script/analyze |
| Injection script | 64 KB | Injection rule script content |
| Frida script | 256 KB | Frida script CRUD |
| Frida attach/spawn | 512 KB | Frida attach/spawn (embeds script) |
| HAR/JSON import | 256 MB | POST /sessions/{id}/import/* |
| Compose response | 1 MB response body, 2 MB request JSON | POST /compose |
| Replay response | 10 MB | POST /flows/{id}/replay inline body |
| Security finding | 64 KB | PATCH /findings/{id}/status |
| Telemetry error | 4 KB | POST /telemetry/error |
| Device commands | 1–4 KB | Tap, input, simulator commands |
| WebSocket inbound | 512 bytes | WebSocket client messages (only pings expected) |
Route Groups
Section titled “Route Groups”The API is organized into 22 named route groups under /api/v1:
| Group | Prefix | Endpoint Count | Purpose |
|---|---|---|---|
| Flows | /flows | 14 | Flow CRUD, stats, host/app/device aggregation, replay, tags, notes, body streaming, WebSocket frames |
| Sessions | /sessions | 16 | Session CRUD, activate, compare, export (HAR/JSON/CSV/Postman/report/PoC), import (HAR/JSON) |
| Proxy | /proxy | 12 | Proxy start/stop/status, system proxy enable/disable/status, throttling, SSL bypass |
| Cert | /cert | 4 | CA download, install, status check |
| Settings | /settings | 7 | Settings CRUD, import/export, LLM validation, TestRail validation, token rotation |
| Setup | /setup | 2 | First-run wizard status and completion |
| Monitor | /monitor | 1 | Database size and memory stats |
| Addons | /addons | 5 | Addon CRUD (hot-reload on update) |
| Agent | /agent | 9 | SSE chat, stop, steer, file upload, conversations CRUD, workspace files |
| Breakpoints | /breakpoints | 7 | Breakpoint rule CRUD, toggle, clear, resume held flows |
| Map Rules | /rules | 6 | Map rule CRUD, toggle |
| Injection Rules | /injection-rules | 6 | Injection rule CRUD, toggle |
| Compose | /compose | 2 | Manual request composition, cURL import |
| Network | /network | 1 | Network interface information |
| Mobile | /mobile | 9 | Platform info, simulators, emulators, cert install, proxy setup, verify |
| Artifacts | /artifacts | 4 | Bug report and export artifact CRUD, download |
| Inspector | /inspector/devices | 16 | Device CRUD, screenshot, hierarchy, element at point, selectors, correlation, interactions, bug report, tap, input, WebView detection |
| Findings | /findings | 5 | Security finding CRUD, stats, status update |
| Security Tools | /security/tools | 3 | External scanner list, install, toggle |
| Frida | /frida | 14 | Status, devices, apps, processes, attach/spawn/detach, sessions, console output, saved scripts CRUD |
| Attacker | /attacker | 4 | Request fuzzer run, stop, status, wordlists |
| Extension | /extension | 4 | Extension status/info, browser actions, inject |
| Interactions | /interactions | 2 | Browser interaction CRUD |
| Journeys | /journeys | 11 | Journey recording CRUD, steps, start/stop recording, active check, complete, export (8 formats: Cypress UI/API, Playwright UI/API, k6, Postman, cURL, HAR), replay (SSE) |
Server Timeouts
Section titled “Server Timeouts”| Timeout | Value | Purpose |
|---|---|---|
| Read | 30 seconds | Maximum time to read the entire request (headers + body). Prevents slow-client attacks. |
| Write | 60 seconds | Maximum time to write the response. Set higher than Read because some responses (SSE streams, large exports) take longer to generate. |
| Idle | 120 seconds | Maximum time a keep-alive connection can sit idle between requests. |
| Shutdown | 10 seconds | Maximum time to gracefully drain active connections during server shutdown. After 10 seconds, remaining connections are forcibly closed. |