Ghost Harness API

Version 1.0.0

This is Ghost's harness surface: the agent loop, offered as a service.

Ghost is also a product in its own right, with its own web and mobile clients. Those clients use the same core through the same contract. What this document defines is the boundary a *separate* product binds to, so it can delegate execution to Ghost without reading Ghost's source.

## Model

A tenant owns everything. An agent is a configured persona plus a tool allowlist. A session is a stateful thread of interaction with one agent. Posting a message into a session starts a run — one pass of the agent loop, which may call tools, may suspend for approval, and eventually produces output.

Ghost's internal storage calls a session a conversation and a run a job. Those names are not part of this contract and may change.

## Two response modes

Every run is created the same way. How you read it differs:

- mode: "async" (default) returns 202 with a queued run. Poll GET /v1/runs/{runId} or subscribe to GET /v1/runs/{runId}/events. - mode: "sync" blocks until the run reaches a terminal state and returns 200 with the completed run and its output. Bounded by timeout_ms; exceeding it returns 504 and the run continues in the background.

Interactive UIs want async plus the event stream. Server-to-server callers usually want sync.

## Suspension

A run can stop without being finished or failed. Two reasons, both durable across process restarts and both held indefinitely:

- awaiting_approval — the agent called a tool the tenant has gated. Resolve with POST /v1/runs/{runId}/approval. Approving resumes the loop from where it paused; denying feeds the refusal back to the model, which acknowledges it and stops. - awaiting_token_refresh — the caller's session token expired mid-run. Resolve with POST /v1/runs/{runId}/resume carrying a fresh token. The run is not failed: a run suspended for approval can wait hours, and losing that work to a routine token rollover would be a bug, not a policy.

The two are distinct statuses so a caller can tell "waiting on a human" from "send me a fresh credential" without inspecting anything else.

## Authentication

Three roles, resolved before any per-endpoint permission check:

| Role | Caller | Credential | |---|---|---| | user | Ghost's own web and mobile clients | UserJwt | | tenant | an integrating product, via this API | TenantApiKey | | control | provisioning | ControlPlaneKey |

Role authorization runs first. A tenant caller can never reach a control-plane operation and vice versa, even if a scope check is wrong.

Ghost issues the tenant credential. A key is minted through the control-plane key-management endpoints, returned exactly once at creation, and stored only as a hash. Revoking a key takes effect on the next request: there is no window during which a withdrawn credential still works.

A key may carry an optional expiry. If it does, a run started with that key stops taking new actions once it passes, and suspends as awaiting_token_refresh rather than failing — resume it with a fresh key via POST /v1/runs/{runId}/resume. Keys without an expiry, which is the default, never enter that state.

## Errors

Every non-2xx response is an Error object. error.request_id correlates with server logs and should be included in any bug report.

## Compatibility

This is a published interface. Within a major version, changes are additive: new endpoints, new optional request fields, new response fields, new enum members. Clients must ignore unknown response fields and tolerate unknown enum members. Anything else requires a new major version and a new path prefix.

Base URL.

Every example on this page uses $GHOST_API. Set it once:

export GHOST_API="https://api.ghostagents.co"
Keys are server-side only.

This API refuses cross-origin browser requests that carry X-Ghost-Api-Key — the header is not in its CORS allow-list, so a browser will block the request before it is sent. Call it from your server. Manage keys, watch usage and read your request log in the console.

Health

Liveness and readiness. Unauthenticated.

GET /healthz

Liveness probe

Returns 200 whenever the process is running. Never touches the database.

Responses
CodeBody
200 Liveness Process is alive.
Example
curl "$GHOST_API/healthz"
GET /readyz

Readiness probe

Returns 200 only when every dependency needed to serve traffic is reachable. Returns 503 with per-dependency detail otherwise. Load balancers should route on this, not /healthz.

Responses
CodeBody
200 Readiness All dependencies healthy.
503 Readiness One or more dependencies unavailable.
Example
curl "$GHOST_API/readyz"
GET /openapi.json

Fetch this API's OpenAPI document

The contract itself, as OpenAPI 3.1 JSON, so a client can generate against the running instance rather than a copy that may have drifted from it.

Unauthenticated: a published interface is not a secret, and requiring a credential to read it would mean a caller needs working auth before it can generate the code that does auth.

Responses
CodeBody
200 object The OpenAPI 3.1 document.
Example
curl "$GHOST_API/openapi.json"

Tenants

Tenant provisioning.

GET /v1/tenants Control plane

List tenants

Control plane only.

Parameters
NameInTypeNotes
limit query integer optional Page size.
cursor query string optional Opaque cursor from a previous response's next_cursor.
Responses
CodeBody
200 TenantList A page of tenants.
401 Error Missing or invalid credential.
403 Error Authenticated, but this credential type may not perform this operation. Note that cross-tenant resource access returns 404, not 403.
Example
curl "$GHOST_API/v1/tenants" \
  -H 'X-Ghost-Control-Key: …'
POST /v1/tenants Control plane

Create a tenant

Control plane only. A session token cannot create tenants.

Request body

TenantCreate

Parameters
NameInTypeNotes
Idempotency-Key header string optional Client-generated key making this request safe to retry. Replaying a key within 24 hours returns the original response instead of acting twice. Reusing a key with a different body returns 409.
Responses
CodeBody
201 Tenant Tenant created.
400 Error Malformed request.
401 Error Missing or invalid credential.
403 Error Authenticated, but this credential type may not perform this operation. Note that cross-tenant resource access returns 404, not 403.
409 Error The resource is not in a state that permits this operation.
422 Error Well-formed but semantically invalid. details lists each failure.
Example
curl -X POST "$GHOST_API/v1/tenants" \
  -H 'X-Ghost-Control-Key: …' \
  -H 'Content-Type: application/json' \
  -d '{ … }'
GET /v1/tenants/{tenantId} Control planeAPI key

Get a tenant

A session token may read only its own tenant. Any other tenantId returns 404, not 403 — a tenant must not be able to probe for the existence of other tenants.

Parameters
NameInTypeNotes
tenantId path string (uuid) required
Responses
CodeBody
200 Tenant The tenant.
401 Error Missing or invalid credential.
404 Error No such resource in this tenant. Also returned when a resource exists but belongs to another tenant, so existence cannot be probed.
Example
curl "$GHOST_API/v1/tenants/{tenantId}" \
  -H 'X-Ghost-Api-Key: ghost_sk_…'
