# Ghost API reference Complete reference, generated from the same source as https://app.ghostgtm.ai/docs/api. Base URL: https://app.ghostgtm.ai/api/v1 --- # Introduction Ghost sits on top of your CRM and turns calls, email, docs and product usage into a graph of relationship, deal and product facts. The API lets your code read that graph and act on it. Every request goes to a single base URL. The surface is versioned by path and ships labeled beta - response shapes may evolve with notice. ``` https://app.ghostgtm.ai/api/v1 ``` ## How the surface is organised - Capabilities and operations - one catalog endpoint lists every operation your key may run, with its JSON input schema. Read-only operations run immediately through /query; everything else is submitted to /operations and returns a durable receipt. - Data - explicit, cursor-paginated pages of accounts, people, facts, sources, deals and the context library. No arbitrary queries. - Definitions and values - the custom fields your workspace defines, and the values assigned to entities. - Relations - versioned edges between accounts and ICPs, personas, use cases, products and competitors. - Compute - quoted, credit-capped graph refreshes. ## Your first request Create a key in Dev, then introspect it. The response tells you which capability families this workspace has enabled. ```bash curl --fail-with-body https://app.ghostgtm.ai/api/v1/me \ -H "Authorization: Bearer $GHOST_API_KEY" ``` Then call GET /capabilities. It is the executable catalog: every operation name, its input schema, required scopes and whether it is available and authorised for the key you hold. > Ghost-MCP and the customer plugin read the same graph. Use the API for full projected records, live CRM validation, scoped graph execution, and capabilities not exposed as MCP tools. --- # Graph execution and CRM validation Apply a small graph edit directly from Claude Code, or run a scoped batch with Ghost-managed AI analysis. ## Choose the execution path | Job | API path | Permission and cost | | --- | --- | --- | | A specific account/contact correction | graph.apply_update through POST /operations | graph:write; graph:read for API reads. Normal API fees, no managed AI call or Ghost model charge. Some change kinds have additional role checks. | | Delegate evidence review and graph population across accounts | graph.agent_preview, then graph.agent_run | graph:read + graph:write + research:run and an owner, admin or analyst role. Add crm:read for live CRM queries. Normal API fees plus capped model usage; admin is not required. | | A bulk pmm_refresh through the compute endpoints | POST /compute/preview, then POST /compute/runs | admin grant AND an owner/admin workspace role. An admin-scoped analyst key does not qualify. Normal API fees plus quoted, capped compute usage. | ## Apply a direct edit Claude Code can read the relevant evidence and submit a small, explicit edit itself; the user does not have to supply the final value before Claude investigates. Resolve the account and contact, read the current value, and obtain or use the user's approval for the exact change. Send graph.apply_update through POST /operations with an Idempotency-Key and reason. No graph.agent_preview, research:run grant or managed analysis run is needed for this direct edit. To leave a proposal for review instead, use graph.propose_update. Example request body for a contact title correction. Replace both example UUIDs with verified IDs in this workspace: customer_id is the Ghost account ID; person_id is the account_people membership ID, not the canonical person ID. Read the live catalog for allowed change kinds and fields. ```json { "operation": "graph.apply_update", "arguments": { "customer_id": "11111111-1111-4111-8111-111111111111", "summary": "Correct the contact title", "changes": [ { "kind": "person_field", "target_label": "Example Contact", "person_id": "22222222-2222-4222-8222-222222222222", "field": "title", "after": "Vice President of Finance" } ] }, "reason": "Apply the reviewed title correction to Vice President of Finance." } ``` Poll the returned receipt and read back the affected record before reporting success. Direct means Claude supplies the edit without delegating judgment to Ghost-managed AI analysis; the operation still returns a receipt and may complete asynchronously. Account firmographic fields remain gap-fill only, and workspace labels require both admin and an owner/admin role. Ghost graph edits do not write the CRM. ## Plan a batch with connected evidence Read /data/accounts/{id} for the account and firmographics, /data/people/{id} for a contact, and follow relatedData links for paginated evidence, deals and contacts. ICPs and all context areas are resources under /data. Stored values and their provenance are distinct from live CRM values. Live HubSpot company, contact and deal properties are available through crm.read_hubspot_company_properties, crm.read_hubspot_contact_properties and crm.read_hubspot_deal_properties at POST /query. Discover exact base/custom field keys in the field catalog first. Live CRM reads need crm:read. CRM mutation operations need crm:write. Neither grant implies the other. Use graph.agent_preview with explicit customerIds, permitted personFields/accountFields, selected contextTargets and exact named connectedReads. The preview validates the scope and quotes a conservative model-credit ceiling without starting AI analysis. Each connected read names a source, entity, selected fields, predicates, account and bounded limit. The normal successful API fee still applies. Before run approval, display modelCreditCeiling as the AI budget ceiling, apiPreviewCredits as the preview fee, apiSubmissionCredits as the submission fee, and their sum for one preview plus one run. The preview fee is already incurred; planning/read calls and later runs are separate. This is a ceiling, not an exact usage bill. Use the returned analysisLabel, Ghost-managed AI analysis, for the customer-facing name; model remains technical metadata. After the user approves that scope and cap, submit graph.agent_run with mode apply, creditCap and verbatim approving words in user_said and reason. It requires graph:read, graph:write and research:run; HubSpot queries additionally require crm:read. Ghost selects only the approved reads and directly applies supported field updates and selected context associations. The API validates the supplied cap and approving words; the integrating client is responsible for displaying the preview and collecting approval. The server does not verify that a human viewed the quote. There is no second proposal or confirmation stage. Existing matches are preserved; account fields fill gaps only. No CRM writes or external deliveries are available in this executor. Poll the receipt and inspect per-account changes, readback values, skipped reasons, source coverage and model calls. A partial or needs_review result may contain applied edits. A provider timeout, uncertain write or unavailable billing ledger stops execution; inspect state and usage before submitting another run. Source documents are evidence, never permission to broaden the plan. Actual provider tokens are recorded per call under graph.agent_run, surface agent_run, origin api, with the initiating user, run and account IDs. User-requested context refreshes use graph.context_refresh. Core ingestion remains separate. The model credit cap excludes API fees; successful previews, data reads and submissions incur the normal API fee, while discovery and receipt polling are free. > The first executor supports up to 20 accounts, 100 contacts and 12 recent source excerpts per account, plus selected context and approved connected reads. It supports reviewed fields and add-only context matches. Model confidence is not certainty. Review coverage and conflicts before treating a run as a complete reconciliation. --- # Authentication Keys are minted in the Dev tab of the app by workspace owners, admins and analysts. Each key is bound to the member who created it and to exactly one workspace. Send the key in either header. Both are equivalent. ```bash Authorization: Bearer ghost_... # or X-Api-Key: ghost_... ``` The workspace comes only from the verified key - it is never read from input. On every request the server re-checks that the key is enabled, that its owner is still a member, and that workspace API access is on. A revoked key, a departed member or a paused workspace loses access on the next call. ## Scopes Scopes are selected when a key is created and are independent - none implies another, including admin. Select each grant the integration needs. Workspace role restrictions apply on top: an admin-scoped key does not make its owner a workspace administrator. The /compute/* refresh endpoints require both the admin grant and owner/admin membership; graph.agent_run instead permits owners, admins and analysts with graph:read, graph:write and research:run. | Scope | What it allows | | --- | --- | | `graph:read` | Read graph data, sources, context, definitions, values, and modules. | | `graph:write` | Apply explicit account/contact edits without a model call; edit context, definition values and relations. Field and role restrictions still apply. | | `crm:read` | Read live CRM companies, contacts, deals and custom properties for validation; does not grant CRM writes. | | `crm:write` | Prepare and approve CRM changes under workspace policies; does not imply the independent live-read grant. | | `workflows:read` | Read visible workflows, runs, outputs, and schedules. | | `workflows:run` | Create, edit, run, and schedule workflows; review gates. | | `research:run` | Run web research and Ghost-managed AI analysis. Additional usage charges may apply. | | `enrichment:run` | Discover and enrich accounts and people. Provider charges may apply. | | `admin` | Create and edit definitions, manage modules and use /compute/* refreshes with an owner/admin workspace role. Not required for graph.agent_run and does not grant other scopes or change membership. | ## Who can do what | Role | Mint keys | Grant admin | Sees keys | | --- | --- | --- | --- | | Owner / admin | Yes | Yes | Every workspace key | | Analyst | Yes | No | Only keys they created | | Member | No | No | None | > New grants are never silently added to existing keys. Live CRM validation needs explicit crm:read; crm:write is independent. Workflows:run separately authorizes approved automation and may include CRM effects; review its definition and destinations. ## Change an existing key's grants A Ghost system administrator can update a specific existing developer API key's grants in place through an audited operator change. The key secret stays the same; rotation is not required just to add or remove a grant. The current Dev UI supports creating and revoking keys, not editing grants, and there is no public API endpoint for a key to change its own permissions. Give the administrator the workspace, key name or ID, and exact grants to add or remove; never send the key secret. After an update, GET /me returns the current grants and workspace role, and GET /capabilities shows authorization for catalog operations. The server reads current grants on each request and revalidates queued execution; removing a grant does not undo effects already applied. Changing key grants does not change workspace membership. --- # Billing Every successful call that does work costs one credit from the workspace's usage balance. Discovery, polling and failures are free. The credit is the platform fee for the call. Work that spends third-party money on top of it - enrichment, research, MCP-backed tools - is metered by the executor at its own cost as well, exactly as it is from chat. | Call | Charge | | --- | --- | | Any 2xx read, write, submit or relation edit | 1 credit | | GET /me, GET /capabilities, the spec and docs | Free | | GET /operations, GET /operations/{id}, GET /compute/runs/{id} | Free - polling work you already paid to submit | | Any 4xx, 5xx or 429 | Free | What a credit costs, and how many the workspace has, depend on its plan - the Usage page in the app shows the current balance and rate. Usage appears there under the API surface, named for what ran (mcp.list_icps, GET /data/relationships). Staff exemptions configured for a workspace apply to API calls the same way they apply to chat. > Retrying a write with the same Idempotency-Key replays the stored response and is not billed again. --- # Errors and rate limits Errors share one JSON shape. Branch on code and the HTTP status; internal failure details are never returned. ```json { "defined": false, "code": "UNAUTHORIZED", "status": 401, "message": "missing" } ``` defined is true when the error is one a procedure declares. Validation errors may carry a data object describing the failing fields. | Status | Meaning | | --- | --- | | 400 | Invalid input, wrong value type, archived definition, self-merge, or a capability that is not configured. | | 401 | Missing or invalid key. | | 403 | Disabled or expired key, no membership, paused workspace access, missing scope or role, or a restricted module. | | 404 | Unknown route, or a record that does not exist in this workspace. Read collections at /data/{resource}, such as /data/icps; records at /data/{resource}/{id}. Discover operation names and input schemas at /capabilities, then use /query for reads or /operations for writes and paid work. | | 409 | Slug collision, idempotency-key conflict, or a retryable transaction conflict. | | 429 | Rate limit exceeded. Respect Retry-After. | | 500 | Infrastructure failure. Retry writes with the same Idempotency-Key. | ## Rate limits 300 requests per minute per key and 1,000 per minute per workspace. Successful checks expose X-RateLimit-Limit and X-RateLimit-Remaining; a 429 adds Retry-After in seconds. If the limiter is unavailable the API fails open and omits the headers. Background operations have a separate ceiling: at most 5 active per key and 20 per workspace. --- # Pagination Collections return items and nextCursor. Pass nextCursor back as cursor until it is null. ```json { "items": [ ... ], "nextCursor": "3f9c2e1a-7b44-4d0e-9b1f-2a6c8d4e5f10" } ``` The default limit is 50 and the maximum is 200. Ordering is ascending by UUID, with workspace and visibility filtering applied before the limit. ```bash curl --fail-with-body "https://app.ghostgtm.ai/api/v1/definitions?limit=50" \ -H "Authorization: Bearer $GHOST_API_KEY" curl --fail-with-body "https://app.ghostgtm.ai/api/v1/definitions?limit=50&cursor=3f9c2e1a-7b44-4d0e-9b1f-2a6c8d4e5f10" \ -H "Authorization: Bearer $GHOST_API_KEY" ``` > Cursors are continuation boundaries, not snapshots. Records inserted while you page may sort before your cursor. Restart the scan when you need a fresh inventory. ## Source text Source content pages by character instead of by row: pass offset and length, and follow nextOffset until it is null. --- # Idempotency Every write accepts an optional Idempotency-Key header. Use it so a retry never repeats a mutation. ```bash curl --fail-with-body https://app.ghostgtm.ai/api/v1/definitions \ -H "Authorization: Bearer $GHOST_API_KEY" \ -H 'Content-Type: application/json' \ -H 'Idempotency-Key: integration-stage-create-001' \ --data '{"name":"Integration stage","moduleKey":"customers","entityKinds":["account"],"valueType":"enum","enumValues":[{"label":"New"},{"label":"Qualified"}],"reason":"Track qualification from our integration"}' ``` - Generate a new key for every logical request. Reuse it only when retrying that same request. - 1–128 visible ASCII characters. Anything else is rejected with 400. - A replay returns the original response, even if later writes changed the resource. It does not re-apply the mutation. - The same key with different input, or a different action, returns 409. - Receipts are scoped to the workspace and the key, and are retained without expiry. Identical value assignments without a key are also no-ops when actor, value, reason, evidence, source and pinning all match the current assignment. ## Long-running operations POST /operations returns a receipt with a status of queued, running, succeeded, failed, needs_review, canceled or outcome_unknown. An interrupted write may have applied effects - when the outcome is unknown, inspect the target before submitting again. A failed batch can contain successful items; read its result rather than repeating the whole batch. --- # Introspect this API key `GET /me` - Scope: Any valid key - Role: Current member - Billing: free Returns the workspace and user the key is bound to, its scopes, and which capability families this workspace has enabled. Call it first to confirm a key works. ## Response | Field | Type | | --- | --- | | `workspaceId` | string | | `userId` | string | | `role` | string | | `scopes` | string[] | | `beta` | boolean | | `capabilities` | object | ## Request ```bash curl --fail-with-body "https://app.ghostgtm.ai/api/v1/me" \ -H "Authorization: Bearer $GHOST_API_KEY" ``` ## Example response ```json { "workspaceId": "ws_7f3a…", "userId": "usr_91c0…", "role": "admin", "scopes": [ "graph:read", "graph:write", "admin" ], "beta": true, "capabilities": { "definitions": true, "graphReads": true, "graphEdits": true, "context": true, "research": true, "enrichment": true, "crm": true, "workflows": true, "operationCatalog": "/api/v1/capabilities", "automaticCapture": false, "relationEditing": true, "graphCompute": true, "moduleVisibility": "definitions" } } ``` --- # Discover operations, schemas and permissions `GET /capabilities` - Scope: Any valid key - Role: Current member - Billing: free The executable catalog. Every operation has a name, description, category, JSON input schema, required scopes, a read-only flag, and whether it is available in this environment and authorised for the calling key. The catalog is an explicit allowlist; adding an internal tool never publishes it. Also returns the projected data resources and the assistant guidance clients should load before answering product questions. ## Response | Field | Type | | --- | --- | | `assistantGuidance` | string | | `productGuide` | object | | `operations` | Operation[] | | `resources` | Resource[] | ## Request ```bash curl --fail-with-body "https://app.ghostgtm.ai/api/v1/capabilities" \ -H "Authorization: Bearer $GHOST_API_KEY" ``` ## Example response ```json { "assistantGuidance": "…", "productGuide": { "icps": {} }, "operations": [ { "name": "graph.filter_accounts", "description": "Deterministic account filter returning a 30-minute scoped handle.", "category": "graph", "requiredScopes": [ "graph:read" ], "conditionalScopes": null, "adminOnly": false, "readOnly": true, "available": true, "authorized": true, "inputSchema": { "type": "object" } } ], "resources": [ { "name": "accounts", "requiredScope": "graph:read", "collectionPath": "/api/v1/data/accounts", "recordPath": "/api/v1/data/accounts/{id}", "entityFilter": false, "columns": [ "id", "name", "slug", "…" ], "accountFilter": true, "userBound": false } ] } ``` --- # Submit an operation `POST /operations` - Scope: Per operation - Role: Current member - Billing: 1 credit per successful call Submits any catalog operation - graph edits, context curation, research, enrichment, CRM and workflow actions, and the MCP tool suite - and returns a durable execution receipt. Arguments are validated against the operation's schema before dispatch. Paid work and writes run in a background worker; nothing is replayed automatically after an ambiguous outcome. ## Headers | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | 1–128 visible ASCII characters. Retrying with the same key returns the original response; reusing it for different input returns 409. | ## Body | Field | Type | Required | Description | | --- | --- | --- | --- | | `operation` | string | yes | Operation name from GET /capabilities, e.g. research.start_async_research. | | `arguments` | object | no | Input matching the operation's inputSchema. Defaults to {}. | | `reason` | string | yes | Why this change is being made. 1–500 characters. Stored in the audit trail. | ## Response | Field | Type | | --- | --- | | `id` | uuid | | `operation` | string | | `status` | queued | running | succeeded | failed | needs_review | canceled | outcome_unknown | | `result` | unknown | null | | `error` | unknown | null | | `created_at` | string | | `started_at` | string | null | | `completed_at` | string | null | ## Request ```bash curl --fail-with-body "https://app.ghostgtm.ai/api/v1/operations" \ -H "Authorization: Bearer $GHOST_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ --data '{ "operation": "mcp.find_email", "arguments": { "name": "Jane Doe", "domain": "example.com" }, "reason": "Find the requested business contact" }' ``` ## Example response ```json { "id": "0e1f4b2c-5d6a-4e7f-8a9b-0c1d2e3f4a5b", "operation": "mcp.find_email", "status": "queued", "result": null, "error": null, "created_at": "2026-09-09T17:02:11.418Z", "started_at": null, "completed_at": null } ``` --- # List execution receipts `GET /operations` - Scope: Per operation - Role: Current member - Billing: free Pages through this user's receipts, limited to those the key's current grants permit reading. ## Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `limit` | integer | no | Page size. Default 50, maximum 200. | | `cursor` | uuid | no | The nextCursor from the previous page. Omit for the first page. | ## Response | Field | Type | | --- | --- | | `items` | Operation[] | | `nextCursor` | uuid | null | ## Request ```bash curl --fail-with-body "https://app.ghostgtm.ai/api/v1/operations?limit=50" \ -H "Authorization: Bearer $GHOST_API_KEY" ``` ## Example response ```json { "items": [ { "id": "0e1f4b2c-5d6a-4e7f-8a9b-0c1d2e3f4a5b", "operation": "mcp.find_email", "status": "succeeded", "result": { "email": "jane.doe@example.com" }, "error": null, "created_at": "2026-09-09T17:02:11.418Z", "started_at": "2026-09-09T17:02:12.007Z", "completed_at": "2026-09-09T17:02:14.901Z" } ], "nextCursor": null } ``` --- # Read an operation `GET /operations/{operationId}` - Scope: Per operation - Role: Current member - Billing: free Returns status, result and failure details for one receipt. Review results carry the confirmation or proposal a follow-up resolution operation needs. ## Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `operationId` | uuid | yes | The receipt id returned by POST /operations. | ## Response | Field | Type | | --- | --- | | `id` | uuid | | `operation` | string | | `status` | queued | running | succeeded | failed | needs_review | canceled | outcome_unknown | | `result` | unknown | null | | `error` | unknown | null | | `created_at` | string | | `started_at` | string | null | | `completed_at` | string | null | ## Request ```bash curl --fail-with-body "https://app.ghostgtm.ai/api/v1/operations/0e1f4b2c-5d6a-4e7f-8a9b-0c1d2e3f4a5b" \ -H "Authorization: Bearer $GHOST_API_KEY" ``` ## Example response ```json { "id": "0e1f4b2c-5d6a-4e7f-8a9b-0c1d2e3f4a5b", "operation": "mcp.find_email", "status": "succeeded", "result": { "email": "jane.doe@example.com", "confidence": 0.92 }, "error": null, "created_at": "2026-09-09T17:02:11.418Z", "started_at": "2026-09-09T17:02:12.007Z", "completed_at": "2026-09-09T17:02:14.901Z" } ``` --- # Cancel an operation `POST /operations/{operationId}/cancel` - Scope: Per operation - Role: Current member - Billing: 1 credit per successful call Cancels a job before its worker claims it. Running external actions cannot be cancelled here. ## Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `operationId` | uuid | yes | The receipt id. | ## Response | Field | Type | | --- | --- | | `id` | uuid | | `operation` | string | | `status` | queued | running | succeeded | failed | needs_review | canceled | outcome_unknown | | `result` | unknown | null | | `error` | unknown | null | | `created_at` | string | | `started_at` | string | null | | `completed_at` | string | null | ## Request ```bash curl --fail-with-body "https://app.ghostgtm.ai/api/v1/operations/0e1f4b2c-5d6a-4e7f-8a9b-0c1d2e3f4a5b/cancel" \ -X POST \ -H "Authorization: Bearer $GHOST_API_KEY" ``` ## Example response ```json { "id": "0e1f4b2c-5d6a-4e7f-8a9b-0c1d2e3f4a5b", "operation": "mcp.find_email", "status": "canceled", "result": null, "error": null, "created_at": "2026-09-09T17:02:11.418Z", "started_at": null, "completed_at": "2026-09-09T17:02:30.112Z" } ``` --- # Run a read-only operation now `POST /query` - Scope: Per operation - Role: Current member - Billing: 1 credit per successful call Executes a catalog operation marked readOnly and returns its result in the HTTP response. Scope and workspace checks are identical to background execution. Use /operations instead when a read needs a durable receipt or may outlast an HTTP request. ## Body | Field | Type | Required | Description | | --- | --- | --- | --- | | `operation` | string | yes | A read-only operation name from GET /capabilities. | | `arguments` | object | no | Input matching the operation's inputSchema. | ## Response | Field | Type | | --- | --- | | `result` | unknown | ## Request ```bash curl --fail-with-body "https://app.ghostgtm.ai/api/v1/query" \ -H "Authorization: Bearer $GHOST_API_KEY" \ -H "Content-Type: application/json" \ --data '{ "operation": "graph.get_account_context", "arguments": { "accountId": "6b1e9a44-2c7d-4f0a-9e3b-1d5c8a7f2e90" } }' ``` ## Example response ```json { "result": { "account": { "id": "6b1e9a44-2c7d-4f0a-9e3b-1d5c8a7f2e90", "name": "Northwind Traders", "commercial_status": "customer" }, "facts": [ { "fact_type": "deal_blocker", "evidence": "... the security review is the last blocker before we sign", "confidence": 0.88 } ] } } ``` --- # Page through a data resource `GET /data/{resource}` - Scope: Per resource - Role: Current member - Billing: 1 credit per successful call Reads a cursor-paginated page of an explicitly projected resource. Graph and context resources need graph:read; research_runs needs research:run; crm_confirmations needs crm:write. Research and CRM confirmation rows are private to the key owner. Field projections, collectionPath and recordPath are listed by GET /capabilities. Account detail includes firmographics and related-data links; contact detail includes stored attributes, work history, observations, provenance and CRM IDs. These are stored records, not live CRM values. ## Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `resource` | enum | yes | accounts · people · relationships · facts · sources · firmographics · deals · labels · classifications · feedback · products · personas · icps · use_cases · case_studies · competitors · proof_points · resources · context_folders · context_documents · crm_confirmations · research_runs | ## Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `accountId` | uuid | no | Restrict to one account. Supported by account-scoped resources only; others return 400. | | `entityId` | uuid | no | Filter relationships touching this entity, or classifications for this entity. Only supported on those two resources. | | `limit` | integer | no | Page size. Default 50, maximum 200. | | `cursor` | uuid | no | The nextCursor from the previous page. Omit for the first page. | ## Response | Field | Type | | --- | --- | | `items` | object[] | | `nextCursor` | uuid | null | ## Request ```bash curl --fail-with-body "https://app.ghostgtm.ai/api/v1/data/accounts?limit=50" \ -H "Authorization: Bearer $GHOST_API_KEY" ``` ## Example response ```json { "items": [ { "id": "6b1e9a44-2c7d-4f0a-9e3b-1d5c8a7f2e90", "name": "Northwind Traders", "slug": "northwind", "entity_type": "customer", "commercial_status": "customer", "active_contract": true, "owner_user_id": "usr_91c0…", "created_at": "2026-03-11T09:14:02.000Z" } ], "nextCursor": "7c2f0b55-3d8e-4a1b-8f4c-2e6d9b8a3f01" } ``` --- # Read one record `GET /data/{resource}/{id}` - Scope: Per resource - Role: Current member - Billing: 1 credit per successful call Reads a single scoped record with the same resource and tenant permissions as its collection. Account detail also includes stored firmographics and related-data links; contact detail includes CRM identity and related links. ## Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `resource` | enum | yes | A resource name from GET /capabilities, such as accounts, people, firmographics or icps. | | `id` | uuid | yes | Record id. | ## Response | Field | Type | | --- | --- | | `…` | The resource's projected columns | ## Request ```bash curl --fail-with-body "https://app.ghostgtm.ai/api/v1/data/accounts/6b1e9a44-2c7d-4f0a-9e3b-1d5c8a7f2e90" \ -H "Authorization: Bearer $GHOST_API_KEY" ``` ## Example response ```json { "id": "6b1e9a44-2c7d-4f0a-9e3b-1d5c8a7f2e90", "name": "Northwind Traders", "slug": "northwind", "entity_type": "customer", "brief_md": "Northwind Traders is a specialty foods distributor…", "commercial_status": "customer", "active_contract": true, "hubspot_company_id": "1839204", "created_at": "2026-03-11T09:14:02.000Z", "updated_at": "2026-09-08T21:40:19.000Z" } ``` --- # Read source text `GET /sources/{sourceId}/content` - Scope: graph:read - Role: Current member - Billing: 1 credit per successful call Returns a source's full text in resumable character pages. Follow nextOffset until it is null. ## Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `sourceId` | uuid | yes | Source id from GET /data/sources. | ## Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `offset` | integer | no | Character offset. Default 0. | | `length` | integer | no | Characters to return. Default 20000, maximum 100000. | ## Response | Field | Type | | --- | --- | | `sourceId` | uuid | | `content` | string | | `totalCharacters` | integer | | `nextOffset` | integer | null | ## Request ```bash curl --fail-with-body "https://app.ghostgtm.ai/api/v1/sources/a41d7e02-9c3b-4f6e-8d1a-5b2c7e9f0a13/content" \ -H "Authorization: Bearer $GHOST_API_KEY" ``` ## Example response ```json { "sourceId": "a41d7e02-9c3b-4f6e-8d1a-5b2c7e9f0a13", "content": "Dana: ... the security review is the last blocker before we sign. Once legal clears it we can move on the $120,000 order form…", "totalCharacters": 48211, "nextOffset": 20000 } ``` --- # List a label's assignments `GET /labels/{labelId}/assignments` - Scope: graph:read - Role: Current member - Billing: 1 credit per successful call Pages through every entity assigned to a workspace label. ## Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `labelId` | uuid | yes | Label id from GET /data/labels. | ## Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `limit` | integer | no | Page size. Default 50, maximum 200. | | `cursor` | uuid | no | The nextCursor from the previous page. Omit for the first page. | ## Response | Field | Type | | --- | --- | | `items` | { entity_id, label_id, added_at, added_by }[] | | `nextCursor` | uuid | null | ## Request ```bash curl --fail-with-body "https://app.ghostgtm.ai/api/v1/labels/c9d2f7a1-4e8b-4c3d-9a6f-7b1e2d3c4f50/assignments?limit=50" \ -H "Authorization: Bearer $GHOST_API_KEY" ``` ## Example response ```json { "items": [ { "entity_id": "6b1e9a44-2c7d-4f0a-9e3b-1d5c8a7f2e90", "label_id": "c9d2f7a1-4e8b-4c3d-9a6f-7b1e2d3c4f50", "added_at": "2026-08-30T15:22:47.000Z", "added_by": "usr_91c0…" } ], "nextCursor": null } ``` --- # Read a graph decision trail `GET /graph/traces` - Scope: graph:read - Role: Current member - Billing: 1 credit per successful call Read before/after changes, authenticated execution, recorded rationale, criteria, evidence and exact definition versions. Current source permissions apply to every page. Missing legacy rationale is unknown. Use source-content retrieval for the full underlying source. ## Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `entity_kind` | string | yes | Canonical kind, e.g. account, account_person, relation, icp, persona, custom_doc or fact. | | `entity_id` | string | yes | Exact saved record ID from a trace_ref. | | `before_revision` | integer | no | Follow next_before_revision to older events. | | `limit` | integer | no | 1–25; default 10. | ## Response | Field | Type | | --- | --- | | `status` | string | | `events` | array | | `next_before_revision` | integer | null | ## Request ```bash curl --fail-with-body "https://app.ghostgtm.ai/api/v1/graph/traces?entity_kind=&entity_id=&limit=50" \ -H "Authorization: Bearer $GHOST_API_KEY" ``` ## Example response ```json { "status": "ready", "entity_kind": "relation", "events": [], "next_before_revision": null } ``` --- # Prepare versioned decision inputs `POST /graph/decision-inputs` - Scope: graph:read - Role: Current member - Billing: 1 credit per successful call Read exact current definition snapshots before evaluating an account or changing a definition. Records a baseline only when needed; does not relabel accounts. Submit the returned definition_ref values in decision_trace.definition_refs. Writes reject stale references and changed/revoked evidence. decision_trace also accepts reason, basis, intent, factors (supports, contradicts, unknown, not_applicable), source evidence with quotes, alternatives, unknowns, event_at and caller-reported origin. Actor identity comes from authentication, never input. ## Body | Field | Type | Required | Description | | --- | --- | --- | --- | | `definitions` | array | yes | 1–30 {entity_kind, entity_id} objects for ICPs, personas or other supported definitions. | ## Response | Field | Type | | --- | --- | | `definitions` | array | | `prepared_at` | timestamp | | `note` | string | ## Request ```bash curl --fail-with-body "https://app.ghostgtm.ai/api/v1/graph/decision-inputs" \ -H "Authorization: Bearer $GHOST_API_KEY" \ -H "Content-Type: application/json" \ --data '{ "definitions": [ { "entity_kind": "icp", "entity_id": "2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f" } ] }' ``` ## Example response ```json { "definitions": [ { "definition_ref": { "entity_kind": "icp", "entity_id": "2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f", "revision": 1, "content_hash": "sha256…" }, "snapshot": { "name": "Operations ICP" } } ], "prepared_at": "2026-09-10T12:00:00Z" } ``` --- # List definitions `GET /definitions` - Scope: graph:read - Role: Current member; module visibility applies - Billing: 1 credit per successful call Lists the workspace's definitions. Non-admins see only definitions in enabled modules whose visibility includes their role. ## Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `includeArchived` | boolean | no | Include archived definitions. Default false. | | `moduleKeys` | enum[] | no | Filter by module: customers · partners · investors · recruiting · advisors · vendors. | | `limit` | integer | no | Page size. Default 50, maximum 200. | | `cursor` | uuid | no | The nextCursor from the previous page. Omit for the first page. | ## Response | Field | Type | | --- | --- | | `items` | Definition[] | | `nextCursor` | uuid | null | ## Request ```bash curl --fail-with-body "https://app.ghostgtm.ai/api/v1/definitions?limit=50" \ -H "Authorization: Bearer $GHOST_API_KEY" ``` ## Example response ```json { "items": [ { "id": "d2c4e6a8-1b3d-4f5e-9a7c-8b0d1e2f3a41", "workspace_id": "ws_7f3a…", "name": "Integration stage", "slug": "integration_stage", "description": null, "module_key": "customers", "entity_kinds": [ "account" ], "value_type": "enum", "cardinality": "exclusive", "enum_values": [ { "label": "New", "slug": "new", "archived": false }, { "label": "Qualified", "slug": "qualified", "archived": false } ], "instruction": null, "refresh_policy": "on_ingest", "capture_enabled": false, "auto_apply_threshold": 0.85, "review_threshold": 0.6, "chat_visible": true, "state": "live", "est_credits_low": null, "est_credits_high": null, "last_run_credits": null, "created_by": "api_key:key_3e…", "reason": "Track qualification from our integration", "created_at": "2026-09-02T10:11:12.000Z", "updated_at": "2026-09-02T10:11:12.000Z", "archived_at": null } ], "nextCursor": null } ``` --- # Create a definition `POST /definitions` - Scope: admin - Role: Owner or admin - Billing: 1 credit per successful call Creates a custom field. The name must produce a nonempty slug; enum slugs must be unique within the definition. captureEnabled: true is rejected until automatic capture ships. ## Headers | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | 1–128 visible ASCII characters. Retrying with the same key returns the original response; reusing it for different input returns 409. | ## Body | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | yes | 1–80 characters. Slugified to snake_case. | | `moduleKey` | enum | yes | customers · partners · investors · recruiting · advisors · vendors | | `entityKinds` | enum[] | yes | 1–4 of account · contact · deal · investor | | `valueType` | enum | yes | enum · text · number · boolean · date · person_ref · account_ref | | `cardinality` | enum | no | exclusive (default) or multi. Enum definitions only. | | `enumValues` | EnumValue[] | no | Up to 200 { label, slug?, description?, color? }. Required when valueType is enum. | | `description` | string | no | Up to 1000 characters. | | `instruction` | string | no | Guidance for the extractor. Up to 2000 characters. | | `refreshPolicy` | enum | no | on_ingest (default) · on_ingest_and_backfill · manual | | `autoApplyThreshold` | number | no | 0–1, default 0.85. Must be ≥ reviewThreshold. | | `reviewThreshold` | number | no | 0–1, default 0.6. | | `chatVisible` | boolean | no | Default true. | | `reason` | string | yes | Why this change is being made. 1–500 characters. Stored in the audit trail. | ## Response | Field | Type | | --- | --- | | `…` | Definition | ## Request ```bash curl --fail-with-body "https://app.ghostgtm.ai/api/v1/definitions" \ -H "Authorization: Bearer $GHOST_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ --data '{ "name": "Integration stage", "moduleKey": "customers", "entityKinds": [ "account" ], "valueType": "enum", "enumValues": [ { "label": "New" }, { "label": "Qualified" } ], "reason": "Track qualification from our integration" }' ``` ## Example response ```json { "id": "d2c4e6a8-1b3d-4f5e-9a7c-8b0d1e2f3a41", "name": "Integration stage", "slug": "integration_stage", "module_key": "customers", "entity_kinds": [ "account" ], "value_type": "enum", "cardinality": "exclusive", "enum_values": [ { "label": "New", "slug": "new", "archived": false }, { "label": "Qualified", "slug": "qualified", "archived": false } ], "state": "live", "reason": "Track qualification from our integration", "created_at": "2026-09-02T10:11:12.000Z" } ``` --- # Update a definition `PATCH /definitions/{definitionId}` - Scope: admin - Role: Owner or admin - Billing: 1 credit per successful call Changes a definition's name, description, instruction, thresholds or policy, and adds or archives enum values. Archived definitions reject changes. ## Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `definitionId` | uuid | yes | Definition id. | ## Headers | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | 1–128 visible ASCII characters. Retrying with the same key returns the original response; reusing it for different input returns 409. | ## Body | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | no | 1–80 characters. | | `description` | string | no | Up to 1000 characters. | | `instruction` | string | no | Up to 2000 characters. | | `addEnumValues` | EnumValue[] | no | Up to 200 new values. | | `archiveEnumValueSlugs` | string[] | no | Slugs to archive. | | `refreshPolicy` | enum | no | on_ingest · on_ingest_and_backfill · manual | | `autoApplyThreshold` | number | no | 0–1 | | `reviewThreshold` | number | no | 0–1 | | `chatVisible` | boolean | no | | | `reason` | string | yes | Why this change is being made. 1–500 characters. Stored in the audit trail. | ## Response | Field | Type | | --- | --- | | `…` | Definition | ## Request ```bash curl --fail-with-body "https://app.ghostgtm.ai/api/v1/definitions/d2c4e6a8-1b3d-4f5e-9a7c-8b0d1e2f3a41" \ -H "Authorization: Bearer $GHOST_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -X PATCH \ -H "Content-Type: application/json" \ --data '{ "addEnumValues": [ { "label": "Proposal sent" } ], "reason": "Sales added a stage" }' ``` ## Example response ```json { "id": "d2c4e6a8-1b3d-4f5e-9a7c-8b0d1e2f3a41", "name": "Integration stage", "enum_values": [ { "label": "New", "slug": "new", "archived": false }, { "label": "Qualified", "slug": "qualified", "archived": false }, { "label": "Proposal sent", "slug": "proposal_sent", "archived": false } ], "state": "live", "updated_at": "2026-09-09T08:01:44.000Z" } ``` --- # Archive a definition `POST /definitions/{definitionId}/archive` - Scope: admin - Role: Owner or admin - Billing: 1 credit per successful call Archives a definition. Existing assignments and history are retained; new assignments are rejected. ## Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `definitionId` | uuid | yes | Definition id. | ## Headers | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | 1–128 visible ASCII characters. Retrying with the same key returns the original response; reusing it for different input returns 409. | ## Body | Field | Type | Required | Description | | --- | --- | --- | --- | | `reason` | string | yes | Why this change is being made. 1–500 characters. Stored in the audit trail. | ## Response | Field | Type | | --- | --- | | `…` | Definition with archived_at set | ## Request ```bash curl --fail-with-body "https://app.ghostgtm.ai/api/v1/definitions/d2c4e6a8-1b3d-4f5e-9a7c-8b0d1e2f3a41/archive" \ -H "Authorization: Bearer $GHOST_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ --data '{ "reason": "Superseded by the CRM lifecycle stage" }' ``` ## Example response ```json { "id": "d2c4e6a8-1b3d-4f5e-9a7c-8b0d1e2f3a41", "name": "Integration stage", "state": "archived", "archived_at": "2026-09-09T08:05:10.000Z" } ``` --- # Merge two enum values `POST /definitions/{definitionId}/merge-value` - Scope: admin - Role: Owner or admin - Billing: 1 credit per successful call Rewrites every assignment of one enum value onto another, preserves original values and evidence in history, links old rows to their successors, and archives the source option. Identical source and destination slugs are rejected. ## Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `definitionId` | uuid | yes | Definition id. | ## Headers | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | 1–128 visible ASCII characters. Retrying with the same key returns the original response; reusing it for different input returns 409. | ## Body | Field | Type | Required | Description | | --- | --- | --- | --- | | `fromSlug` | string | yes | The value being retired. | | `intoSlug` | string | yes | The value that absorbs it. | | `reason` | string | yes | Why this change is being made. 1–500 characters. Stored in the audit trail. | ## Response | Field | Type | | --- | --- | | `rewritten` | integer | ## Request ```bash curl --fail-with-body "https://app.ghostgtm.ai/api/v1/definitions/d2c4e6a8-1b3d-4f5e-9a7c-8b0d1e2f3a41/merge-value" \ -H "Authorization: Bearer $GHOST_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ --data '{ "fromSlug": "qualifed", "intoSlug": "qualified", "reason": "Typo duplicate" }' ``` ## Example response ```json { "rewritten": 14 } ``` --- # Preview a definition `POST /definitions/preview` - Scope: graph:read - Role: Current member; module visibility applies - Billing: 1 credit per successful call Estimates the reach and backfill cost of a definition before it exists. Counts and credit ranges are best-effort heuristics, not billable quotes. Takes the same body as POST /definitions. ## Body | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | yes | | | `moduleKey` | enum | yes | | | `entityKinds` | enum[] | yes | | | `valueType` | enum | yes | | | `reason` | string | yes | Why this change is being made. 1–500 characters. Stored in the audit trail. | ## Response | Field | Type | | --- | --- | | `entitiesInScope` | { account, contact, deal, investor } | | `sourcesToBackfill` | integer | | `estCreditsPerSource` | number | | `estBackfillCredits` | { low, high } | | `conflictingFlatLabels` | { name, slug, assignments }[] | ## Request ```bash curl --fail-with-body "https://app.ghostgtm.ai/api/v1/definitions/preview" \ -H "Authorization: Bearer $GHOST_API_KEY" \ -H "Content-Type: application/json" \ --data '{ "name": "Integration stage", "moduleKey": "customers", "entityKinds": [ "account" ], "valueType": "enum", "enumValues": [ { "label": "New" }, { "label": "Qualified" } ], "reason": "Sizing before we create it" }' ``` ## Example response ```json { "entitiesInScope": { "account": 312, "contact": 0, "deal": 0, "investor": 0 }, "sourcesToBackfill": 1840, "estCreditsPerSource": 0.02, "estBackfillCredits": { "low": 29, "high": 44 }, "conflictingFlatLabels": [ { "name": "Qualified", "slug": "qualified", "assignments": 27 } ] } ``` --- # Assign a value `POST /values` - Scope: graph:write - Role: Current member; module visibility applies - Billing: 1 credit per successful call Assigns a value to an entity for a definition. value is always a string; numbers, booleans and dates are validated and echoed back parsed in value_json. Previous assignments are retained as history. ## Headers | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | 1–128 visible ASCII characters. Retrying with the same key returns the original response; reusing it for different input returns 409. | ## Body | Field | Type | Required | Description | | --- | --- | --- | --- | | `definitionId` | uuid | yes | | | `entityKind` | enum | yes | account · contact · deal · investor | | `entityId` | string | yes | Opaque entity id. Not resolved or created here. | | `value` | string | yes | 1–2000 characters. Enum slugs, finite numbers, true/false, or ISO dates. | | `evidence` | string | no | Up to 2000 characters, quoted verbatim. | | `sourceId` | uuid | no | Source the evidence came from. | | `pinned` | boolean | no | Protect from automatic refresh. Default false. | | `reason` | string | yes | Why this change is being made. 1–500 characters. Stored in the audit trail. | ## Response | Field | Type | | --- | --- | | `…` | Value | ## Request ```bash curl --fail-with-body "https://app.ghostgtm.ai/api/v1/values" \ -H "Authorization: Bearer $GHOST_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ --data '{ "definitionId": "d2c4e6a8-1b3d-4f5e-9a7c-8b0d1e2f3a41", "entityKind": "account", "entityId": "6b1e9a44-2c7d-4f0a-9e3b-1d5c8a7f2e90", "value": "qualified", "evidence": "... the security review is the last blocker before we sign", "reason": "Qualified on the 9/8 call" }' ``` ## Example response ```json { "id": "f1a2b3c4-d5e6-4f70-8a9b-0c1d2e3f4a5b", "field_id": "d2c4e6a8-1b3d-4f5e-9a7c-8b0d1e2f3a41", "entity_kind": "account", "entity_id": "6b1e9a44-2c7d-4f0a-9e3b-1d5c8a7f2e90", "value": "qualified", "value_json": "qualified", "confidence": null, "evidence": "... the security review is the last blocker before we sign", "source_id": null, "written_by_kind": "api_key", "written_by_id": "key_3e…", "pinned": false, "reason": "Qualified on the 9/8 call", "written_at": "2026-09-09T08:10:02.000Z", "superseded_by": null } ``` --- # List an entity's values `GET /values` - Scope: graph:read - Role: Current member; module visibility applies - Billing: 1 credit per successful call Returns the live assignments for one entity, each with a definition summary. ## Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `entityKind` | enum | yes | account · contact · deal · investor | | `entityId` | string | yes | Entity id. | | `limit` | integer | no | Page size. Default 50, maximum 200. | | `cursor` | uuid | no | The nextCursor from the previous page. Omit for the first page. | ## Response | Field | Type | | --- | --- | | `items` | (Value & { definition })[] | | `nextCursor` | uuid | null | ## Request ```bash curl --fail-with-body "https://app.ghostgtm.ai/api/v1/values?entityKind=account&entityId=6b1e9a44-2c7d-4f0a-9e3b-1d5c8a7f2e90&limit=50" \ -H "Authorization: Bearer $GHOST_API_KEY" ``` ## Example response ```json { "items": [ { "id": "f1a2b3c4-d5e6-4f70-8a9b-0c1d2e3f4a5b", "entity_kind": "account", "entity_id": "6b1e9a44-2c7d-4f0a-9e3b-1d5c8a7f2e90", "value": "qualified", "value_json": "qualified", "pinned": false, "written_at": "2026-09-09T08:10:02.000Z", "definition": { "id": "d2c4e6a8-1b3d-4f5e-9a7c-8b0d1e2f3a41", "name": "Integration stage", "slug": "integration_stage", "value_type": "enum", "module_key": "customers" } } ], "nextCursor": null } ``` --- # List modules `GET /modules` - Scope: graph:read - Role: Current member - Billing: 1 credit per successful call Returns the six built-in modules with their enabled state and role visibility. Customers cannot be disabled; customers and partners default on. ## Response | Field | Type | | --- | --- | | `…` | Module[] | ## Request ```bash curl --fail-with-body "https://app.ghostgtm.ai/api/v1/modules" \ -H "Authorization: Bearer $GHOST_API_KEY" ``` ## Example response ```json [ { "module_key": "customers", "enabled": true, "visibility_roles": [ "owner", "admin", "analyst", "member" ], "enabled_at": "2026-03-11T09:14:02.000Z", "reason": null }, { "module_key": "partners", "enabled": true, "visibility_roles": [ "owner", "admin", "analyst" ], "enabled_at": "2026-03-11T09:14:02.000Z", "reason": null }, { "module_key": "investors", "enabled": false, "visibility_roles": [ "owner", "admin" ], "enabled_at": null, "reason": null } ] ``` --- # Enable or disable a module `POST /modules` - Scope: admin - Role: Owner or admin - Billing: 1 credit per successful call Toggles a module and sets which roles can see its definitions. Toggling does not start ingestion or backfills. ## Headers | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | 1–128 visible ASCII characters. Retrying with the same key returns the original response; reusing it for different input returns 409. | ## Body | Field | Type | Required | Description | | --- | --- | --- | --- | | `moduleKey` | enum | yes | customers · partners · investors · recruiting · advisors · vendors | | `enabled` | boolean | yes | | | `visibilityRoles` | enum[] | no | Any of owner · admin · analyst · member. At least one. | | `reason` | string | yes | Why this change is being made. 1–500 characters. Stored in the audit trail. | ## Response | Field | Type | | --- | --- | | `success` | boolean | ## Request ```bash curl --fail-with-body "https://app.ghostgtm.ai/api/v1/modules" \ -H "Authorization: Bearer $GHOST_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ --data '{ "moduleKey": "investors", "enabled": true, "visibilityRoles": [ "owner", "admin" ], "reason": "Fundraise tracking" }' ``` ## Example response ```json { "success": true } ``` --- # Read live relations and history `GET /relations` - Scope: graph:read - Role: Current member - Billing: 1 credit per successful call Returns the live edges for one entity and relation type, plus the full version history across them. Nothing is deleted: a retired edge stays in history with superseded_at set. ## Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `fromKind` | enum | yes | customer · account_person | | `fromId` | uuid | yes | | | `relationType` | enum | yes | matches_icp · matches_persona · interested_in_use_case · uses_product · discussed_competitor | ## Response | Field | Type | | --- | --- | | `live` | Relation[] | | `history` | RelationVersion[] | ## Request ```bash curl --fail-with-body "https://app.ghostgtm.ai/api/v1/relations?fromKind=customer&fromId=6b1e9a44-2c7d-4f0a-9e3b-1d5c8a7f2e90&relationType=matches_icp" \ -H "Authorization: Bearer $GHOST_API_KEY" ``` ## Example response ```json { "live": [ { "id": "8a7b6c5d-4e3f-4a2b-9c1d-0e9f8a7b6c5d", "from_entity_id": "6b1e9a44-2c7d-4f0a-9e3b-1d5c8a7f2e90", "from_entity_kind": "customer", "to_entity_id": "2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f", "to_entity_kind": "icp", "relation_type": "matches_icp", "source": "human:usr_91c0…", "confidence": 1, "evidence": null, "superseded_by": null, "superseded_at": null, "created_at": "2026-08-14T12:00:00.000Z" } ], "history": [ { "id": "v1", "relation_id": "8a7b6c5d-4e3f-4a2b-9c1d-0e9f8a7b6c5d", "version_no": 1, "action": "confirm", "actor_kind": "human", "actor_id": "usr_91c0…", "reason": "Confirmed on review", "created_at": "2026-08-14T12:00:00.000Z" } ] } ``` --- # Preview a relation edit `POST /relations/preview` - Scope: graph:read - Role: Current member - Billing: 1 credit per successful call Takes the same body as POST /relations and returns what would be retired, added, revived or confirmed, the current edges with their sourceClass, and the modes that make sense right now. No write. ## Body | Field | Type | Required | Description | | --- | --- | --- | --- | | `fromKind` | enum | yes | customer · account_person | | `fromId` | uuid | yes | | | `relationType` | enum | yes | | | `toKind` | enum | yes | icp · persona · use_case · product · competitor | | `toId` | uuid | no | Required for add and replace. | | `mode` | enum | yes | add · replace · retire | | `reason` | string | yes | Why this change is being made. 1–500 characters. Stored in the audit trail. | ## Response | Field | Type | | --- | --- | | `current` | (Relation & { sourceClass })[] | | `wouldRetire` | Relation[] | | `wouldAdd` | { toKind, toId } | null | | `wouldRevive` | Relation | null | | `wouldConfirm` | Relation | null | | `options` | { mode, description }[] | ## Request ```bash curl --fail-with-body "https://app.ghostgtm.ai/api/v1/relations/preview" \ -H "Authorization: Bearer $GHOST_API_KEY" \ -H "Content-Type: application/json" \ --data '{ "fromKind": "customer", "fromId": "6b1e9a44-2c7d-4f0a-9e3b-1d5c8a7f2e90", "relationType": "matches_icp", "toKind": "icp", "toId": "2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f", "mode": "replace", "replace": { "scope": "all_llm" }, "reason": "Rep confirmed the ICP on the call" }' ``` ## Example response ```json { "current": [ { "id": "9b8c…", "to_entity_id": "5e6f…", "relation_type": "matches_icp", "source": "llm:pmm-label-refresh", "sourceClass": "llm" } ], "wouldRetire": [ { "id": "9b8c…", "source": "llm:pmm-label-refresh" } ], "wouldAdd": { "toKind": "icp", "toId": "2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f" }, "wouldRevive": null, "wouldConfirm": null, "options": [ { "mode": "add", "description": "Add another relation alongside the current live ones." }, { "mode": "replace", "description": "Replace the selected current relation(s) with a new one." }, { "mode": "retire", "description": "Retire a current relation without adding a replacement." } ] } ``` --- # Add, replace or retire a relation `POST /relations` - Scope: graph:write - Role: Owner, admin or analyst - Billing: 1 credit per successful call Writes one versioned change. add creates a live edge beside existing ones; replace supersedes named edges, every LLM-derived edge, or all edges with the new one; retire supersedes with no replacement. Adding an edge the pipeline already holds promotes it to yours instead of duplicating it. Human decisions outrank the pipeline: the scheduled refresh never overwrites an edge a person or key wrote. ## Headers | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | 1–128 visible ASCII characters. Retrying with the same key returns the original response; reusing it for different input returns 409. | ## Body | Field | Type | Required | Description | | --- | --- | --- | --- | | `fromKind` | enum | yes | customer · account_person | | `fromId` | uuid | yes | | | `relationType` | enum | yes | matches_icp · matches_persona · interested_in_use_case · uses_product · discussed_competitor | | `toKind` | enum | yes | icp · persona · use_case · product · competitor | | `toId` | uuid | no | Required for add and replace. | | `mode` | enum | yes | add · replace · retire | | `replace` | { scope, relationIds? } | no | replace only. scope is named · all_llm · all; named requires relationIds. | | `retireRelationId` | uuid | no | retire only. | | `evidence` | string | no | Up to 2000 characters. | | `reason` | string | yes | Why this change is being made. 1–500 characters. Stored in the audit trail. | ## Response | Field | Type | | --- | --- | | `added` | Relation | null | | `retired` | Relation[] | | `revived` | Relation | null | | `confirmed` | Relation | null | | `liveId` | string | null | ## Request ```bash curl --fail-with-body "https://app.ghostgtm.ai/api/v1/relations" \ -H "Authorization: Bearer $GHOST_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ --data '{ "fromKind": "customer", "fromId": "6b1e9a44-2c7d-4f0a-9e3b-1d5c8a7f2e90", "relationType": "matches_icp", "toKind": "icp", "toId": "2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f", "mode": "add", "reason": "Rep confirmed the ICP on the call" }' ``` ## Example response ```json { "added": { "id": "8a7b6c5d-4e3f-4a2b-9c1d-0e9f8a7b6c5d", "from_entity_id": "6b1e9a44-2c7d-4f0a-9e3b-1d5c8a7f2e90", "to_entity_id": "2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f", "relation_type": "matches_icp", "source": "api_key:key_3e…", "created_at": "2026-09-09T08:20:00.000Z" }, "retired": [], "revived": null, "confirmed": null, "liveId": "8a7b6c5d-4e3f-4a2b-9c1d-0e9f8a7b6c5d" } ``` --- # Read one relation's history `GET /relations/{relationId}/history` - Scope: graph:read - Role: Current member - Billing: 1 credit per successful call The version log for a single edge: who changed it, what, why, before and after state, and the edge that replaced it. ## Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `relationId` | uuid | yes | | ## Response | Field | Type | | --- | --- | | `…` | RelationVersion[] | ## Request ```bash curl --fail-with-body "https://app.ghostgtm.ai/api/v1/relations/8a7b6c5d-4e3f-4a2b-9c1d-0e9f8a7b6c5d/history" \ -H "Authorization: Bearer $GHOST_API_KEY" ``` ## Example response ```json [ { "id": "v2", "relation_id": "8a7b6c5d-4e3f-4a2b-9c1d-0e9f8a7b6c5d", "version_no": 2, "action": "retire", "actor_kind": "api_key", "actor_id": "key_3e…", "source": "api_key:key_3e…", "reason": "Account moved segments", "before_state": { "superseded_at": null }, "after_state": { "superseded_at": "2026-09-09T09:00:00.000Z" }, "replaced_by_relation_id": null, "created_at": "2026-09-09T09:00:00.000Z" } ] ``` --- # Quote a compute run `POST /compute/preview` - Scope: admin - Role: Owner or admin - Billing: 1 credit per successful call Returns a credit range for a pmm_refresh from the workspace's own trailing average, or a heuristic when there is no history. Omit target to quote every account. ## Body | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | enum | yes | pmm_refresh | | `target` | { customerIds? } | no | 1–500 account ids. Omit for every live account. | ## Response | Field | Type | | --- | --- | | `kind` | pmm_refresh | | `unit` | account | | `unitCount` | integer | | `estimate` | { creditsLow, creditsHigh } | | `basis` | workspace_trailing_avg | heuristic | ## Request ```bash curl --fail-with-body "https://app.ghostgtm.ai/api/v1/compute/preview" \ -H "Authorization: Bearer $GHOST_API_KEY" \ -H "Content-Type: application/json" \ --data '{ "kind": "pmm_refresh", "target": { "customerIds": [ "6b1e9a44-2c7d-4f0a-9e3b-1d5c8a7f2e90" ] } }' ``` ## Example response ```json { "kind": "pmm_refresh", "unit": "account", "unitCount": 1, "estimate": { "creditsLow": 3, "creditsHigh": 5 }, "basis": "workspace_trailing_avg" } ``` --- # Start a compute run `POST /compute/runs` - Scope: admin - Role: Owner or admin - Billing: 1 credit per successful call Enqueues a capped refresh. The worker stops at creditCap and marks the run capped. ## Headers | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string | no | 1–128 visible ASCII characters. Retrying with the same key returns the original response; reusing it for different input returns 409. | ## Body | Field | Type | Required | Description | | --- | --- | --- | --- | | `kind` | enum | yes | pmm_refresh | | `target` | { customerIds? } | no | | | `creditCap` | number | yes | Hard ceiling on credits spent. | | `reason` | string | yes | Why this change is being made. 1–500 characters. Stored in the audit trail. | ## Response | Field | Type | | --- | --- | | `runId` | uuid | | `status` | queued | ## Request ```bash curl --fail-with-body "https://app.ghostgtm.ai/api/v1/compute/runs" \ -H "Authorization: Bearer $GHOST_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ --data '{ "kind": "pmm_refresh", "target": { "customerIds": [ "6b1e9a44-2c7d-4f0a-9e3b-1d5c8a7f2e90" ] }, "creditCap": 5, "reason": "Refresh after the ICP change" }' ``` ## Example response ```json { "runId": "3d4e5f6a-7b8c-4d9e-8f0a-1b2c3d4e5f6a", "status": "queued" } ``` --- # Read a compute run `GET /compute/runs/{runId}` - Scope: admin - Role: Owner or admin - Billing: free Status, credits spent against the cap, and the result or error. ## Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `runId` | uuid | yes | | ## Response | Field | Type | | --- | --- | | `runId` | uuid | | `kind` | string | | `status` | queued | running | completed | capped | failed | | `creditsSpent` | number | | `creditCap` | number | | `triggerRunId` | string | null | | `result` | object | null | | `error` | string | null | ## Request ```bash curl --fail-with-body "https://app.ghostgtm.ai/api/v1/compute/runs/3d4e5f6a-7b8c-4d9e-8f0a-1b2c3d4e5f6a" \ -H "Authorization: Bearer $GHOST_API_KEY" ``` ## Example response ```json { "runId": "3d4e5f6a-7b8c-4d9e-8f0a-1b2c3d4e5f6a", "kind": "pmm_refresh", "status": "completed", "creditsSpent": 4.2, "creditCap": 5, "triggerRunId": "run_01j…", "result": { "accountsRefreshed": 1, "edgesUpdated": 3 }, "error": null } ``` ---