Settings & Miscellaneous
This page covers Ghost’s remaining API endpoints that don’t fit into the main categories (flows, sessions, proxy, agent, devices, security). These include application configuration, the first-run wizard, addon management, artifact storage, resource monitoring, browser interactions, journey recordings, traffic modification rules, and the request composer.
Settings
Section titled “Settings”Ghost’s settings control proxy behavior, LLM configuration, storage policies, security scanning, and integrations. Settings are stored in ~/.ghost/config.toml and loaded into memory at startup.
Get Settings
Section titled “Get Settings”GET /api/v1/settingsReturns all application settings. Sensitive fields are masked — API keys show only the last 4 characters (e.g., "****abc1").
Response:
{ "proxy": { "port": 4545, "cert_cache_size": 10000, "host_include": [], "host_exclude": [], "upstream_proxy": "", "ssl_bypass_hosts": ["*.apple.com"], "noise_enabled": true, "noise_threshold": 20, "noise_sample_rate": 10, "noise_window_secs": 30 }, "api": { "port": 5565 }, "store": { "max_flow_body_size": 0, "auto_purge_enabled": false, "auto_purge_max_age_hours": 0, "auto_purge_max_flows": 0 }, "llm": { "provider": "anthropic", "api_key": "****sk-1", "model": "claude-sonnet-4-20250514", "ollama_endpoint": "" }, "logging": { "level": "info" }, "security": { "target_hosts": ["*.example.com"] }, "telemetry": { "sentry_dsn": "https://...", "telemetry_enabled": false, "telemetry_endpoint": "" }, "testrail": { "url": "https://company.testrail.io", "email": "user@company.com", "api_key": "****xyz1" }}Masked fields:
llm.api_key— shows"****" + last 4 characters(or just"****"if 4 chars or shorter). Empty if not set.testrail.api_key— same masking logic- The API bearer token is completely omitted from the response (not even masked)
Update Settings
Section titled “Update Settings”PUT /api/v1/settingsUpdates settings. Send only the sections you want to change — sections you omit are left unchanged.
Request body (1 MB limit):
{ "proxy": { "noise_threshold": 30 }, "llm": { "provider": "openai", "api_key": "sk-...", "model": "gpt-4o" }}Hot-reload behavior — when settings change, Ghost immediately applies them to running subsystems without needing a restart:
- Security interceptor — if security target hosts change, the interceptor’s host filter is updated immediately
- Proxy host filters — include/exclude lists take effect on the next request
- Noise detection — threshold, sample rate, and window are reconfigured (defaults: threshold=20, sampleRate=10, window=30s for any unspecified values)
- TestRail client — if TestRail config changes, the client is replaced with new credentials
- LLM agent — if LLM config changes, the agent is reconfigured with the new provider/model/key. If reconfiguration fails, it’s logged as a warning but the update still succeeds
Response: {"ok": true}
Export Settings
Section titled “Export Settings”GET /api/v1/settings/exportDownloads settings as a JSON file for backup or transfer to another machine.
Content-Disposition: attachment; filename="ghost-settings.json"
What is NOT included in exports:
llm.api_key— cleared to empty stringtestrail.api_key— cleared to empty stringtelemetry.sentry_dsn— cleared to empty string
This ensures exported settings files don’t contain sensitive credentials.
Import Settings
Section titled “Import Settings”POST /api/v1/settings/importApplies imported settings from a JSON file.
What is NEVER imported (even if present in the file):
llm.api_key— API keys must be set manually for securitytestrail.api_key— same reasonllmsection is skipped entirely ifprovideris emptytestrailsection is skipped if bothurlandemailare empty
SSRF protection: TestRail URLs are validated to not point to private/local addresses.
After import, the same hot-reload sequence runs as with regular updates.
Response: {"ok": true}
Validate LLM Connection
Section titled “Validate LLM Connection”POST /api/v1/settings/llm/validateTests whether an LLM provider is reachable and the API key works, without actually saving the settings.
Request body:
{ "provider": "anthropic", "api_key": "sk-ant-...", "model": "claude-sonnet-4-20250514", "ollama_endpoint": ""}The handler creates a temporary provider, sends a test message ("Say ok"), and reports the result.
Timeout: 15 seconds
Response (success): {"valid": true}
Response (failure): {"valid": false, "error": "authentication failed: invalid API key"} — always HTTP 200 (the validation result is in the body, not the status code)
Note: Ollama doesn’t require an API key.
Validate TestRail Connection
Section titled “Validate TestRail Connection”POST /api/v1/settings/testrail/validateTests TestRail API connectivity by calling GetProjects.
Request body (4 KB limit — tighter than the usual 1 MB):
{ "url": "https://company.testrail.io", "email": "user@company.com", "api_key": "..."}Missing fields fall back to the current config values — so you can validate with just a new API key while keeping the existing URL and email.
SSRF protection: The URL must be valid HTTP(S) and must NOT point to a private/local IP address.
Response (success): {"valid": true, "project_count": 5, "projects": [...]}
Response (failure): {"valid": false, "error": "TestRail connection failed: ..."} — always HTTP 200
Rotate API Token
Section titled “Rotate API Token”POST /api/v1/settings/rotate-tokenGenerates a new API bearer token. The old token is immediately invalidated — any existing connections using it will need to update.
Atomic safety: The in-memory token is updated first (so the auth middleware immediately rejects old tokens), then the new token is persisted to the config file. If the config file write fails, the in-memory token is reverted to the old value — this prevents a split where the running server expects one token but the config file has another.
Response:
{ "token": "a1b2c3d4e5f6...64-character-hex-string", "message": "Token rotated successfully. Update your clients with the new token."}Setup Wizard
Section titled “Setup Wizard”Get Setup Status
Section titled “Get Setup Status”GET /api/v1/setup/statusReturns whether the first-run wizard has been completed.
Response:
{ "completed": true, "proxy_method": "system_proxy"}Complete Setup
Section titled “Complete Setup”POST /api/v1/setup/completeMarks the setup wizard as completed and configures the proxy method.
Request body (1 KB limit):
{ "proxy_method": "system_proxy"}Valid proxy methods:
"system_proxy"— Ghost configures the OS to route traffic through itself (recommended for desktop testing)"manual"— user configures their browser/app to use Ghost as a proxy manually"device"— for mobile device testing where the device’s Wi-Fi proxy is set to Ghost’s IP
If proxy_method is "system_proxy" and the proxy is already running, Ghost immediately enables the OS system proxy via PAC.
Response: {"ok": true}
Addons
Section titled “Addons”Addons are JavaScript plugins that run inside Ghost’s proxy pipeline. Each addon gets its own isolated JavaScript VM (goja engine) and can inspect, modify, tag, or block traffic.
List Addons
Section titled “List Addons”GET /api/v1/addonsResponse: Array of addon objects.
Create Addon
Section titled “Create Addon”POST /api/v1/addonsRequest body (1 MB limit):
{ "name": "Tag API Errors", "code": "ghost.onResponse(function(flow) { if (flow.response.status_code >= 400) flow.tag('error'); })", "enabled": true, "priority": 10}| Field | Required | Default | Description |
|---|---|---|---|
name | Yes | — | Addon name |
code | No | — | JavaScript code |
enabled | No | true | Whether the addon is active |
priority | No | 0 | Execution order (lower = runs first) |
If enabled is true, the addon is immediately loaded into the goja VM.
Response: 201 Created with the addon object (includes generated id, created_at, updated_at).
Broadcasts addon.created WebSocket event.
Get Addon
Section titled “Get Addon”GET /api/v1/addons/{id}Update Addon
Section titled “Update Addon”PUT /api/v1/addons/{id}Partial update — only non-nil fields in the request are applied. The addon’s goja VM is hot-reloaded: the old VM is unloaded and a new one is created with the updated code. This clears the addon’s in-memory store (since it was stored in the old VM).
Response: Updated addon object.
Broadcasts addon.updated WebSocket event.
Delete Addon
Section titled “Delete Addon”DELETE /api/v1/addons/{id}Removes the addon from the database and unloads its VM.
Response: 204 No Content (no body).
Broadcasts addon.deleted WebSocket event with {"id": "..."}.
Addon WebSocket Events
Section titled “Addon WebSocket Events”| Event | Payload |
|---|---|
addon.created | Addon DTO |
addon.updated | Addon DTO |
addon.deleted | {"id": "..."} |
addon.log | Console output from addon’s ghost.log(), ghost.warn(), ghost.error() calls |
Artifacts
Section titled “Artifacts”Artifacts are persistent files generated by Ghost — bug reports, session exports, AI-generated reports. They’re stored in the database with their content and metadata.
List Artifacts
Section titled “List Artifacts”GET /api/v1/artifacts?session_id=01HWXYZ...&type=bug_reportQuery parameters:
session_id— filter by sessiontype— filter by artifact type (e.g.,"bug_report","session_export")limit/offset— pagination
Response: Array of artifact summaries (without content — use GET by ID for full content).
[ { "id": "01HWXYZ...", "type": "bug_report", "title": "Login button not responding", "summary": "Touch event captured but no network request fired", "format": "json", "device_id": "A1B2C3...", "device_name": "iPhone 16 Pro", "session_id": "01HWABC...", "size_bytes": 45678, "metadata": {"ai_enhanced": "true"}, "created_at": "2024-01-15T10:30:00Z" }]Get Artifact
Section titled “Get Artifact”GET /api/v1/artifacts/{id}Returns the full artifact including content.
Download Artifact
Section titled “Download Artifact”GET /api/v1/artifacts/{id}/downloadDownloads the artifact as a file with the appropriate content type.
Content-Type mapping by format:
| Format | Content-Type | Extension |
|---|---|---|
json | application/json; charset=utf-8 | .json |
har | application/json; charset=utf-8 | .har |
csv | text/csv; charset=utf-8 | .csv |
html | text/html; charset=utf-8 | .html |
markdown | text/markdown; charset=utf-8 | .md |
| (other) | application/octet-stream | .{format} |
Delete Artifact
Section titled “Delete Artifact”DELETE /api/v1/artifacts/{id}Response: {"ok": true}
Broadcasts artifact.deleted WebSocket event.
Resource Monitor
Section titled “Resource Monitor”GET /api/v1/monitorReturns system resource usage — useful for debugging performance issues or checking database growth.
Response:
{ "db_size_bytes": 52428800, "db_size_human": "50.0 MB", "mem_alloc_bytes": 134217728, "mem_alloc_human": "128.0 MB", "mem_sys_bytes": 268435456, "goroutines": 42, "total_flows": 15000, "uptime_seconds": 7200, "uptime_human": "2h 0m", "num_gc": 156}| Field | Description |
|---|---|
db_size_bytes / db_size_human | SQLite database file size (computed from page_count × page_size) |
mem_alloc_bytes / mem_alloc_human | Currently allocated heap memory (Go runtime) |
mem_sys_bytes | Total memory obtained from the operating system |
goroutines | Number of active Go goroutines (lightweight threads) |
total_flows | Total number of flows across all sessions |
uptime_seconds / uptime_human | How long the server has been running |
num_gc | Number of garbage collection cycles completed |
Browser Interactions
Section titled “Browser Interactions”Create Interaction
Section titled “Create Interaction”POST /api/v1/interactionsRecords a browser interaction event captured by the Ghost browser extension (click, form input, navigation, etc.).
Request body:
{ "type": "click", "selector": "#submit-button", "element_text": "Submit", "element_type": "button", "page_url": "https://example.com/checkout", "page_title": "Checkout", "data": "", "session_id": "01HWXYZ...", "timestamp": 1705312815000}| Field | Required | Description |
|---|---|---|
type | Yes | Interaction type (click, input, navigation, etc.) |
page_url | Yes | URL of the page where the interaction happened |
selector | No | CSS selector of the target element |
element_text | No | Visible text of the element |
element_type | No | HTML element type (button, input, etc.) |
page_title | No | Document title |
data | No | Additional data (form values, etc.) |
session_id | No | Ghost session ID |
timestamp | No | Unix milliseconds. Defaults to current time if not provided |
List Interactions
Section titled “List Interactions”GET /api/v1/interactions?session_id=01HWXYZ...Query parameters:
session_id(required)flow_id— filter to a specific flowlimit/offset— pagination
Journeys
Section titled “Journeys”Journey recordings capture sequences of browser interactions and HTTP flows, correlated via X-Ghost-Interaction headers injected by the browser extension. See the Journey Recording feature doc for the full architecture and correlation mechanism.
Journey Object
Section titled “Journey Object”{ "id": "01JEXAMPLE...", "session_id": "01JWSESSION...", "name": "Checkout Flow", "status": "completed", "flow_count": 15, "interaction_count": 4, "started_at": "2026-03-09T14:32:00.000000000Z", "completed_at": "2026-03-09T14:35:12.000000000Z", "created_at": "2026-03-09T14:32:00.000000000Z"}Status values: recording, completed, failed, replaying.
Journey Step Object
Section titled “Journey Step Object”{ "id": "01JSTEP...", "journey_id": "01JEXAMPLE...", "step_order": 3, "type": "action", "interaction_id": "01JINT...", "flow_id": "01JFLOW...", "data": "{\"type\":\"click\",\"selector\":\"#submit-btn\"}", "timestamp": "2026-03-09T14:33:05.123456789Z"}Step types: action (correlated interaction + flow), flow (standalone HTTP request), interaction (user action without network activity).
CRUD Endpoints
Section titled “CRUD Endpoints”| Method | Path | Description |
|---|---|---|
GET | /api/v1/journeys?session_id=X | List all journeys for a session (newest first). Returns 400 if session_id is empty. Always returns an array. |
POST | /api/v1/journeys | Create a journey. Body: {"session_id": "...", "name": "..."} (both required, 1 MB body cap). Returns 201. |
GET | /api/v1/journeys/{id} | Get a single journey. Returns 404 if not found. |
PUT | /api/v1/journeys/{id} | Update journey name. Currently returns 501 (not yet implemented). |
DELETE | /api/v1/journeys/{id} | Delete journey and all steps (CASCADE). Returns 204. Broadcasts journey:deleted via WebSocket. |
GET | /api/v1/journeys/{id}/steps | List all steps ordered by step_order ascending. Always returns an array. |
Recording Control
Section titled “Recording Control”These endpoints manage the recording lifecycle and require the browser extension to be connected.
Start Recording
Section titled “Start Recording”POST /api/v1/journeys/startRequest body:
{ "session_id": "01JWSESSION...", "name": "Login Flow" }Both fields required. Returns 400 if extension is not connected. Returns 409 Conflict if the session already has an active recording (only one recording per session is allowed).
Response: 200 OK with the journey object (status: "recording").
Side effect: Sends journey.recording_start to the browser extension, which enables declarativeNetRequest header injection.
Stop Recording
Section titled “Stop Recording”POST /api/v1/journeys/stopRequest body:
{ "session_id": "01JWSESSION..." }Returns 404 if no active recording exists for the session.
Response: 200 OK with the finalized journey (status: "completed", counts populated).
Side effect: Sends journey.recording_stop to the browser extension, which removes the header injection rule.
Check Active Recording
Section titled “Check Active Recording”GET /api/v1/journeys/active?session_id=01JWSESSION...Response:
{ "is_recording": true, "journey_id": "01JEXAMPLE...", "session_id": "01JWSESSION..." }Or {"is_recording": false} if no recording is active.
Manual Complete
Section titled “Manual Complete”POST /api/v1/journeys/{id}/completeManually completes a journey (e.g., recovering a failed journey). Updates status to "completed", sets completed_at, and recalculates flow_count and interaction_count.
Export
Section titled “Export”GET /api/v1/journeys/{id}/export?format=cypress-ui| Parameter | Required | Values |
|---|---|---|
format | Yes | curl, postman, playwright-api, playwright-ui, cypress-api, cypress-ui, k6, har |
Responses:
| Status | When |
|---|---|
| 200 | Success — file download with Content-Disposition: attachment |
| 400 | Missing or unsupported format |
| 404 | Journey not found |
| 422 | Journey has no exportable flows (API formats) or no UI interactions (UI formats) |
Content types by format:
| Format | Content-Type | Filename Pattern |
|---|---|---|
curl | text/x-shellscript | journey-{id8}.sh |
postman | application/json | journey-{id8}.postman_collection.json |
playwright-api | text/typescript | journey-{id8}.api.spec.ts |
playwright-ui | text/typescript | journey-{id8}.ui.spec.ts |
cypress-api | application/javascript | journey-{id8}.api.cy.js |
cypress-ui | application/javascript | journey-{id8}.ui.cy.js |
k6 | application/javascript | journey-{id8}.k6.js |
har | application/json | journey-{id8}.har |
Replay (SSE Stream)
Section titled “Replay (SSE Stream)”GET /api/v1/journeys/{id}/replay?delay_ms=500| Parameter | Required | Range | Default |
|---|---|---|---|
delay_ms | No | 0–10,000 ms | 0 |
Returns a text/event-stream SSE connection. Each event has a type and JSON data:
event: step_startdata: {"type":"step_start","step":1,"total":5,"flow_id":"01J...","method":"GET","url":"https://..."}
event: step_completedata: {"type":"step_complete","step":1,"total":5,"original":{"status_code":200,"body_size":1234,"duration_ms":45},"replayed":{"status_code":200,"body_size":1230,"duration_ms":52},"diff":{"status_changed":false,"body_size_delta":-4,"duration_delta_ms":7}}
event: donedata: {"type":"done","total":5}SSE headers:
| Header | Value |
|---|---|
Content-Type | text/event-stream |
Cache-Control | no-cache |
Connection | keep-alive |
X-Accel-Buffering | no |
Max response body per replay step: 10 MB. Write deadline per SSE event: 30 seconds.
WebSocket Events
Section titled “WebSocket Events”| Event | Payload | When |
|---|---|---|
journey:started | { journey_id, session_id, name } | Recording begins |
journey:step_added | { journey_id, step_order, type, flow_id?, interaction_id? } | Step added during recording |
journey:completed | { journey_id, flow_count, interaction_count, duration_ms } | Recording finishes |
journey:deleted | { journey_id } | Journey deleted |
Breakpoints
Section titled “Breakpoints”Breakpoints pause HTTP flows mid-flight so you can inspect and modify them before they continue. See the Breakpoints feature doc for detailed behavior.
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/breakpoints | List all breakpoint rules |
| POST | /api/v1/breakpoints | Create a rule (ID defaults to ULID, phase defaults to “request”) |
| PUT | /api/v1/breakpoints/{id} | Update a rule |
| DELETE | /api/v1/breakpoints/{id} | Delete a rule |
| POST | /api/v1/breakpoints/{id}/toggle | Toggle a rule’s enabled state |
| DELETE | /api/v1/breakpoints | Clear all rules |
| POST | /api/v1/breakpoints/resume/{flowID} | Resume a paused flow (action defaults to “continue”) |
All endpoints return 503 if the breakpoint manager is not initialized.
Map Rules
Section titled “Map Rules”Map rules modify traffic passing through the proxy — redirect requests, replace responses with local files, or rewrite URLs/headers/bodies using regex. See the Map Rules feature doc for detailed behavior.
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/rules | List all map rules |
| POST | /api/v1/rules | Create a rule (type defaults to “local”, validates local file paths against traversal) |
| GET | /api/v1/rules/{id} | Get a rule |
| PUT | /api/v1/rules/{id} | Update a rule |
| DELETE | /api/v1/rules/{id} | Delete a rule |
| POST | /api/v1/rules/{id}/toggle | Toggle a rule’s enabled state |
WebSocket events: rule.created, rule.updated, rule.deleted
Injection Rules
Section titled “Injection Rules”Injection rules specify JavaScript scripts to inject into web page responses. See the JavaScript Injection feature doc for detailed behavior.
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/injection-rules | List rules for the active session |
| POST | /api/v1/injection-rules | Create a rule (url_pattern required, script max 64 KB, starts enabled) |
| GET | /api/v1/injection-rules/{id} | Get a rule |
| PUT | /api/v1/injection-rules/{id} | Update a rule (preserves existing enabled state — use toggle) |
| DELETE | /api/v1/injection-rules/{id} | Delete a rule |
| POST | /api/v1/injection-rules/{id}/toggle | Toggle a rule’s enabled state |
WebSocket events: injection_rule.created, injection_rule.updated, injection_rule.deleted
Request Composer
Section titled “Request Composer”Compose Request
Section titled “Compose Request”POST /api/v1/composeSends an ephemeral HTTP request — built from scratch or modified from an existing flow. Unlike replay (which stores the result as a new flow), compose returns the response directly without saving it.
Request body (2 MB limit):
{ "method": "GET", "url": "https://api.example.com/users", "headers": {"Authorization": "Bearer token123"}, "body": ""}Response body cap: 1 MB
Response:
{ "status_code": 200, "status_text": "OK", "headers": {"Content-Type": "application/json"}, "body": "{\"users\":[...]}", "body_encoding": "utf8", "duration_ms": 123.45}Import cURL
Section titled “Import cURL”POST /api/v1/compose/import/curlParses a cURL command string into its constituent parts for use in the request composer.
Request body:
{ "curl": "curl -X POST https://api.example.com/users -H 'Content-Type: application/json' -d '{\"name\":\"test\"}'"}Response: The decomposed request components (method, URL, headers, body).
Extension
Section titled “Extension”Extension Status
Section titled “Extension Status”GET /api/v1/extension/statusReturns the browser extension’s connection status.
Extension Info
Section titled “Extension Info”GET /api/v1/extension/infoReturns information about the connected extension.
Browser Actions
Section titled “Browser Actions”POST /api/v1/extension/actionsSends an action to the browser extension (e.g., take screenshot, navigate, read page).
Inject Script
Section titled “Inject Script”POST /api/v1/extension/injectInjects a script into the active tab via the extension.