PATCH /v1/tenants/{tenantId} Control planeAPI key

Update a tenant

Request body

TenantUpdate

Parameters
NameInTypeNotes
tenantId path string (uuid) required
Responses
CodeBody
200 Tenant Updated tenant.
400 Error Malformed request.
401 Error Missing or invalid credential.
404 Error No such resource in this tenant. Also returned when a resource exists but belongs to another tenant, so existence cannot be probed.
422 Error Well-formed but semantically invalid. details lists each failure.
Example
curl -X PATCH "$GHOST_API/v1/tenants/{tenantId}" \
  -H 'X-Ghost-Api-Key: ghost_sk_…' \
  -H 'Content-Type: application/json' \
  -d '{ … }'
DELETE /v1/tenants/{tenantId} Control plane

Delete a tenant

Control plane only. Irreversible. Cancels running runs, revokes all API keys, and destroys tenant data including credentials.

Parameters
NameInTypeNotes
tenantId path string (uuid) required
Responses
CodeBody
204 Tenant deleted.
401 Error Missing or invalid credential.
403 Error Authenticated, but this credential type may not perform this operation. Note that cross-tenant resource access returns 404, not 403.
404 Error No such resource in this tenant. Also returned when a resource exists but belongs to another tenant, so existence cannot be probed.
Example
curl -X DELETE "$GHOST_API/v1/tenants/{tenantId}" \
  -H 'X-Ghost-Control-Key: …'
GET /v1/tenants/{tenantId}/keys Control plane

List a tenant's API keys

Metadata only. Nothing in this response can reconstruct a key, which is what makes listing safe to expose at all. Revoked keys are included, so an audit can see what once existed.

Parameters
NameInTypeNotes
tenantId path string (uuid) required
Responses
CodeBody
200 object The tenant's keys.
401 Error Missing or invalid credential.
403 Error Authenticated, but this credential type may not perform this operation. Note that cross-tenant resource access returns 404, not 403.
404 Error No such resource in this tenant. Also returned when a resource exists but belongs to another tenant, so existence cannot be probed.
Example
curl "$GHOST_API/v1/tenants/{tenantId}/keys" \
  -H 'X-Ghost-Control-Key: …'
POST /v1/tenants/{tenantId}/keys Control plane

Mint an API key for a tenant

Returns the key in plaintext. This is the only response that will ever contain it: Ghost stores a hash and a display prefix, so a key that is lost must be revoked and replaced rather than recovered.

Control-plane only. A tenant cannot mint its own keys — a stolen key would otherwise be able to issue itself a replacement and survive the revocation of the key that was noticed.

expires_at is optional and must be in the future. A key that expires gives runs started with it a credential horizon: past it the agent stops taking new actions and the run suspends as awaiting_token_refresh instead of failing.

Request body

ApiKeyCreate

Parameters
NameInTypeNotes
tenantId path string (uuid) required
Idempotency-Key header string optional Client-generated key making this request safe to retry. Replaying a key within 24 hours returns the original response instead of acting twice. Reusing a key with a different body returns 409.
Responses
CodeBody
201 ApiKeyWithSecret Key created. The plaintext is in this response only.
401 Error Missing or invalid credential.
403 Error Authenticated, but this credential type may not perform this operation. Note that cross-tenant resource access returns 404, not 403.
404 Error No such resource in this tenant. Also returned when a resource exists but belongs to another tenant, so existence cannot be probed.
422 Error Well-formed but semantically invalid. details lists each failure.
Example
curl -X POST "$GHOST_API/v1/tenants/{tenantId}/keys" \
  -H 'X-Ghost-Control-Key: …' \
  -H 'Content-Type: application/json' \
  -d '{ … }'
DELETE /v1/tenants/{tenantId}/keys/{keyId} Control plane

Revoke an API key

Takes effect on the next request. The row is marked rather than deleted, so an audit of what could have made a given call survives the revocation.

Idempotent: revoking an already-revoked key reports the key as it stands rather than erroring, because "ensure this key cannot be used" is satisfied either way.

Parameters
NameInTypeNotes
tenantId path string (uuid) required
keyId path string (uuid) required
Responses
CodeBody
200 ApiKey The revoked key.
401 Error Missing or invalid credential.
403 Error Authenticated, but this credential type may not perform this operation. Note that cross-tenant resource access returns 404, not 403.
404 Error No such resource in this tenant. Also returned when a resource exists but belongs to another tenant, so existence cannot be probed.
Example
curl -X DELETE "$GHOST_API/v1/tenants/{tenantId}/keys/{keyId}" \
  -H 'X-Ghost-Control-Key: …'

Agents

Agent configuration within a tenant.

GET /v1/agents API keySession

List agents

Parameters
NameInTypeNotes
limit query integer optional Page size.
cursor query string optional Opaque cursor from a previous response's next_cursor.
kind query "standard" | "meta" optional
status query "draft" | "active" | "paused" optional
Responses
CodeBody
200 AgentList A page of agents.
401 Error Missing or invalid credential.
Example
curl "$GHOST_API/v1/agents" \
  -H 'X-Ghost-Api-Key: ghost_sk_…'
POST /v1/agents API keySession

Create an agent

Request body

AgentCreate

Parameters
NameInTypeNotes
Idempotency-Key header string optional Client-generated key making this request safe to retry. Replaying a key within 24 hours returns the original response instead of acting twice. Reusing a key with a different body returns 409.
Responses
CodeBody
201 Agent Agent created.
400 Error Malformed request.
401 Error Missing or invalid credential.
422 Error Well-formed but semantically invalid. details lists each failure.
Example
curl -X POST "$GHOST_API/v1/agents" \
  -H 'X-Ghost-Api-Key: ghost_sk_…' \
  -H 'Content-Type: application/json' \
  -d '{ … }'
GET /v1/agents/{agentId} API keySession

Get an agent

Parameters
NameInTypeNotes
agentId path string (uuid) required
Responses
CodeBody
200 Agent The agent.
401 Error Missing or invalid credential.
404 Error No such resource in this tenant. Also returned when a resource exists but belongs to another tenant, so existence cannot be probed.
Example
curl "$GHOST_API/v1/agents/{agentId}" \
  -H 'X-Ghost-Api-Key: ghost_sk_…'
