API Reference
The Gostly control plane exposes a JSON HTTP API for managing recorded traffic, scheduling LEARN → MOCK transitions, training per-service adapters, and reading drift detection state. Everything the dashboard does, it does through this API.
Base URL
The control plane listens on port 8000 by default in a customer self-host deployment. All paths in this reference are relative to that base.
http://localhost:8000
When fronted by the bundled reverse proxy, the same surface is reachable at the deployment’s public hostname.
Authentication
Most endpoints require an API key sent in the X-Gostly-Api-Key header. Generate one from the dashboard at /settings → API Keys, or call POST /v1/api-keys/generate against a seeded session. The raw key is returned once; only the prefix is retrievable afterwards.
curl -H "X-Gostly-Api-Key: gsk_..." \
http://localhost:8000/v1/transition/jobsDashboard surfaces use the session cookie (gostly_session, HttpOnly + SameSite=Lax) instead of the API key. Programmatic / CI callers should always prefer the API key.
Tenancy
Every authenticated request is scoped to a single tenant. Tenant identity is derived from the license key at the API container; Postgres Row-Level Security enforces tenant isolation as a defense-in-depth layer beneath the application’s WHERE clauses. See Security for the structural invariants.
Error envelope
Errors return a structured JSON body with a stable error_code that callers should branch on, plus a human-readable detail for operator dashboards. Tier-gated 403s additionally carry the feature name so a client can render a tier-specific upgrade prompt.
{
"detail": "Mock not found",
"error_code": "not_found"
}Status → error_code mapping:
404→not_found422→validation_error403→tenant_isolation|feature_gated(the latter carriesfeature)409→scrubbing_required500→internal_error(unclassified — alert)
Idempotency
Stripe-style. Side-effecting POSTs accept an Idempotency-Key header (any UUID-ish string the client picks). The server caches the response under (tenant_id, key)for 24h. A retry with the same key replays the cached response without re-firing the side effect — safe to use behind network retries.
# First call — actually enqueues the job
curl -X POST http://localhost:8000/v1/transition/start \
-H "X-Gostly-Api-Key: gsk_..." \
-H "Idempotency-Key: ci-build-42" \
-H "Content-Type: application/json" \
-d '{"service_id":"stripe"}'
# → 202 {"job_id":"abc-123","status":"queued"}
# Retry with the same key — same response, no new job
curl -X POST http://localhost:8000/v1/transition/start \
-H "X-Gostly-Api-Key: gsk_..." \
-H "Idempotency-Key: ci-build-42" \
-d '{"service_id":"stripe"}'
# → 202 {"job_id":"abc-123","status":"queued"} ← same job_idWhich endpoints support Idempotency-Key
POST /v1/transition/starttoday. Other side-effecting POSTs (training, mode change) are obvious next adopters — tracked as follow-ups. Endpoints that don’t recognise the header simply ignore it; you can safely send it everywhere from a retry-aware client.
Keys are tenant-scoped, so two tenants picking the same client UUID don’t collide. Don’t use the same key across endpoints intentionally — each (tenant, key) maps to one cached response.
Pagination
List endpoints accept limit + offset query params and return a count field on the current shape. A canonical envelope is being rolled out across endpoints:
{
"data": [...],
"total": N,
"limit": L,
"offset": O,
"has_more": true | false
}Migrations land one endpoint at a time so each can update its dashboard caller in lockstep. Until then, individual list endpoints carry their per-endpoint shape (mocks, proposals, events, ...) — check the per-section table below for the response key.
Rate limits
The control plane applies per-IP sliding-window limits on expensive operations:
POST /v1/traffic/normalize— 10 / minutePOST /v1/training/finetune— 3 / minutePOST /v1/transition/start— 5 / minutePOST /v1/eval/run— 6 / minute- Global ceiling — 600 / minute / IP across all paths
Exceeding a limit returns 429 with a detailstring identifying the bucket. The current implementation is in-process — multi-replica deployments will see per-replica windows. The Caddy / Redis-backed upgrade is a planned follow-up.
Deprecation policy
When a path is renamed, the old path stays live for at least one release cycle. The OpenAPI schema marks deprecated paths with deprecated: true. Active deprecations:
/v1/sequences/statecharts/*→ canonical/v1/statecharts/*. The/v1/sequences/infix dates back to the dropped recorded-sequences model; statecharts subsume it. New code should use the canonical path.
Health
Two endpoints with different latency contracts:
| Method | Path | Purpose | Tier |
|---|---|---|---|
| GET | /health | Synchronous probe of agent + inference + database; ~hundreds of ms when deps are slow. | all |
| GET | /v1/health/summary | Cached state, refreshed every 5s in the background. Returns immediately even during a dependency outage; each component carries stale_for_seconds. | all |
| GET | /metrics | Prometheus scrape endpoint on the proxy. | all |
Dashboards + uptime checkers should hit /v1/health/summary. Reserve /health for when you actually need a fresh probe.
Mode control
Modes: LEARN (record), MOCK (serve recorded), PASSTHROUGH (forward, don’t record), TRANSITIONING(read-only during a LEARN → MOCK pipeline; returns 503 + Retry-After).
| Method | Path | Purpose | Tier |
|---|---|---|---|
| POST | /v1/mode | Set global mode. Body: { mode: 'learn' | 'mock' | 'passthrough' } | all |
| GET | /v1/mode | Get current mode (proxied from agent /health). | all |
Services
A serviceis a registered upstream — the API binds traffic to it via either a hostname or a path prefix.
| Method | Path | Purpose | Tier |
|---|---|---|---|
| GET | /services | List registered services for the tenant. | all |
| POST | /services | Register a service. Body: { name, upstream_url, routing_type, routing_value }. | all |
| PUT | /services/{id} | Update service config (including per-service mode override). | all |
| DELETE | /services/{id} | Remove a service. Mock entries with this service_upstream_id stay; sync deletes follow on next transition. | all |
Mocks library
| Method | Path | Purpose | Tier |
|---|---|---|---|
| GET | /mocks | List mock entries. Filters: method, service_id. Paginated via limit + offset. | all |
| GET | /mocks/export | Export the library. Defaults to scrubbed_only=true; the unscrubbed export is gated. | all |
| GET | /mocks/unmatched | Requests that found no match while in MOCK mode (operator surface for filling coverage gaps). | all |
| GET | /mocks/coverage | Coverage stats per endpoint. | all |
| POST | /mocks/import/openapi | Cold-start from an OpenAPI 3.x spec. | all |
| POST | /mocks/import/postman | Cold-start from a Postman v2 collection. | all |
| POST | /mocks/import/wiremock | Cold-start from a WireMock mappings folder (single JSON or zip). | all |
| DELETE | /mocks/{id} | Delete one entry. | all |
| DELETE | /mocks | Clear the entire library for the tenant. | all |
Traffic + transition
Traffic in LEARN mode flows into a per-service JSONL on the agent’s data volume. The transition pipeline promotes that traffic into the mock library, running PII scrub and pattern extraction along the way.
| Method | Path | Purpose | Tier |
|---|---|---|---|
| GET | /v1/traffic | Browse recorded traffic for the tenant. | all |
| POST | /v1/traffic/normalize | Normalise recorded mocks: extract patterns + URI templates. Rate-limited (10/min). | all |
| POST | /v1/transition/start | Enqueue a LEARN → MOCK pipeline. Accepts Idempotency-Key. Returns 202 + { job_id }. Rate-limited (5/min). | all |
| GET | /v1/transition/jobs | List recent transition jobs for the tenant (newest first, max 20). | all |
| GET | /v1/transition/{job_id}/status | Job state + progress (0..1) + current_stage + records_processed/total. | all |
Drift detection
Drift detection runs schema diffs against your mock library and the live upstream. The dashboard surfaces it at /quality; the API is what powers it.
| Method | Path | Purpose | Tier |
|---|---|---|---|
| GET | /v1/drift/services | Per-service drift summary rows (count, max severity, affected endpoints, last_detected_at). | pro |
| GET | /v1/drift/events | Drift events. Filters: service_id, unacknowledged_only, limit. | pro |
| GET | /v1/drift/events/{id} | One drift event with the full schema diff. | pro |
| POST | /v1/drift/events/{id}/acknowledge | Mark a drift event acknowledged (optional reason_text). | pro |
| GET | /v1/drift/freshness-score | 0-100 freshness score for a service. Reads from the materialized view; falls back to inline compute when older than 5min. | pro |
| GET | /v1/drift/sparkline | 30-day per-day major/minor drift counts (for the sparkline cell). | pro |
Statecharts
Statecharts (Bet #3 v2) define per-resource lifecycle for the mock engine — given an action on a resource, advance state and serve the appropriate response. Five bundled fixtures ship out of the box (charge, customer, invoice, order, subscription); tenants can override them or upload custom machines.
| Method | Path | Purpose | Tier |
|---|---|---|---|
| GET | /v1/statecharts | Catalog: id-sorted summary rows merged from bundled fixtures + tenant overrides + custom uploads. | pro |
| GET | /v1/statecharts/{id} | Full graph for one machine — states + transitions. | pro |
| GET | /v1/statecharts/{id}/resources | Captured resources currently in each state of this machine (heat-map overlay). | pro |
| PATCH | /v1/statecharts/{id} | Upsert an override for a bundled fixture, or update an existing custom machine. | pro |
| POST | /v1/statecharts | Create a fully-custom machine. URL routing wires it up automatically (e.g. id 'shipment' binds POST /shipments). | pro |
| DELETE | /v1/statecharts/{id} | Delete a tenant override (reverts to bundled) or hard-delete a custom machine. Bundled fixtures aren't deletable. | pro |
Renamed from /v1/sequences/statecharts/*
The legacy /v1/sequences/statecharts/* paths still work and are decorated deprecated: truein OpenAPI. They’ll be removed after one release cycle. New code should use the canonical paths above.
Webhooks
Inbound webhook capture + operator-triggered replay. Scheduled / fanout / re-signing replay is v2 (out of scope today).
| Method | Path | Purpose | Tier |
|---|---|---|---|
| GET | /v1/webhooks/{service_id} | List captured webhook events for a service. Filters: signature_kind, limit, offset. | all |
| GET | /v1/webhooks/{service_id}/{webhook_id} | Get one captured webhook with full headers + body. | all |
| POST | /v1/webhooks/{service_id}/{webhook_id}/replay | Replay one captured webhook against a target URL. Body: { target_url, preserve_signature, timeout_seconds }. | all |
| DELETE | /v1/webhooks/{service_id}/{webhook_id} | Delete a captured webhook. | all |
Training (Pro+)
Per-service LoRA fine-tuning. The inference server spawns training as a background subprocess and webhooks progress back.
| Method | Path | Purpose | Tier |
|---|---|---|---|
| POST | /training/sessions | Create a training session draft. | pro |
| GET | /training/sessions | List training sessions for the tenant. | pro |
| GET | /training/sessions/{id} | Session details + linked mocks + status. | pro |
| POST | /training/sessions/{id}/train | Start LoRA fine-tuning. Rate-limited (3/min). | pro |
| GET | /training/sessions/{id}/status | Poll training progress. | pro |
| POST | /training/sessions/{id}/activate | Register adapter as active for its service. Sidecar mode requires `docker compose restart ghost-llamacpp` afterwards. | pro |
| DELETE | /training/sessions/{id} | Delete a training session + its adapter files. | pro |
| POST | /training/generate-prompt | Generate a fine-tuning prompt preview from recorded patterns. | pro |
Operational
| Method | Path | Purpose | Tier |
|---|---|---|---|
| GET | /v1/api-keys | Current key prefix + metadata (never returns the raw key). | all |
| POST | /v1/api-keys/generate | Generate a new key. Raw key returned only in this response — store it now. | all |
| DELETE | /v1/api-keys | Revoke the current key. | all |
| GET | /config | Read merged config defaults + tenant overrides. | all |
| PUT | /config/{key} | Upsert a tenant config override. Push to agent fires automatically. | all |
| GET | /v1/scrub/config | Per-service PII / credential scrub rules. | all |
| POST | /v1/scrub/test | Dry-run a payload through the scrub pipeline (no persistence). | all |
| GET | /v1/activity | Recent activity log (team-tier surface, Pro+ also). | all |
| GET | /v1/onboarding/status | 5-step onboarding checklist state. Drives the dashboard's helper pill. | all |
| GET | /insights | Aggregated dashboard data (mocks count, drift count, fidelity, eval recents) for the /insights page. | all |
| POST | /v1/eval/run | Trigger an on-demand Tier-0 eval for a service (~30s). Rate-limited (6/min). | pro |
| GET | /v1/eval/runs | List recent eval runs. | pro |
| GET | /v1/wide-events | Wide-event stream (workload class, latency, match type) — Obs1. | pro |
| GET | /v1/fidelity | Mock Fidelity Score (F1) per service. | pro |
| GET | /v1/resources/{service_id} | Captured resource index for a service. Used by statechart heat-map overlays. | pro |
| GET | /v1/repair/proposals | AI mock-repair proposals queue (operator review surface). | pro |
| POST | /v1/repair/proposals/{id}/approve | Approve a repair proposal. The applier patches the mock library + reloads the agent. | pro |
| POST | /v1/repair/proposals/{id}/reject | Reject a repair proposal with an optional reason. | pro |