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.
Every example on this page uses $GHOST_API. Set it once:
export GHOST_API="https://api.ghostagents.co"
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.
/healthz
Liveness probe
Returns 200 whenever the process is running. Never touches the database.
| Code | Body | |
|---|---|---|
| 200 | Liveness |
Process is alive. |
curl "$GHOST_API/healthz"
/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.
| Code | Body | |
|---|---|---|
| 200 | Readiness |
All dependencies healthy. |
| 503 | Readiness |
One or more dependencies unavailable. |
curl "$GHOST_API/readyz"
/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.
| Code | Body | |
|---|---|---|
| 200 | object |
The OpenAPI 3.1 document. |
curl "$GHOST_API/openapi.json"
Tenants
Tenant provisioning.
/v1/tenants
Control plane
List tenants
Control plane only.
| Name | In | Type | Notes | |
|---|---|---|---|---|
limit |
query | integer | optional | Page size. |
cursor |
query | string | optional | Opaque cursor from a previous response's next_cursor. |
| Code | Body | |
|---|---|---|
| 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. |
curl "$GHOST_API/v1/tenants" \ -H 'X-Ghost-Control-Key: …'
/v1/tenants
Control plane
Create a tenant
Control plane only. A session token cannot create tenants.
TenantCreate
| Name | In | Type | Notes | |
|---|---|---|---|---|
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. |
| Code | Body | |
|---|---|---|
| 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. |
curl -X POST "$GHOST_API/v1/tenants" \
-H 'X-Ghost-Control-Key: …' \
-H 'Content-Type: application/json' \
-d '{ … }'
/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.
| Name | In | Type | Notes | |
|---|---|---|---|---|
tenantId |
path | string (uuid) | required |
| Code | Body | |
|---|---|---|
| 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. |
curl "$GHOST_API/v1/tenants/{tenantId}" \
-H 'X-Ghost-Api-Key: ghost_sk_…'
/v1/tenants/{tenantId}
Control planeAPI key
Update a tenant
TenantUpdate
| Name | In | Type | Notes | |
|---|---|---|---|---|
tenantId |
path | string (uuid) | required |
| Code | Body | |
|---|---|---|
| 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. |
curl -X PATCH "$GHOST_API/v1/tenants/{tenantId}" \
-H 'X-Ghost-Api-Key: ghost_sk_…' \
-H 'Content-Type: application/json' \
-d '{ … }'
/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.
| Name | In | Type | Notes | |
|---|---|---|---|---|
tenantId |
path | string (uuid) | required |
| Code | Body | |
|---|---|---|
| 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. |
curl -X DELETE "$GHOST_API/v1/tenants/{tenantId}" \
-H 'X-Ghost-Control-Key: …'
/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.
| Name | In | Type | Notes | |
|---|---|---|---|---|
tenantId |
path | string (uuid) | required |
| Code | Body | |
|---|---|---|
| 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. |
curl "$GHOST_API/v1/tenants/{tenantId}/keys" \
-H 'X-Ghost-Control-Key: …'
/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.
ApiKeyCreate
| Name | In | Type | Notes | |
|---|---|---|---|---|
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. |
| Code | Body | |
|---|---|---|
| 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. |
curl -X POST "$GHOST_API/v1/tenants/{tenantId}/keys" \
-H 'X-Ghost-Control-Key: …' \
-H 'Content-Type: application/json' \
-d '{ … }'
/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.
| Name | In | Type | Notes | |
|---|---|---|---|---|
tenantId |
path | string (uuid) | required | |
keyId |
path | string (uuid) | required |
| Code | Body | |
|---|---|---|
| 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. |
curl -X DELETE "$GHOST_API/v1/tenants/{tenantId}/keys/{keyId}" \
-H 'X-Ghost-Control-Key: …'
Agents
Agent configuration within a tenant.
/v1/agents
API keySession
List agents
| Name | In | Type | Notes | |
|---|---|---|---|---|
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 |
| Code | Body | |
|---|---|---|
| 200 | AgentList |
A page of agents. |
| 401 | Error |
Missing or invalid credential. |
curl "$GHOST_API/v1/agents" \ -H 'X-Ghost-Api-Key: ghost_sk_…'
/v1/agents
API keySession
Create an agent
AgentCreate
| Name | In | Type | Notes | |
|---|---|---|---|---|
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. |
| Code | Body | |
|---|---|---|
| 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. |
curl -X POST "$GHOST_API/v1/agents" \
-H 'X-Ghost-Api-Key: ghost_sk_…' \
-H 'Content-Type: application/json' \
-d '{ … }'
/v1/agents/{agentId}
API keySession
Get an agent
| Name | In | Type | Notes | |
|---|---|---|---|---|
agentId |
path | string (uuid) | required |
| Code | Body | |
|---|---|---|
| 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. |
curl "$GHOST_API/v1/agents/{agentId}" \
-H 'X-Ghost-Api-Key: ghost_sk_…'
/v1/agents/{agentId}
API keySession
Update an agent
AgentUpdate
| Name | In | Type | Notes | |
|---|---|---|---|---|
agentId |
path | string (uuid) | required |
| Code | Body | |
|---|---|---|
| 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. |
curl -X PATCH "$GHOST_API/v1/agents/{agentId}" \
-H 'X-Ghost-Api-Key: ghost_sk_…' \
-H 'Content-Type: application/json' \
-d '{ … }'
/v1/agents/{agentId}
API keySession
Delete an agent
Fails with 409 if the agent has running or suspended runs. Cancel them first.
| Name | In | Type | Notes | |
|---|---|---|---|---|
agentId |
path | string (uuid) | required |
| Code | Body | |
|---|---|---|
| 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. |
curl -X DELETE "$GHOST_API/v1/agents/{agentId}" \
-H 'X-Ghost-Api-Key: ghost_sk_…'
Sessions
Session lifecycle.
/v1/sessions
API keySession
List sessions
| Name | In | Type | Notes | |
|---|---|---|---|---|
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 |
| Code | Body | |
|---|---|---|
| 200 | SessionList |
A page of sessions, most recently active first. |
| 401 | Error |
Missing or invalid credential. |
curl "$GHOST_API/v1/sessions" \ -H 'X-Ghost-Api-Key: ghost_sk_…'
/v1/sessions
API keySession
Create a session
SessionCreate
| Name | In | Type | Notes | |
|---|---|---|---|---|
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. |
| Code | Body | |
|---|---|---|
| 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. |
curl -X POST "$GHOST_API/v1/sessions" \
-H 'X-Ghost-Api-Key: ghost_sk_…' \
-H 'Content-Type: application/json' \
-d '{ … }'
/v1/sessions/{sessionId}
API keySession
Get a session
| Name | In | Type | Notes | |
|---|---|---|---|---|
sessionId |
path | string (uuid) | required |
| Code | Body | |
|---|---|---|
| 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. |
curl "$GHOST_API/v1/sessions/{sessionId}" \
-H 'X-Ghost-Api-Key: ghost_sk_…'
/v1/sessions/{sessionId}
API keySession
Update a session
Title and archived state only. Message history is immutable.
SessionUpdate
| Name | In | Type | Notes | |
|---|---|---|---|---|
sessionId |
path | string (uuid) | required |
| Code | Body | |
|---|---|---|
| 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. |
curl -X PATCH "$GHOST_API/v1/sessions/{sessionId}" \
-H 'X-Ghost-Api-Key: ghost_sk_…' \
-H 'Content-Type: application/json' \
-d '{ … }'
/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.
| Name | In | Type | Notes | |
|---|---|---|---|---|
sessionId |
path | string (uuid) | required |
| Code | Body | |
|---|---|---|
| 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. |
curl -X DELETE "$GHOST_API/v1/sessions/{sessionId}" \
-H 'X-Ghost-Api-Key: ghost_sk_…'
Messages
Message ingress and egress.
/v1/sessions/{sessionId}/messages
API keySession
List messages in a session
Message egress. Chronological, oldest first.
| Name | In | Type | Notes | |
|---|---|---|---|---|
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 |
| Code | Body | |
|---|---|---|
| 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. |
curl "$GHOST_API/v1/sessions/{sessionId}/messages" \
-H 'X-Ghost-Api-Key: ghost_sk_…'
/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.
MessageCreate
| Name | In | Type | Notes | |
|---|---|---|---|---|
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. |
| Code | Body | |
|---|---|---|
| 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. |
curl -X POST "$GHOST_API/v1/sessions/{sessionId}/messages" \
-H 'X-Ghost-Api-Key: ghost_sk_…' \
-H 'Content-Type: application/json' \
-d '{ … }'
/v1/sessions/{sessionId}/messages/{messageId}
API keySession
Get a message
| Name | In | Type | Notes | |
|---|---|---|---|---|
sessionId |
path | string (uuid) | required | |
messageId |
path | string (uuid) | required |
| Code | Body | |
|---|---|---|
| 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. |
curl "$GHOST_API/v1/sessions/{sessionId}/messages/{messageId}" \
-H 'X-Ghost-Api-Key: ghost_sk_…'
Runs
Run status, events, approval, and cancellation.
/v1/runs
API keySession
List runs
| Name | In | Type | Notes | |
|---|---|---|---|---|
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 |
| Code | Body | |
|---|---|---|
| 200 | RunList |
A page of runs, newest first. |
| 401 | Error |
Missing or invalid credential. |
curl "$GHOST_API/v1/runs" \ -H 'X-Ghost-Api-Key: ghost_sk_…'
/v1/runs/{runId}
API keySession
Get a run
| Name | In | Type | Notes | |
|---|---|---|---|---|
runId |
path | string (uuid) | required |
| Code | Body | |
|---|---|---|
| 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. |
curl "$GHOST_API/v1/runs/{runId}" \
-H 'X-Ghost-Api-Key: ghost_sk_…'
/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.
| Name | In | Type | Notes | |
|---|---|---|---|---|
runId |
path | string (uuid) | required | |
after_seq |
query | integer | optional | Resume after this sequence number. Omit to receive from the beginning. |
| Code | Body | |
|---|---|---|
| 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. |
curl "$GHOST_API/v1/runs/{runId}/events" \
-H 'X-Ghost-Api-Key: ghost_sk_…'
/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.
ApprovalDecision
| Name | In | Type | Notes | |
|---|---|---|---|---|
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. |
| Code | Body | |
|---|---|---|
| 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. |
curl -X POST "$GHOST_API/v1/runs/{runId}/approval" \
-H 'X-Ghost-Api-Key: ghost_sk_…' \
-H 'Content-Type: application/json' \
-d '{ … }'
/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.
| Name | In | Type | Notes | |
|---|---|---|---|---|
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. |
| Code | Body | |
|---|---|---|
| 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. |
curl -X POST "$GHOST_API/v1/runs/{runId}/resume" \
-H 'X-Ghost-Api-Key: ghost_sk_…'
/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.
| Name | In | Type | Notes | |
|---|---|---|---|---|
runId |
path | string (uuid) | required |
| Code | Body | |
|---|---|---|
| 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. |
curl -X POST "$GHOST_API/v1/runs/{runId}/cancel" \
-H 'X-Ghost-Api-Key: ghost_sk_…'
Tools
Tool registry and direct invocation.
/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.
| Name | In | Type | Notes | |
|---|---|---|---|---|
agentId |
path | string (uuid) | required |
| Code | Body | |
|---|---|---|
| 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. |
curl "$GHOST_API/v1/agents/{agentId}/tools" \
-H 'X-Ghost-Api-Key: ghost_sk_…'
/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.
AgentToolAllowlist
| Name | In | Type | Notes | |
|---|---|---|---|---|
agentId |
path | string (uuid) | required |
| Code | Body | |
|---|---|---|
| 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. |
curl -X PUT "$GHOST_API/v1/agents/{agentId}/tools" \
-H 'X-Ghost-Api-Key: ghost_sk_…' \
-H 'Content-Type: application/json' \
-d '{ … }'
/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.
| Name | In | Type | Notes | |
|---|---|---|---|---|
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 |
| Code | Body | |
|---|---|---|
| 200 | ToolList |
A page of tool descriptors. |
| 401 | Error |
Missing or invalid credential. |
curl "$GHOST_API/v1/tools" \ -H 'X-Ghost-Api-Key: ghost_sk_…'
/v1/tools/{toolName}
API keySession
Get a tool descriptor
| Name | In | Type | Notes | |
|---|---|---|---|---|
toolName |
path | string | required |
| Code | Body | |
|---|---|---|
| 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. |
curl "$GHOST_API/v1/tools/{toolName}" \
-H 'X-Ghost-Api-Key: ghost_sk_…'
/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.
ToolInvocationCreate
| Name | In | Type | Notes | |
|---|---|---|---|---|
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. |
| Code | Body | |
|---|---|---|
| 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. |
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.
/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.
| Name | In | Type | Notes | |
|---|---|---|---|---|
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 |
| Code | Body | |
|---|---|---|
| 200 | UsageReport |
Usage for the window. |
| 400 | Error |
Malformed request. |
| 401 | Error |
Missing or invalid credential. |
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.
/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.
| Code | Body | |
|---|---|---|
| 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. |
curl "$GHOST_API/v1/me/keys" \ -H 'Authorization: Bearer <session-jwt>'
/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.
ApiKeyCreate
| Name | In | Type | Notes | |
|---|---|---|---|---|
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. |
| Code | Body | |
|---|---|---|
| 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. |
curl -X POST "$GHOST_API/v1/me/keys" \
-H 'Authorization: Bearer <session-jwt>' \
-H 'Content-Type: application/json' \
-d '{ … }'
/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.
| Name | In | Type | Notes | |
|---|---|---|---|---|
keyId |
path | string (uuid) | required |
| Code | Body | |
|---|---|---|
| 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. |
curl -X DELETE "$GHOST_API/v1/me/keys/{keyId}" \
-H 'Authorization: Bearer <session-jwt>'
/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.
| Code | Body | |
|---|---|---|
| 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. |
curl "$GHOST_API/v1/me/billing" \ -H 'Authorization: Bearer <session-jwt>'
/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.
CheckoutCreate
| Code | Body | |
|---|---|---|
| 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. |
curl -X POST "$GHOST_API/v1/me/billing/checkout" \
-H 'Authorization: Bearer <session-jwt>' \
-H 'Content-Type: application/json' \
-d '{ … }'
/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.
| Name | In | Type | Notes | |
|---|---|---|---|---|
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. |
| Code | Body | |
|---|---|---|
| 200 | object |
A page of requests. |
| 400 | Error |
Malformed request. |
| 401 | Error |
Missing or invalid credential. |
curl "$GHOST_API/v1/logs" \ -H 'X-Ghost-Api-Key: ghost_sk_…'
/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.
| Name | In | Type | Notes | |
|---|---|---|---|---|
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. |
| Code | Body | |
|---|---|---|
| 200 | object |
Summary for the window. |
| 400 | Error |
Malformed request. |
| 401 | Error |
Missing or invalid credential. |
curl "$GHOST_API/v1/logs/summary" \ -H 'X-Ghost-Api-Key: ghost_sk_…'
Schemas
The shapes referenced above.
ErrorTypeStable, machine-readable failure class. Treat unknown members as a generic failure of the response's HTTP status class.
Error| Field | Type | Notes | |
|---|---|---|---|
error |
object | required |
Liveness| Field | Type | Notes | |
|---|---|---|---|
status |
string | required | |
version |
string | optional |
Readiness| Field | Type | Notes | |
|---|---|---|---|
status |
"ok" | "degraded" | required | |
version |
string | optional | |
dependencies |
object[] | required |
Page| Field | Type | Notes | |
|---|---|---|---|
next_cursor |
string,null | required | Pass as cursor for the next page. null on the last page. |
Tenant| Field | Type | Notes | |
|---|---|---|---|
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 |
TenantCreate| Field | Type | Notes | |
|---|---|---|---|
name |
string | required | |
metadata |
object | optional |
TenantUpdate| Field | Type | Notes | |
|---|---|---|---|
name |
string | optional | |
metadata |
object | optional |
TenantListAgentKindstandard agents do work. A meta agent orchestrates other agents in its tenant. Exactly one meta agent exists per tenant and it is created automatically.
AgentStatusAgent| Field | Type | Notes | |
|---|---|---|---|
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 |
AgentCreate| Field | Type | Notes | |
|---|---|---|---|
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 |
AgentUpdate| Field | Type | Notes | |
|---|---|---|---|
name |
string | optional | |
role |
string,null | optional | |
instructions |
string,null | optional | |
status |
AgentStatus | optional | |
timezone |
string | optional | |
metadata |
object | optional |
AgentListAgentToolAllowlist| Field | Type | Notes | |
|---|---|---|---|
tools |
ToolName[] | required |
SessionStatusSession| Field | Type | Notes | |
|---|---|---|---|
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 |
SessionCreate| Field | Type | Notes | |
|---|---|---|---|
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 |
SessionContextItem| Field | Type | Notes | |
|---|---|---|---|
label |
string | required | Short name for this fact, e.g. contact or account_status. |
content |
string | required | |
provenance |
object | optional |
SessionUpdate| Field | Type | Notes | |
|---|---|---|---|
title |
string,null | optional | |
status |
SessionStatus | optional | |
metadata |
object | optional |
SessionListMessageRoleuser is inbound. agent is the substantive reply. card is a structured payload the client renders as a widget rather than prose.
MessageProvenanceKindWhere 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.
Message| Field | Type | Notes | |
|---|---|---|---|
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 |
MessageCreate| Field | Type | Notes | |
|---|---|---|---|
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 |
MessageListApiKeyCreate| Field | Type | Notes | |
|---|---|---|---|
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. |
RequestLogEntryOne HTTP exchange against this API.
| Field | Type | Notes | |
|---|---|---|---|
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 |
ApiKeyA key's metadata. Deliberately contains nothing from which the key itself could be reconstructed.
| Field | Type | Notes | |
|---|---|---|---|
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 |
ApiKeyWithSecretCreditPackA 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.
| Field | Type | Notes | |
|---|---|---|---|
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 |
PaymentRecordOne settled payment. Written by the webhook when money arrived, so a row here means credits were granted — not that a checkout was started.
| Field | Type | Notes | |
|---|---|---|---|
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 |
BillingOverview| Field | Type | Notes | |
|---|---|---|---|
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. |
CheckoutCreate| Field | Type | Notes | |
|---|---|---|---|
pack |
string | required | A CreditPack.id from getOwnBilling. |
CheckoutSession| Field | Type | Notes | |
|---|---|---|---|
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. |
RunStatusTerminal 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.
RunStopReasonWhy 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.
PendingApprovalThe gated tool call a suspended run is waiting on.
| Field | Type | Notes | |
|---|---|---|---|
tool |
ToolName | required | |
arguments |
object | required | |
reason |
string | optional | Why approval is required. |
RunUsage| Field | Type | Notes | |
|---|---|---|---|
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. |
Run| Field | Type | Notes | |
|---|---|---|---|
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 |
RunListRunEventTypeplan, 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.
RunEvent| Field | Type | Notes | |
|---|---|---|---|
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. |
ApprovalDecision| Field | Type | Notes | |
|---|---|---|---|
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 |
ToolNameSnake_case for built-in tools. MCP tools are namespaced mcp__<server>__<tool>.
ToolOwnerKindWho 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.
ToolExecutorKindWhat 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.
Tool| Field | Type | Notes | |
|---|---|---|---|
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. |
ToolListToolInvocationCreate| Field | Type | Notes | |
|---|---|---|---|
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. |
ToolInvocation| Field | Type | Notes | |
|---|---|---|---|
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 |
UsageBucket| Field | Type | Notes | |
|---|---|---|---|
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 |
UsageReport| Field | Type | Notes | |
|---|---|---|---|
from |
string (date-time) | required | |
to |
string (date-time) | required | |
group_by |
"total" | "agent" | "day" | required | |
buckets |
UsageBucket[] | required | |
allowance |
object | required |