PATCH /v1/agents/{agentId} API keySession

Update an agent

Request body

AgentUpdate

Parameters
NameInTypeNotes
agentId path string (uuid) required
Responses
CodeBody
200 Agent Updated agent.
400 Error Malformed request.
401 Error Missing or invalid credential.
404 Error No such resource in this tenant. Also returned when a resource exists but belongs to another tenant, so existence cannot be probed.
422 Error Well-formed but semantically invalid. details lists each failure.
Example
curl -X PATCH "$GHOST_API/v1/agents/{agentId}" \
  -H 'X-Ghost-Api-Key: ghost_sk_…' \
  -H 'Content-Type: application/json' \
  -d '{ … }'
DELETE /v1/agents/{agentId} API keySession

Delete an agent

Fails with 409 if the agent has running or suspended runs. Cancel them first.

Parameters
NameInTypeNotes
agentId path string (uuid) required
Responses
CodeBody
204 Agent deleted.
401 Error Missing or invalid credential.
404 Error No such resource in this tenant. Also returned when a resource exists but belongs to another tenant, so existence cannot be probed.
409 Error The resource is not in a state that permits this operation.
Example
curl -X DELETE "$GHOST_API/v1/agents/{agentId}" \
  -H 'X-Ghost-Api-Key: ghost_sk_…'

Sessions

Session lifecycle.

GET /v1/sessions API keySession

List sessions

Parameters
NameInTypeNotes
limit query integer optional Page size.
cursor query string optional Opaque cursor from a previous response's next_cursor.
agent_id query string (uuid) optional
status query "active" | "archived" optional
Responses
CodeBody
200 SessionList A page of sessions, most recently active first.
401 Error Missing or invalid credential.
Example
curl "$GHOST_API/v1/sessions" \
  -H 'X-Ghost-Api-Key: ghost_sk_…'
POST /v1/sessions API keySession

Create a session

Request body

SessionCreate

Parameters
NameInTypeNotes
Idempotency-Key header string optional Client-generated key making this request safe to retry. Replaying a key within 24 hours returns the original response instead of acting twice. Reusing a key with a different body returns 409.
Responses
CodeBody
201 Session Session created.
400 Error Malformed request.
401 Error Missing or invalid credential.
404 Error The referenced agent does not exist in this tenant.
422 Error Well-formed but semantically invalid. details lists each failure.
Example
curl -X POST "$GHOST_API/v1/sessions" \
  -H 'X-Ghost-Api-Key: ghost_sk_…' \
  -H 'Content-Type: application/json' \
  -d '{ … }'
GET /v1/sessions/{sessionId} API keySession

Get a session

Parameters
NameInTypeNotes
sessionId path string (uuid) required
Responses
CodeBody
200 Session The session.
401 Error Missing or invalid credential.
404 Error No such resource in this tenant. Also returned when a resource exists but belongs to another tenant, so existence cannot be probed.
Example
curl "$GHOST_API/v1/sessions/{sessionId}" \
  -H 'X-Ghost-Api-Key: ghost_sk_…'
PATCH /v1/sessions/{sessionId} API keySession

Update a session

Title and archived state only. Message history is immutable.

Request body

SessionUpdate

Parameters
NameInTypeNotes
sessionId path string (uuid) required
Responses
CodeBody
200 Session Updated session.
400 Error Malformed request.
401 Error Missing or invalid credential.
404 Error No such resource in this tenant. Also returned when a resource exists but belongs to another tenant, so existence cannot be probed.
422 Error Well-formed but semantically invalid. details lists each failure.
Example
curl -X PATCH "$GHOST_API/v1/sessions/{sessionId}" \
  -H 'X-Ghost-Api-Key: ghost_sk_…' \
  -H 'Content-Type: application/json' \
  -d '{ … }'
DELETE /v1/sessions/{sessionId} API keySession

Delete a session

Deletes the session and its messages. Fails with 409 if a run is currently active or suspended.

Parameters
NameInTypeNotes
sessionId path string (uuid) required
Responses
CodeBody
204 Session deleted.
401 Error Missing or invalid credential.
404 Error No such resource in this tenant. Also returned when a resource exists but belongs to another tenant, so existence cannot be probed.
409 Error The resource is not in a state that permits this operation.
Example
curl -X DELETE "$GHOST_API/v1/sessions/{sessionId}" \
  -H 'X-Ghost-Api-Key: ghost_sk_…'

Messages

Message ingress and egress.

GET /v1/sessions/{sessionId}/messages API keySession

List messages in a session

Message egress. Chronological, oldest first.

Parameters
NameInTypeNotes
sessionId path string (uuid) required
limit query integer optional Page size.
cursor query string optional Opaque cursor from a previous response's next_cursor.
role query "user" | "agent" | "card" optional
Responses
CodeBody
200 MessageList A page of messages.
401 Error Missing or invalid credential.
404 Error No such resource in this tenant. Also returned when a resource exists but belongs to another tenant, so existence cannot be probed.
Example
curl "$GHOST_API/v1/sessions/{sessionId}/messages" \
  -H 'X-Ghost-Api-Key: ghost_sk_…'
POST /v1/sessions/{sessionId}/messages API keySession

Post a message and start a run

Message ingress. The content is treated as data throughout: it is never interpolated into a position where the model can read it as an instruction, regardless of what it contains.

Fails with 409 if the session already has an active or suspended run. One run per session at a time.

Request body

MessageCreate

Parameters
NameInTypeNotes
sessionId path string (uuid) required
Idempotency-Key header string optional Client-generated key making this request safe to retry. Replaying a key within 24 hours returns the original response instead of acting twice. Reusing a key with a different body returns 409.
Responses
CodeBody
200 Run Returned when mode is sync. The run reached a terminal state. A run that ended at awaiting_approval is also returned here — check status rather than assuming success.
202 Run Returned when mode is async. The run is queued.
400 Error Malformed request.
401 Error Missing or invalid credential.
402 Error The tenant's allowance is exhausted. The circuit breaker is open.
404 Error No such resource in this tenant. Also returned when a resource exists but belongs to another tenant, so existence cannot be probed.
409 Error The resource is not in a state that permits this operation.
422 Error Well-formed but semantically invalid. details lists each failure.
429 Error Too many requests.
504 Error mode: sync exceeded timeout_ms. The run was not cancelled and continues in the background; error.run_id identifies it.
Example
curl -X POST "$GHOST_API/v1/sessions/{sessionId}/messages" \
  -H 'X-Ghost-Api-Key: ghost_sk_…' \
  -H 'Content-Type: application/json' \
  -d '{ … }'
