Skip to content

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.

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 /api/v1/settings

Returns 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)
PUT /api/v1/settings

Updates 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:

  1. Security interceptor — if security target hosts change, the interceptor’s host filter is updated immediately
  2. Proxy host filters — include/exclude lists take effect on the next request
  3. Noise detection — threshold, sample rate, and window are reconfigured (defaults: threshold=20, sampleRate=10, window=30s for any unspecified values)
  4. TestRail client — if TestRail config changes, the client is replaced with new credentials
  5. 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}

GET /api/v1/settings/export

Downloads 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 string
  • testrail.api_key — cleared to empty string
  • telemetry.sentry_dsn — cleared to empty string

This ensures exported settings files don’t contain sensitive credentials.

POST /api/v1/settings/import

Applies 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 security
  • testrail.api_key — same reason
  • llm section is skipped entirely if provider is empty
  • testrail section is skipped if both url and email are 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}

POST /api/v1/settings/llm/validate

Tests 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.

POST /api/v1/settings/testrail/validate

Tests 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

POST /api/v1/settings/rotate-token

Generates 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."
}
GET /api/v1/setup/status

Returns whether the first-run wizard has been completed.

Response:

{
"completed": true,
"proxy_method": "system_proxy"
}
POST /api/v1/setup/complete

Marks 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 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.

GET /api/v1/addons

Response: Array of addon objects.

POST /api/v1/addons

Request 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
}
FieldRequiredDefaultDescription
nameYesAddon name
codeNoJavaScript code
enabledNotrueWhether the addon is active
priorityNo0Execution 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 /api/v1/addons/{id}
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 /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": "..."}.

EventPayload
addon.createdAddon DTO
addon.updatedAddon DTO
addon.deleted{"id": "..."}
addon.logConsole output from addon’s ghost.log(), ghost.warn(), ghost.error() calls

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.

GET /api/v1/artifacts?session_id=01HWXYZ...&type=bug_report

Query parameters:

  • session_id — filter by session
  • type — 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 /api/v1/artifacts/{id}

Returns the full artifact including content.

GET /api/v1/artifacts/{id}/download

Downloads the artifact as a file with the appropriate content type.

Content-Type mapping by format:

FormatContent-TypeExtension
jsonapplication/json; charset=utf-8.json
harapplication/json; charset=utf-8.har
csvtext/csv; charset=utf-8.csv
htmltext/html; charset=utf-8.html
markdowntext/markdown; charset=utf-8.md
(other)application/octet-stream.{format}
DELETE /api/v1/artifacts/{id}

Response: {"ok": true}

Broadcasts artifact.deleted WebSocket event.

GET /api/v1/monitor

Returns 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
}
FieldDescription
db_size_bytes / db_size_humanSQLite database file size (computed from page_count × page_size)
mem_alloc_bytes / mem_alloc_humanCurrently allocated heap memory (Go runtime)
mem_sys_bytesTotal memory obtained from the operating system
goroutinesNumber of active Go goroutines (lightweight threads)
total_flowsTotal number of flows across all sessions
uptime_seconds / uptime_humanHow long the server has been running
num_gcNumber of garbage collection cycles completed
POST /api/v1/interactions

Records 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
}
FieldRequiredDescription
typeYesInteraction type (click, input, navigation, etc.)
page_urlYesURL of the page where the interaction happened
selectorNoCSS selector of the target element
element_textNoVisible text of the element
element_typeNoHTML element type (button, input, etc.)
page_titleNoDocument title
dataNoAdditional data (form values, etc.)
session_idNoGhost session ID
timestampNoUnix milliseconds. Defaults to current time if not provided
GET /api/v1/interactions?session_id=01HWXYZ...

Query parameters:

  • session_id (required)
  • flow_id — filter to a specific flow
  • limit / offset — pagination

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.

{
"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.

{
"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).

MethodPathDescription
GET/api/v1/journeys?session_id=XList all journeys for a session (newest first). Returns 400 if session_id is empty. Always returns an array.
POST/api/v1/journeysCreate 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}/stepsList all steps ordered by step_order ascending. Always returns an array.

These endpoints manage the recording lifecycle and require the browser extension to be connected.

POST /api/v1/journeys/start

Request 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.

POST /api/v1/journeys/stop

Request 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.

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.

POST /api/v1/journeys/{id}/complete

Manually completes a journey (e.g., recovering a failed journey). Updates status to "completed", sets completed_at, and recalculates flow_count and interaction_count.

GET /api/v1/journeys/{id}/export?format=cypress-ui
ParameterRequiredValues
formatYescurl, postman, playwright-api, playwright-ui, cypress-api, cypress-ui, k6, har

Responses:

StatusWhen
200Success — file download with Content-Disposition: attachment
400Missing or unsupported format
404Journey not found
422Journey has no exportable flows (API formats) or no UI interactions (UI formats)

Content types by format:

FormatContent-TypeFilename Pattern
curltext/x-shellscriptjourney-{id8}.sh
postmanapplication/jsonjourney-{id8}.postman_collection.json
playwright-apitext/typescriptjourney-{id8}.api.spec.ts
playwright-uitext/typescriptjourney-{id8}.ui.spec.ts
cypress-apiapplication/javascriptjourney-{id8}.api.cy.js
cypress-uiapplication/javascriptjourney-{id8}.ui.cy.js
k6application/javascriptjourney-{id8}.k6.js
harapplication/jsonjourney-{id8}.har
GET /api/v1/journeys/{id}/replay?delay_ms=500
ParameterRequiredRangeDefault
delay_msNo0–10,000 ms0

Returns a text/event-stream SSE connection. Each event has a type and JSON data:

event: step_start
data: {"type":"step_start","step":1,"total":5,"flow_id":"01J...","method":"GET","url":"https://..."}
event: step_complete
data: {"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: done
data: {"type":"done","total":5}

SSE headers:

HeaderValue
Content-Typetext/event-stream
Cache-Controlno-cache
Connectionkeep-alive
X-Accel-Bufferingno

Max response body per replay step: 10 MB. Write deadline per SSE event: 30 seconds.

EventPayloadWhen
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 pause HTTP flows mid-flight so you can inspect and modify them before they continue. See the Breakpoints feature doc for detailed behavior.

MethodPathDescription
GET/api/v1/breakpointsList all breakpoint rules
POST/api/v1/breakpointsCreate 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}/toggleToggle a rule’s enabled state
DELETE/api/v1/breakpointsClear 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 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.

MethodPathDescription
GET/api/v1/rulesList all map rules
POST/api/v1/rulesCreate 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}/toggleToggle a rule’s enabled state

WebSocket events: rule.created, rule.updated, rule.deleted

Injection rules specify JavaScript scripts to inject into web page responses. See the JavaScript Injection feature doc for detailed behavior.

MethodPathDescription
GET/api/v1/injection-rulesList rules for the active session
POST/api/v1/injection-rulesCreate 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}/toggleToggle a rule’s enabled state

WebSocket events: injection_rule.created, injection_rule.updated, injection_rule.deleted

POST /api/v1/compose

Sends 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
}
POST /api/v1/compose/import/curl

Parses 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).

GET /api/v1/extension/status

Returns the browser extension’s connection status.

GET /api/v1/extension/info

Returns information about the connected extension.

POST /api/v1/extension/actions

Sends an action to the browser extension (e.g., take screenshot, navigate, read page).

POST /api/v1/extension/inject

Injects a script into the active tab via the extension.