Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Meza-dev/Ghostly/llms.txt

Use this file to discover all available pages before exploring further.

Ghostly ships with two authentication methods that cover every integration scenario: API key authentication for long-lived, non-interactive clients such as the CLI, MCP server, and SDK; and JWT Bearer authentication for the web dashboard and any browser client that needs to stream live run events. Most protected endpoints accept either method transparently — the server resolves the caller’s identity from whichever credential is present.

Method 1 — API Key (X-Api-Key header)

Pass your API key in the X-Api-Key header on every request:
curl http://localhost:4000/v1/runs \
  -H "X-Api-Key: <your-api-key>"
API keys are 64-character hex strings generated from 32 cryptographically random bytes. They are stored in the database and looked up on every request; the full key value is shown only once at creation time — after that, the listing endpoint returns only the first 8 characters followed by ••••••••. Generate an API key:
1

Via the dashboard

Open the Ghostly dashboard, navigate to Settings → API Keys, and click Create. Give the key a descriptive label (e.g. ci-pipeline or local-sdk), then copy the value shown — you won’t see it again.
2

Via the CLI

ghostly keygen
The key is written to ~/.ghostly/auth.json and printed to stdout. The local API server reads the same file to validate incoming requests.
Used by: CLI (ghostly), MCP server (@ghostly-io/cli), the @ghostly-io/client SDK, direct curl scripts, and any CI integration.
The @ghostly-io/client SDK sets the X-Api-Key header automatically on every request — you only need to pass the key once at construction time. See the SDK usage section below.

Method 2 — JWT Bearer Token (Authorization header)

Obtain a JSON Web Token by calling POST /v1/auth/login, then attach it as a Bearer credential:
Authorization: Bearer <token>
Tokens are signed with HMAC-SHA256 using the JWT_SECRET environment variable (default: "ghostly-secret") and are valid for 7 days from the time of issue. The payload contains the user’s id, email, role, issued-at (iat), and expiry (exp) timestamps. Used by: web dashboard, and any client consuming the SSE event-stream endpoint.
The SSE run-events endpoint (GET /v1/runs/:id/events/stream) accepts the JWT via a ?token= query parameter instead of the Authorization header. This is necessary because the browser’s native EventSource API does not support setting custom headers. Example URL:
http://localhost:4000/v1/runs/abc123/events/stream?token=<jwt>
The default JWT_SECRET value ("ghostly-secret") and the seed admin credentials (admin@ghostly.local / admin123) are provided for local development convenience only. Set a strong JWT_SECRET and change the admin password before exposing the API on any network interface reachable by other machines.

Endpoints

POST /v1/auth/login

Exchange an email address and password for a signed JWT. This endpoint is public — no prior authentication is required. Request body
email
string
required
The user’s email address.
password
string
required
The user’s plaintext password. Ghostly hashes passwords at rest; the value is never stored or logged.
curl -X POST http://localhost:4000/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "admin@ghostly.local",
    "password": "admin123"
  }'
Responses

POST /v1/auth/register

Create a new user account. Requires an authenticated admin caller — only users with role: "admin" may register new accounts. Authentication: JWT Bearer or API key (admin role required) Request body
email
string
required
The email address for the new account. Must be unique — returns 409 if already registered.
password
string
required
The plaintext password for the new account. Ghostly hashes it immediately on receipt.
role
"admin" | "member"
Role to assign. Defaults to "member" if omitted or any unrecognised value is passed.
curl -X POST http://localhost:4000/v1/auth/register \
  -H "Authorization: Bearer <admin-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "alice@example.com",
    "password": "supersecret",
    "role": "member"
  }'
Responses

GET /v1/auth/me

Return the identity of the currently authenticated user. Useful for verifying that a token or API key is valid and resolving the associated user record. Authentication: JWT Bearer or API key
curl http://localhost:4000/v1/auth/me \
  -H "Authorization: Bearer <token>"
Responses

@ghostly-io/client SDK

The official TypeScript client handles API key authentication for you. Construct a GhostlyClient with your baseUrl and apiKey — every subsequent SDK call will include the X-Api-Key header automatically.
import { GhostlyClient } from "@ghostly-io/client";

const client = new GhostlyClient({
  baseUrl: "http://localhost:4000",
  apiKey: process.env.GHOSTLY_API_KEY!,
});

// All methods use X-Api-Key internally — no extra config needed
const projects = await client.listProjects();
const run = await client.startRun({
  baseUrl: "https://example.com",
  project: projects[0].id,
  steps: [
    { action: "goto", url: "https://example.com" },
    { action: "snapshot" },
  ],
});

console.log("Run started:", run.id);
For SSE event streaming you still need a JWT, since EventSource cannot carry custom headers. Use client.runEventsUrl() to build the correct URL with the token embedded as a query parameter:
const token = "<jwt-from-login>";
const url = client.runEventsUrl(run.id, token);

const source = new EventSource(url);
source.onmessage = (e) => {
  const event = JSON.parse(e.data);
  console.log(event.type, event.payload);
};

API Key Management

API keys are managed through the /v1/api-keys routes, which require JWT authentication. Refer to the table below for a quick summary, and see the API Keys reference for full endpoint documentation.
MethodPathDescription
GET/v1/api-keysList all keys (values are masked after the first 8 chars)
POST/v1/api-keysCreate a new key — full value returned only at creation
DELETE/v1/api-keys/:idPermanently delete a key
The full API key is returned only in the POST /v1/api-keys response. Store it immediately in a secrets manager or .env file — you cannot retrieve the full value again from the listing endpoint.

Build docs developers (and LLMs) love