GET /v1/sessions/{sessionId}/messages/{messageId} API keySession

Get a message

Parameters
NameInTypeNotes
sessionId path string (uuid) required
messageId path string (uuid) required
Responses
CodeBody
200 Message The message.
401 Error Missing or invalid credential.
404 Error No such resource in this tenant. Also returned when a resource exists but belongs to another tenant, so existence cannot be probed.
Example
curl "$GHOST_API/v1/sessions/{sessionId}/messages/{messageId}" \
  -H 'X-Ghost-Api-Key: ghost_sk_…'

Runs

Run status, events, approval, and cancellation.

GET /v1/runs API keySession

List runs

Parameters
NameInTypeNotes
limit query integer optional Page size.
cursor query string optional Opaque cursor from a previous response's next_cursor.
session_id query string (uuid) optional
agent_id query string (uuid) optional
status query "queued" | "running" | "awaiting_approval" | "awaiting_token_refresh" | "succeeded" | "failed" | "cancelled" optional
Responses
CodeBody
200 RunList A page of runs, newest first.
401 Error Missing or invalid credential.
Example
curl "$GHOST_API/v1/runs" \
  -H 'X-Ghost-Api-Key: ghost_sk_…'
GET /v1/runs/{runId} API keySession

Get a run

Parameters
NameInTypeNotes
runId path string (uuid) required
Responses
CodeBody
200 Run The run.
401 Error Missing or invalid credential.
404 Error No such resource in this tenant. Also returned when a resource exists but belongs to another tenant, so existence cannot be probed.
Example
curl "$GHOST_API/v1/runs/{runId}" \
  -H 'X-Ghost-Api-Key: ghost_sk_…'
GET /v1/runs/{runId}/events API keySession

Stream run events

Server-sent events for the streaming response mode. Emits the run's history from after_seq and then streams live until the run reaches a terminal state, at which point the server closes the connection.

Each SSE data: frame is one JSON-encoded RunEvent. Reconnect with after_seq set to the last seq you processed; events are durable and replayable, so no events are lost across a reconnect.

A terminal event is always emitted — complete, error, cancelled, or awaiting_approval. Clients should treat connection close without a terminal event as a transport failure and reconnect.

Parameters
NameInTypeNotes
runId path string (uuid) required
after_seq query integer optional Resume after this sequence number. Omit to receive from the beginning.
Responses
CodeBody
200 string An SSE stream of RunEvent frames.
401 Error Missing or invalid credential.
404 Error No such resource in this tenant. Also returned when a resource exists but belongs to another tenant, so existence cannot be probed.
Example
curl "$GHOST_API/v1/runs/{runId}/events" \
  -H 'X-Ghost-Api-Key: ghost_sk_…'
POST /v1/runs/{runId}/approval API keySession

Approve or deny a suspended run

Valid only while the run is awaiting_approval; any other status returns 409.

Approving executes the pending tool and resumes the loop. Denying feeds the refusal back to the model as the tool's result, so it can acknowledge and stop rather than silently retrying.

Returns the resumed run. In sync mode the call blocks until the resumed run reaches a terminal state.

Request body

ApprovalDecision

Parameters
NameInTypeNotes
runId path string (uuid) required
Idempotency-Key header string optional Client-generated key making this request safe to retry. Replaying a key within 24 hours returns the original response instead of acting twice. Reusing a key with a different body returns 409.
Responses
CodeBody
200 Run Decision applied; run resumed. Shape depends on mode.
202 Run Decision applied; resumed run is queued.
400 Error Malformed request.
401 Error Missing or invalid credential.
402 Error The tenant's allowance is exhausted. The circuit breaker is open.
404 Error No such resource in this tenant. Also returned when a resource exists but belongs to another tenant, so existence cannot be probed.
409 Error The resource is not in a state that permits this operation.
422 Error Well-formed but semantically invalid. details lists each failure.
504 Error mode: sync exceeded timeout_ms. The run was not cancelled and continues in the background; error.run_id identifies it.
Example
curl -X POST "$GHOST_API/v1/runs/{runId}/approval" \
  -H 'X-Ghost-Api-Key: ghost_sk_…' \
  -H 'Content-Type: application/json' \
  -d '{ … }'
POST /v1/runs/{runId}/resume API keySession

Resume a run suspended for a token refresh

Valid only while the run is awaiting_token_refresh; any other status returns 409. Use POST /v1/runs/{runId}/approval for the approval case.

Carry a fresh, unexpired TenantApiKey on the request. Ghost validates it, checks its tenant claim matches the run's tenant, and continues the loop from where it paused. The run's accumulated state, turn count, and usage are preserved — this is a continuation, not a retry.

There is no request body: the credential in the header is the entire input.

Parameters
NameInTypeNotes
runId path string (uuid) required
Idempotency-Key header string optional Client-generated key making this request safe to retry. Replaying a key within 24 hours returns the original response instead of acting twice. Reusing a key with a different body returns 409.
Responses
CodeBody
200 Run Run resumed and reached a terminal state (sync semantics).
202 Run Run resumed and is queued.
401 Error Missing or invalid credential.
402 Error The tenant's allowance is exhausted. The circuit breaker is open.
404 Error No such resource in this tenant. Also returned when a resource exists but belongs to another tenant, so existence cannot be probed.
409 Error The resource is not in a state that permits this operation.
Example
curl -X POST "$GHOST_API/v1/runs/{runId}/resume" \
  -H 'X-Ghost-Api-Key: ghost_sk_…'
POST /v1/runs/{runId}/cancel API keySession

Cancel a run

Requests cancellation of a queued, running, or suspended run. Cancellation is cooperative: an in-flight tool call is allowed to finish, so the run may take a moment to reach cancelled. Work already performed is still metered.

