Reference

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 /settingsAPI 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/jobs

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

  • 404not_found
  • 422validation_error
  • 403tenant_isolation | feature_gated (the latter carries feature)
  • 409scrubbing_required
  • 500internal_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_id

Which 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 / minute
  • POST /v1/training/finetune — 3 / minute
  • POST /v1/transition/start — 5 / minute
  • POST /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:

MethodPathPurposeTier
GET/healthSynchronous probe of agent + inference + database; ~hundreds of ms when deps are slow.all
GET/v1/health/summaryCached state, refreshed every 5s in the background. Returns immediately even during a dependency outage; each component carries stale_for_seconds.all
GET/metricsPrometheus 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).

MethodPathPurposeTier
POST/v1/modeSet global mode. Body: { mode: 'learn' | 'mock' | 'passthrough' }all
GET/v1/modeGet 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.

MethodPathPurposeTier
GET/servicesList registered services for the tenant.all
POST/servicesRegister 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

MethodPathPurposeTier
GET/mocksList mock entries. Filters: method, service_id. Paginated via limit + offset.all
GET/mocks/exportExport the library. Defaults to scrubbed_only=true; the unscrubbed export is gated.all
GET/mocks/unmatchedRequests that found no match while in MOCK mode (operator surface for filling coverage gaps).all
GET/mocks/coverageCoverage stats per endpoint.all
POST/mocks/import/openapiCold-start from an OpenAPI 3.x spec.all
POST/mocks/import/postmanCold-start from a Postman v2 collection.all
POST/mocks/import/wiremockCold-start from a WireMock mappings folder (single JSON or zip).all
DELETE/mocks/{id}Delete one entry.all
DELETE/mocksClear 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.

MethodPathPurposeTier
GET/v1/trafficBrowse recorded traffic for the tenant.all
POST/v1/traffic/normalizeNormalise recorded mocks: extract patterns + URI templates. Rate-limited (10/min).all
POST/v1/transition/startEnqueue a LEARN → MOCK pipeline. Accepts Idempotency-Key. Returns 202 + { job_id }. Rate-limited (5/min).all
GET/v1/transition/jobsList recent transition jobs for the tenant (newest first, max 20).all
GET/v1/transition/{job_id}/statusJob 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.

MethodPathPurposeTier
GET/v1/drift/servicesPer-service drift summary rows (count, max severity, affected endpoints, last_detected_at).pro
GET/v1/drift/eventsDrift 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}/acknowledgeMark a drift event acknowledged (optional reason_text).pro
GET/v1/drift/freshness-score0-100 freshness score for a service. Reads from the materialized view; falls back to inline compute when older than 5min.pro
GET/v1/drift/sparkline30-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.

MethodPathPurposeTier
GET/v1/statechartsCatalog: 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}/resourcesCaptured 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/statechartsCreate 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).

MethodPathPurposeTier
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}/replayReplay 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.

MethodPathPurposeTier
POST/training/sessionsCreate a training session draft.pro
GET/training/sessionsList training sessions for the tenant.pro
GET/training/sessions/{id}Session details + linked mocks + status.pro
POST/training/sessions/{id}/trainStart LoRA fine-tuning. Rate-limited (3/min).pro
GET/training/sessions/{id}/statusPoll training progress.pro
POST/training/sessions/{id}/activateRegister 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-promptGenerate a fine-tuning prompt preview from recorded patterns.pro

Operational

MethodPathPurposeTier
GET/v1/api-keysCurrent key prefix + metadata (never returns the raw key).all
POST/v1/api-keys/generateGenerate a new key. Raw key returned only in this response — store it now.all
DELETE/v1/api-keysRevoke the current key.all
GET/configRead merged config defaults + tenant overrides.all
PUT/config/{key}Upsert a tenant config override. Push to agent fires automatically.all
GET/v1/scrub/configPer-service PII / credential scrub rules.all
POST/v1/scrub/testDry-run a payload through the scrub pipeline (no persistence).all
GET/v1/activityRecent activity log (team-tier surface, Pro+ also).all
GET/v1/onboarding/status5-step onboarding checklist state. Drives the dashboard's helper pill.all
GET/insightsAggregated dashboard data (mocks count, drift count, fidelity, eval recents) for the /insights page.all
POST/v1/eval/runTrigger an on-demand Tier-0 eval for a service (~30s). Rate-limited (6/min).pro
GET/v1/eval/runsList recent eval runs.pro
GET/v1/wide-eventsWide-event stream (workload class, latency, match type) — Obs1.pro
GET/v1/fidelityMock 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/proposalsAI mock-repair proposals queue (operator review surface).pro
POST/v1/repair/proposals/{id}/approveApprove a repair proposal. The applier patches the mock library + reloads the agent.pro
POST/v1/repair/proposals/{id}/rejectReject a repair proposal with an optional reason.pro