Cancelling an already-terminal run returns 409.

Parameters
NameInTypeNotes
runId path string (uuid) required
Responses
CodeBody
202 Run Cancellation requested.
401 Error Missing or invalid credential.
404 Error No such resource in this tenant. Also returned when a resource exists but belongs to another tenant, so existence cannot be probed.
409 Error The resource is not in a state that permits this operation.
Example
curl -X POST "$GHOST_API/v1/runs/{runId}/cancel" \
  -H 'X-Ghost-Api-Key: ghost_sk_…'

Tools

Tool registry and direct invocation.

GET /v1/agents/{agentId}/tools API keySession

List tools this agent may call

The effective allowlist: the intersection of the tenant's enabled tools and this agent's grants. This is the authoritative answer to "can this agent call X" and is enforced at dispatch, not only at prompt assembly.

Parameters
NameInTypeNotes
agentId path string (uuid) required
Responses
CodeBody
200 ToolList Tools available to this agent.
401 Error Missing or invalid credential.
404 Error No such resource in this tenant. Also returned when a resource exists but belongs to another tenant, so existence cannot be probed.
Example
curl "$GHOST_API/v1/agents/{agentId}/tools" \
  -H 'X-Ghost-Api-Key: ghost_sk_…'
PUT /v1/agents/{agentId}/tools API keySession

Replace this agent's tool allowlist

Full replacement, not a merge. Names not present in the tenant's tool registry are rejected with 422.

Request body

AgentToolAllowlist

Parameters
NameInTypeNotes
agentId path string (uuid) required
Responses
CodeBody
200 ToolList Updated allowlist.
400 Error Malformed request.
401 Error Missing or invalid credential.
404 Error No such resource in this tenant. Also returned when a resource exists but belongs to another tenant, so existence cannot be probed.
422 Error Well-formed but semantically invalid. details lists each failure.
Example
curl -X PUT "$GHOST_API/v1/agents/{agentId}/tools" \
  -H 'X-Ghost-Api-Key: ghost_sk_…' \
  -H 'Content-Type: application/json' \
  -d '{ … }'
GET /v1/tools API keySession

List tools available to this tenant

The tenant's tool registry. Every descriptor declares its owner (who defined it) and executor (what runs it) separately, so callers do not infer routing from naming conventions.

Parameters
NameInTypeNotes
limit query integer optional Page size.
cursor query string optional Opaque cursor from a previous response's next_cursor.
owner_kind query "core" | "composio" | "mcp" | "external" optional
Responses
CodeBody
200 ToolList A page of tool descriptors.
401 Error Missing or invalid credential.
Example
curl "$GHOST_API/v1/tools" \
  -H 'X-Ghost-Api-Key: ghost_sk_…'
GET /v1/tools/{toolName} API keySession

Get a tool descriptor

Parameters
NameInTypeNotes
toolName path string required
Responses
CodeBody
200 Tool The tool descriptor.
401 Error Missing or invalid credential.
404 Error No such resource in this tenant. Also returned when a resource exists but belongs to another tenant, so existence cannot be probed.
Example
curl "$GHOST_API/v1/tools/{toolName}" \
  -H 'X-Ghost-Api-Key: ghost_sk_…'
POST /v1/tools/{toolName}/invocations API keySession

Invoke a tool directly

Runs one tool outside any agent loop, using the calling tenant's credentials. Useful for testing a connection and for products that want Ghost's tool layer without its reasoning layer.

The tool must be on the tenant's allowlist, and on the agent's allowlist when agent_id is supplied. Direct invocation is metered like any other tool call.

Request body

ToolInvocationCreate

Parameters
NameInTypeNotes
toolName path string required
Idempotency-Key header string optional Client-generated key making this request safe to retry. Replaying a key within 24 hours returns the original response instead of acting twice. Reusing a key with a different body returns 409.
Responses
CodeBody
200 ToolInvocation The tool ran. A tool that failed on its own terms still returns 200 with ok: false — the HTTP status describes the invocation, not the tool's verdict.
400 Error Malformed request.
401 Error Missing or invalid credential.
402 Error The tenant's allowance is exhausted. The circuit breaker is open.
403 Error Tool exists but is not on the allowlist for this tenant or agent.
404 Error No such resource in this tenant. Also returned when a resource exists but belongs to another tenant, so existence cannot be probed.
422 Error Well-formed but semantically invalid. details lists each failure.
429 Error Too many requests.
Example
curl -X POST "$GHOST_API/v1/tools/{toolName}/invocations" \
  -H 'X-Ghost-Api-Key: ghost_sk_…' \
  -H 'Content-Type: application/json' \
  -d '{ … }'

Usage

Per-tenant metering.

GET /v1/usage API keySession

Get metered usage

Per-tenant accounting over a time window. Covers tokens, wall-clock seconds, and billable actions. When the tenant's allowance is exhausted, cost-bearing endpoints return 402 and in-flight runs are suspended by the circuit breaker.

Parameters
NameInTypeNotes
from query string (date-time) optional Inclusive start. Defaults to the start of the current billing period.
to query string (date-time) optional Exclusive end. Defaults to now.
group_by query "total" | "agent" | "day" optional
Responses
CodeBody
200 UsageReport Usage for the window.
400 Error Malformed request.
401 Error Missing or invalid credential.
Example
curl "$GHOST_API/v1/usage" \
  -H 'X-Ghost-Api-Key: ghost_sk_…'

Console

Self-service for a signed-in operator, used by the tenant console. Every route derives its tenant from the session and names none, so there is no path parameter that could point one tenant at another's data.

GET /v1/me/keys Session

List your own tenant's API keys

Metadata only — nothing here can reconstruct a key. Revoked keys are included so an operator can see what once existed.

Responses
CodeBody
200 object Your tenant's keys.
401 Error Missing or invalid credential.
403 Error Authenticated, but this credential type may not perform this operation. Note that cross-tenant resource access returns 404, not 403.
404 Error No such resource in this tenant. Also returned when a resource exists but belongs to another tenant, so existence cannot be probed.
Example
curl "$GHOST_API/v1/me/keys" \
  -H 'Authorization: Bearer <session-jwt>'
POST /v1/me/keys Session

Mint an API key for your own tenant

The self-service counterpart to POST /v1/tenants/{tenantId}/keys. Same result, different credential: this one is authorised by an operator's signed-in session rather than by the control plane, and it takes no tenant id — the tenant comes from the session.

A UserJwt only. Deliberately NOT callable with a TenantApiKey: the reason key management is privileged is that a *stolen key* must not be able to issue itself a replacement and outlive the revocation of the key that was noticed. A session is a different credential class, so allowing it here changes nothing about that threat — allowing a tenant key would reopen it.

As with the control-plane route, the plaintext appears in this response and nowhere else.

Request body

ApiKeyCreate

Parameters
NameInTypeNotes
Idempotency-Key header string optional Client-generated key making this request safe to retry. Replaying a key within 24 hours returns the original response instead of acting twice. Reusing a key with a different body returns 409.
Responses
CodeBody
201 ApiKeyWithSecret Key created. The plaintext is in this response only.
401 Error Missing or invalid credential.
403 Error Authenticated, but this credential type may not perform this operation. Note that cross-tenant resource access returns 404, not 403.
404 Error No such resource in this tenant. Also returned when a resource exists but belongs to another tenant, so existence cannot be probed.
422 Error Well-formed but semantically invalid. details lists each failure.
Example
curl -X POST "$GHOST_API/v1/me/keys" \
  -H 'Authorization: Bearer <session-jwt>' \
  -H 'Content-Type: application/json' \
  -d '{ … }'
DELETE /v1/me/keys/{keyId} Session

Revoke one of your own API keys

Takes effect on the next request. Idempotent: revoking an already-revoked key reports it as it stands rather than erroring.

A key belonging to another tenant reports 404 rather than 403, so the endpoint cannot be used to discover which key ids exist.

Parameters
NameInTypeNotes
keyId path string (uuid) required
Responses
CodeBody
200 ApiKey The revoked key.
401 Error Missing or invalid credential.
403 Error Authenticated, but this credential type may not perform this operation. Note that cross-tenant resource access returns 404, not 403.
404 Error No such resource in this tenant. Also returned when a resource exists but belongs to another tenant, so existence cannot be probed.
Example
curl -X DELETE "$GHOST_API/v1/me/keys/{keyId}" \
  -H 'Authorization: Bearer <session-jwt>'
GET /v1/me/billing Session

Balance, credit packs, and payment history

Everything the console's billing view needs in one read: the current balance, whether the spend breaker is open, what can be bought, and what has been paid for so far.

mode reports which Stripe account this deployment is wired to, or null when none is configured. A client should treat null as "purchasing is unavailable here" rather than offering a button that cannot work.

A UserJwt only. Purchasing is an act by a person, and a credential that could buy credits would turn a leaked key into a bill.

Responses
CodeBody
200 BillingOverview Billing overview for your tenant.
401 Error Missing or invalid credential.
403 Error Authenticated, but this credential type may not perform this operation. Note that cross-tenant resource access returns 404, not 403.
404 Error No such resource in this tenant. Also returned when a resource exists but belongs to another tenant, so existence cannot be probed.
Example
curl "$GHOST_API/v1/me/billing" \
  -H 'Authorization: Bearer <session-jwt>'
POST /v1/me/billing/checkout Session

Start a credit purchase

Creates a Stripe Checkout Session for one credit pack and returns the URL to send the buyer to.

This does not grant credits. It returns a place to pay. Credits are granted only when Stripe reports the payment settled, which arrives as a webhook and may be after the buyer is already back on this site. A client should re-read GET /v1/me/billing on return rather than assuming the balance has moved.

The card is saved against the tenant's Stripe customer for later off-session use. Nothing charges it today.

Where the buyer is returned to is derived from the Origin of this request and checked against Ghost's allowlist — it is not a parameter, so there is no redirect target a caller can choose.

Request body

CheckoutCreate

Responses
CodeBody
201 CheckoutSession A checkout session. Send the buyer to url.
400 Error Malformed request.
401 Error Missing or invalid credential.
403 Error Authenticated, but this credential type may not perform this operation. Note that cross-tenant resource access returns 404, not 403.
404 Error No such resource in this tenant. Also returned when a resource exists but belongs to another tenant, so existence cannot be probed.
422 Error Well-formed but semantically invalid. details lists each failure.
Example
curl -X POST "$GHOST_API/v1/me/billing/checkout" \
  -H 'Authorization: Bearer <session-jwt>' \
  -H 'Content-Type: application/json' \
  -d '{ … }'
GET /v1/logs API keySession

List API requests

The HTTP exchange log for your tenant, newest first. Distinct from /v1/runs, which reports what agents did: this reports what your integration *sent*, including the calls that never became a run because they were rejected. A 401 from a stale key or a 422 from a malformed body appears here and nowhere else.

Retained for retention_days (30). Older rows are pruned nightly.

Readable with either credential. A tenant key may read its own traffic — that discloses nothing the key does not already have.

Parameters
NameInTypeNotes
limit query integer optional
cursor query string optional Opaque cursor from next_cursor.
filter query "errors" optional errors narrows to responses with status >= 400. Any other value is rejected rather than ignored — a filter that silently does nothing is worse than no filter.
request_id query string optional Exact match on the id echoed in X-Request-Id and in every error envelope, for tracing one reported call.
caller query "api" | "console" | "all" optional Which credential made the call. api is your integration's traffic (an API key); console is the signed-in console reading this page, which polls from every screen and would otherwise drown out the calls you came here to look at. all returns both.
Responses
CodeBody
200 object A page of requests.
400 Error Malformed request.
401 Error Missing or invalid credential.
Example
curl "$GHOST_API/v1/logs" \
  -H 'X-Ghost-Api-Key: ghost_sk_…'
GET /v1/logs/summary API keySession

Summarise recent API requests

Totals over a window, for a console header. Computed over the most recent 5000 calls in the window so one busy tenant cannot make this an unbounded scan.

Parameters
NameInTypeNotes
hours query integer optional
caller query "api" | "console" | "all" optional As on /v1/logs. Pass the same value to both, or the summary will count traffic the list below it is hiding.
Responses
CodeBody
200 object Summary for the window.
400 Error Malformed request.
401 Error Missing or invalid credential.
Example
curl "$GHOST_API/v1/logs/summary" \
  -H 'X-Ghost-Api-Key: ghost_sk_…'

Schemas

The shapes referenced above.

SCHEMAErrorType

Stable, machine-readable failure class. Treat unknown members as a generic failure of the response's HTTP status class.

SCHEMAError
FieldTypeNotes
error object required
SCHEMALiveness
FieldTypeNotes
status string required
version string optional
SCHEMAReadiness
FieldTypeNotes
status "ok" | "degraded" required
version string optional
dependencies object[] required
SCHEMAPage
FieldTypeNotes
next_cursor string,null required Pass as cursor for the next page. null on the last page.
SCHEMATenant
FieldTypeNotes
id string (uuid) required
name string required
status "active" | "suspended" required suspended means the circuit breaker is open. Cost-bearing operations return 402 until the allowance is restored.
metadata object optional Opaque caller-defined data. Ghost never interprets this.
created_at string (date-time) required
updated_at string (date-time) required
SCHEMATenantCreate
FieldTypeNotes
name string required
metadata object optional
SCHEMATenantUpdate
FieldTypeNotes
name string optional
metadata object optional
SCHEMATenantList
SCHEMAAgentKind

standard agents do work. A meta agent orchestrates other agents in its tenant. Exactly one meta agent exists per tenant and it is created automatically.

SCHEMAAgentStatus
SCHEMAAgent
FieldTypeNotes
id string (uuid) required
tenant_id string (uuid) required
kind AgentKind required
name string required
role string,null optional
instructions string,null optional Operator-authored system instructions for this agent.
status AgentStatus required
timezone string optional
metadata object optional
created_at string (date-time) required
updated_at string (date-time) required
SCHEMAAgentCreate
FieldTypeNotes
name string required
role string,null optional
instructions string,null optional
status object optional
timezone string optional
tools ToolName[] optional Initial tool allowlist. Defaults to empty.
metadata object optional
SCHEMAAgentUpdate
FieldTypeNotes
name string optional
role string,null optional
instructions string,null optional
status AgentStatus optional
timezone string optional
metadata object optional
SCHEMAAgentList
SCHEMAAgentToolAllowlist
FieldTypeNotes
tools ToolName[] required
SCHEMASessionStatus
SCHEMASession
FieldTypeNotes
id string (uuid) required
tenant_id string (uuid) required
agent_id string (uuid) required
title string,null optional
status SessionStatus required
active_run_id string,null (uuid) optional The run currently occupying this session, if any. Non-null means posting a message will return 409.
last_message_at string,null (date-time) optional
metadata object optional
created_at string (date-time) required
updated_at string (date-time) required
SCHEMASessionCreate
FieldTypeNotes
agent_id string (uuid) required
title string,null optional
context SessionContextItem[] optional Facts the caller wants available to the agent for this session, and only this session. Ghost uses them but does not store them as memory, so the caller stays the single owner of each fact and the two systems cannot drift.

Use this for records the caller is authoritative for — a CRM contact, an account state, a campaign brief. Content is treated as data throughout, exactly like message content.

metadata object optional
SCHEMASessionContextItem
FieldTypeNotes
label string required Short name for this fact, e.g. contact or account_status.
content string required
provenance object optional
SCHEMASessionUpdate
FieldTypeNotes
title string,null optional
status SessionStatus optional
metadata object optional
SCHEMASessionList
SCHEMAMessageRole

user is inbound. agent is the substantive reply. card is a structured payload the client renders as a widget rather than prose.

SCHEMAMessageProvenanceKind

Where this content came from. Anything other than external_user is machine-originated. Content is wrapped so the model reads it as data regardless of value.

SCHEMAMessage
FieldTypeNotes
id string (uuid) required
session_id string (uuid) required
role MessageRole required
content string required Plain text for user and agent. For card, a JSON document whose shape is given by card_kind.
card_kind string,null optional Discriminator for card messages. Null otherwise.
provenance MessageProvenanceKind optional
run_id string,null (uuid) optional The run that produced this message, for agent output.
created_at string (date-time) required
SCHEMAMessageCreate
FieldTypeNotes
content string required Inbound content. Treated as data. Instruction-shaped text here cannot change the agent's tool allowlist or its system instructions.
mode "async" | "sync" optional async returns immediately with a queued run. sync blocks until the run reaches a terminal state.
timeout_ms integer optional Applies to sync only. Exceeding it returns 504.
provenance object optional
metadata object optional
SCHEMAMessageList
SCHEMAApiKeyCreate
FieldTypeNotes
name string required Operator-facing label, so a tenant holding several keys can tell them apart without being able to see any of them.
expires_at string (date-time) optional Optional, and must be in the future. When set, this is the credential horizon for every run the key starts.
SCHEMARequestLogEntry

One HTTP exchange against this API.

FieldTypeNotes
id string required Opaque, monotonically increasing. A string rather than a number because the underlying column is a 64-bit integer, which a JavaScript caller would silently round past 2^53.
request_id string required The id echoed in X-Request-Id and carried in every error envelope. Quote this when reporting a problem.
method string required
route string required The route template (/v1/sessions/{sessionId}), never the concrete path. Templates group; concrete paths would both explode cardinality and copy your resource ids into a log retained longer than they are.
status integer required
latency_ms integer required
api_key_id string (uuid) | null optional Which key made the call. Null for a first-party session, and null once the key has been deleted — revoking a key does not erase what it did.
principal_role "tenant" | "user" | "control" | null optional Null when the request failed before authenticating.
error_type string | null optional The error envelope's type on failure, so failures can be grouped by cause without parsing messages. Null on success.
created_at string (date-time) required
SCHEMAApiKey

A key's metadata. Deliberately contains nothing from which the key itself could be reconstructed.

FieldTypeNotes
id string (uuid) required
tenant_id string (uuid) required
name string required
key_prefix string required The leading, non-secret portion — enough to identify a key in a list or a log line, nowhere near enough to use one.
expires_at string (date-time) | null optional
last_used_at string (date-time) | null optional Best-effort. Good for spotting an unused key, not a billing record.
revoked_at string (date-time) | null optional Set rather than deleted, so an audit survives the revocation.
created_at string (date-time) required
SCHEMAApiKeyWithSecret
SCHEMACreditPack

A fixed amount of credits at a fixed price. Packs rather than arbitrary amounts: there is no amount to validate, the ledger stays readable, and a client has something to render without inventing a currency input.

FieldTypeNotes
id string required Pass this to createOwnCheckout.
amount_cents integer required What is charged, in cents.
credits integer required What lands in the wallet. Stated rather than derived from the price: the packs discount by volume, so credits and price are deliberately not proportional.
label string required
SCHEMAPaymentRecord

One settled payment. Written by the webhook when money arrived, so a row here means credits were granted — not that a checkout was started.

FieldTypeNotes
id string (uuid) required
amount_cents integer required
currency string required
credits integer required What this payment bought, recorded at the time. A later change to the credit rate cannot retroactively restate an old payment.
livemode boolean required False for a test-mode payment, which is real history but not real money.
created_at string (date-time) required
SCHEMABillingOverview
FieldTypeNotes
balance integer required Credits currently held.
breaker_open boolean required True when the tenant is suspended and cost-bearing calls are returning 402. The same flag UsageReport.allowance reports.
mode "test" | "live" | null required Which Stripe account this deployment is wired to. Null means purchasing is not configured here.
packs CreditPack[] required
payments PaymentRecord[] required The 20 most recent settled payments, newest first.
SCHEMACheckoutCreate
FieldTypeNotes
pack string required A CreditPack.id from getOwnBilling.
SCHEMACheckoutSession
FieldTypeNotes
url string (uri) required Stripe-hosted. Send the buyer here; do not embed it, and do not treat reaching it as a purchase.
expires_at string (date-time) | null required When the session stops being usable.
SCHEMARunStatus

Terminal states are succeeded, failed, and cancelled.

The two awaiting_* states are suspensions, not endings: the run holds its state indefinitely and survives process restarts. They are separate statuses so a caller can tell "waiting on a human" from "send me a fresh credential" without inspecting anything else.

SCHEMARunStopReason

Why the loop stopped. incomplete means the agent ran out of turns or stated an intention it never carried out — the output is honest about this rather than presenting a promise as a result.

SCHEMAPendingApproval

The gated tool call a suspended run is waiting on.

FieldTypeNotes
tool ToolName required
arguments object required
reason string optional Why approval is required.
SCHEMARunUsage
FieldTypeNotes
input_tokens integer required
output_tokens integer required
tool_calls integer required
duration_ms integer required
cost number,null optional Billable cost in the tenant's accounting unit.
SCHEMARun
FieldTypeNotes
id string (uuid) required
tenant_id string (uuid) required
session_id string (uuid) required
agent_id string (uuid) required
status RunStatus required
stop_reason RunStopReason | null optional Set once the run reaches a terminal state.
turns integer optional Loop turns consumed.
turn_cap integer optional Hard ceiling for this run.
output string,null optional The agent's final substantive text. Null while the run is non-terminal, and null when the run ended without producing prose.
messages Message[] optional Messages this run wrote. Present in sync responses.
pending_approval PendingApproval | null optional Present only while status is awaiting_approval.
usage RunUsage | null optional
error string,null optional Failure detail when status is failed.
created_at string (date-time) required
updated_at string (date-time) required
SCHEMARunList
SCHEMARunEventType

plan, action_start, action_done, and reflect narrate progress and are safe to render as an activity feed. output_delta carries streamed prose. complete, error, cancelled, awaiting_approval, and awaiting_token_refresh are terminal for the stream.

SCHEMARunEvent
FieldTypeNotes
seq integer required Monotonic within a run. Use as the SSE resume cursor.
run_id string (uuid) required
type RunEventType required
created_at string (date-time) required
tool ToolName | null optional Set on action_start and action_done.
label string,null optional Human-readable headline for this step.
summary string,null optional Short result description on action_done and complete.
ok boolean,null optional Whether the action succeeded. Set on action_done.
delta string,null optional Text fragment on output_delta.
pending_approval PendingApproval | null optional
message string,null optional Failure detail on error.
SCHEMAApprovalDecision
FieldTypeNotes
decision "approve" | "deny" required
reason string optional Shown to the agent on denial so it can respond sensibly. Treated as data, not instruction.
mode "async" | "sync" optional
timeout_ms integer optional
SCHEMAToolName

Snake_case for built-in tools. MCP tools are namespaced mcp__<server>__<tool>.

SCHEMAToolOwnerKind

Who defined this tool. core is built into Ghost, composio comes from a connected Composio toolkit, mcp from a registered MCP server, and external is declared by the calling product as an HTTP endpoint.

SCHEMAToolExecutorKind

What runs this tool. Deliberately separate from owner so routing is declared rather than inferred from the tool's name — a naming convention is not a routing table.

http posts the arguments to the URL the owner declared, carrying the caller's session token so the endpoint can authorize the tenant itself.

SCHEMATool
FieldTypeNotes
name ToolName required
title string,null optional
description string required
input_schema object required JSON Schema for the tool's arguments.
output_schema object,null optional
owner object required
executor object required
requires_approval boolean optional When true, a run calling this tool suspends at awaiting_approval rather than executing it.
enabled boolean optional Whether this tool is on the allowlist in the queried scope.
SCHEMAToolList
SCHEMAToolInvocationCreate
FieldTypeNotes
arguments object required Must validate against the tool's input_schema.
agent_id string,null (uuid) optional Run as this agent, applying its allowlist in addition to the tenant's. Omit to use the tenant allowlist alone.
SCHEMAToolInvocation
FieldTypeNotes
id string (uuid) required
tool ToolName required
ok boolean required Whether the tool itself succeeded.
result object optional Tool output. Shape follows the tool's output_schema.
error string,null optional Failure detail when ok is false. Never contains credentials.
duration_ms integer required
created_at string (date-time) required
SCHEMAUsageBucket
FieldTypeNotes
group_key string,null optional Agent id or ISO date, per group_by. Null for the total.
input_tokens integer required
output_tokens integer required
tool_calls integer required
runs integer required
duration_ms integer required
cost number,null optional
SCHEMAUsageReport
FieldTypeNotes
from string (date-time) required
to string (date-time) required
group_by "total" | "agent" | "day" required
buckets UsageBucket[] required
allowance object required