# graph8 documentation (full) > 298 pages from https://docs.graph8.com, converted to Markdown for AI agents. Each page starts with its title and source URL. Index: https://docs.graph8.com/llms.txt --- # Agency Keys & Cross-Org Targeting > Mint a single agency-scoped API key to operate any client org across the entire Developer API without one key per client. Source: https://docs.graph8.com/developers/agency-keys/ An **agency** is an org that manages a portfolio of client orgs linked in graph8 as parent → child. An agency can mint a single **agency-scoped API key** and use it to operate any of its client orgs through the entire Developer API — contacts, sequences, dialer, workflows, appointments, deals, everything — without minting one key per client. > **Note:** > Agency key support was added on 2026-06-19. If your org does not have linked client orgs, or is not flagged as an agency, the `is_agency: true` flag returns **400**. --- ## Mint an Agency Key `POST /api-keys` Create an agency-scoped API key on the **agency (parent) org** by passing `is_agency: true` in the request body. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | string | **Yes** | Human-readable label for the key | | `is_agency` | boolean | No | Set `true` to mint an agency-scoped key. Omit or set `false` for a normal key | > **Caution:** > - Requires the standard API-key admin permission on the agency org. > - Only allowed when the org is a **recognized agency** (it has linked client orgs, or is flagged `is_agency`). Otherwise the API returns **400**. > - The secret token is shown **once** in the response. Store it securely immediately. ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/api-keys" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "CIENCE agency ops", "is_agency": true }' ``` ### Response ```json { "data": { "id": "key_abc123", "name": "CIENCE agency ops", "is_agency": true, "token": "sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" } } ``` ### Errors | Status | Meaning | |--------|---------| | `400` | Org is not a recognized agency (no linked client orgs, not flagged `is_agency`) | | `403` | Caller lacks API-key admin permission | --- ## Cross-Org Targeting — `X-Target-Org-Id` Once you have an agency key, send it with the `X-Target-Org-Id` header to act on a specific client org. The request then reads and writes **that client org's** data. ### Targeting Rules | Request | Result | |---------|--------| | Agency key, **no** `X-Target-Org-Id` | **200** — resolves to the **agency's own** org | | Agency key + `X-Target-Org-Id` for an **authorized** client (case-insensitive) | **200** — operates that client org | | Agency key + `X-Target-Org-Id` set to the agency's own org ID | **200** — operates the agency org | | Agency key + `X-Target-Org-Id` for an org **outside** the agency's authorized set | **403** — not authorized for that target | | **Normal** key + `X-Target-Org-Id` | Header **ignored** — resolves to the key's own org (no error) | ### Notes - `X-Target-Org-Id` is honored on the **Developer API** (`/api/v1/*`) only. It is not used by in-app or customer-hub hybrid endpoints, which keep single-org semantics. - `X-Target-Org-Id` is **distinct** from `X-Org-Id`. `X-Org-Id` remains a same-org safety guard; `X-Target-Org-Id` is the agency selector. Do not conflate them. For an agency key, `X-Org-Id` (if sent) still checks against the **agency's own** org, independent of any `X-Target-Org-Id`. - The target is always **clamped** against the agency's live authorized client set — a caller-supplied value can only select within that set, never widen it. - **Billing:** credit-consuming actions performed in a client org are charged to **that client org**, not the agency. - Cross-org **writes** are supported, with one exception: destructive marketplace operations (for example, approving an SDR invoice that fires a Stripe charge) still require a personal admin key and are refused for agency machine keys. ### Example **cURL** ```bash # List contacts in a specific client org curl "https://be.graph8.com/api/v1/contacts" \ -H "Authorization: Bearer $AGENCY_KEY" \ -H "X-Target-Org-Id: org_client123" # Create a contact in a client org curl -X POST "https://be.graph8.com/api/v1/contacts" \ -H "Authorization: Bearer $AGENCY_KEY" \ -H "X-Target-Org-Id: org_client123" \ -H "Content-Type: application/json" \ -d '{ "list_id": 5, "first_name": "Jane", "last_name": "Smith", "work_email": "jane@clientco.com" }' ``` --- ## Get Agency Credential Info `GET /agency/me` Describe the agency credential associated with the current API key. Returns metadata about the agency org and the number of client orgs the key can target. > **Note:** > Requires an agency-scoped key. Returns **403** if the key is a normal (non-agency) key. ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/agency/me" \ -H "Authorization: Bearer $AGENCY_KEY" ``` ### Response ```json { "data": { "agency_org_id": "org_agency123", "agency_org_name": "CIENCE", "is_agency": true, "client_count": 52, "target_header": "X-Target-Org-Id" } } ``` | Field | Type | Description | |-------|------|-------------| | `agency_org_id` | string | The org ID of the agency itself | | `agency_org_name` | string | Display name of the agency org | | `is_agency` | boolean | Always `true` for a valid agency key | | `client_count` | integer | Number of client orgs this key is authorized to target | | `target_header` | string | The header name to use for cross-org targeting (`X-Target-Org-Id`) | ### Errors | Status | Meaning | |--------|---------| | `403` | Key is not an agency-scoped key | --- ## List Authorized Client Orgs `GET /agency/clients` List all client orgs this agency key is authorized to target. Each `org_id` in the response is a valid value for the `X-Target-Org-Id` header. > **Note:** > Requires an agency-scoped key. Returns **403** if the key is a normal (non-agency) key. ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/agency/clients" \ -H "Authorization: Bearer $AGENCY_KEY" ``` ### Response ```json { "data": [ { "org_id": "org_client123" }, { "org_id": "org_client456" } ] } ``` | Field | Type | Description | |-------|------|-------------| | `org_id` | string | Client org ID. Use this value as `X-Target-Org-Id` to operate that org | ### Errors | Status | Meaning | |--------|---------| | `403` | Key is not an agency-scoped key | --- # REST API FAQ > Common questions about the graph8 Developer REST API — auth headers, pagination, rate limits, errors, idempotency, webhooks, and credits Source: https://docs.graph8.com/developers/api-faq/ Common questions about the graph8 Developer REST API at `https://be.graph8.com/api/v1`. If your question isn't here, see the [REST API overview](https://docs.graph8.com/developers/), [Authentication](https://docs.graph8.com/developers/authentication/), or the interactive Swagger at [be.graph8.com/api/v1/docs](https://be.graph8.com/api/v1/docs). > **Tip:** > Try the developer tier free - 1,000 credits, no card required. [Get started](https://app.graph8.com/signup). ## Base URL and auth ### What's the base URL? `https://be.graph8.com/api/v1` for production. Every endpoint in the dev docs hangs off this prefix. ### What auth headers do I send? Two required headers on every call: ```http Authorization: Bearer X-Org-Id: ``` The `X-Org-Id` is omitted in most examples because it's automatically set when the API key was issued — graph8 reads the org binding off the key. You only need to send `X-Org-Id` when running cross-org service tokens. ### Where do I get the API key? - **Personal**: **Profile -> Developer** (recommended for individuals). - **Org**: **Settings -> API** (admin only — for shared service accounts). See [Authentication](https://docs.graph8.com/developers/authentication/). ### Is there a public surface that doesn't need an API key? Yes — a small set of "write-key" endpoints under `/api/v1/public/*` for browser tracking, visitor lookup, and copilot chat. See [Public Endpoints](https://docs.graph8.com/developers/public-endpoints/). ## Surface boundaries ### Is there only one API surface? There are two: | Surface | Mount | Auth | Use case | |---|---|---|---| | **Developer API** | `/api/v1/*` | Bearer + `X-Org-Id` | Public — what the SDK, CLI, and MCP server all proxy | | **Internal Studio** | `/v1/*` and `/campaign-builder/*` | PropelAuth session | First-party UI in `app.graph8.com` | If you're building an integration, **only use `/api/v1/*`**. The internal surface is undocumented and changes without notice. ### How many endpoints are there? ~150 on `/api/v1/*`. The full inventory is documented per-page in this section (Contacts, Companies, Lists, Search, Enrichment, Sequences, Inbox, Webhooks, GTM Campaigns, Audience Sync, CRM Sync, Public Endpoints) plus the interactive Swagger at [be.graph8.com/api/v1/docs](https://be.graph8.com/api/v1/docs). ### Does the MCP server expose anything the REST API doesn't? Almost. The MCP server adds `g8_tool_search` (a discovery meta-tool) and `g8_load_playbook` (markdown content). Everything else is a thin wrapper over `/api/v1/*`. ## Pagination ### What's the standard pagination shape? Most list endpoints accept `page` (default 1) and `limit` (default varies, max 200) and return: ```json { "data": [...], "pagination": { "page": 1, "limit": 25, "total": 1238, "has_next": true } } ``` Some endpoints use cursor pagination (`cursor` + `next_cursor`) — see [Pagination](https://docs.graph8.com/developers/pagination/) for the rules. ### How do I iterate through all pages? ```bash PAGE=1 while : ; do resp=$(curl -s -H "Authorization: Bearer $G8_API_KEY" \ "https://be.graph8.com/api/v1/contacts?page=$PAGE&limit=200") echo "$resp" | jq -c '.data[]' has_next=$(echo "$resp" | jq '.pagination.has_next') [[ "$has_next" == "true" ]] || break PAGE=$((PAGE + 1)) done ``` Or use the SDK / CLI which handle pagination natively. ## Rate limits ### What's the rate limit? **50 requests per second + 1,000 requests per minute per org**, applied across all `/api/v1/*` endpoints. The per-second cap absorbs bursts; the per-minute cap governs sustained throughput. Exceeding either returns `429 Too Many Requests` with a `Retry-After` header (seconds). Every response also carries `X-RateLimit-Limit-Second`, `X-RateLimit-Limit-Minute`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` so you can self-pace before you get throttled — see [Rate Limits](https://docs.graph8.com/developers/rate-limits/). ### Is there a monthly cap? No. The 50 rps cap is the only ceiling — there's no monthly volume limit on index-backed endpoints (search, lookup) on either PAYG or Platform. See [Pricing](https://docs.graph8.com/developers/pricing/). ### How do I implement backoff? ```python import time, httpx async def call_with_backoff(client, path, **kw): for attempt in range(5): resp = await client.request(method, path, **kw) if resp.status_code != 429: return resp wait = int(resp.headers.get("Retry-After", "1")) await asyncio.sleep(wait * (attempt + 1)) return resp ``` See [Rate Limits](https://docs.graph8.com/developers/rate-limits/) for the full strategy. ## Credits ### Which endpoints consume credits? Mental model: **graph8-owned data is free on both plans. Third-party data, AI, voice, sends, and meeting bookings charge credits on both plans.** Canonical matrix at [Pricing](https://docs.graph8.com/developers/pricing/). **Always free on both paid plans (within the 50 rps cap)** graph8-owned data: | Endpoint family | What it is | |---|---| | `/search/contacts`, `/search/companies` | B2B index search (700M+ / 100M+ records) | | `/search/contacts/save`, `/search/companies/save` | Save matching records to a list | | `/intent/*` (reads) | Intent + keyword + page-visitor data | | `/public/visitors/*`, `/public/signals/company` | Anonymous-visitor IP resolution + summary intent score | First-party CRM + metadata: | Endpoint family | What it is | |---|---| | `/contacts`, `/companies`, `/lists`, `/deals`, `/tasks`, `/notes`, `/fields` (all CRUD) | First-party CRM | | `/quotes/*` (CRUD; **sending** a quote is free email) | Quote-to-cash metadata | | `/workflows/*`, `/skills/*`, `/pipelines/*` (CRUD only — `execute` charges per LLM) | Automation graphs | | `/pages/*` (CRUD + publish; **chat edit** charges per LLM) | Landing pages | | `/icps`, `/personas`, `/global-context/documents`, `/intelligence-data`, `/research-reports` | Studio reads | | `/webhooks/*` (subscription mgmt + delivery) | Webhook plumbing | | `/voice/dialer/calls/{room}/{transcript,grading}` | Voice reads (recording + transcript) | | `/inbox/meetings` (list + get) | Meeting reads — booking confirmation charges 20 cr separately | **Always charges credits (both plans — drawn from balance or 75k bundle)** | Endpoint | Cost | |---|---| | `/enrichment/lookup/person`, `/enrichment/lookup/company` (single-record index lookup) | 2 credits per lookup, charged only when a match is found | | `/enrichment/verify-email` | 1 cr per verify (no charge when the verifier call fails and `status` comes back `unknown`) | | `/enrichment/enrich` (waterfall — 14 third-party providers) | 0.5-20 cr per provider hit (typically 1-3) | | `/api/v1/public/copilot/chat` and SDK `g8.copilot.ask` | Per LLM tokens | | `/inbox/{id}/draft` (AI reply draft) | Per LLM tokens | | `/skills/{id}/execute` with `type=llm` | Per LLM tokens | | `/pages/{id}/chat` (landing-page chat edit) | Per LLM tokens | | Studio AI generation (brief, brand kit, intelligence) | Per LLM tokens | | `/voice/dialer/sessions/{id}/resume` (real outbound) | 20 cr / minute audio | | **Meeting booking** (charged when the booking is confirmed) | 20 cr flat per booking | | `/sequences/{id}/contacts`, `/campaigns/{id}/launch` | 1 cr / step | | Newsletter send | 1 cr / recipient | | `/enrichment/verify-email?provider=kickbox\|zerobounce` (external) | 1 cr / verify | | Intent signal processing (background) | 1 cr / 10 events | | Event-stream ingest at scale | 1 cr / 10 events | | `/audience-syncs/{id}/trigger` push | Per record where destination meters | ### Is search really unlimited? Yes — on **both PAYG and Platform**, within the 50 rps cap. Search hits graph8's own indexes (700M+ contacts / 100M+ companies) with no third-party COGS, so neither plan meters search, lookup, save-to-list, or internal email verify. The 50 rps cap exists to keep the platform stable, not to meter usage. (Per-record constants exist in `developer_api/constants.py` as fallback rates for free-trial and grandfathered legacy orgs; plan-aware gating skips them for PAYG and Platform.) ### How do I check my credit balance via API? `GET /usage` returns the current balance for the authenticated org. It requires the `usage:read` scope. ```bash curl -H "Authorization: Bearer $G8_API_KEY" https://be.graph8.com/api/v1/usage # { # "data": { # "customer_id": "org_a1b2c3d4", # "credits": 75000.0, # "held_credits": 0.0, # "available_credits": 12450.0, # "total_earned": 75000.0, # "total_used": 62550.0 # } # } ``` `credits` is the total balance, `held_credits` is what is currently reserved by in-flight work, and `available_credits` is what you can actually spend (`credits - held_credits`). For the itemised ledger behind those totals, use `GET /usage/transactions` — it supports both page-number (`page` / `limit`) and opaque cursor (`cursor`) pagination. ## Errors ### What status codes can I expect? | Code | Meaning | Action | |---|---|---| | 200 / 201 | Success | — | | 204 | Success, no content (DELETE) | — | | 400 | Validation error | Inspect `error.field` | | 401 | Bad / missing API key | Re-issue key | | 402 | Out of credits (PAYG) | Top up | | 403 | Wrong org / no permission | Check token scope | | 404 | Record not found | — | | 409 | Conflict (e.g. duplicate email) | Use upsert via `/contacts/assert` | | 422 | Semantic error | Inspect `error.message` | | 429 | Rate limit | Backoff with `Retry-After` | | 5xx | graph8 error | Retry with exponential backoff | See [Errors](https://docs.graph8.com/developers/errors/) for the full error envelope. ### What's the error envelope shape? ```json { "error": { "code": "validation_error", "message": "work_email is required", "field": "work_email", "request_id": "req_abc123" } } ``` Include `request_id` in any support ticket — it lets us trace the call end-to-end. ## Idempotency, upserts, and webhooks ### How do I upsert (create-or-update) a contact? Use `PUT /api/v1/contacts/assert`: ```bash curl -X PUT https://be.graph8.com/api/v1/contacts/assert \ -H "Authorization: Bearer $G8_API_KEY" \ -H "Content-Type: application/json" \ -d '{"work_email":"jane@acme.com","first_name":"Jane","last_name":"Smith"}' ``` Returns `{ created: bool, id, ... }`. Same shape for `/companies/assert` and `/deals/assert`. See [Assert / Upsert](https://docs.graph8.com/developers/assert/). ### Are webhooks supported? Yes. Subscribe via `POST /api/v1/webhooks`: ```json { "url": "https://my.app.com/g8-webhook", "events": ["reply_received", "meeting_booked", "form_submitted", "contact_enriched"], "secret": "whsec_xxx" } ``` Payloads are signed with HMAC-SHA256 over the body using the `secret`. Verify the `X-G8-Signature` header before processing. See [Webhooks](https://docs.graph8.com/developers/webhooks/). ### What events fire? `reply_received`, `meeting_booked`, `contact_enriched`, `contact_created`, `sequence_completed`, `campaign_launched`, `form_submitted`, `visitor_identified`. New events get added — listen via `events: ["*"]` to receive everything. ## Auto-CRM-capture ### What's auto-CRM-capture? Every contact or company returned by an enrichment lookup / search is saved into your CRM automatically (tagged `source: 'api_lookup'`). The next time you query `GET /contacts`, the record is already there. See [Pricing → Auto-CRM-Capture](https://docs.graph8.com/developers/pricing/#auto-crm-capture). ### How do I opt out per call? ```bash curl "https://be.graph8.com/api/v1/enrichment/lookup/person?capture=false" \ -H "Authorization: Bearer $G8_API_KEY" \ -d '{"email":"jane@acme.com"}' ``` Or pass `"capture": false` in the JSON body for POST endpoints. Per-key and org-wide toggles exist in **Settings -> API**. ## See also - [REST API overview](https://docs.graph8.com/developers/) — endpoint index - [Authentication](https://docs.graph8.com/developers/authentication/) — key creation, headers - [Pagination](https://docs.graph8.com/developers/pagination/) — page / limit / cursor rules - [Errors](https://docs.graph8.com/developers/errors/) — full status-code matrix - [Rate Limits](https://docs.graph8.com/developers/rate-limits/) — 50 rps cap and backoff - [Pricing](https://docs.graph8.com/developers/pricing/) — credit costs, plans, auto-capture - [Webhooks](https://docs.graph8.com/developers/webhooks/) — event subscriptions - [SDK FAQ](https://docs.graph8.com/developers/sdk-faq/) / [CLI FAQ](https://docs.graph8.com/developers/cli-faq/) / [MCP FAQ](https://docs.graph8.com/developers/mcp-faq/) --- # API Keys: Live & Test Mode > Mint live and test API keys with g8_live_/g8_test_ prefixes Source: https://docs.graph8.com/developers/api-keys/ graph8 API keys come in two **modes**, Stripe-style: | Mode | Prefix | Use for | |------|--------|---------| | **Live** | `g8_live_…` | Production traffic against your real data | | **Test** | `g8_test_…` | Integration + CI; tells test keys from live ones at a glance | The mode is **authoritative** — it's stamped on the key at creation and read back on every request. Editing the prefix on a key string does **not** change its mode; the prefix is only a cosmetic label. Legacy keys with no prefix are treated as **live**, so existing integrations are unaffected. ## Create a key `POST /v1/api-keys` with an optional `mode` (default `live`). The token is returned **once** — store it securely. **curl** ```bash curl -X POST https://be.graph8.com/v1/api-keys \ -H "Authorization: Bearer $G8_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "CI test key", "mode": "test"}' # => { "api_key_id": "...", "api_key_token": "g8_test_...", "name": "CI test key", "mode": "test" } ``` **Python** ```python import httpx r = httpx.post( "https://be.graph8.com/v1/api-keys", headers={"Authorization": f"Bearer {api_key}"}, json={"name": "CI test key", "mode": "test"}, ) token = r.json()["api_key_token"] # g8_test_... ``` **TypeScript** ```ts const res = await fetch("https://be.graph8.com/v1/api-keys", { method: "POST", headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" }, body: JSON.stringify({ name: "CI test key", mode: "test" }), }); const { api_key_token } = await res.json(); // g8_test_... ``` ## Using a key Send it as a bearer token — the `g8_live_` / `g8_test_` prefix is accepted and stripped automatically before validation: ```bash curl https://be.graph8.com/api/v1/contacts -H "Authorization: Bearer g8_test_..." ``` > **Note: Test-mode data isolation** > Test keys are **tagged** test-mode today (authoritative, server-side). Full **sandbox data isolation** — a test key only sees sandbox data — ships with the dedicated sandbox environment. Until then, treat a test key as operating against real data. ## Listing keys `GET /v1/api-keys` returns each key with its `mode` and `last_used_at` (unix seconds; `null` if the key has never been used). Tokens are never returned by the list. ```bash curl https://be.graph8.com/v1/api-keys -H "Authorization: Bearer $G8_API_KEY" # { # "keys": [ # { "api_key_id": "...", "name": "CI test key", "mode": "test", # "is_agency": false, "created_at": 1719700000, "last_used_at": 1719786400 } # ], # "total": 1 # } ``` `last_used_at` is stamped best-effort at authentication time (roughly per-minute granularity) — use it to spot stale keys you can revoke. --- # API and MCP coverage > Understand which operations are exposed through MCP and when to use the API. Source: https://docs.graph8.com/developers/api-mcp-coverage/ The API and MCP expose overlapping surfaces. The presence of an API endpoint does not imply that a tool exists for that exact operation. ## Choose an interface 1. Search the [MCP tool reference](https://docs.graph8.com/developers/mcp-tools/) for the action you need. 2. If no tool provides it, consult the [API reference](https://docs.graph8.com/developers/) and check its authentication, scope and input requirements. 3. Confirm whether the operation is intended for your API principal. Public, app-token, administrative and session endpoints have different audiences. 4. Check the response and the destination record after a write. Do not retry a consequential action blindly after a timeout. In the audited contract, all **564 API operations** have a direct call from a registered MCP tool. MCP server 0.64.0 includes [145 additional tools](https://docs.graph8.com/developers/mcp-reference/api-coverage/) with input validation and progressive discovery. The final approval call relays a decision already authorized by the signed-in human in graph8. This is source coverage, not a guarantee that every operation is enabled in your organization. ## Common reasons for a difference - A tool combines several API calls into a task. - A tool is discoverable but not initially visible in the client. - An API route serves authentication, infrastructure or an app-specific principal. - An API operation has not yet been wrapped as a tool. `g8_execute` invokes registered tools; it does not turn an arbitrary API route into a tool. ## Related - [Authentication](https://docs.graph8.com/developers/authentication/) - [Key scopes](https://docs.graph8.com/developers/key-scopes/) - [Errors](https://docs.graph8.com/developers/errors/) - [Idempotency](https://docs.graph8.com/developers/idempotency/) Five internal operator reports require an existing internally minted operator API key. Ordinary organization keys and Owner/Admin roles do not grant access. See [operator report setup](https://docs.graph8.com/developers/mcp-reference/api-coverage/#operator-reports-in-0600). Nine app-object operations use a separate local app-token connection. See [app-token connection setup](https://docs.graph8.com/developers/mcp-reference/api-coverage/#app-token-connection). Public copilot and enrichment use a separate local write-key connection. See [public-widget connection setup](https://docs.graph8.com/developers/mcp-reference/api-coverage/#public-widget-connection). ## Trusted workflows in 0.63.0 Three previously separate API operations now have direct tools with isolated authority. Human approval also has a read-only handoff to the authenticated review screen. | API operation | MCP workflow | | --- | --- | | `POST /apps/{app_id}/deployments/{deployment_id}/status` | `g8_app_report_observed_build` derives progress from the actual Kubernetes build Job. The agent cannot supply status, a digest, or controller authority. | See [credential-provider and controller setup](https://docs.graph8.com/developers/mcp-reference/api-coverage/#trusted-connections). Ordinary organization credentials and privileged roles cannot substitute for those connections. ## Human-authorized decision relay in 0.64.0 Use `g8_agent_submit_human_decision` with an `approval_id`. Without a stored authorization, it returns a link to the exact approval in graph8. Open the link, sign in, review the action and choose the decision to authorize. Then call the tool again within five minutes using your personal API key belonging to the same signed-in human. The tool cannot choose a decision or submit edited action details. The backend consumes the human authorization once, only if the action, owner and visibility still match. Expired or changed approvals require fresh human review. Organization API keys cannot submit this relay. A timeout requires checking the approval status before another attempt. The measured direct count is **564/564**. A tool confirmation or elicitation response cannot substitute for human authorization. A recorded decision is distinct from downstream execution and its receipts. QA integration also adds app-record change history through `g8_app_object_record_changes`, using the existing app-token connection. The current registry has 599 tools; the existing feature IDs and categories remain unchanged. --- # Appointments Management > Manage event types, bookings, schedules, availability, routing forms, workflows, and calendar integrations via the graph8 API Source: https://docs.graph8.com/developers/appointments/ > **Note: Auth model** > All routes in this section are under the `/appointments` prefix. Authentication uses API-key Bearer auth, org-scoped. `is_admin` is always forced `false` — an API key never receives org-wide admin privileges and always sees only the caller's own / hosted data. > **Tip: Credits** > A booking is charged 20 credits, once, when the booking is confirmed: at `POST /appointments/bookings` when it is created confirmed, or at `POST /appointments/bookings/{booking_uid}/confirm` when a booking that needs host approval is accepted. Requests waiting for approval, rejections and reschedules are free, and cancelling does not refund. `POST /appointments/bookings` requires an active subscription (`402` if lapsed). All other appointments routes do not charge credits. > **Caution: OAuth connect flows are web-only** > OAuth-based calendar and conferencing connect flows — `GET /calendars/connect/{provider}` for Google/Office365 and `GET /conferencing/connect/{provider}/auth-url` for Zoom/Teams — require a browser OAuth round-trip and are **not available via API key**. Use the web app for initial OAuth connections, then manage them here. --- ## Event Types ### Get Event Type Detail `GET /appointments/event-types/{event_type_id}` Get the full detail for one bookable event type (title, slug, duration, locations, hosts, attached schedule, buffers, booking fields). #### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `event_type_id` | integer | Event type ID | #### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/appointments/event-types/42" \ -H "Authorization: Bearer $API_KEY" ``` #### Response ```json { "data": { "id": 42, "title": "Intro call", "slug": "intro-call", "length": 30 } } ``` Returns `404` when the ID is not in the caller's org. --- ### Create Event Type `POST /appointments/event-types` → `201` Create a bookable event type. #### Request Body | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `title` | string | Yes | — | Display name for the event type | | `slug` | string | Yes | — | URL slug — must be unique in the org (`409` on collision) | | `length` | integer | Yes | — | Meeting duration in minutes | | `description` | string | No | — | Description shown on the booking page | | `scheduling_type` | string | No | — | `round_robin`, `collective`, or `null` (individual) | | `schedule_id` | integer | No | — | Attach an availability schedule | | `time_zone` | string | No | — | IANA timezone string (e.g. `"Europe/London"`) | | `hidden` | boolean | No | `false` | Hide from the public booking page | | `requires_confirmation` | boolean | No | `false` | Host must approve each booking | | `minimum_booking_notice` | integer | No | `120` | Minimum advance notice in minutes | | `before_event_buffer` | integer | No | `0` | Buffer before event in minutes | | `after_event_buffer` | integer | No | `0` | Buffer after event in minutes | | `slot_interval` | integer | No | `null` | Override slot cadence in minutes | | `seats_per_time_slot` | integer | No | `null` | For group events | | `disable_guests` | boolean | No | `false` | Prevent attendees from adding guests | | `disable_cancelling` | boolean | No | `false` | Prevent attendees from cancelling | | `disable_rescheduling` | boolean | No | `false` | Prevent attendees from rescheduling | | `period_type` | string | No | `"unlimited"` | `unlimited`, `rolling`, or `range` | | `period_days` | integer | No | `null` | Number of days ahead (for `rolling` period) | | `period_start_date` | string | No | `null` | ISO date — start of bookable range (for `range` period) | | `period_end_date` | string | No | `null` | ISO date — end of bookable range (for `range` period) | | `success_redirect_url` | string | No | `null` | Redirect URL after booking | | `event_color` | string | No | — | Hex color string (e.g. `"#7E2DF3"`) | | `event_name` | string | No | `null` | Custom booking title template | | `locations` | array | No | — | List of location objects (e.g. `[{"type": "zoom"}]`) | | `booking_fields` | array | No | `[]` | Custom form fields for the booking page | | `layouts` | array | No | `["month"]` | Booking-page layout options | | `default_layout` | string | No | `"month"` | Default booking-page layout | | `recurring_event` | object | No | `null` | Recurring event configuration | | `initial_hosts` | array | No | `[]` | Host user IDs to attach | | `use_common_schedule` | boolean | No | `null` | When `true`, all hosts use the event-level `schedule_id` | | `metadata` | object | No | `null` | Arbitrary metadata dict | #### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/appointments/event-types" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "title": "Intro call", "slug": "intro-call", "length": 30, "description": "A 30-minute introduction call.", "scheduling_type": "round_robin", "schedule_id": 7, "time_zone": "Europe/London", "hidden": false, "requires_confirmation": false, "minimum_booking_notice": 120, "before_event_buffer": 0, "after_event_buffer": 0, "locations": [{"type": "zoom"}], "event_color": "#7E2DF3" }' ``` #### Response ```json { "data": { "id": 42, "title": "Intro call", "slug": "intro-call", "length": 30 } } ``` --- ### Update Event Type `PATCH /appointments/event-types/{event_type_id}` Partially update an event type. Only the fields you send are changed (`exclude_unset` semantics). Same fields as create, all optional. #### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `event_type_id` | integer | Event type ID | #### Example **cURL** ```bash curl -X PATCH "https://be.graph8.com/api/v1/appointments/event-types/42" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"title": "Updated Intro Call", "length": 45}' ``` #### Response ```json { "data": { "id": 42, "title": "Updated Intro Call", "length": 45 } } ``` Returns `400` when the body is empty, `403` when the event type belongs to another user, `409` on slug collision. --- ### Delete Event Type `DELETE /appointments/event-types/{event_type_id}` Delete an event type. Returns `403` when the caller is not the host/owner. #### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `event_type_id` | integer | Event type ID | #### Example **cURL** ```bash curl -X DELETE "https://be.graph8.com/api/v1/appointments/event-types/42" \ -H "Authorization: Bearer $API_KEY" ``` #### Response ```json { "data": { "deleted": true, "event_type_id": 42 } } ``` --- ### Duplicate Event Type `POST /appointments/event-types/{event_type_id}/duplicate` → `201` Duplicate an event type. Generates a unique `{slug}-copy` slug and copies all hosts verbatim. #### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `event_type_id` | integer | Event type ID to duplicate | #### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/appointments/event-types/42/duplicate" \ -H "Authorization: Bearer $API_KEY" ``` #### Response ```json { "data": { "id": 43, "title": "Intro call", "slug": "intro-call-copy", "length": 30 } } ``` --- ## Bookings ### List Bookings `GET /appointments/bookings` List bookings visible to the caller (their own plus event types they host). `is_admin` is always `false`. #### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `status` | string | — | Filter by status: `accepted`, `pending`, `cancelled`, or `rejected` | | `event_type_id` | integer | — | Filter by event type | | `after_start` | string | — | ISO datetime — return bookings starting after this time | | `before_end` | string | — | ISO datetime — return bookings ending before this time | | `attendee_email` | string | — | Filter by attendee email | | `sort_by` | string | `start_time` | Sort field: `start_time`, `end_time`, `created_at`, or `status` | | `sort_order` | string | `desc` | `asc` or `desc` | | `skip` | integer | `0` | Number of records to skip | | `take` | integer | `20` | Number of records to return (max `100`) | #### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/appointments/bookings?status=accepted&take=20" \ -H "Authorization: Bearer $API_KEY" ``` #### Response ```json { "data": [ { "id": 1, "uid": "uuid", "title": "Intro call with Jane Doe", "start_time": "2026-09-01T09:00:00Z", "end_time": "2026-09-01T09:30:00Z", "status": "accepted", "location": "zoom", "event_type": { "id": 42, "title": "Intro call", "slug": "intro-call", "length": 30 }, "attendees": [{ "id": 1, "email": "jane@acme.com", "name": "Jane Doe", "no_show": false }], "sequence_id": null, "sequence_name": null, "from_reschedule": null, "created_at": "2026-08-15T12:00:00Z" } ], "pagination": { "page": 1, "limit": 20, "total": 1, "has_next": false } } ``` --- ### Get Booking Detail `GET /appointments/bookings/{booking_uid}` Full booking detail including `description`, `responses`, `meeting_url`, `references`, `internal_notes`, `recurring_event_id`, `cancellation_reason`, `rejection_reason`, `no_show_host`, and `rating`. #### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `booking_uid` | string | Booking UUID | #### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/appointments/bookings/some-booking-uuid" \ -H "Authorization: Bearer $API_KEY" ``` #### Response ```json { "data": { "id": 1, "uid": "some-booking-uuid", "title": "Intro call", "status": "accepted", "meeting_url": "https://zoom.us/j/...", "description": null, "internal_notes": [] } } ``` --- ### Create Booking `POST /appointments/bookings` → `201` Book an appointment. **Charges 20 credits when the booking is confirmed:** here, if the event type does not need host approval (`402` when fewer than 20 credits are available), otherwise when the host confirms it. **Requires an active subscription (`402` if lapsed).** Returns `409` on slot conflict. #### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `event_type_id` | integer | Yes | Event type to book | | `start_time` | string | Yes | ISO-8601 UTC start time (e.g. `"2026-09-01T09:00:00Z"`) | | `attendees` | array | Yes | At least one attendee object with `name`, `email`, and `time_zone` | | `location` | string | No | Location key (e.g. `"zoom"`) | | `responses` | object | No | Answers to custom `booking_fields` | | `guests` | array | No | Additional email addresses to invite | | `metadata` | object | No | Arbitrary metadata | | `slot_uid` | string | No | Pre-reserved slot UID | #### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/appointments/bookings" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "event_type_id": 42, "start_time": "2026-09-01T09:00:00Z", "attendees": [ { "name": "Jane Doe", "email": "jane@acme.com", "time_zone": "America/New_York" } ], "location": "zoom", "guests": ["extra@acme.com"] }' ``` #### Response ```json { "data": { "id": 1, "uid": "new-booking-uuid", "status": "accepted", "meeting_url": "https://zoom.us/j/..." } } ``` --- ### Cancel Booking `POST /appointments/bookings/{booking_uid}/cancel` Cancel a booking. The request body is optional. #### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `booking_uid` | string | Booking UUID | #### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `cancellation_reason` | string | No | Reason message sent to attendees | | `cancel_subsequent` | boolean | No | For recurring bookings, cancel all following occurrences | #### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/appointments/bookings/some-booking-uuid/cancel" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"cancellation_reason": "Scheduling conflict", "cancel_subsequent": false}' ``` #### Response ```json { "data": { "id": 1, "uid": "some-booking-uuid", "status": "cancelled" } } ``` --- ### Reschedule Booking `POST /appointments/bookings/{booking_uid}/reschedule` → `201` Reschedule a booking to a new time. Free — no credit charge. A booking waiting for host approval stays pending until the host confirms it. Returns `409` for a booking that is cancelled, rejected or already rescheduled. #### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `booking_uid` | string | Booking UUID of the original booking | #### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `start_time` | string | Yes | New ISO-8601 UTC start time | | `rescheduling_reason` | string | No | Reason for rescheduling | #### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/appointments/bookings/some-booking-uuid/reschedule" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"start_time": "2026-09-02T10:00:00Z", "rescheduling_reason": "Earlier slot available"}' ``` #### Response Returns a new booking row representing the rescheduled appointment. ```json { "data": { "id": 2, "uid": "new-rescheduled-uuid", "status": "accepted", "start_time": "2026-09-02T10:00:00Z" } } ``` --- ### Booking Insights `GET /appointments/bookings/insights` Aggregated booking insights (counts, show/no-show rates, per-event-type breakdown) for the caller's org. #### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `date_from` | string | — | ISO date lower bound (e.g. `"2026-01-01"`) | | `date_to` | string | — | ISO date upper bound (e.g. `"2026-12-31"`) | | `event_type_ids` | string | — | Comma-separated event-type integer IDs to filter by | #### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/appointments/bookings/insights?date_from=2026-01-01&date_to=2026-12-31" \ -H "Authorization: Bearer $API_KEY" ``` #### Response ```json { "data": { "total_bookings": 120, "accepted": 98, "cancelled": 15, "no_show_rate": 0.07 } } ``` --- ### Confirm or Reject Booking `POST /appointments/bookings/{booking_uid}/confirm` Confirm or reject a booking that requires host approval (`requires_confirmation=true` on the event type). **Confirming charges 20 credits** (once per booking); rejecting is free. #### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `booking_uid` | string | Booking UUID | #### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `confirmed` | boolean | Yes | `true` to accept, `false` to reject | | `reason` | string | No | Message to attendee on rejection | #### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/appointments/bookings/some-booking-uuid/confirm" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"confirmed": true}' ``` #### Response ```json { "data": { "id": 1, "uid": "some-booking-uuid", "status": "accepted" } } ``` --- ### Mark Booking Absent `POST /appointments/bookings/{booking_uid}/mark-absent` Mark the host and/or specific attendees as no-show. #### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `booking_uid` | string | Booking UUID | #### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `host` | boolean | No | Flag the host as absent | | `attendees` | array | No | List of attendee objects to flag; each must include `email` | #### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/appointments/bookings/some-booking-uuid/mark-absent" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"host": false, "attendees": [{"email": "jane@acme.com"}]}' ``` #### Response ```json { "data": { "id": 1, "uid": "some-booking-uuid", "no_show_host": false } } ``` --- ### Change Booking Location `PATCH /appointments/bookings/{booking_uid}/location` Change the meeting location for a booked appointment and re-notify both parties. #### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `booking_uid` | string | Booking UUID | #### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `location` | string | Yes | Location key: `zoom`, `google_meet`, `in_person`, a custom URL, etc. | #### Example **cURL** ```bash curl -X PATCH "https://be.graph8.com/api/v1/appointments/bookings/some-booking-uuid/location" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"location": "google_meet"}' ``` #### Response ```json { "data": { "id": 1, "uid": "some-booking-uuid", "location": "google_meet" } } ``` --- ### Request Reschedule from Attendee `POST /appointments/bookings/{booking_uid}/request-reschedule` Send a reschedule-request email to the attendee (host-side action). Does **not** move or cancel the original booking; the attendee reschedules via the link in the email. #### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `booking_uid` | string | Booking UUID | #### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `reason` | string | No | Optional message from the host included in the email | #### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/appointments/bookings/some-booking-uuid/request-reschedule" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"reason": "Something came up on my end."}' ``` #### Response ```json { "data": { "id": 1, "uid": "some-booking-uuid", "status": "accepted" } } ``` --- ### Add Guests to Booking `POST /appointments/bookings/{booking_uid}/guests` Invite additional guests (email addresses) to an existing booking. Sends notification emails. #### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `booking_uid` | string | Booking UUID | #### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `guests` | array | Yes | List of email address strings to invite | #### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/appointments/bookings/some-booking-uuid/guests" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"guests": ["extra@acme.com", "other@partner.com"]}' ``` #### Response ```json { "data": { "id": 1, "uid": "some-booking-uuid", "status": "accepted" } } ``` --- ### Add Internal Note to Booking `POST /appointments/bookings/{booking_uid}/notes` → `201` Append an internal (host-only) note to a booking. Not visible to attendees. #### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `booking_uid` | string | Booking UUID | #### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `text` | string | Yes | Note body text | #### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/appointments/bookings/some-booking-uuid/notes" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"text": "Spoke about Q3 budget."}' ``` #### Response ```json { "data": { "id": 1, "text": "Spoke about Q3 budget.", "created_by_id": 7, "created_by_name": "Jane", "created_at": "2026-09-01T09:35:00Z" } } ``` --- ### Associate Booking with Sequence `PATCH /appointments/bookings/{booking_uid}/sequence` Associate a booking with a sequencer sequence, or remove the link by passing `null`. #### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `booking_uid` | string | Booking UUID | #### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `sequence_id` | string | No | Sequence UUID to link, or `null` to detach | #### Example **cURL** ```bash curl -X PATCH "https://be.graph8.com/api/v1/appointments/bookings/some-booking-uuid/sequence" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"sequence_id": "seq-uuid"}' ``` #### Response ```json { "data": { "id": 1, "uid": "some-booking-uuid", "sequence_id": "seq-uuid" } } ``` --- ## Schedules & Availability > **Note:** > These are **appointment availability schedules** — "when am I free?" working-hours profiles used by event types. They are distinct from the sequencer send-window schedules at `GET /schedules`. ### List Schedules `GET /appointments/schedules` List the caller's availability schedules. Each item includes `id`, `name`, `time_zone`, `is_default`, `availability`, `overrides`, `created_at`, `owner_name`, and `owner_email`. #### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/appointments/schedules" \ -H "Authorization: Bearer $API_KEY" ``` #### Response ```json { "data": [{ "id": 7, "name": "Business Hours", "time_zone": "America/New_York", "is_default": true }] } ``` --- ### Get Schedule Detail `GET /appointments/schedules/{schedule_id}` Full schedule detail including `linked_event_types` — the list of event types currently using this schedule. #### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `schedule_id` | integer | Schedule ID | #### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/appointments/schedules/7" \ -H "Authorization: Bearer $API_KEY" ``` #### Response ```json { "data": { "id": 7, "name": "Business Hours", "time_zone": "America/New_York", "is_default": true, "linked_event_types": [{ "id": 42, "title": "Intro call" }] } } ``` --- ### Create Schedule `POST /appointments/schedules` → `201` Create a named availability schedule. `days` is an array of weekday integers: `0` = Sunday, `1` = Monday, … `6` = Saturday. #### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | string | Yes | Schedule display name | | `time_zone` | string | Yes | IANA timezone string (e.g. `"America/New_York"`) | | `is_default` | boolean | No | Immediately set as the caller's default schedule | | `availability` | array | No | Weekly recurring rules; defaults to Mon–Fri 09:00–17:00 | | `overrides` | array | No | Date-specific override windows | Each `availability` item: `{ "days": [1,2,3,4,5], "start_time": "09:00", "end_time": "17:00" }`. Each `overrides` item: `{ "date": "2026-12-25", "start_time": "00:00", "end_time": "00:00" }`. #### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/appointments/schedules" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Business Hours", "time_zone": "America/New_York", "is_default": false, "availability": [ { "days": [1, 2, 3, 4, 5], "start_time": "09:00", "end_time": "17:00" } ], "overrides": [ { "date": "2026-12-25", "start_time": "00:00", "end_time": "00:00" } ] }' ``` #### Response ```json { "data": { "id": 7, "name": "Business Hours", "time_zone": "America/New_York", "is_default": false } } ``` --- ### Update Schedule `PATCH /appointments/schedules/{schedule_id}` Partially update a schedule. When `availability` is provided it replaces **all** weekly recurring rules. When `overrides` is provided it replaces **all** date-specific overrides. Omit a key to leave that set unchanged. #### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `schedule_id` | integer | Schedule ID | #### Request Body All fields optional. | Field | Type | Description | |-------|------|-------------| | `name` | string | New display name | | `time_zone` | string | New IANA timezone | | `is_default` | boolean | Set as default | | `availability` | array | Replaces all weekly recurring rules | | `overrides` | array | Replaces all date-specific overrides | #### Example **cURL** ```bash curl -X PATCH "https://be.graph8.com/api/v1/appointments/schedules/7" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "Extended Hours", "availability": [{"days": [1,2,3,4,5], "start_time": "08:00", "end_time": "18:00"}]}' ``` #### Response ```json { "data": { "id": 7, "name": "Extended Hours", "time_zone": "America/New_York" } } ``` --- ### Delete Schedule `DELETE /appointments/schedules/{schedule_id}` Delete a schedule. Returns `409` when this is the caller's last schedule (every user must keep at least one). Returns `404` when the schedule does not exist or belongs to another user. #### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `schedule_id` | integer | Schedule ID | #### Example **cURL** ```bash curl -X DELETE "https://be.graph8.com/api/v1/appointments/schedules/7" \ -H "Authorization: Bearer $API_KEY" ``` #### Response ```json { "data": { "deleted": true, "schedule_id": 7 } } ``` --- ### Get Schedule Availability `GET /appointments/schedules/{schedule_id}/availability` Return the weekly recurring rules and date-specific overrides for a schedule. #### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `schedule_id` | integer | Schedule ID | #### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/appointments/schedules/7/availability" \ -H "Authorization: Bearer $API_KEY" ``` #### Response ```json { "data": { "schedule_id": 7, "availability": [ { "id": 1, "days": [1, 2, 3, 4, 5], "start_time": "09:00", "end_time": "17:00", "date": null } ], "overrides": [ { "id": 2, "days": null, "start_time": "00:00", "end_time": "00:00", "date": "2026-12-25" } ] } } ``` --- ### Update Schedule Availability `PATCH /appointments/schedules/{schedule_id}/availability` Replace weekly rules and/or date overrides. At least one of `availability` or `overrides` is required (returns `400` otherwise). Omit a key to leave that set unchanged. #### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `schedule_id` | integer | Schedule ID | #### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `availability` | array | Conditional | Replaces all weekly recurring rules | | `overrides` | array | Conditional | Replaces all date-specific overrides (pass `[]` to clear all) | #### Example **cURL** ```bash curl -X PATCH "https://be.graph8.com/api/v1/appointments/schedules/7/availability" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "availability": [{ "days": [1,2,3,4,5], "start_time": "08:00", "end_time": "18:00" }], "overrides": [] }' ``` #### Response ```json { "data": { "schedule_id": 7, "availability": [{ "id": 1, "days": [1,2,3,4,5], "start_time": "08:00", "end_time": "18:00", "date": null }], "overrides": [] } } ``` --- ### Set Default Schedule `POST /appointments/schedules/{schedule_id}/set-default` Mark a schedule as the caller's default. Used when an event type has no schedule attached. Returns `404` when the schedule does not exist or belongs to another user. #### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `schedule_id` | integer | Schedule ID | #### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/appointments/schedules/7/set-default" \ -H "Authorization: Bearer $API_KEY" ``` #### Response ```json { "data": { "id": 7, "name": "Business Hours", "is_default": true } } ``` --- ## Out of Office ### List Out-of-Office Entries `GET /appointments/out-of-office` List the API caller's out-of-office entries. API keys do not receive team-wide visibility. #### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/appointments/out-of-office" \ -H "Authorization: Bearer $API_KEY" ``` #### Response ```json { "data": [ { "id": 1, "uuid": "ooo-entry-uuid", "start": "2026-08-01T04:00:00Z", "end": "2026-08-16T04:00:00Z", "start_date": "2026-08-01", "end_date": "2026-08-15", "time_zone": "America/New_York", "notes": "Summer holiday", "public_note": "I will be back on August 16.", "to_user_id": null, "to_user_name": null, "reason": "vacation", "conflict_count": 0, "created_at": "2026-07-01T10:00:00Z" } ] } ``` --- ### Create Out-of-Office Entry `POST /appointments/out-of-office` → `201` Create an out-of-office entry. While active, the caller's slots are blocked for booking. #### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `start` | string | Yes | Local `YYYY-MM-DD` date, or legacy ISO-8601 datetime | | `end` | string | Yes | Inclusive local `YYYY-MM-DD` date, or legacy ISO-8601 datetime | | `time_zone` | string | For local dates | IANA timezone used for the date range | | `notes` | string | No | Internal notes | | `public_note` | string | No | Message shown to public bookers while the organizer is away | | `to_user_id` | integer | No | Offer this active teammate's public event types as an alternative | | `reason` | string | No | Free-text label (e.g. `"vacation"`) | #### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/appointments/out-of-office" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "start": "2026-08-01", "end": "2026-08-15", "time_zone": "America/New_York", "notes": "Summer holiday", "public_note": "I will be back on August 16.", "to_user_id": 12, "reason": "vacation" }' ``` #### Response ```json { "data": { "id": 1, "uuid": "ooo-entry-uuid", "start": "2026-08-01T04:00:00Z", "end": "2026-08-16T04:00:00Z", "start_date": "2026-08-01", "end_date": "2026-08-15", "time_zone": "America/New_York", "notes": "Summer holiday", "public_note": "I will be back on August 16.", "to_user_id": 12, "to_user_name": "Coverage Teammate", "reason": "vacation", "conflict_count": 1, "created_at": "2026-07-01T10:00:00Z" } } ``` --- ### Update Out-of-Office Entry `PATCH /appointments/out-of-office/{ooo_uuid}` Update dates, timezone, notes, reason, or coverage. Send `start` and `end` together when changing the date range. API keys can update only the caller's entries. --- ### Delete Out-of-Office Entry `DELETE /appointments/out-of-office/{ooo_uuid}` Delete an out-of-office entry owned by the API caller. #### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `ooo_uuid` | string | Out-of-office entry UUID | #### Example **cURL** ```bash curl -X DELETE "https://be.graph8.com/api/v1/appointments/out-of-office/ooo-entry-uuid" \ -H "Authorization: Bearer $API_KEY" ``` #### Response ```json { "data": { "deleted": true, "ooo_uuid": "ooo-entry-uuid" } } ``` --- ## Routing Forms ### List Routing Forms `GET /appointments/routing-forms` List routing forms owned by the calling user. Each item includes `response_count` (total submissions). #### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/appointments/routing-forms" \ -H "Authorization: Bearer $API_KEY" ``` #### Response ```json { "data": [{ "id": 3, "name": "Sales Triage", "response_count": 42 }], "pagination": { "total": 3 } } ``` --- ### Get Routing Form Detail `GET /appointments/routing-forms/{form_id}` Full routing form detail including fields, routes, and `response_count`. Returns `404` when not found or owned by another user. #### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `form_id` | integer | Routing form ID | #### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/appointments/routing-forms/3" \ -H "Authorization: Bearer $API_KEY" ``` #### Response ```json { "data": { "id": 3, "name": "Sales Triage", "fields": [], "routes": [], "response_count": 42 } } ``` --- ### Create Routing Form `POST /appointments/routing-forms` → `201` Create a routing form with optional fields and conditional routes. #### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | string | Yes | Form display name | | `description` | string | No | Form description | | `disabled` | boolean | No | Disable the form (default `false`) | | `fields` | array | No | List of form question field objects | | `routes` | array | No | List of conditional routing rule objects | Each `fields` item: | Field | Type | Required | Description | |-------|------|----------|-------------| | `label` | string | Yes | Question label | | `type` | string | No | `select`, `text`, `number`, `email`, `phone`, or `textarea` (default `"select"`) | | `required` | boolean | No | Whether the field is required (default `true`) | | `identifier` | string | No | Slug used in conditions and responses; auto-generated when omitted | | `options` | array | No | Options for `select` / radio fields. Each option has `value` (required) and optionally `label` and `event_type_id`. Conditions reference fields via `field_id` = the field's `identifier`. | Each `routes` item: | Field | Type | Required | Description | |-------|------|----------|-------------| | `conditions` | array | No | Conditions: `[{ "field": "company_size", "operator": "eq", "value": "sm" }]` | | `action_type` | string | No | `"redirect_event_type"` (default) | | `action_value` | string | No | Event type ID for `redirect_event_type` | | `is_fallback` | boolean | No | Designate as the catch-all route | | `position` | integer | No | Display ordering | #### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/appointments/routing-forms" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Sales Triage", "description": "Route inbound leads to the right event type.", "fields": [ { "label": "Company size", "type": "select", "required": true, "identifier": "company_size", "options": [{ "label": "1-50", "value": "sm" }] } ], "routes": [ { "conditions": [{ "field": "company_size", "operator": "eq", "value": "sm" }], "action_type": "redirect_event_type", "action_value": "42", "is_fallback": false, "position": 0 } ] }' ``` #### Response ```json { "data": { "id": 3, "name": "Sales Triage", "fields": [], "routes": [], "response_count": 0 } } ``` --- ### Update Routing Form `PATCH /appointments/routing-forms/{form_id}` Partially update a routing form. `fields` and `routes` are replaced wholesale when present. `slug` must be unique in the org (`409` on collision). #### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `form_id` | integer | Routing form ID | #### Request Body All fields optional. | Field | Type | Description | |-------|------|-------------| | `name` | string | Form display name | | `slug` | string | URL slug — must be unique in the org | | `description` | string | Form description | | `disabled` | boolean | Enable or disable the form | | `fields` | array | Replaces all fields | | `routes` | array | Replaces all routes | #### Example **cURL** ```bash curl -X PATCH "https://be.graph8.com/api/v1/appointments/routing-forms/3" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "Updated Sales Triage", "disabled": false}' ``` #### Response ```json { "data": { "id": 3, "name": "Updated Sales Triage", "disabled": false } } ``` --- ### Delete Routing Form `DELETE /appointments/routing-forms/{form_id}` Delete a routing form permanently. Cascades to its fields and routes. #### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `form_id` | integer | Routing form ID | #### Example **cURL** ```bash curl -X DELETE "https://be.graph8.com/api/v1/appointments/routing-forms/3" \ -H "Authorization: Bearer $API_KEY" ``` #### Response ```json { "data": { "deleted": true, "form_id": 3 } } ``` --- ### Duplicate Routing Form `POST /appointments/routing-forms/{form_id}/duplicate` → `201` Duplicate a routing form with all fields and routes. The copy is named `"{name} (copy)"` with a unique auto-generated slug. #### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `form_id` | integer | Routing form ID to duplicate | #### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/appointments/routing-forms/3/duplicate" \ -H "Authorization: Bearer $API_KEY" ``` #### Response ```json { "data": { "id": 4, "name": "Sales Triage (copy)", "fields": [], "routes": [], "response_count": 0 } } ``` --- ### List Routing Form Responses `GET /appointments/routing-forms/{form_id}/responses` List captured form responses. Paginated via `skip` / `take`. #### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `form_id` | integer | Routing form ID | #### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `skip` | integer | `0` | Number of records to skip | | `take` | integer | `50` | Number of records to return (max `500`) | #### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/appointments/routing-forms/3/responses?skip=0&take=50" \ -H "Authorization: Bearer $API_KEY" ``` #### Response ```json { "data": { "responses": [ { "id": 1, "form_id": 3, "responses": { "company_size": "sm" }, "routed_to_event_type_id": 42, "created_at": "2026-09-01T10:00:00Z" } ], "total_count": 1 }, "pagination": { "skip": 0, "take": 50, "total": 1, "has_next": false } } ``` --- ## Workflows > **Note: Authz** > Write operations (create / update / delete / toggle) are **owner-only** — an API key may only mutate workflows it created. List and get are org-wide read-only. Appointment workflows fire automated notifications (email / SMS reminders) at configurable trigger points in the booking lifecycle. ### List Workflows `GET /appointments/workflows` List all workflows for the org. #### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/appointments/workflows" \ -H "Authorization: Bearer $API_KEY" ``` #### Response ```json { "data": [{ "id": 5, "name": "24h Reminder", "trigger": "before_event", "time": 1440, "is_active_on_all": true }] } ``` --- ### Get Workflow Detail `GET /appointments/workflows/{workflow_id}` Full workflow detail including steps and linked event types. #### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `workflow_id` | integer | Workflow ID | #### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/appointments/workflows/5" \ -H "Authorization: Bearer $API_KEY" ``` #### Response ```json { "data": { "id": 5, "name": "24h Reminder", "trigger": "before_event", "time": 1440, "steps": [], "linked_event_types": [] } } ``` --- ### Create Workflow `POST /appointments/workflows` → `201` Create a workflow. #### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | string | Yes | Workflow display name | | `trigger` | string | Yes | Trigger event: `new_booking`, `before_event`, `after_event`, `booking_cancelled`, or `booking_rescheduled` | | `time` | integer | Conditional | Minutes offset — required when trigger is `before_event` or `after_event` | | `time_unit` | string | Conditional | Time unit — required when trigger is `before_event` or `after_event` | | `is_active_on_all` | boolean | No | Apply org-wide (default `false`) | | `event_type_ids` | array | No | Attach to specific event type IDs; ignored when `is_active_on_all` is `true` | | `steps` | array | Yes | At least one step object | Each `steps` item: | Field | Type | Required | Description | |-------|------|----------|-------------| | `action` | string | Yes | `EMAIL_HOST`, `EMAIL_ATTENDEE`, `EMAIL_ADDRESS`, `SMS_ATTENDEE`, `SMS_NUMBER`, `WHATSAPP_ATTENDEE`, or `WHATSAPP_NUMBER` | | `template` | string | No | Message template (default `"REMINDER"`) | | `email_subject` | string | No | Email subject line | | `reminder_body` | string | No | Message body | | `send_to` | string | No | Required when `action` is `EMAIL_ADDRESS` | | `sender` | string | No | Sender name or address | | `include_calendar_event` | boolean | No | Attach calendar invite (default `false`) | #### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/appointments/workflows" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "24h Reminder", "trigger": "before_event", "time": 1440, "time_unit": "minutes", "is_active_on_all": true, "steps": [ { "action": "EMAIL_ATTENDEE", "template": "REMINDER", "email_subject": "Reminder: Your meeting is tomorrow", "reminder_body": "See you soon.", "include_calendar_event": false } ] }' ``` #### Response ```json { "data": { "id": 5, "name": "24h Reminder", "trigger": "before_event", "time": 1440, "is_active_on_all": true } } ``` --- ### Update Workflow `PATCH /appointments/workflows/{workflow_id}` Partially update a workflow. Only owned workflows may be updated (`403` otherwise). When provided, `steps` replaces **all** steps and `event_type_ids` replaces **all** linked event-type attachments. #### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `workflow_id` | integer | Workflow ID | #### Request Body All fields optional. | Field | Type | Description | |-------|------|-------------| | `name` | string | Workflow display name | | `trigger` | string | Trigger event | | `time` | integer | Minutes offset | | `time_unit` | string | Time unit | | `is_active_on_all` | boolean | Apply org-wide | | `event_type_ids` | array | Replaces all linked event type IDs | | `steps` | array | Replaces all steps | #### Example **cURL** ```bash curl -X PATCH "https://be.graph8.com/api/v1/appointments/workflows/5" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "48h Reminder", "time": 2880}' ``` #### Response ```json { "data": { "id": 5, "name": "48h Reminder", "trigger": "before_event", "time": 2880 } } ``` --- ### Delete Workflow `DELETE /appointments/workflows/{workflow_id}` → `204` Delete a workflow and cancel any pending reminder rows. Only owned workflows may be deleted. #### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `workflow_id` | integer | Workflow ID | #### Example **cURL** ```bash curl -X DELETE "https://be.graph8.com/api/v1/appointments/workflows/5" \ -H "Authorization: Bearer $API_KEY" ``` No response body (`204 No Content`). --- ### Toggle Workflow `POST /appointments/workflows/{workflow_id}/toggle` Activate or deactivate a workflow. Only owned workflows may be toggled. Deactivating cancels all pending reminder rows. #### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `workflow_id` | integer | Workflow ID | #### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `active` | boolean | Yes | `true` to activate, `false` to deactivate | #### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/appointments/workflows/5/toggle" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"active": false}' ``` #### Response ```json { "data": { "id": 5, "name": "24h Reminder", "active": false } } ``` --- ## Integrations (Calendars & Conferencing) > **Caution: OAuth connect flows are web-only** > `GET /calendars/connect/{provider}` (Google/Office365) and `GET /conferencing/connect/{provider}/auth-url` (Zoom/Teams) require a browser OAuth round-trip and are **not available via API key**. Use the web app to connect OAuth providers, then manage selections and defaults here. ### List Calendar Accounts `GET /appointments/calendars` List calendar accounts (Google Calendar, Outlook) connected to the caller's appointments profile, each with sub-calendars and their selection state. #### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/appointments/calendars" \ -H "Authorization: Bearer $API_KEY" ``` #### Response ```json [ { "credential_id": 1, "provider": "google_calendar", "email": "jane@acme.com", "is_valid": true, "sub_calendars": [ { "external_id": "jane@acme.com", "name": "Jane's Calendar", "is_selected": true, "is_primary": true, "read_only": false } ] } ] ``` --- ### Add Calendar to Conflict-Checking `POST /appointments/calendars/selected` → `201` Add a sub-calendar to the caller's conflict-checking set. #### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `credential_id` | integer | Yes | Calendar credential ID | | `integration` | string | Yes | Provider slug (e.g. `"google_calendar"`) | | `external_id` | string | Yes | Calendar's external ID | #### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/appointments/calendars/selected" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "credential_id": 1, "integration": "google_calendar", "external_id": "jane@acme.com" }' ``` #### Response ```json { "data": { "id": "...", "integration": "google_calendar", "external_id": "jane@acme.com", "credential_id": 1 } } ``` --- ### Remove Calendar from Conflict-Checking `DELETE /appointments/calendars/selected` Remove a sub-calendar from conflict checking. Returns `204`, no body. #### Query Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `credential_id` | integer | Yes | Calendar credential ID | | `integration` | string | Yes | Provider slug | | `external_id` | string | Yes | Calendar's external ID | #### Example **cURL** ```bash curl -X DELETE "https://be.graph8.com/api/v1/appointments/calendars/selected?credential_id=1&integration=google_calendar&external_id=jane@acme.com" \ -H "Authorization: Bearer $API_KEY" ``` Returns `204 No Content`. --- ### Disconnect Calendar Account `DELETE /appointments/calendars/{credential_id}` Disconnect a calendar account (best-effort token revocation plus credential row deletion). Returns `404` when not found; `403` when it belongs to another user. #### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `credential_id` | integer | Calendar credential ID | #### Example **cURL** ```bash curl -X DELETE "https://be.graph8.com/api/v1/appointments/calendars/1" \ -H "Authorization: Bearer $API_KEY" ``` Returns `204 No Content`. --- ### Get Destination Calendar `GET /appointments/calendars/destination` Get the destination calendar where booking events are pushed. Returns `{"data": null}` when none is configured. #### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `event_type_id` | integer | — | When provided, returns the event-type-specific override with fallback to the user-level default | #### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/appointments/calendars/destination" \ -H "Authorization: Bearer $API_KEY" ``` #### Response ```json { "data": { "id": 5, "integration": "google_calendar", "external_id": "jane@acme.com", "credential_id": 1, "event_type_id": null, "primary_email": "jane@acme.com" } } ``` --- ### Set Destination Calendar `PUT /appointments/calendars/destination` Set (upsert) the destination calendar where booking events are pushed. Optionally scope to a specific event type. #### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `integration` | string | Yes | Provider slug (e.g. `"google_calendar"`) | | `external_id` | string | Yes | Calendar's external ID | | `credential_id` | integer | No | Credential ID | | `event_type_id` | integer | No | Event-type-scoped override; `null` sets the user-level default | #### Example **cURL** ```bash curl -X PUT "https://be.graph8.com/api/v1/appointments/calendars/destination" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "integration": "google_calendar", "external_id": "jane@acme.com", "credential_id": 1, "event_type_id": null }' ``` #### Response ```json { "data": { "id": 5, "integration": "google_calendar", "external_id": "jane@acme.com", "credential_id": 1 } } ``` --- ### List Conferencing Providers `GET /appointments/conferencing` List conferencing providers connected to the caller's profile (Zoom, Google Meet, Microsoft Teams, Roam, Whereby, Jitsi). Google Meet is synthesized from an existing Google Calendar credential. #### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/appointments/conferencing" \ -H "Authorization: Bearer $API_KEY" ``` #### Response ```json [{ "provider": "google_meet", "name": "Google Meet", "is_default": true, "credential_id": null, "link": null }] ``` --- ### Connect Conferencing Provider `POST /appointments/conferencing/connect/{provider}` → `201` Connect a no-OAuth / link-based conferencing provider. Supported values for `{provider}`: `google_meet`, `roam`, `roam_auto`, `whereby`, `jitsi`. **Zoom and `office365_video` (Teams) are excluded** — they require a browser OAuth flow. - `google_meet`: clears the `google_meet_suppressed` flag; requires an existing `google_calendar` credential. - `roam` / `whereby` / `jitsi`: creates a credential row; optional `link` is the meeting URL. - `roam_auto`: creates a stub credential row (no OAuth tokens). #### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `provider` | string | Provider slug: `google_meet`, `roam`, `roam_auto`, `whereby`, or `jitsi` | #### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `link` | string | No | Meeting URL — only relevant for `roam`, `whereby`, and `jitsi` | #### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/appointments/conferencing/connect/google_meet" \ -H "Authorization: Bearer $API_KEY" ``` #### Response ```json { "data": { "provider": "google_meet", "status": "connected" } } ``` --- ### Disconnect Conferencing Provider `DELETE /appointments/conferencing/{provider}/disconnect` Disconnect a conferencing provider. For `google_meet`, sets `google_meet_suppressed` and clears the default if it pointed at Meet. #### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `provider` | string | Provider slug | #### Example **cURL** ```bash curl -X DELETE "https://be.graph8.com/api/v1/appointments/conferencing/google_meet/disconnect" \ -H "Authorization: Bearer $API_KEY" ``` Returns `204 No Content`. --- ### Update Conferencing Provider Link `PUT /appointments/conferencing/{provider}/link` Set or update the meeting URL for a link-based provider (`roam`, `whereby`, `jitsi`). The provider must already be installed. The URL is validated against the provider's expected URL pattern. #### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `provider` | string | Provider slug: `roam`, `whereby`, or `jitsi` | #### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `link` | string | Yes | Meeting URL — validated against provider's URL pattern | #### Example **cURL** ```bash curl -X PUT "https://be.graph8.com/api/v1/appointments/conferencing/roam/link" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"link": "https://meet.example.com/room"}' ``` #### Response ```json { "data": { "provider": "roam", "link": "https://meet.example.com/room" } } ``` --- ### Get Default Conferencing Provider `GET /appointments/conferencing/default` Get the current default conferencing provider. Returns `{"data": {"provider": null}}` when none is configured. #### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/appointments/conferencing/default" \ -H "Authorization: Bearer $API_KEY" ``` #### Response ```json { "data": { "provider": "google_meet", "credential_id": null, "link": null } } ``` --- ### Set Default Conferencing Provider `POST /appointments/conferencing/{provider}/default` Set the default conferencing provider for new meetings. For link-based providers (`roam`, `whereby`, `jitsi`) a meeting URL is required — either in the request body or stored from a prior `PUT /appointments/conferencing/{provider}/link`. #### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `provider` | string | Provider slug to set as default | #### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `credential_id` | integer | No | Credential ID for the provider | | `link` | string | No | Meeting URL — required for link-based providers if not already set | #### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/appointments/conferencing/zoom/default" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"credential_id": 5}' ``` #### Response ```json { "data": { "provider": "zoom", "credential_id": 5, "link": null } } ``` --- # Assert / Upsert > Idempotent create-or-update for contacts, companies, and deals Source: https://docs.graph8.com/developers/assert/ Assert endpoints provide idempotent create-or-update (upsert) semantics. If a matching record exists it gets updated; otherwise a new one is created. This makes them safe to call repeatedly without creating duplicates. --- ## Assert Contact `PUT /contacts/assert` Create or update a single contact. Matches by `work_email` first, then falls back to `linkedin_url`. Requires `list_id` and at least one match key (`work_email` or `linkedin_url`). ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `list_id` | integer | Yes | Target list ID | | `work_email` | string | No* | Work email (primary match key) | | `linkedin_url` | string | No* | LinkedIn URL (fallback match key) | | `first_name` | string | No | First name | | `last_name` | string | No | Last name | | `job_title` | string | No | Job title | | `company_domain` | string | No | Company domain | | ... | | | Any other contact field | *At least one of `work_email` or `linkedin_url` is required. ### Example **cURL** ```bash curl -X PUT "https://api.graph8.com/api/v1/contacts/assert" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "list_id": 5, "work_email": "jane@acme.com", "first_name": "Jane", "last_name": "Doe", "job_title": "VP of Sales" }' ``` **Python** ```python response = requests.put( f"{BASE_URL}/contacts/assert", headers=HEADERS, json={ "list_id": 5, "work_email": "jane@acme.com", "first_name": "Jane", "last_name": "Doe", "job_title": "VP of Sales" } ) ``` ### Response ```json { "data": { "action": "created", "count": 1 } } ``` The `action` field tells you what happened: `"created"` for a new record, `"updated"` for an existing match. --- ## Batch Assert Contacts `PUT /contacts/assert/batch` Create or update up to 100 contacts at once. Each contact should include `work_email` or `linkedin_url` as a match key. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `list_id` | integer | Yes | Target list ID | | `contacts` | object[] | Yes | Up to 100 contact objects | ### Example **cURL** ```bash curl -X PUT "https://api.graph8.com/api/v1/contacts/assert/batch" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "list_id": 5, "contacts": [ {"work_email": "jane@acme.com", "first_name": "Jane"}, {"work_email": "bob@acme.com", "first_name": "Bob"} ] }' ``` **Python** ```python response = requests.put( f"{BASE_URL}/contacts/assert/batch", headers=HEADERS, json={ "list_id": 5, "contacts": [ {"work_email": "jane@acme.com", "first_name": "Jane"}, {"work_email": "bob@acme.com", "first_name": "Bob"} ] } ) ``` ### Response ```json { "data": { "total": 2, "created": 2, "updated": 0, "errors": [] } } ``` --- ## Assert Company `PUT /companies/assert` Create or update a company. Matches by `domain` first, then falls back to `linkedin_url`. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `list_id` | integer | Yes | Target list ID | | `domain` | string | No* | Company domain (primary match key) | | `linkedin_url` | string | No* | LinkedIn URL (fallback match key) | | `name` | string | No | Company name | | `industry` | string | No | Industry | | `employee_count` | integer | No | Number of employees | | ... | | | Any other company field | *At least one of `domain` or `linkedin_url` is required. ### Example **cURL** ```bash curl -X PUT "https://api.graph8.com/api/v1/companies/assert" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "list_id": 5, "domain": "acme.com", "name": "Acme Inc", "industry": "Technology" }' ``` **Python** ```python response = requests.put( f"{BASE_URL}/companies/assert", headers=HEADERS, json={ "list_id": 5, "domain": "acme.com", "name": "Acme Inc", "industry": "Technology" } ) ``` ### Response ```json { "data": { "action": "created", "count": 1 } } ``` --- ## Batch Assert Companies `PUT /companies/assert/batch` Create or update up to 100 companies at once. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `list_id` | integer | Yes | Target list ID | | `companies` | object[] | Yes | Up to 100 company objects | --- ## Assert Deal `PUT /deals/assert` Create or update a deal. Matches by `name` + `company_id`. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | string | Yes | Deal name (match key) | | `company_id` | string | No | Company ID (match key, combined with name) | | `amount` | number | No | Monetary value | | `currency` | string | No | Currency code | | `stage_id` | string | No | Pipeline stage ID | | `pipeline_id` | string | No | Pipeline ID | | `description` | string | No | Deal description | | `close_date` | string | No | Expected close date (ISO 8601) | ### Example **cURL** ```bash curl -X PUT "https://api.graph8.com/api/v1/deals/assert" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Enterprise Deal", "amount": 50000, "company_id": "comp-123" }' ``` **Python** ```python response = requests.put( f"{BASE_URL}/deals/assert", headers=HEADERS, json={ "name": "Enterprise Deal", "amount": 50000, "company_id": "comp-123" } ) ``` ### Response ```json { "data": { "action": "created", "deal_id": "deal-new-abc" } } ``` --- ## Assert Deal-Contact Association `PUT /contacts/{contact_id}/deals/assert` Link a contact to a deal. Idempotent — if the association already exists, returns `"exists"` without error. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `deal_id` | string | Yes | Deal ID to associate | ### Example **cURL** ```bash curl -X PUT "https://api.graph8.com/api/v1/contacts/12345/deals/assert" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"deal_id": "deal-abc"}' ``` ### Response ```json { "data": { "action": "created", "deal_id": "deal-abc", "contact_id": 12345 } } ``` The `action` field returns `"created"` for a new link, or `"exists"` if the association was already present. --- # Audience Syncs > Mirror contact lists to ad platforms (Meta, LinkedIn, Google Ads, X) on a recurring schedule Source: https://docs.graph8.com/developers/audience-syncs/ > **Tip: Using the SDK or CLI?** > The [JavaScript SDK](https://docs.graph8.com/developers/sdk/) and [CLI](https://docs.graph8.com/developers/cli/) both wrap these endpoints. The CLI commands are `g8 audience-syncs list`, `g8 audience-syncs create`, and `g8 audience-syncs trigger`. Audience syncs push a graph8 audience (contact list) to an ad platform and keep it in sync on a cadence. graph8 supports four platforms: **Meta** (Facebook/Instagram custom audiences), **LinkedIn** (Matched Audiences), **Google Ads** (Customer Match), and **X** (Tailored Audiences). Each sync config has a **mode**: - `mirror` - keep the platform audience exactly in sync with the source list (adds new members, removes dropped members). - `append_only` - only add new members; never remove. A worker re-runs each sync on its `refresh_cadence_hours`. You can also trigger a sync manually. | Endpoint | Method | Description | |----------|--------|-------------| | `/audience-syncs` | GET | List all sync configs for the org | | `/audience-syncs` | POST | Create a new sync config | | `/audience-syncs/{id}` | GET | Get a single sync config | | `/audience-syncs/{id}` | PATCH | Update a sync config | | `/audience-syncs/{id}` | DELETE | Soft delete a sync config | | `/audience-syncs/{id}/trigger` | POST | Manually trigger a sync run | | `/audience-syncs/{id}/runs` | GET | List recent sync runs | | `/audience-syncs/{id}/errors` | GET | List failed sync runs only | For machine-readable schemas see the [interactive API docs](https://be.graph8.com/api/v1/docs). --- ## List Audience Syncs `GET /audience-syncs` Returns every sync config for the authenticated org. ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/audience-syncs" \ -H "Authorization: Bearer $API_KEY" ``` **Python** ```python response = requests.get( f"{BASE_URL}/audience-syncs", headers=HEADERS, ) configs = response.json()["data"] ``` ### Response ```json { "data": [ { "id": 12, "audience_id": 5, "platform": "meta", "platform_audience_name": "Q2 Outbound - Mirror", "mode": "mirror", "refresh_cadence_hours": 24, "is_active": true, "status": "active", "last_sync_at": "2026-04-15T08:00:00Z", "created_at": "2026-04-01T10:00:00Z" } ] } ``` --- ## Create Audience Sync `POST /audience-syncs` Create a new sync config. Returns `409 Conflict` if a config already exists for the same `audience_id` + `platform`. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `audience_id` | integer | **Yes** | Source list ID (must be > 0) | | `platform` | string | **Yes** | One of `meta`, `linkedin`, `google`, `x` | | `platform_audience_name` | string | No | Display name on the ad platform (max 255) | | `mode` | string | No | `mirror` (default) or `append_only` | | `refresh_cadence_hours` | integer | No | 0-720, default 24 | | `platform_config` | object | No | Platform-specific config (e.g. ad account ID, OAuth scopes) | | `suppression_list_ids` | integer[] | No | Lists whose members should be excluded | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/audience-syncs" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "audience_id": 5, "platform": "meta", "platform_audience_name": "Q2 Outbound", "mode": "mirror", "refresh_cadence_hours": 24, "platform_config": {"ad_account_id": "act_1234567890"} }' ``` **Python** ```python response = requests.post( f"{BASE_URL}/audience-syncs", headers=HEADERS, json={ "audience_id": 5, "platform": "meta", "platform_audience_name": "Q2 Outbound", "mode": "mirror", "refresh_cadence_hours": 24, "platform_config": {"ad_account_id": "act_1234567890"}, }, ) ``` ### Response `201 Created` Returns the same shape as a list item. ### Errors | Status | Meaning | |--------|---------| | `409` | A sync config already exists for this audience + platform | | `422` | `platform` not in allowed set, or `mode` invalid | --- ## Get Audience Sync `GET /audience-syncs/{config_id}` Returns a single sync config by ID. ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/audience-syncs/12" \ -H "Authorization: Bearer $API_KEY" ``` ### Errors | Status | Meaning | |--------|---------| | `404` | Sync config not found | --- ## Update Audience Sync `PATCH /audience-syncs/{config_id}` Partial update. Send only the fields you want to change. ### Request Body | Field | Type | Description | |-------|------|-------------| | `mode` | string | `mirror` or `append_only` | | `refresh_cadence_hours` | integer | 0-720 | | `is_active` | boolean | Pause/unpause without deleting | | `suppression_list_ids` | integer[] | Replace suppression list set | ### Example **cURL** ```bash curl -X PATCH "https://be.graph8.com/api/v1/audience-syncs/12" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"is_active": false}' ``` --- ## Delete Audience Sync `DELETE /audience-syncs/{config_id}` Soft delete. The platform audience is **not** removed from the ad platform - graph8 simply stops syncing. ### Response ```json { "data": { "status": "deleted", "id": 12 } } ``` --- ## Trigger Audience Sync `POST /audience-syncs/{config_id}/trigger` Run the sync immediately, outside of its scheduled cadence. Returns the sync result payload (members added/removed counts). ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/audience-syncs/12/trigger" \ -H "Authorization: Bearer $API_KEY" ``` ### Errors | Status | Meaning | |--------|---------| | `500` | Underlying ad platform returned an error - check `/runs` or `/errors` for the message | --- ## List Sync Runs `GET /audience-syncs/{config_id}/runs` Returns recent runs for a config (success and failure both). ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `limit` | integer | 20 | Max runs to return (1-100) | ### Response ```json { "data": [ { "id": 4521, "started_at": "2026-04-15T08:00:00Z", "finished_at": "2026-04-15T08:00:14Z", "status": "success", "members_added": 12, "members_removed": 3, "total_members": 1450, "error_message": null } ] } ``` --- ## List Sync Errors `GET /audience-syncs/{config_id}/errors` Same shape as `/runs`, filtered to `status = "failed"`. Use this when surfacing failures in your dashboard. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `limit` | integer | 20 | Max errors to return (1-100) | ### Response ```json { "data": [ { "id": 4520, "started_at": "2026-04-15T07:30:00Z", "error_message": "Meta API: invalid access token", "details": { "config_id": 12, "status": "failed" } } ] } ``` --- # Authentication > Authenticate API requests with org API keys Source: https://docs.graph8.com/developers/authentication/ graph8 has three authentication methods depending on how you're integrating: | Method | Use case | Where to get it | |--------|----------|----------------| | **API Key** | REST API, CLI (server-side), SDK server features | Settings > MCP & API > API tab | | **Write Key** | SDK client-side (tracking, visitor ID, forms, widgets) | Settings > MCP & API > tracking snippet | | **OAuth** | MCP Server (Claude.ai, Claude Desktop, Cursor) | Automatic - paste URL and sign in | OAuth is how AI hosts connect with no key to manage: > **Tip: Which key do I need?** > - Building a **browser app**? Use the [JavaScript SDK](https://docs.graph8.com/developers/sdk/) with a **write key** (`g8.init({ writeKey })`) > - Building a **server/script**? Use the REST API or [CLI](https://docs.graph8.com/developers/cli/) with an **API key** > - Using an **AI agent**? Use the [MCP Server](https://docs.graph8.com/developers/mcp/) with **OAuth** (no key needed) The Developer API authenticates requests using organization API keys. JWT tokens are not accepted — use an API key for all programmatic access. ## Creating an API Key 1. Go to **Settings -> API** in your graph8 workspace 2. Click **Create API Key** 3. Enter a name (e.g., "CRM Sync" or "Data Export") 4. Optionally set an expiration (1-365 days) 5. **Copy the key immediately** — it's only shown once > **Tip:** > Store your API key in an environment variable or secrets manager. You cannot retrieve it after closing the creation dialog. ## Using Your API Key Include the key in the `Authorization` header with the `Bearer` prefix: **cURL** ```bash export API_KEY="your_api_key_here" curl "https://be.graph8.com/api/v1/contacts" \ -H "Authorization: Bearer $API_KEY" ``` **Python** ```python import requests API_KEY = "your_api_key_here" BASE_URL = "https://be.graph8.com/api/v1" HEADERS = {"Authorization": f"Bearer {API_KEY}"} response = requests.get(f"{BASE_URL}/contacts", headers=HEADERS) print(response.json()) ``` **TypeScript** ```typescript const API_KEY = "your_api_key_here"; const BASE_URL = "https://be.graph8.com/api/v1"; const response = await fetch(`${BASE_URL}/contacts`, { headers: { Authorization: `Bearer ${API_KEY}` } }); const data = await response.json(); console.log(data); ``` ## Key Characteristics | Property | Details | |----------|---------| | **Scope** | Organization-level — access to all data in your org | | **Format** | Opaque token (not a JWT) | | **Expiration** | Optional, 1-365 days from creation | | **Rotation** | Delete and create a new key, or use the Rotate button in Settings | | **Rate Limit** | 50 requests/second + 1,000 requests/minute (per org) - see [Rate Limits](https://docs.graph8.com/developers/rate-limits/) | **Each API key is bound to exactly one organization** at the time it is minted. The server resolves the org from the key itself — request bodies and query strings never carry an `org_id`, and you cannot point a key at a different org. This is what makes the API safe by default for multi-tenant data: a key can only ever read and write its own org's data. > **Note: Agencies** > One exception exists for agencies that manage a portfolio of client orgs — an **agency-scoped key** can target any of its own authorized client orgs per-request via the `X-Target-Org-Id` header. It still cannot reach any org outside the agency's linked set. See [Agency Keys & Cross-Org Targeting](#agency-keys--cross-org-targeting) below. Normal single-org keys are unaffected. ## `X-Org-Id` Header (Optional Guard, Not a Switch) You may send an `X-Org-Id` header. It is a **safety assertion**, not an org selector: | Request | Result | |---------|--------| | API key only (no `X-Org-Id`) | **200** — resolves to the key's org | | API key + `X-Org-Id` matching the key's org (case-insensitive) | **200** | | API key + `X-Org-Id` for a **different** org | **403** — cross-tenant request refused | Use it as a defensive check when your integration manages keys for several orgs and you want the server to reject a mismatched key/org pairing rather than silently operate on the wrong tenant. ### Example **cURL** ```bash # No X-Org-Id needed — key resolves the org automatically curl "https://be.graph8.com/api/v1/contacts" \ -H "Authorization: Bearer $API_KEY" # Optional safety assertion — 403 if key does not belong to this org curl "https://be.graph8.com/api/v1/contacts" \ -H "Authorization: Bearer $API_KEY" \ -H "X-Org-Id: org_your_org_id" ``` ## Working with Multiple Organizations For a **normal single-org key** there is **no per-request org switching**. If your account belongs to several orgs, mint **one API key per org** and send the key for the org you intend to act on. Because the org is fixed by the key, a write always lands in that key's org. > **Note: MCP clients differ** > Interactive MCP clients (Claude, Cursor, ChatGPT, etc.) authenticate as a *user* who may belong to multiple orgs, so they support an explicit, sticky org choice via the `g8_current_org`, `g8_list_orgs`, and `g8_switch_org` tools. That selection is a property of the MCP session, not of these REST keys — switching org in MCP does not change any REST API key's bound org, and vice versa. ## Agency Keys & Cross-Org Targeting An **agency** is an org that manages a portfolio of client orgs (linked in graph8 as parent→child). An agency can mint a single **agency-scoped API key** and use it to operate any of its client orgs through the entire Developer API — contacts, sequences, dialer, workflows, appointments, deals, everything — without minting one key per client. ## POST /v1/api-keys `POST /v1/api-keys` Mint a new API key. To create an agency-scoped key that can target client orgs, pass `is_agency: true` in the request body. Only allowed when the authenticating org is a recognized agency (has linked client orgs or is flagged as an agency); otherwise returns **400**. ### Request Body | Parameter | Type | Description | |-----------|------|-------------| | `name` | string | Human-readable label for the key (e.g. "CIENCE agency ops") | | `is_agency` | boolean | Set to `true` to mint an agency-scoped key. Omit or set `false` for a normal single-org key. | ### Response Returns the newly created key. The `token` field is shown **once** and cannot be retrieved again. | Field | Type | Description | |-------|------|-------------| | `token` | string | The API key value — copy immediately, shown once only | | `name` | string | Label you provided | | `is_agency` | boolean | `true` if this is an agency-scoped key | ### Example **cURL** ```bash # Mint a normal single-org key curl -X POST "https://be.graph8.com/api/v1/api-keys" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "CRM Sync"}' # Mint an agency-scoped key (agency org only) curl -X POST "https://be.graph8.com/api/v1/api-keys" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "CIENCE agency ops", "is_agency": true}' ``` ### Response ```json { "token": "g8_live_", "name": "CIENCE agency ops", "is_agency": true } ``` ## Using an Agency Key — `X-Target-Org-Id` Send the agency key plus an `X-Target-Org-Id` header naming the client org you want to act on. The request then reads and writes that client org's data. ### `X-Target-Org-Id` Behavior | Request | Result | |---------|--------| | Agency key, no `X-Target-Org-Id` | **200** — resolves to the **agency's own** org | | Agency key + `X-Target-Org-Id` for an **authorized** client (case-insensitive) | **200** — operates that client org | | Agency key + `X-Target-Org-Id` = the agency's own org | **200** — operates the agency org | | Agency key + `X-Target-Org-Id` for an org **outside** the agency's set | **403** — not authorized for that target | | Normal key + `X-Target-Org-Id` | header **ignored** — resolves to the key's own org (no error) | > **Note: Important details about `X-Target-Org-Id`** > - `X-Target-Org-Id` is honored on the Developer API (`/api/v1/*`) only. It is not used by in-app or customer-hub hybrid endpoints, which keep single-org semantics. > - `X-Target-Org-Id` is distinct from `X-Org-Id`. `X-Org-Id` remains a same-org safety guard; `X-Target-Org-Id` is the agency selector. For an agency key, `X-Org-Id` (if sent) still checks against the agency's own org, independent of any `X-Target-Org-Id`. > - The target is always clamped against the agency's live authorized client set — a caller-supplied value can only select within that set, never widen it. > - Credit-consuming actions performed in a client org are charged to **that client org**, not the agency. ### Example **cURL** ```bash # Agency key acting on a client org curl "https://be.graph8.com/api/v1/contacts" \ -H "Authorization: Bearer $AGENCY_API_KEY" \ -H "X-Target-Org-Id: org_client_org_id" ``` ## Authentication Errors | Status | Meaning | Fix | |--------|---------|-----| | `401` | Missing or invalid `Authorization` header | Check your API key is correct | | `401` | JWT token provided instead of API key | Use an org API key, not a browser session token | | `401` | API key not associated with an organization | Ensure the key was created in Settings -> API | | `429` | Rate limit exceeded | Wait and retry with backoff (see [Rate Limits](https://docs.graph8.com/developers/rate-limits/)) | ### Example Error Response ```json { "detail": "Missing Authorization header. Use: Authorization: Bearer " } ``` ## Security Best Practices **Do:** - Store keys in environment variables or a secrets manager - Use separate keys for development and production - Set expiration dates on keys - Rotate keys periodically - Delete unused keys promptly **Don't:** - Commit keys to version control - Share keys via email or chat - Expose keys in frontend JavaScript or client-side code - Log keys in application output > **Caution:** > If you suspect a key has been compromised, delete it immediately in **Settings -> API** and create a new one. --- # Automation Platforms > Connect workflow automation tools to graph8 Studio via MCP Source: https://docs.graph8.com/developers/automation-platforms/ Connect workflow automation platforms to graph8 using the Model Context Protocol (MCP). Build powerful automations with your campaigns, accounts, and intelligence data. > **Tip:** > Headless automation platforms authenticate to the remote graph8 MCP server with a **personal or org API key** sent as an `Authorization: Bearer` header — not the interactive browser OAuth flow used by Claude Desktop or Cursor. There is no `session_id` to copy. ## Supported Platforms **n8n** Open-source workflow automation. **Supported now.** [Setup Guide](https://docs.graph8.com/developers/n8n-setup/) **Zapier** No-code automation platform. **Planned.** Integration coming soon **Make** Visual automation builder. **Planned.** Integration coming soon ## Why Use MCP for Automation? | Benefit | Description | |---------|-------------| | **Unified Access** | One connection for all graph8 data | | **Real-Time Data** | Always current campaigns, accounts, and intelligence | | **Secure** | Uses your permissions, revocable anytime | | **Standardized** | Same MCP protocol across all platforms | ## Common Automation Use Cases ### Marketing Operations - **Campaign Sync** — Push new campaigns to project management tools - **Content Distribution** — Auto-share campaign content to social media schedulers - **Performance Tracking** — Sync campaign metrics to analytics dashboards ### Sales Enablement - **CRM Enrichment** — Add graph8 intelligence to CRM records - **Deal Alerts** — Notify reps when company intelligence is updated - **Pipeline Sync** — Keep deal data synchronized across systems ### Research & Intelligence - **Competitive Alerts** — Trigger actions when competitive intel is updated - **Company Monitoring** — Watch for changes in target companies - **Report Distribution** — Auto-send intelligence reports to stakeholders ## Getting Started ### Step 1: Get an API key 1. Go to **Profile → Developer** in graph8 for a **personal API key**, or **Settings → API** (admin) for an **org API key**. 2. Copy the key — it starts with `g8_`. 3. Store it in your automation platform's credential manager, not inline in a node. ### Step 2: Choose Your Platform | Platform | Status | Guide | |----------|--------|-------| | n8n | Available | [Setup Guide](https://docs.graph8.com/developers/n8n-setup/) | | Zapier | Coming Soon | — | | Make | Coming Soon | — | ### Step 3: Configure the Connection Point the platform at the remote graph8 MCP endpoint and send your API key as a Bearer header: ``` POST https://be.graph8.com/mcp/ Authorization: Bearer YOUR_G8_API_KEY Content-Type: application/json ``` The body is standard MCP JSON-RPC — see the [n8n Setup Guide](https://docs.graph8.com/developers/n8n-setup/) for a full request example. ## Available Tools All automation platforms have access to the graph8 MCP tools your role grants. Tool names are prefixed with `g8_`. A few common ones: > **Note:** > Exact tool names are **mode-dependent**. When the endpoint runs in combined `all` mode, GTM tools are namespaced — e.g. `g8_gtm_list_campaigns` instead of `g8_list_campaigns`. The unprefixed names below are the canonical form; confirm the exact names your role grants in the [MCP Tool Reference](https://docs.graph8.com/developers/mcp-tools/). ### Campaigns | Tool | Description | |------|-------------| | `g8_list_campaigns` | List campaigns with an optional status filter | | `g8_get_campaign` | Get details for a specific campaign | ### Companies | Tool | Description | |------|-------------| | `g8_search_companies` | Search companies already in your CRM | | `g8_find_companies` | Search graph8's company index for new accounts | | `g8_lookup_company` | Enrich a single company by domain or name | ### Deals | Tool | Description | |------|-------------| | `g8_get_deals` | List pipeline deals | | `g8_get_deal` | Get deal details | ### Intelligence | Tool | Description | |------|-------------| | `g8_list_intelligence_data` | List intelligence data for the org | | `g8_tool_search` | Discover and activate other tools by keyword | For the complete list, see the [MCP Tool Reference](https://docs.graph8.com/developers/mcp-tools/). ## Best Practices ### Credentials & keys graph8 API keys are long-lived — there is no 24-hour session to refresh. - Store keys in your platform's credential manager, referenced by header (`Authorization: Bearer g8_...`) - Set up monitoring for `401` authentication failures - Rotate or revoke keys from **Profile → Developer** or **Settings → API**; revocation is immediate ### Rate Limiting - Add delays between high-volume API calls - Batch operations where possible - Cache frequently accessed data ### Error Handling - Check for authentication errors and refresh session - Handle rate limit responses with exponential backoff - Log failures for debugging > **Tip:** > For production automations, consider setting up a dedicated service account in graph8 Studio with appropriate permissions. ## Zapier Integration (Coming Soon) We're working on a native Zapier integration that will include: - **Triggers**: Campaign created, Account updated, Intelligence ready - **Actions**: Create campaign brief, Search accounts, Get intelligence - **Searches**: Find campaigns, Find accounts, Find deals Join the waitlist by contacting support@graph8.com. ## Make Integration (Coming Soon) The Make (formerly Integromat) integration will offer: - Visual scenario builder with graph8 modules - Real-time webhooks for instant triggers - Advanced data transformation tools ## Frequently Asked Questions ### Which automation platform should I choose? n8n is available now and offers the most flexibility. Choose Zapier (coming soon) for no-code simplicity, or Make for visual workflow building. All will use the same MCP protocol. ### Can I use the REST API instead of MCP? Yes, graph8 Studio has a full REST API. MCP is optimized for AI-native tools and conversational interfaces. Use REST API for traditional integrations and MCP for AI workflows. ### How do I migrate automations between platforms? MCP tools are standardized, so the same operations work across platforms. You'll need to rebuild the workflow logic in each platform, but the graph8 calls remain identical. ### Is there a way to batch multiple operations? Currently, each MCP call is individual. For bulk operations, loop through items in your automation platform. Consider adding delays to avoid rate limits. ### How do I report issues with the MCP integration? Contact support@graph8.com with details about your platform, the operation attempted, and any error messages. Never share your API key. ## Related - [MCP Overview](https://docs.graph8.com/developers/mcp) - [n8n Setup Guide](https://docs.graph8.com/developers/n8n-setup/) - [What You Can Ask](https://docs.graph8.com/developers/what-you-can-ask/) --- # Claude Code Setup > Connect Claude Code to graph8 via MCP using ~/.claude/mcp.json with OAuth or stdio Source: https://docs.graph8.com/developers/claude-code-setup/ Claude Code is Anthropic's CLI agent for coding tasks. Connect it to graph8's MCP server and your terminal agent can scan repos, install tracking, prospect contacts, and run campaigns - all from prompts. > **Tip:** > Try the developer tier free - 1,000 credits, no card required. [Get started](https://app.graph8.com/signup). ## Prerequisites - Claude Code installed ([docs](https://docs.anthropic.com/claude/docs/claude-code)) - A graph8 account with a personal API key (stdio) or any active session (OAuth) ## Hosted MCP (remote OAuth) Add graph8 to `~/.claude/mcp.json`: ```json { "mcpServers": { "graph8": { "url": "https://be.graph8.com/mcp/" } } } ``` 1. Save the file. 2. Run `claude mcp list` and confirm `graph8` is listed. 3. Start a session: `claude`. 4. Ask anything that uses graph8 tools - the CLI opens a browser tab on first call for OAuth. Sign in and return to the terminal. ## Self-hosted MCP (stdio) For headless setups or CI: ```json { "mcpServers": { "graph8": { "command": "uvx", "args": ["g8-mcp-server"], "env": { "G8_API_KEY": "g8_...", "G8_MCP_MODE": "dev" } } } } ``` Pick `G8_MCP_MODE` based on what you are doing: - `dev` - codebase work: repo scan, tracking install, progressive forms (17 tools) - `gtm` - campaign work: sequences, inbox, audience syncs (23 tools) - `all` - everything (35 tools) See [Modes](https://docs.graph8.com/developers/mcp/#modes). ## Example prompts Tie these to graph8 skills (planned) so you can invoke a full flow with one prompt: - **Prospecting and lists**: "Find 50 VP Engineering at Series B SaaS in the US. Show me the top 10 by signal score and ask before saving." - **Running a campaign**: "Create a 5-step email sequence targeting trial users. Use g8_create_sequence then g8_get_sequence_preview before launching." - **Installing tracking**: "Connect this Next.js repo to graph8, scan it, and install the tracking snippet. Then add a progressive email-capture form to the pricing page." - **AI inbox replies**: "Check my graph8 inbox for replies in the last 24 hours. Generate AI draft replies for the warm ones. Ask me to approve each draft before sending." ## Slash-command shortcuts Claude Code supports slash commands defined in `.claude/commands/`. A `/prospect` command for a SaaS team could look like: ```markdown --- name: prospect description: Find and save SaaS prospects via graph8 MCP. --- Use the graph8 MCP server to find prospects matching: $ARGUMENTS. 1. Use g8_find_contacts to preview matches. 2. Show me the top 10 by signal score. 3. On my confirmation, save them with g8_build_contact_list. 4. Report the new list ID. ``` Then run `/prospect VP Engineering, Series B SaaS, US` from any session. ## Worked example: full GTM bootstrap Use the built-in graph8 prompt template: ``` Use the graph8 gtm_setup prompt template with repo_url=https://github.com/acme/app. ``` This runs the canonical sequence: connect repo -> scan -> install -> launch. See [End-to-end examples](https://docs.graph8.com/developers/mcp-examples/#full-gtm-setup-walkthrough). ## Related - [MCP Server overview](https://docs.graph8.com/developers/mcp/) - [MCP Tool Reference](https://docs.graph8.com/developers/mcp-tools/) - [MCP FAQ](https://docs.graph8.com/developers/mcp-faq/) - [VS Code setup](https://docs.graph8.com/developers/vscode-setup/) --- # Claude Desktop Setup > Connect Claude Desktop (and Claude.ai) to graph8 via MCP using the remote OAuth connector Source: https://docs.graph8.com/developers/claude-desktop-setup/ Connect Claude to graph8 so you can ask about your campaigns, accounts, contacts, and intelligence — and run graph8 tools — directly in a conversation. > **Tip:** > Claude now connects to graph8 as a **remote connector over OAuth**. There is no config file to edit, no `session_id` to copy, and no `npx` command to run. You add the graph8 server URL once in Claude's UI and sign in when prompted. ## The UI moved — where to add graph8 now Anthropic replaced the old `claude_desktop_config.json` editing flow with an in-app **Connectors** UI. If you are looking for the config file or a "paste this JSON" step, that flow no longer exists for remote servers. Add graph8 here instead: | Client | Where to add a custom connector | |--------|---------------------------------| | **Claude.ai** (web) | **Settings → Connectors → Add custom connector** | | **Claude Desktop** (app) | **Settings → Connectors → Add custom connector** (older builds label this **MCP Servers → Add**) | If your menu shows **Extensions** or **Plugins** instead of **Connectors**, use **Add custom connector** / **Add** in that panel — it is the same remote-MCP entry point under a different label depending on your Claude version. ## Prerequisites - Claude Desktop installed ([download](https://claude.ai/download)) or a [Claude.ai](https://claude.ai) account - A graph8 account ([get started free](https://app.graph8.com/signup) — 1,000 credits, no card required) ## Setup 1. **Open the connector settings** In Claude.ai go to **Settings → Connectors → Add custom connector**. In Claude Desktop go to **Settings → Connectors → Add custom connector** (or **MCP Servers → Add** on older builds). 2. **Paste the graph8 server URL** ``` https://be.graph8.com/mcp/ ``` 3. **Add and connect** Click **Add**, then **Connect**. Claude opens a browser tab to graph8. 4. **Authorize (one time)** Sign in to graph8 and approve the connection. You are returned to Claude automatically. No API key or session ID is required — the OAuth grant is tied to your graph8 account and the organization you have active when you connect. 5. **Verify** Start a new conversation and ask: *"What campaigns do I have in graph8?"* > **Note:** > Prefer to run the server locally (headless setups, CI, or air-gapped work)? Use the stdio option with a personal API key — see [Claude Code Setup → Self-hosted MCP (stdio)](https://docs.graph8.com/developers/claude-code-setup/#self-hosted-mcp-stdio) for the `g8-mcp-server` command. ## Example Questions Once connected, try asking Claude: ``` Show me my active campaigns in graph8 ``` ``` What accounts are in the healthcare industry? ``` ``` Get the intelligence report for [account name] ``` ``` Search graph8 for content about competitive positioning ``` ``` What deals are in my pipeline? ``` ## How It Works Claude uses MCP (Model Context Protocol) to connect to graph8: 1. When you mention graph8 data, Claude recognizes it needs external information. 2. Claude calls the appropriate graph8 tool (like `list_campaigns` or `search`). 3. The graph8 MCP server authenticates with your OAuth grant and fetches the data. 4. Claude formats and presents the results in the conversation. ## Multiple Organizations The connection uses the organization you had active in graph8 when you authorized. To switch: 1. Switch organizations in graph8. 2. In Claude, remove the graph8 connector and add it again (**Settings → Connectors**). 3. Re-authorize; the new grant binds to the newly active organization. ## Managing the Connection - **Disconnect**: remove the graph8 connector from **Settings → Connectors** in Claude, and/or revoke the grant from your graph8 Profile. - **Re-authorize**: if Claude reports an authentication error, open the connector and click **Connect** again to refresh the OAuth grant. ## Troubleshooting Claude specifics live here. Generic MCP issues (auth, permissions, "tool not found") are answered in the [MCP FAQ](https://docs.graph8.com/developers/mcp-faq/). ### I can't find "Connectors" in the menu Depending on your Claude version the entry may be labeled **Connectors**, **MCP Servers**, **Extensions**, or **Plugins**. In every case, look for **Add custom connector** / **Add** and paste `https://be.graph8.com/mcp/`. Update Claude to the latest version if you see none of these. ### Claude doesn't recognize graph8 commands - Confirm the graph8 connector shows **Connected** in **Settings → Connectors**. - Start a fresh conversation after connecting. - If it still fails, remove and re-add the connector, then re-authorize. ### Other issues - Session expired or authentication failed → [FAQ](https://docs.graph8.com/developers/mcp-faq/#authentication-failed-or-401) - Permission denied → [FAQ](https://docs.graph8.com/developers/mcp-faq/#permission-denied) - Tool not found → [FAQ](https://docs.graph8.com/developers/mcp-faq/#tool-not-found) ## Privacy & Security - Your graph8 data is fetched in real time and not stored by Claude. - All requests use your authenticated OAuth grant, scoped to your account and active organization. - You can revoke access anytime from your graph8 Profile or by removing the connector in Claude. - API calls are logged for audit purposes. ## Related - [MCP Overview](https://docs.graph8.com/developers/mcp) - [Claude Code Setup](https://docs.graph8.com/developers/claude-code-setup/) - [Cursor IDE Setup](https://docs.graph8.com/developers/cursor-setup/) --- # CLI > graph8 developer CLI for tracking snippets and progressive forms Source: https://docs.graph8.com/developers/cli/ The `g8` CLI gives you terminal access to graph8's developer tools — get framework-specific tracking snippets and progressive form templates directly from your terminal. > **Tip:** > Try the developer tier free - 1,000 credits, no card required. [Get started](https://app.graph8.com/signup). > > AI agents: fetch the full doc index at [https://docs.graph8.com/llms.txt](https://docs.graph8.com/llms.txt). > **Note:** > Looking for the MCP server? The [MCP Server](https://docs.graph8.com/developers/mcp/) connects AI agents via OAuth — no installation needed. The CLI is a separate tool for terminal usage. ## Personal vs Admin Credentials For most users, CLI auth should come from your **Profile**. | Use Case | Where to get credentials | What to use | |----------|--------------------------|-------------| | Personal CLI usage on your machine | **Profile -> Developer** | `g8 login` (OAuth) or personal API key | | Shared service account or org-wide automation | **Settings -> API** | Org API key | > **Tip:** > Your PropelAuth access token from Profile is not the same as an API key. For CLI commands, use `g8 login` or `G8_API_KEY`. ## Installation ```bash pip install g8-mcp-server ``` This installs the `g8` command. Requires Python 3.10+. ## Authentication Three ways to authenticate: ### `g8 login` (recommended) ```bash g8 login ``` Opens your browser to sign in via OAuth. Once authenticated, a token is saved to `~/.g8/credentials.json` and used automatically for all future commands. No API key needed. ### API key If you prefer using an API key, use your **personal API key** from **Profile -> Developer**. Admins can also use org keys from **Settings -> API** for shared integrations. **Store it locally:** ```bash g8 login --api-key ``` Prompts you to paste an API key, validates it, and saves it to `~/.g8/credentials.json`. **Or set it as an environment variable:** ```bash export G8_API_KEY="your_api_key_here" ``` > **Note:** > **Precedence:** `G8_API_KEY` env var > `~/.g8/credentials.json` (OAuth or API key). The env var always wins when both are present. ### `g8 logout` Remove stored credentials: ```bash g8 logout ``` ### `g8 whoami` Check your current authentication status: ```bash g8 whoami # Auth source: OAuth (~/.g8/credentials.json) # Logged in as: thomas@graph8.com ``` ## Commands ### `g8 snippet` Get the graph8 tracking snippet for any supported framework. The snippet loads `p.js` — the graph8 tracking script — on every page. **Usage:** ```bash # Get snippet for a framework g8 snippet --framework nextjs # List all supported frameworks g8 snippet --list # Get snippet for a specific repo g8 snippet --framework react --repo-id # Output as JSON g8 snippet --framework html --format json ``` **Options:** | Option | Description | |--------|-------------| | `--framework` | Target framework (required unless `--list`) | | `--list` | List all supported frameworks | | `--repo-id` | Repository ID for repo-specific config (optional) | | `--format` | Output format: `text` (default) or `json` | **Supported Frameworks:** | Framework | Rendering | Description | |-----------|-----------|-------------| | `html` | SSR | HTML / Vanilla JS — script tag in `` | | `react` | CSR | React — `useEffect` component | | `nextjs` | SSR | Next.js — `next/script` for App Router and Pages Router | | `vue` | CSR | Vue — `onMounted` composable | | `wordpress` | SSR | WordPress — `functions.php` hook | | `webflow` | SSR | Webflow — Custom Code in project settings | | `shopify` | SSR | Shopify — `theme.liquid` snippet | > **Note:** > **SSR** (server-side rendered) frameworks load the snippet in the HTML layout — it's available from the first paint. Call `g8.track()` and `g8.identify()` directly. > > **CSR** (client-side rendered) frameworks inject the snippet via `useEffect` or `onMounted` after hydration. Use `window.g8?.track()` with optional chaining since the script may not be ready immediately. **Example output:** ```bash $ g8 snippet --framework nextjs ``` ``` graph8 Tracking Snippet — Next.js Setup Steps: 1. App Router: add the Script to app/layout.tsx 2. Pages Router: add the Script to pages/_app.tsx 3. Use window.g8?.track() and window.g8?.identify() in client components Code: ———————————————————————————————————————————————————————————— // app/layout.tsx (App Router) import Script from 'next/script'; export default function RootLayout({ children }) { return ( ", "config": { "write_key": "wk_...", "tracking_host": "https://t.graph8.com", "project_id": "uuid", "domains": ["graph8.com"] } } } ``` --- ## Search Knowledge Base `POST /repos/{repo_id}/kb/search` Semantic search over the per-repo knowledge base that graph8 builds from your scan results and installed documentation. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `repo_id` | string (UUID) | Repository ID | ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `query` | string | **Yes** | Natural-language search query | | `limit` | integer | No | Number of results to return — `1` to `50`, default `10` | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/repos/uuid/kb/search" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "query": "how does authentication work", "limit": 10 }' ``` --- ## List Knowledge Base Docs `GET /repos/{repo_id}/kb` Returns a list of knowledge base documents for a repository, grouped by section. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `repo_id` | string (UUID) | Repository ID | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/repos/uuid/kb" \ -H "Authorization: Bearer $API_KEY" ``` --- # ClickHouse > Read-only enrichment and per-org visitor analytics against graph8's ClickHouse instances Source: https://docs.graph8.com/developers/clickhouse/ > **Note:** > These endpoints back the `g8_clickhouse_*` MCP tool family and give direct, > read-only access to three ClickHouse instances: the global mashup enrichment > cluster and your per-org visitor analytics instance. > > The intent ClickHouse (`intent.keywords` / `intent.crawl_pages`) is **not** > re-exposed here — use the existing `/intent/*` routes instead. --- ## Bulk Enrichment Lookup `POST /clickhouse/mashup` Bulk enrichment lookup against the ClickHouse Cloud `mashup.*` tables: - `fivexfive_b2b` — 251 million rows - `fivexfive_b2c` — 424 million rows - `upid_hem` — 1.95 billion-row HEM to UP_ID resolver - `default.mashup_new` — denormalized view Requires an active subscription. The handler refuses to run without a primary-key filter — a single call cannot trigger a full-cluster scan. > **Caution:** > `by_domain` mode is rejected for the `b2c` profile because `b2c` rows have no > `COMPANY_DOMAIN` column. `by_hem` mode joins through `mashup.upid_hem` first to > resolve each HEM value to a UP_ID before fetching enrichment data. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `mode` | string | **Yes** | Lookup key type. One of: `by_upid`, `by_hem`, `by_domain` | | `values` | array of strings | **Yes** | Primary key values to look up. 1–100 items. | | `profile` | string | No | Dataset profile. One of: `b2b` (default), `b2c`, `joined` | | `limit` | integer | No | Maximum rows to return. 1–1000. Default `1000`. | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/clickhouse/mashup" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "mode": "by_upid", "values": ["UPID1", "UPID2"], "profile": "b2b", "limit": 1000 }' ``` ### Response ```json { "data": { "mode": "by_upid", "profile": "b2b", "records": [ { "UP_ID": "u1", "COMPANY_DOMAIN": "acme.com", "...": "..." } ], "matched": 1, "requested": 2 } } ``` | Field | Type | Description | |-------|------|-------------| | `mode` | string | The lookup mode used for this request | | `profile` | string | The dataset profile used | | `records` | array | Matched enrichment rows. Column names vary by profile. | | `matched` | integer | Number of input values that returned at least one record | | `requested` | integer | Total number of values submitted | --- ## Per-Org Visitor Analytics `POST /clickhouse/org-analytics` Per-org visitor and intent-resolved analytics from your org's dedicated ClickHouse visitor instance. The ClickHouse database name is derived from the authenticated user's `organization.id` (lower-cased) — the request body never carries an `org_id`. Cross-tenant queries are physically unreachable. A 90-day window cap applies to all requests. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `dataset` | string | **Yes** | Which visitor table to query. One of: `people_visitors`, `company_visitors`, `5x5_visitors`, `intent_resolved` | | `since` | string | **Yes** | Start of the time window (inclusive). ISO date or datetime, e.g. `"2026-05-01"` | | `until` | string | No | End of the time window (exclusive). ISO date or datetime. Defaults to now. | | `filters` | object | No | Key-value pairs to filter rows. Unknown column keys are silently dropped. Example: `{"contact_id": 12345}` | | `aggregation` | string | No | How to aggregate results. One of: `raw` (default), `daily_counts`, `top_n` | | `limit` | integer | No | Maximum rows to return. 1–1000. Default `100`. | #### Aggregation modes | Mode | Returns | |------|---------| | `raw` | Matching rows ordered by the dataset's timestamp descending | | `daily_counts` | `[{"day": "2026-05-01", "cnt": 12}, ...]` | | `top_n` | `[{"value": "<group_key>", "cnt": N}, ...]` grouped by the dataset's primary key (`contact_id` for `people_visitors`, `company_id` for `company_visitors`, `hem` for `5x5_visitors`, `keyword_id` for `intent_resolved`) | ### Example **cURL** ```bash # Raw rows — people visitors in May 2026 curl -X POST "https://be.graph8.com/api/v1/clickhouse/org-analytics" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "dataset": "people_visitors", "since": "2026-05-01", "until": "2026-05-30", "aggregation": "raw", "limit": 100 }' # Daily visit counts with a contact filter curl -X POST "https://be.graph8.com/api/v1/clickhouse/org-analytics" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "dataset": "people_visitors", "since": "2026-05-01", "filters": {"contact_id": 12345}, "aggregation": "daily_counts" }' # Top companies by visit count curl -X POST "https://be.graph8.com/api/v1/clickhouse/org-analytics" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "dataset": "company_visitors", "since": "2026-05-01", "aggregation": "top_n", "limit": 20 }' ``` ### Response ```json { "data": { "dataset": "people_visitors", "aggregation": "raw", "rows": [ { "contact_id": 1, "visited_at": "2026-05-30T10:00:00Z" } ], "total": 1, "window": { "since": "2026-05-01T00:00:00+00:00", "until": "2026-05-30T00:00:00+00:00" } } } ``` | Field | Type | Description | |-------|------|-------------| | `dataset` | string | The dataset queried | | `aggregation` | string | The aggregation mode applied | | `rows` | array | Result rows. Shape depends on `dataset` and `aggregation`. | | `total` | integer | Number of rows returned | | `window.since` | string | Resolved start of the query window (UTC ISO datetime) | | `window.until` | string | Resolved end of the query window (UTC ISO datetime) | --- # Companies > List, update, and delete companies and their contacts in your workspace Source: https://docs.graph8.com/developers/companies/ Manage companies in your graph8 workspace — your first-party CRM data. Companies arrive in your workspace automatically when contacts are saved, enriched, or imported — you don't need to create them manually. Each contact is linked to a company, and company records are created or updated as contacts flow in. These endpoints give you full read/write access at no credit cost. > **Tip: Looking for new companies?** > These endpoints only return companies **you already have**. To discover new companies from graph8's global B2B database, use the [Search](https://docs.graph8.com/developers/search/#search-companies) endpoints instead. --- ## List Companies `GET /companies` Returns a paginated list of companies. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `page` | integer | `1` | Page number (1-indexed) | | `limit` | integer | `50` | Items per page (1-200) | | `domain` | string | — | Filter by domain (exact match) | | `industry` | string | — | Filter by industry (partial match, case-insensitive) | | `name` | string | — | Filter by company name (partial match, case-insensitive) | | `employee_count_min` | integer | — | Minimum employee count | | `employee_count_max` | integer | — | Maximum employee count | | `country` | string | — | Filter by country (exact match) | | `state` | string | — | Filter by state (exact match) | | `city` | string | — | Filter by city (partial match, case-insensitive) | | `description` | string | — | Filter by company description (partial match, case-insensitive) | | `revenue_min` | integer | — | Minimum annual revenue | | `revenue_max` | integer | — | Maximum annual revenue | | `founded_year_min` | integer | — | Minimum founding year | | `founded_year_max` | integer | — | Maximum founding year | | `linkedin_followers_min` | integer | — | Minimum LinkedIn followers | | `linkedin_followers_max` | integer | — | Maximum LinkedIn followers | | `include_custom_fields` | boolean | `false` | When `true`, each company gains a `custom_fields` object of its custom column values keyed by column slug (`name` from `GET /fields/companies`). Default `false` keeps the legacy shape and adds no extra query. | All filters are optional and can be combined. When multiple filters are provided, results must match all of them (AND logic). ### Example **cURL** ```bash # Filter by industry curl "https://be.graph8.com/api/v1/companies?industry=Technology&limit=10" \ -H "Authorization: Bearer $API_KEY" # Search by company name with employee count range curl "https://be.graph8.com/api/v1/companies?name=acme&employee_count_min=100&employee_count_max=1000" \ -H "Authorization: Bearer $API_KEY" # Search by company description (useful for ICP identification) curl "https://be.graph8.com/api/v1/companies?description=enterprise%20software" \ -H "Authorization: Bearer $API_KEY" # Filter by revenue range and founding year curl "https://be.graph8.com/api/v1/companies?revenue_min=1000000&revenue_max=50000000&founded_year_min=2015" \ -H "Authorization: Bearer $API_KEY" # Filter by LinkedIn followers (social presence) curl "https://be.graph8.com/api/v1/companies?linkedin_followers_min=1000&industry=SaaS" \ -H "Authorization: Bearer $API_KEY" ``` **Python** ```python # Filter by industry response = requests.get( f"{BASE_URL}/companies", headers=HEADERS, params={"industry": "Technology", "limit": 10} ) companies = response.json() # Search by name with employee count range response = requests.get( f"{BASE_URL}/companies", headers=HEADERS, params={"name": "acme", "employee_count_min": 100, "employee_count_max": 1000} ) # Filter by revenue range and founding year response = requests.get( f"{BASE_URL}/companies", headers=HEADERS, params={"revenue_min": 1000000, "revenue_max": 50000000, "founded_year_min": 2015} ) ``` **TypeScript** ```typescript // Filter by industry const response = await fetch( `${BASE_URL}/companies?industry=Technology&limit=10`, { headers: { Authorization: `Bearer ${API_KEY}` } } ); const companies = await response.json(); // Search by name with employee count range const filtered = await fetch( `${BASE_URL}/companies?name=acme&employee_count_min=100&employee_count_max=1000`, { headers: { Authorization: `Bearer ${API_KEY}` } } ); // Filter by revenue range and founding year const revenueFiltered = await fetch( `${BASE_URL}/companies?revenue_min=1000000&revenue_max=50000000&founded_year_min=2015`, { headers: { Authorization: `Bearer ${API_KEY}` } } ); ``` ### Response ```json { "data": [ { "id": 42, "name": "Acme Inc", "description": "Enterprise software company specializing in cloud solutions", "domain": "acme.com", "website": "https://acme.com", "industry": "Technology", "employee_count": "500", "revenue": "25000000", "founded_year": 2015, "city": "San Francisco", "state": "CA", "country": "US", "linkedin_url": "https://linkedin.com/company/acme", "linkedin_followers": "12500", "logo_url": "https://logo.clearbit.com/acme.com", "owner_id": "user_x", "owner_name": "John", "custom_fields": null } ], "pagination": { "page": 1, "limit": 50, "total": 1, "has_next": false } } ``` --- ## Get Company `GET /companies/{company_id}` Returns detailed information about a single company. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `company_id` | integer | Company ID | ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `include_custom_fields` | boolean | `false` | When `true`, adds a `custom_fields` object of custom column values keyed by column slug (same shape as the list endpoint). | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/companies/42" \ -H "Authorization: Bearer $API_KEY" ``` **Python** ```python response = requests.get(f"{BASE_URL}/companies/42", headers=HEADERS) company = response.json()["data"] ``` **TypeScript** ```typescript const response = await fetch(`${BASE_URL}/companies/42`, { headers: { Authorization: `Bearer ${API_KEY}` } }); const { data: company } = await response.json(); ``` ### Response ```json { "data": { "id": 42, "name": "Acme Inc", "description": "Enterprise software company", "domain": "acme.com", "website": "https://acme.com", "logo_url": "https://logo.clearbit.com/acme.com", "phone": "+1-555-0200", "address": "123 Market St", "city": "San Francisco", "state": "CA", "country": "US", "zip": "94105", "founded_year": 2015, "employee_count": "500", "revenue": "25000000", "industry": "Technology", "industry_group": "Software", "linkedin_url": "https://linkedin.com/company/acme", "linkedin_followers": "12500", "facebook_url": null, "twitter_url": "https://twitter.com/acme", "crunchbase_url": null, "meta_data": null, "contact_count": 15, "custom_fields": null, "created_at": "2026-01-10T08:00:00Z", "updated_at": "2026-02-20T16:45:00Z" } } ``` ### Errors | Status | Meaning | |--------|---------| | `404` | Company not found | --- ## Get Company Contacts `GET /companies/{company_id}/contacts` Returns contacts associated with a company. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `company_id` | integer | Company ID | ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `limit` | integer | `100` | Items per page (1-200) | | `offset` | integer | `0` | Offset for pagination | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/companies/42/contacts?limit=50" \ -H "Authorization: Bearer $API_KEY" ``` **Python** ```python response = requests.get( f"{BASE_URL}/companies/42/contacts", headers=HEADERS, params={"limit": 50} ) contacts = response.json()["data"] ``` **TypeScript** ```typescript const response = await fetch(`${BASE_URL}/companies/42/contacts?limit=50`, { headers: { Authorization: `Bearer ${API_KEY}` } }); const { data: contacts } = await response.json(); ``` ### Response ```json { "data": [ { "id": 1, "first_name": "Jane", "last_name": "Smith", "full_name": "Jane Smith", "work_email": "jane@acme.com", "job_title": "VP of Engineering", "seniority_level": "VP", "direct_phone": "+1-555-0100", "linkedin_url": "https://linkedin.com/in/janesmith", "city": "San Francisco", "state": "CA", "country": "US" } ], "pagination": { "page": 1, "limit": 50, "total": 15, "has_next": false } } ``` --- ## Update Company `PATCH /companies/{company_id}` Update one or more fields on a company. Only include the fields you want to change. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `company_id` | integer | Company ID | ### Request Body All fields are optional. Include only the fields to update. | Field | Type | Description | |-------|------|-------------| | `name` | string | Company name | | `domain` | string | Primary domain | | `website` | string | Website URL | | `phone` | string | Phone number | | `address` | string | Street address | | `city` | string | City | | `state` | string | State or province | | `country` | string | Country | | `zip` | string | ZIP or postal code | | `industry` | string | Industry | | `employee_count` | integer | Number of employees | | `linkedin_url` | string | LinkedIn company page URL | ### Example **cURL** ```bash curl -X PATCH "https://be.graph8.com/api/v1/companies/42" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"industry": "Enterprise Software", "employee_count": 550}' ``` **Python** ```python response = requests.patch( f"{BASE_URL}/companies/42", headers=HEADERS, json={"industry": "Enterprise Software", "employee_count": 550} ) ``` **TypeScript** ```typescript const response = await fetch(`${BASE_URL}/companies/42`, { method: "PATCH", headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json" }, body: JSON.stringify({ industry: "Enterprise Software", employee_count: 550 }) }); ``` ### Response ```json { "data": { "updated": 1 } } ``` ### Errors | Status | Meaning | |--------|---------| | `400` | No fields provided to update | | `404` | Company not found | --- ## Upsert Company `PUT /companies/assert` Create or update a company by domain, then by LinkedIn URL. If a company with the given `domain` (or `linkedin_url`) already exists it is updated; otherwise a new record is created and added to the specified list. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `list_id` | integer | **Yes** | Target list to add the company to | | `domain` | string | No* | Company domain (e.g. `acme.com`). At least one of `domain` or `linkedin_url` is required. | | `linkedin_url` | string | No* | LinkedIn company page URL. At least one of `domain` or `linkedin_url` is required. | | `name` | string | No | Company name | | `industry` | string | No | Industry | | `employee_count` | integer | No | Number of employees | | `annual_revenue` | string | No | Annual revenue | | `city` | string | No | City | | `state` | string | No | State or province | | `country` | string | No | Country | | `website` | string | No | Website URL | | `description` | string | No | Company description | ### Example **cURL** ```bash curl -X PUT "https://be.graph8.com/api/v1/companies/assert" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "list_id": 12345, "domain": "acme.com", "name": "Acme Inc", "industry": "Technology", "employee_count": 100, "annual_revenue": "5000000", "city": "San Francisco", "state": "CA", "country": "US", "website": "https://acme.com", "description": "Enterprise software provider" }' ``` **Python** ```python response = requests.put( f"{BASE_URL}/companies/assert", headers=HEADERS, json={ "list_id": 12345, "domain": "acme.com", "name": "Acme Inc", "industry": "Technology", "employee_count": 100 } ) result = response.json()["data"] ``` **TypeScript** ```typescript const response = await fetch(`${BASE_URL}/companies/assert`, { method: "PUT", headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json" }, body: JSON.stringify({ list_id: 12345, domain: "acme.com", name: "Acme Inc", industry: "Technology", employee_count: 100 }) }); const { data: result } = await response.json(); ``` ### Response ```json { "data": { "action": "created", "count": 1 } } ``` The `action` field is either `"created"` or `"updated"` depending on whether a matching company already existed. --- ## List Company Columns `GET /companies/columns` Returns the list of columns (fields) defined for companies in a given list. Equivalent to the contacts columns endpoint but scoped to companies. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `list_id` | integer | — | Filter columns by list | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/companies/columns?list_id=12345" \ -H "Authorization: Bearer $API_KEY" ``` **Python** ```python response = requests.get( f"{BASE_URL}/companies/columns", headers=HEADERS, params={"list_id": 12345} ) columns = response.json() ``` **TypeScript** ```typescript const response = await fetch(`${BASE_URL}/companies/columns?list_id=12345`, { headers: { Authorization: `Bearer ${API_KEY}` } }); const columns = await response.json(); ``` ### Response ```json [ { "id": 1, "title": "Verified Domain", "name": "verified_domain", "data_type": "string", "is_global": false } ] ``` --- ## Create Company Column `POST /companies/columns/create` → 201 Create a custom column for companies. Accepts the same body as `POST /contacts/columns/create`, including the optional `enrichment` block (strongly recommended for enrichable columns). Column slug (`name`) values are company-scoped (e.g. `verified_domain`). Pass `enrichment.field_to_enrich` (e.g. `"company.domain"`) when you want the waterfall to write to a field other than the column itself. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `list_id` | integer | **Yes** | List to attach the column to | | `title` | string | **Yes** | Display title for the column | | `name` | string | **Yes** | Column slug (must be unique within the org, company-scoped) | | `data_type` | string | **Yes** | Data type (e.g. `string`, `number`, `boolean`, `date`) | | `enrichment` | object | No | Optional enrichment config. Include `field_to_enrich` (e.g. `"company.domain"`) to route waterfall writes to a specific field. Strongly recommended for enrichable columns. | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/companies/columns/create" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "list_id": 12345, "title": "Verified Domain", "name": "verified_domain", "data_type": "string", "enrichment": { "field_to_enrich": "company.domain" } }' ``` **Python** ```python response = requests.post( f"{BASE_URL}/companies/columns/create", headers=HEADERS, json={ "list_id": 12345, "title": "Verified Domain", "name": "verified_domain", "data_type": "string", "enrichment": {"field_to_enrich": "company.domain"} } ) column = response.json() ``` **TypeScript** ```typescript const response = await fetch(`${BASE_URL}/companies/columns/create`, { method: "POST", headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json" }, body: JSON.stringify({ list_id: 12345, title: "Verified Domain", name: "verified_domain", data_type: "string", enrichment: { field_to_enrich: "company.domain" } }) }); const column = await response.json(); ``` --- ## Delete Company `DELETE /companies/{company_id}` Soft-delete a company. The company is marked as deleted but not permanently removed. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `company_id` | integer | Company ID | ### Example **cURL** ```bash curl -X DELETE "https://be.graph8.com/api/v1/companies/42" \ -H "Authorization: Bearer $API_KEY" ``` **Python** ```python response = requests.delete(f"{BASE_URL}/companies/42", headers=HEADERS) ``` **TypeScript** ```typescript const response = await fetch(`${BASE_URL}/companies/42`, { method: "DELETE", headers: { Authorization: `Bearer ${API_KEY}` } }); ``` ### Response ```json { "data": { "deleted": true } } ``` ### Errors | Status | Meaning | |--------|---------| | `404` | Company not found | > **Caution:** > Deleting a company does not delete its associated contacts. Contacts remain in your workspace; only the company record is soft-deleted (its `deleted_at` timestamp is set). The company will no longer appear in list results. --- # Contacts > List, create, update, and delete contacts in your workspace Source: https://docs.graph8.com/developers/contacts/ > **Tip: Using the SDK or CLI?** > The [CLI](https://docs.graph8.com/developers/cli/#g8-search-contacts) supports `g8 search-contacts`, `g8 create-contact`, and `g8 add-to-list`. The [JavaScript SDK](https://docs.graph8.com/developers/sdk/) tracks visitors and identifies users via `g8.identify()`. Manage contacts in your graph8 workspace — your first-party CRM data. Contacts get into your workspace through several paths: saving results from the [Search](https://docs.graph8.com/developers/search/) endpoints, importing from a CSV or CRM, creating manually via the API, or automatically during enrichment. Once a contact is in your workspace, these endpoints give you full read/write access at no credit cost. > **Tip: Looking for new contacts?** > These endpoints only return contacts **you already have**. To discover new contacts from graph8's global B2B database, use the [Search](https://docs.graph8.com/developers/search/) endpoints instead. --- ## List Contacts `GET /contacts` Returns a paginated list of contacts. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `page` | integer | `1` | Page number (1-indexed) | | `limit` | integer | `50` | Items per page (1-200) | | `email` | string | — | Filter by work email (exact match) | | `list_id` | integer | — | Filter by list membership | | `name` | string | — | Filter by first or last name (partial match, case-insensitive) | | `job_title` | string | — | Filter by job title (partial match, case-insensitive) | | `seniority_level` | string | — | Filter by seniority level (exact match) | | `company_name` | string | — | Filter by company name (partial match, case-insensitive) | | `company_id` | integer | — | Filter by company ID | | `country` | string | — | Filter by country (exact match) | | `state` | string | — | Filter by state (exact match) | | `city` | string | — | Filter by city (partial match, case-insensitive) | | `job_department` | string | — | Filter by job department (partial match, case-insensitive) | | `industry` | string | — | Filter by company industry (partial match, case-insensitive) | | `include_custom_fields` | boolean | `false` | When `true`, each contact gains a `custom_fields` object of its custom column values keyed by column slug (`name` from `GET /fields`). Default `false` keeps the legacy shape and adds no extra query. | All filters are optional and can be combined. When multiple filters are provided, results must match all of them (AND logic). ### Example **cURL** ```bash # Basic pagination curl "https://be.graph8.com/api/v1/contacts?limit=10&page=1" \ -H "Authorization: Bearer $API_KEY" # Search by name and company curl "https://be.graph8.com/api/v1/contacts?name=jane&company_name=acme" \ -H "Authorization: Bearer $API_KEY" # Filter by seniority and country curl "https://be.graph8.com/api/v1/contacts?seniority_level=VP&country=US" \ -H "Authorization: Bearer $API_KEY" # Filter by department and industry curl "https://be.graph8.com/api/v1/contacts?job_department=Engineering&industry=SaaS" \ -H "Authorization: Bearer $API_KEY" ``` **Python** ```python # Basic pagination response = requests.get( f"{BASE_URL}/contacts", headers=HEADERS, params={"limit": 10, "page": 1} ) contacts = response.json() # Search by name and company response = requests.get( f"{BASE_URL}/contacts", headers=HEADERS, params={"name": "jane", "company_name": "acme"} ) # Filter by department and industry response = requests.get( f"{BASE_URL}/contacts", headers=HEADERS, params={"job_department": "Engineering", "industry": "SaaS"} ) ``` **TypeScript** ```typescript // Basic pagination const response = await fetch(`${BASE_URL}/contacts?limit=10&page=1`, { headers: { Authorization: `Bearer ${API_KEY}` } }); const contacts = await response.json(); // Search by name and company const filtered = await fetch( `${BASE_URL}/contacts?name=jane&company_name=acme`, { headers: { Authorization: `Bearer ${API_KEY}` } } ); // Filter by department and industry const deptFiltered = await fetch( `${BASE_URL}/contacts?job_department=Engineering&industry=SaaS`, { headers: { Authorization: `Bearer ${API_KEY}` } } ); ``` ### Response ```json { "data": [ { "id": 101, "first_name": "Jane", "last_name": "Doe", "work_email": "jane@acme.com", "direct_phone": "+1...", "mobile_phone": "+1...", "job_title": "VP Sales", "job_department": "Sales", "seniority_level": "VP", "linkedin_url": "https://...", "city": "Berlin", "state": "BE", "country": "DE", "company_id": 1, "owner_id": "user_x", "owner_name": "John", "custom_fields": null } ], "pagination": { "page": 1, "limit": 50, "total": 1, "has_next": false } } ``` When called with `?include_custom_fields=true`, `custom_fields` is an object like `{"udo_lead_score_1712000000": "92", "udo_persona_1712000111": "Champion"}` (only set values appear; keys are the column slug). Rows with no custom values return `{}`. --- ## Get Contact `GET /contacts/{contact_id}` Returns detailed information about a single contact, including associated company data. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `contact_id` | integer | Contact ID | ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `include_custom_fields` | boolean | `false` | When `true`, the response gains a `custom_fields` object (same shape as the list endpoint). Default omits it (`custom_fields: null`). | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/contacts/1" \ -H "Authorization: Bearer $API_KEY" ``` **Python** ```python response = requests.get(f"{BASE_URL}/contacts/1", headers=HEADERS) contact = response.json()["data"] ``` **TypeScript** ```typescript const response = await fetch(`${BASE_URL}/contacts/1`, { headers: { Authorization: `Bearer ${API_KEY}` } }); const { data: contact } = await response.json(); ``` ### Response ```json { "data": { "id": 1, "first_name": "Jane", "last_name": "Smith", "full_name": "Jane Smith", "work_email": "jane@acme.com", "personal_emails": null, "direct_phone": "+1-555-0100", "mobile_phone": null, "job_title": "VP of Engineering", "job_department": "Engineering", "seniority_level": "VP", "linkedin_url": "https://linkedin.com/in/janesmith", "twitter_url": null, "facebook_url": null, "city": "San Francisco", "state": "CA", "country": "US", "about": null, "confidence_score": 0.95, "company": { "id": 42, "name": "Acme Inc", "domain": "acme.com", "website": "https://acme.com", "industry": "Technology", "employee_count": "500", "city": "San Francisco", "state": "CA", "country": "US", "linkedin_url": "https://linkedin.com/company/acme", "logo_url": "https://logo.clearbit.com/acme.com" }, "meta_data": {}, "custom_fields": null, "created_at": "2026-01-15T10:30:00Z", "updated_at": "2026-02-01T14:20:00Z" } } ``` ### Errors | Status | Meaning | |--------|---------| | `404` | Contact not found | --- ## Upsert Contact `PUT /contacts/assert` **The recommended way to write a contact.** Single contact upsert: matches an existing contact by `work_email` first, then by `linkedin_url`, updates it if found, and creates it if not. It is idempotent, so a retry never produces a duplicate or a `409`. Use it whenever the contact might already exist. SDK: `g8.contacts.upsert()`. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `list_id` | integer | **Yes** | Target list | | `work_email` | string | Conditional | Work email — at least one of `work_email` or `linkedin_url` is required | | `linkedin_url` | string | Conditional | LinkedIn profile URL — at least one of `work_email` or `linkedin_url` is required | | `first_name` | string | No | First name | | `last_name` | string | No | Last name | | `personal_emails` | string | No | Personal email addresses | | `direct_phone` | string | No | Direct phone number | | `mobile_phone` | string | No | Mobile phone number | | `job_title` | string | No | Job title | | `job_department` | string | No | Department | | `seniority_level` | string | No | Seniority level | | `city` | string | No | City | | `state` | string | No | State or province | | `country` | string | No | Country | | `company_domain` | string | No | Company domain for matching | ### Example **cURL** ```bash curl -X PUT "https://be.graph8.com/api/v1/contacts/assert" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "list_id": 12345, "work_email": "jane@acme.com", "first_name": "Jane", "last_name": "Doe", "job_title": "VP Sales", "company_domain": "acme.com" }' ``` **SDK** ```typescript import { g8 } from "@graph8/sdk"; g8.init({ apiKey: process.env.G8_API_KEY }); const { action } = await g8.contacts.upsert({ list_id: 12345, work_email: "jane@acme.com", first_name: "Jane", job_title: "VP Sales", }); // action is "created" or "updated" ``` ### Response ```json { "data": { "action": "created", "count": 1 } } ``` The `action` field is either `"created"` or `"updated"`. --- ## Create Contact `POST /contacts` Create a new contact and add it to a list. > **Caution: Create-only** > `POST /contacts` never updates. If a contact with the same `work_email` or `linkedin_url` already exists it returns `409 Conflict` and writes nothing. To create-or-update in one call, use [Upsert Contact](#upsert-contact) (`PUT /contacts/assert`). ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `list_id` | integer | **Yes** | Target list to add the contact to | | `first_name` | string | No | First name | | `last_name` | string | No | Last name | | `work_email` | string | No | Work email address | | `personal_emails` | string | No | Personal email addresses | | `direct_phone` | string | No | Direct phone number | | `mobile_phone` | string | No | Mobile phone number | | `job_title` | string | No | Job title | | `job_department` | string | No | Department | | `seniority_level` | string | No | Seniority level | | `linkedin_url` | string | No | LinkedIn profile URL | | `city` | string | No | City | | `state` | string | No | State or province | | `country` | string | No | Country | | `company_domain` | string | No | Company domain for matching | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/contacts" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "list_id": 5, "first_name": "John", "last_name": "Doe", "work_email": "john@techcorp.io", "job_title": "CTO", "company_domain": "techcorp.io" }' ``` **Python** ```python response = requests.post( f"{BASE_URL}/contacts", headers=HEADERS, json={ "list_id": 5, "first_name": "John", "last_name": "Doe", "work_email": "john@techcorp.io", "job_title": "CTO", "company_domain": "techcorp.io" } ) result = response.json()["data"] ``` **TypeScript** ```typescript const response = await fetch(`${BASE_URL}/contacts`, { method: "POST", headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json" }, body: JSON.stringify({ list_id: 5, first_name: "John", last_name: "Doe", work_email: "john@techcorp.io", job_title: "CTO", company_domain: "techcorp.io" }) }); const { data: result } = await response.json(); ``` ### Response `201 Created` ```json { "data": { "status": "ok", "count": 1, "validation_errors": [] } } ``` ### Errors | Status | When | What to do | |--------|------|------------| | `409 Conflict` | A contact with the same `work_email` / `linkedin_url` already exists. Nothing is written. | `detail.contact_id` is the existing contact. `detail.hint` and `detail.upsert` name `PUT /contacts/assert`; `detail.update` names `PATCH /contacts/{id}`. | ```json { "type": "conflict", "code": "409", "message": "conflict", "detail": { "status": "duplicate", "contact_id": 42, "merged": false, "matched_by": "CONTACT_WORK_EMAIL", "field_diff": [], "validation_errors": [], "message": "A contact with work email already exists.", "hint": "A contact with this identity already exists (contact_id 42). POST /contacts only creates. To create-or-update idempotently, call PUT /api/v1/contacts/assert with the same fields plus list_id (SDK: g8.contacts.upsert()), or PATCH /api/v1/contacts/42 to update the existing contact.", "upsert": { "method": "PUT", "path": "/api/v1/contacts/assert", "operation_id": "assert_contact_contacts_assert_put", "requires": ["list_id", "work_email or linkedin_url"] }, "update": { "method": "PATCH", "path": "/api/v1/contacts/42", "contact_id": 42 } }, "request_id": "req_..." } ``` --- ## Update Contact `PATCH /contacts/{contact_id}` Update one or more fields on a contact. Only include the fields you want to change. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `contact_id` | integer | Contact ID | ### Request Body All fields are optional. Include only the fields to update. | Field | Type | Description | |-------|------|-------------| | `first_name` | string | First name | | `last_name` | string | Last name | | `work_email` | string | Work email address | | `direct_phone` | string | Direct phone number | | `mobile_phone` | string | Mobile phone number | | `job_title` | string | Job title | | `job_department` | string | Department | | `seniority_level` | string | Seniority level | | `linkedin_url` | string | LinkedIn profile URL | | `city` | string | City | | `state` | string | State or province | | `country` | string | Country | ### Example **cURL** ```bash curl -X PATCH "https://be.graph8.com/api/v1/contacts/1" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"job_title": "CTO", "city": "New York"}' ``` **Python** ```python response = requests.patch( f"{BASE_URL}/contacts/1", headers=HEADERS, json={"job_title": "CTO", "city": "New York"} ) ``` **TypeScript** ```typescript const response = await fetch(`${BASE_URL}/contacts/1`, { method: "PATCH", headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json" }, body: JSON.stringify({ job_title: "CTO", city: "New York" }) }); ``` ### Response ```json { "data": { "updated": 1 } } ``` ### Errors | Status | Meaning | |--------|---------| | `400` | No fields provided to update | | `404` | Contact not found | --- ## Delete Contact `DELETE /contacts/{contact_id}` Soft-delete a contact. The contact is marked as deleted but not permanently removed. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `contact_id` | integer | Contact ID | ### Example **cURL** ```bash curl -X DELETE "https://be.graph8.com/api/v1/contacts/1" \ -H "Authorization: Bearer $API_KEY" ``` **Python** ```python response = requests.delete(f"{BASE_URL}/contacts/1", headers=HEADERS) ``` **TypeScript** ```typescript const response = await fetch(`${BASE_URL}/contacts/1`, { method: "DELETE", headers: { Authorization: `Bearer ${API_KEY}` } }); ``` ### Response ```json { "data": { "deleted": true } } ``` ### Errors | Status | Meaning | |--------|---------| | `404` | Contact not found | > **Caution:** > Deleted contacts cannot be restored via the API. Contact support if you need to recover deleted data. --- ## Bulk Upsert Contacts `PUT /contacts/assert/batch` Bulk upsert of up to 100 contacts. Uses the same match keys as the single upsert (`work_email` then `linkedin_url`). ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `list_id` | integer | **Yes** | Target list for all contacts in this batch | | `contacts` | array | **Yes** | List of contact objects (1–100 items). Each item follows the same field set as `PUT /contacts/assert`, with at least one of `work_email` or `linkedin_url` per row. | ### Example **cURL** ```bash curl -X PUT "https://be.graph8.com/api/v1/contacts/assert/batch" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "list_id": 12345, "contacts": [ { "work_email": "alice@acme.com", "first_name": "Alice", "last_name": "Smith", "job_title": "CEO", "company_domain": "acme.com" }, { "linkedin_url": "https://linkedin.com/in/bobdoe", "first_name": "Bob", "last_name": "Doe" } ] }' ``` ### Response ```json { "data": { "total": 100, "created": 87, "updated": 13, "errors": [] } } ``` --- ## List Contact Columns `GET /contacts/columns` List custom contact columns defined in your workspace. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `list_id` | integer | — | Filter columns by list. Omit to return all (including global) columns. | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/contacts/columns" \ -H "Authorization: Bearer $API_KEY" # Filter by list curl "https://be.graph8.com/api/v1/contacts/columns?list_id=12345" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": [ { "id": 1, "title": "Job Level", "name": "job_level", "data_type": "text", "is_global": true } ] } ``` --- ## Create Contact Column `POST /contacts/columns/create` Create a custom contact column. Optionally provision an enrichment waterfall pipeline in the same request so the column is immediately enrichable. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `title` | string | **Yes** | Display name for the column | | `data_type` | string | **Yes** | Column data type — currently only `"text"` is supported | | `list_id` | integer | No | Scope the column to a specific list. Omit (or `null`) for a global column. **Required when `enrichment` is provided** (waterfall pipelines are list-scoped). | | `created_by` | string | No | Email of the user creating the column | | `enrichment` | object | No | Enrichment waterfall config — strongly recommended for enrichable columns. When supplied, a companion waterfall pipeline is provisioned in the same request so the UI Enrich button and `POST /enrichment/waterfall/enrich` work out-of-the-box. Without it the column exists but is not enrichable until you call `POST /enrichment/waterfall/configs`. | #### `enrichment` object | Field | Type | Description | |-------|------|-------------| | `type` | string | Enrichment type — use `"waterfall"` | | `providers` | array | Ordered list of provider slugs. Mutually exclusive with `steps`. Allowed values: `graph8`, `hunter`, `prospeo`, `dropcontact`, `icypeas`, `leadmagic`, `emaillistverify`, `apollo`, `lusha`, `rocketreach`. | | `email_verification` | object | Optional email verification config (see below) | #### `enrichment.email_verification` object | Field | Type | Description | |-------|------|-------------| | `provider` | string | Verification provider (e.g. `"zerobounce"`) | | `enabled` | boolean | Whether verification is active | | `use_system_credentials` | boolean | Use the platform's credentials instead of your own | | `accept_catchall` | boolean | Whether to accept catch-all addresses | | `valid_statuses` | array | List of statuses to accept (e.g. `["valid"]`) | ### Example **cURL** ```bash # Basic column (no enrichment) curl -X POST "https://be.graph8.com/api/v1/contacts/columns/create" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "title": "Job Level", "data_type": "text", "created_by": "user@graph8.com" }' # Column with enrichment waterfall curl -X POST "https://be.graph8.com/api/v1/contacts/columns/create" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "title": "Job Level", "data_type": "text", "list_id": 12345, "created_by": "user@graph8.com", "enrichment": { "type": "waterfall", "providers": ["hunter", "prospeo"], "email_verification": { "provider": "zerobounce", "enabled": true, "use_system_credentials": true, "accept_catchall": false, "valid_statuses": ["valid"] } } }' ``` ### Response `201 Created` **Without enrichment:** ```json { "data": { "id": 1, "title": "Job Level", "data_type": "text", "name": "job_level", "object_id": "12345", "is_global": false, "enrichment": null } } ``` **With enrichment:** ```json { "data": { "id": 1, "title": "Job Level", "data_type": "text", "name": "job_level", "object_id": "12345", "is_global": false, "enrichment": { "column_id": "job_level", "config_id": "uuid", "name": "Job Level Enrichment", "type": "waterfall", "providers": ["hunter", "prospeo"], "email_verification": { "enabled": true, "provider": "zerobounce" } } } } ``` > **Note:** > - `list_id` is **required** when `enrichment` is set — waterfall pipelines are list-scoped. > - `providers` and `steps` are mutually exclusive. Pass one, neither, or `email_verification` alone. > - Unknown providers return `400` (`unsupported_provider`). > - Without `enrichment`, the column exists but has no enrichment plan — `POST /enrichment/waterfall/enrich` will return `422` (`waterfall_config_missing`) until you call `POST /enrichment/waterfall/configs`. > - Legacy payloads (title / data_type / list_id / created_by only) still work unchanged; `enrichment` in the response is `null`. --- # CrewAI Setup > Connect CrewAI multi-agent crews to graph8 via MCPServerAdapter Source: https://docs.graph8.com/developers/crewai-setup/ Wire a CrewAI crew into graph8's MCP server. CrewAI's `MCPServerAdapter` exposes graph8 tools to every agent in the crew, so a prospector agent and an enricher agent can both pull from the same toolset. > **Tip:** > Try the developer tier free - 1,000 credits, no card required. [Get started](https://app.graph8.com/signup). ## Prerequisites - Python 3.10+ - `crewai`, `crewai-tools` ```bash pip install crewai 'crewai-tools[mcp]' ``` ## Hosted MCP (remote OAuth) ```python import os from crewai import Agent, Crew, Task from crewai_tools import MCPServerAdapter server_params = { "url": "https://be.graph8.com/mcp/", "transport": "streamable-http", } with MCPServerAdapter(server_params) as mcp_tools: prospector = Agent( role="SaaS Prospector", goal="Find VP Engineering at Series B SaaS companies", backstory="You preview prospects but never save without approval.", tools=mcp_tools, verbose=True, ) enricher = Agent( role="Data Enricher", goal="Verify emails and enrich firmographics on saved prospects", backstory="You only act on lists the user has approved.", tools=mcp_tools, verbose=True, ) find_task = Task( description=( "Use g8_find_contacts to preview 25 VP Engineering at Series B SaaS " "in the US. Return name, company, title, and signal score." ), agent=prospector, expected_output="A table of 25 prospects.", ) enrich_task = Task( description=( "After the user approves the list, use g8_enrich_contacts to fill " "missing emails and phones." ), agent=enricher, expected_output="An enriched contact list with verified emails.", context=[find_task], ) crew = Crew(agents=[prospector, enricher], tasks=[find_task, enrich_task]) print(crew.kickoff()) ``` The first run prompts for OAuth. Both agents share the same authenticated MCP session via the adapter. ## Self-hosted MCP (stdio) ```python import os from crewai_tools import MCPServerAdapter from mcp import StdioServerParameters server_params = StdioServerParameters( command="uvx", args=["g8-mcp-server"], env={ "G8_API_KEY": os.environ["G8_API_KEY"], "G8_MCP_MODE": "gtm", }, ) with MCPServerAdapter(server_params) as mcp_tools: # ... same crew as above pass ``` ## Worked example: prospector + enricher + sequence loader ```python sequence_loader = Agent( role="Sequence Loader", goal="Enroll approved contacts into the right sequence", backstory="You always confirm with the user before calling g8_add_to_sequence.", tools=mcp_tools, verbose=True, ) load_task = Task( description=( "Find the 'New SaaS Outreach' sequence via g8_list_sequences. " "Use g8_get_sequence_preview to show the steps. Ask the user to confirm " "before calling g8_add_to_sequence for every contact on the enriched list." ), agent=sequence_loader, expected_output="Confirmation that contacts are enrolled, with a summary.", context=[enrich_task], ) ``` graph8's MCP server flags `g8_build_contact_list`, `g8_enrich_contacts`, and `g8_add_to_sequence` as credit-charging or outreach-sending. Your CrewAI tasks should always include an explicit confirmation step. ## Related - [MCP Server overview](https://docs.graph8.com/developers/mcp/) - [MCP Tool Reference](https://docs.graph8.com/developers/mcp-tools/) - [MCP FAQ](https://docs.graph8.com/developers/mcp-faq/) - [End-to-end examples](https://docs.graph8.com/developers/mcp-examples/) --- # CRM Syncs > Push contacts, companies, and list memberships to HubSpot, Salesforce, Pipedrive, Zoho, or SugarCRM Source: https://docs.graph8.com/developers/crm-syncs/ Push graph8 records to a connected CRM. Five providers are supported: | Provider | URL | |----------|-----| | `hubspot` | hubspot.com | | `salesforce` | salesforce.com | | `pipedrive` | pipedrive.com | | `zoho` | zoho.com (CRM) | | `sugarcrm` | sugarcrm.com | You must connect the CRM in **Settings -> Integrations** first - the `{provider}` path parameter resolves to the existing Nango connection for the org. If no connection exists you'll get `404 Not Found`. | Endpoint | Method | Description | |----------|--------|-------------| | `/crm-syncs` | GET | List configured CRM integrations | | `/crm-syncs/{provider}/contacts/push` | POST | Push contacts | | `/crm-syncs/{provider}/companies/push` | POST | Push companies | | `/crm-syncs/{provider}/lists/push` | POST | Modify list memberships (add/remove) | | `/crm-syncs/{provider}/fields` | GET | Discover available fields for an entity type | | `/crm-syncs/{provider}/status` | GET | Check connection health | For machine-readable schemas see the [interactive API docs](https://be.graph8.com/api/v1/docs). --- ## List CRM Syncs `GET /crm-syncs` Returns connected CRM providers for the org. Disconnected providers are not returned. ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/crm-syncs" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": [ { "provider": "hubspot", "status": "connected", "connection_id": "nango-conn-abc123", "last_sync_at": null, "connected_at": null } ] } ``` --- ## Push Contacts `POST /crm-syncs/{provider}/contacts/push` Bulk-create contacts on the CRM. The endpoint iterates each record and reports per-record errors in the response - one bad record does not fail the whole batch. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `records` | object[] | **Yes** | Contact payloads in the CRM's native field format (e.g. `email`, `firstname`, `lastname` for HubSpot) | | `provider_fields` | object | No | Optional field mapping overrides | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/crm-syncs/hubspot/contacts/push" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "records": [ {"email": "jane@acme.com", "firstname": "Jane", "lastname": "Smith"}, {"email": "bob@acme.com", "firstname": "Bob", "lastname": "Jones"} ] }' ``` ### Response ```json { "data": { "pushed_count": 2, "failed_count": 0, "errors": [] } } ``` When a record fails, `errors[]` contains `{ "record": "", "error": "" }`. ### Errors | Status | Meaning | |--------|---------| | `404` | No active `{provider}` connection. Connect in Integrations first | --- ## Push Companies `POST /crm-syncs/{provider}/companies/push` Same shape as contact push. Each record is a company payload (e.g. `name`, `domain`, `industry` for HubSpot). ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/crm-syncs/salesforce/companies/push" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "records": [ {"Name": "Acme Inc", "Website": "acme.com", "Industry": "Technology"} ] }' ``` --- ## Push List Memberships `POST /crm-syncs/{provider}/lists/push` Add or remove contacts from CRM lists. Each `records[]` entry targets one list. ### Request Body Each item in `records[]` should look like: | Field | Type | Required | Description | |-------|------|----------|-------------| | `list_id` | string | **Yes** | CRM list identifier | | `add_contact_ids` | string[] | No | CRM contact IDs to add | | `remove_contact_ids` | string[] | No | CRM contact IDs to remove | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/crm-syncs/hubspot/lists/push" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "records": [{ "list_id": "42", "add_contact_ids": ["101", "102"], "remove_contact_ids": ["999"] }] }' ``` ### Response ```json { "data": { "added": 2, "removed": 1, "errors": [] } } ``` --- ## Discover Fields `GET /crm-syncs/{provider}/fields` Return the schema (custom + standard fields) for an entity type on the CRM. Useful before a push so you can validate field names dynamically. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `entity_type` | string | `contacts` | One of `contacts`, `companies`, `leads`, `deals` | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/crm-syncs/hubspot/fields?entity_type=contacts" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": { "fields": [ { "name": "First Name", "slug": "firstname", "type": "string", "required": false, "label": "First Name" } ] } } ``` ### Errors | Status | Meaning | |--------|---------| | `502` | The CRM rejected the discovery request - check `message` for the upstream error | --- ## Get Status `GET /crm-syncs/{provider}/status` Health-check the connection by issuing a small read. Returns `connected: false` (with a `message`) if the connection is broken or the OAuth token expired - the endpoint never raises 5xx for connection failures. ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/crm-syncs/hubspot/status" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": { "provider": "hubspot", "connected": true, "connection_id": "nango-conn-abc123", "crm_type": "hubspot", "message": "Connection is healthy", "last_sync_at": null } } ``` --- ## Field Mapping Reference Every provider has a canonical map from graph8 fields to the provider's native field names. Use `GET /crm-syncs/{provider}/fields` to introspect at runtime; the tables below are the maintained reference. ### Upsert keys (per provider) | Provider | Contact upsert key | Company upsert key | |---|---|---| | HubSpot | `email` | `domain` | | Salesforce | `Email` | `Website` (used as domain) | | Pipedrive | `email` + `name` | `name` (no domain field) | | Zoho | `Email` | `Website` | | SugarCRM | `email` | `website` | ### Contact field map (canonical → provider) | graph8 | HubSpot | Salesforce | Pipedrive | Zoho | SugarCRM | |---|---|---|---|---|---| | `work_email` | `email` | `Email` | `email[]` | `Email` | `email1` | | `first_name` | `firstname` | `FirstName` | `name` (derived) | `First_Name` | `first_name` | | `last_name` | `lastname` | `LastName` (default `-`) | `name` (derived) | `Last_Name` (fallback first_name) | `last_name` | | `job_title` | `jobtitle` | `Title` | `org.title` | `Title` | `title` | | `seniority_level` | `seniority` | — | — | — | — | | `direct_phone` | `phone` | `Phone` | `phone[]` | `Phone` | `phone_work` | | `mobile_phone` | `mobilephone` | `MobilePhone` | `phone[]` | `Mobile` | `phone_mobile` | | `linkedin_url` | `hs_linkedin_url` | `Twitter` (custom) | (custom) | (custom) | `linkedin_c` | | `city` | `city` | `MailingCity` | (custom) | `City` | `primary_address_city` | | `country` | `country` | `MailingCountry` | (custom) | `Country` | `primary_address_country` | | `source` | `hs_lead_source` | `LeadSource` | (custom) | `Lead_Source` | `lead_source` | Notes: - Salesforce requires `LastName` — graph8 defaults to `"-"` if missing. - Pipedrive uses array shapes for email and phone (`[{ value, primary }]`). - SugarCRM custom fields are suffixed `_c` (e.g. `linkedin_c`); HubSpot auto-discovers custom fields via the Properties API. ### Company field map (canonical → provider) | graph8 | HubSpot | Salesforce | Pipedrive | Zoho | SugarCRM | |---|---|---|---|---|---| | `name` | `name` | `Name` | `name` | `Account_Name` | `name` | | `domain` | `domain` | `Website` | (custom) | `Website` | `website` | | `industry` | `industry` | `Industry` | (custom) | `Industry` | `industry` | | `employee_count` | `numberofemployees` (int) | `NumberOfEmployees` | (custom) | `Employees` | `employees` | | `revenue` | `annualrevenue` | `AnnualRevenue` | (custom) | `Annual_Revenue` | `annual_revenue` | | `country` | `country` | `BillingCountry` | (custom) | `Billing_Country` | `billing_address_country` | | `founded_year` | `founded_year` | (custom) | (custom) | (custom) | (custom) | ### Direction support | Provider | Push (g8 → CRM) | Pull (CRM → g8) | Deals sync | Lifecycle / status owned by | |---|---|---|---|---| | HubSpot | ✅ full | ✅ full | ✅ | CRM (`lifecyclestage`, `hs_lead_status` overwrite g8 on pull) | | Salesforce | ✅ contacts/companies | ⚠️ minimal (email + first/last only by default) | — | CRM | | Pipedrive | ✅ contacts/companies (push-only) | — | ⚠️ partial | CRM | | Zoho | ✅ contacts/companies | ✅ partial | ⚠️ partial | CRM | | SugarCRM | ✅ full | ✅ full | ✅ | CRM (SugarHint enrichment fields pulled inbound) | ### Custom field handling - **HubSpot**: auto-discovers all custom properties via the Properties API. Pass them as bare property names in `records[].properties`. - **Salesforce**: pass-through. Custom fields use `__c` suffix natively. - **Pipedrive**: custom fields are prefixed `org.` for company customs, `person.` for contact customs. - **Zoho**: pass-through. Pre-sanitized (integers and currency parsed before push). - **SugarCRM**: custom fields use `_c` suffix (e.g. `lead_score_c`). ### Type coercion + quirks | Provider | Quirk | |---|---| | All | `email`, `phone`, `domain` are always strings. ZIP / postal codes are also strings (Salesforce + Zoho will 400 on integers). | | Salesforce | Requires `LastName` — graph8 defaults to `"-"`. | | Zoho | Requires `Last_Name` — graph8 falls back to `First_Name` if missing. | | Pipedrive | Employee count ranges (`50-200`) are converted to a midpoint integer. | | HubSpot | `numberofemployees` is integer, NOT the bucket string used elsewhere. | | HubSpot ambiguous fields | `phone`, `address`, `city`, `state`, `country`, `zip`, `mobilephone` are personal contact fields. Don't include them on contact-→-company migration mappings (graph8 auto-filters). | ### Rate limits + quirks - **HubSpot**: ~10 rps per integration; bulk endpoints accept 100 records per request. - **Salesforce**: API request limits depend on the org's edition (typically 15k-1M / 24h). The connector uses bulk where available. - **Pipedrive**: ~10 rps; no bulk upsert — graph8 paginates internally. - **Zoho**: ~10 rps; require token refresh every hour. - **SugarCRM**: instance-specific; check with your SugarCRM admin. `GET /crm-syncs/{provider}/status` exposes the current rate-limit budget and last-sync timestamp. --- # Cursor IDE Setup > Connect Cursor IDE to graph8 via MCP using the remote OAuth connector Source: https://docs.graph8.com/developers/cursor-setup/ Connect Cursor IDE to graph8 so you can prospect contacts, run campaigns, and query your accounts and intelligence while you code. > **Tip:** > graph8 connects to Cursor as a **remote MCP server over OAuth** — just a URL. There is no `session_id` to copy and no API key required for the remote flow. Cursor prompts you to sign in on first use. ## Prerequisites - Cursor IDE installed ([download](https://cursor.com)) with MCP support (recent versions) - A graph8 account ([get started free](https://app.graph8.com/signup) — 1,000 credits, no card required) ## Setup (remote OAuth — recommended) 1. **Add graph8 to your MCP config** Create or edit `.cursor/mcp.json` in your project root (or `~/.cursor/mcp.json` for all projects): ```json { "mcpServers": { "graph8": { "url": "https://be.graph8.com/mcp/" } } } ``` 2. **Reload Cursor** Quit and reopen Cursor, or reload the window, so it picks up the new server. 3. **Authorize (one time)** The first time a graph8 tool is used, Cursor opens a browser tab. Sign in to graph8 and approve the connection — no API key or session ID needed. The grant binds to your account and the organization you have active when you authorize. 4. **Verify** Open Cursor's AI chat and ask: *"What campaigns do I have in graph8?"* > **Note:** > You can also add MCP servers from Cursor's UI: **Settings → MCP → Add new global MCP server**, then paste the same `url`. The `.cursor/mcp.json` file above is the equivalent config it writes. ## Self-hosted (stdio) For headless setups, CI, or when you prefer a personal API key over OAuth, run the server locally: ```json { "mcpServers": { "graph8": { "command": "uvx", "args": ["g8-mcp-server"], "env": { "G8_API_KEY": "g8_...", "G8_MCP_MODE": "all" } } } } ``` Create a personal API key from **Profile → Developer**. See [MCP Overview → Modes](https://docs.graph8.com/developers/mcp/#modes) for `G8_MCP_MODE` options (`dev`, `gtm`, `all`). ## Example Questions Once connected, try asking Cursor: ``` Show me my active campaigns in graph8 ``` ``` What accounts are in the healthcare industry? ``` ``` Get the intelligence report for [account name] ``` ``` Search graph8 for content about enterprise pricing ``` ## Multiple Organizations The connection uses the organization you had active when you authorized. To switch: change organizations in graph8, then re-authorize the graph8 server in Cursor (remove and re-add, or trigger a fresh OAuth prompt). ## Troubleshooting Cursor specifics live here. Generic MCP issues (auth, permissions, JSON parse, "tool not found") are answered in the [MCP FAQ](https://docs.graph8.com/developers/mcp-faq/). ### "MCP server not found" or no graph8 tools - Confirm `mcp.json` is valid JSON and in the correct location (`.cursor/mcp.json` in the project root, or `~/.cursor/mcp.json`). - Reload Cursor after editing — the MCP client only picks up changes on reload. ### Other issues - Session expired or authentication failed → [FAQ](https://docs.graph8.com/developers/mcp-faq/#authentication-failed-or-401) - Permission denied → [FAQ](https://docs.graph8.com/developers/mcp-faq/#permission-denied) - Tool not found → [FAQ](https://docs.graph8.com/developers/mcp-faq/#tool-not-found) - OAuth redirect loop → [FAQ](https://docs.graph8.com/developers/mcp-faq/#oauth-redirect-loop) ## Managing the Connection - **Disconnect**: remove the `graph8` entry from `mcp.json` and reload Cursor. You can also revoke the OAuth grant from your graph8 Profile. - **Re-authorize**: if Cursor reports an authentication error, trigger a graph8 tool again to reopen the OAuth prompt. ## Frequently Asked Questions ### Does MCP work in Cursor's chat and composer? graph8 tools are available in Cursor's AI chat panel. When you ask about graph8 data, Cursor invokes the appropriate MCP tools automatically. ### Can multiple team members share one connection? No. Each OAuth grant (or personal API key) is tied to an individual graph8 account and its permissions. Each teammate connects with their own account. ### How do I disconnect Cursor from graph8? Remove the `graph8` entry from `mcp.json` and reload Cursor, and/or revoke the session from your graph8 Profile. ## Related - [MCP Overview](https://docs.graph8.com/developers/mcp) - [Claude Code Setup](https://docs.graph8.com/developers/claude-code-setup/) - [Claude Desktop Setup](https://docs.graph8.com/developers/claude-desktop-setup/) --- # Data Pipeline > How events flow from your app to destinations - streams, functions, and real-time routing Source: https://docs.graph8.com/developers/data-pipeline/ graph8 includes a built-in customer data platform (CDP) powered by ClickHouse and PostgreSQL. Every event you send is stored, processed, and available immediately - contacts, companies, enrichment, sequences, and campaigns all work out of the box. No warehouse setup required. **Optionally**, you can route a copy of your events to your own warehouse (BigQuery, Snowflake, etc.) or other tools. This is for teams that want their graph8 data alongside their other data sources. > **Tip: CDP included, warehouses optional** > Unlike Segment where you MUST configure a destination to store data, graph8's CDP stores everything by default. Adding BigQuery or Snowflake is optional - for teams that also want data in their own warehouse. ## How Data Flows ``` Your App (SDK / API) │ ▼ Streams (collect events) │ ▼ Functions (transform, filter, enrich) │ ├──→ graph8 CDP (automatic - contacts, enrichment, sequences, campaigns) │ └──→ Your warehouse (optional - BigQuery, Snowflake, Postgres, ...) + analytics (GA4, Amplitude, Mixpanel) + CRM (HubSpot) ``` Every `g8.track()`, `g8.identify()`, and `g8.page()` call automatically enters the graph8 CDP. If you've configured additional destinations, events are also fanned out to those simultaneously. ## Streams A stream is a data source - your website, mobile app, or server. Each stream has: | Component | Purpose | |-----------|---------| | **Browser key** (write key) | Public key for the [JavaScript SDK](https://docs.graph8.com/developers/sdk/) | | **Server key** | Private key for server-side tracking | | **Domains** | Allowed source domains (CORS) | When you set up graph8, a default "Website" stream is created automatically. Use the write key in your SDK init: ```typescript g8.init({ writeKey: 'YOUR_BROWSER_KEY' }); ``` Manage streams in **Connections > Sites** in your graph8 dashboard. ## Functions Functions are JavaScript code that runs on every event before it reaches destinations. Use them to: - **Filter** out test/bot traffic - **Transform** event shapes to match destination schemas - **Enrich** events with external data (API calls, warehouse lookups) - **Route** events to specific tables or destinations See the full [Functions guide](https://docs.graph8.com/developers/functions/) for API reference and code examples. ## Destinations Events are routed to all connected destinations in real-time. No extra code needed - configure once in the dashboard, every event flows automatically. **Supported destinations:** **Data Warehouses** BigQuery, Snowflake, ClickHouse, Redshift, Postgres, MySQL, S3 **Analytics & Marketing** Google Analytics 4, Amplitude, Mixpanel, Facebook Conversion API **CRM & Sales** HubSpot, plus any HTTP API via webhook destinations **graph8 CDP** Contacts, companies, enrichment, sequences, campaigns (automatic) See the full [Destinations guide](https://docs.graph8.com/developers/destinations/) for setup instructions. ## Live Events Debugger See events arriving in real-time. After installing the SDK, open **Connections > Live Events** to verify data is flowing correctly. - Filter by stream, destination, or event type - Inspect full event payloads - See function execution results - Debug destination delivery See the [Live Events guide](https://docs.graph8.com/developers/live-events/) for details. ## Schema Management graph8 automatically manages database schemas for warehouse destinations: - **Auto-create tables** when new event types arrive - **Auto-add columns** when events contain new properties - **Type inference** from JSON to SQL types (string, integer, timestamp, etc.) No DDL required. Send events, graph8 handles the schema. ## Replay When you add a new destination, you can replay historical events to backfill it. All events are stored in the data lake and can be replayed with date filtering. ## What graph8 Replaces | Before | After | |--------|-------| | Segment (tracking + destinations) | `g8.track()` + Connections | | Clearbit (enrichment) | `g8.enrich.person()` | | Outreach (sequences) | `g8.sequences.add()` | | Calendly (booking) | `g8.calendar.show()` | | Intercom (chat + copilot) | `g8.chat.open()` + `g8.copilot.open()` | | Typeform (forms) | `g8.forms.lookup()` | | HubSpot CRM | Built-in contacts, companies, lists, deals | One platform. One SDK. One bill. --- # Deals > Create, manage, and track deals across your pipeline stages Source: https://docs.graph8.com/developers/deals/ Deals represent revenue opportunities in your pipeline. Each deal has a name, value, stage, and can be associated with a company and contacts. --- ## List Pipelines `GET /deals/pipelines` Returns all deal pipelines and their stages. Use this to get valid `pipeline_id` and `stage_id` values before creating or updating deals. ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/deals/pipelines" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": [{ "id": "pipeline_uuid", "name": "Sales", "is_default": true, "stages": [{ "id": "stage_uuid", "name": "Discovery", "probability": 30, "position": 0, "stage_type": "open", "color": "#6B7280", "required_elements": ["pain_identified"], "recommended_elements": [], "channel_scripts": { "call": "...", "email": "...", "linkedin": "..." } }] }] } ``` --- ## List Deals `GET /deals` Returns all deals across the organization with pagination and optional filters. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `page` | integer | `1` | Page number | | `limit` | integer | `50` | Items per page | | `stage_id` | string | — | Filter by stage ID | | `pipeline_id` | string | — | Filter by pipeline ID | | `search` | string | — | Search by deal name (partial match) | All query parameters are optional. ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/deals?search=Acme&limit=50" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": [{ "id": "deal_uuid", "name": "...", "description": "...", "amount": 50000.0, "currency": "USD", "stage_id": "...", "stage_name": "Discovery", "pipeline_id": "...", "company_id": 1, "owner_id": "user_x", "close_date": "2026-09-30", "created_at": "...", "updated_at": "...", "contact_count": 3, "primary_contact": { "id": 5500429, "name": "Vince", "email": "v@example.com", "title": "VP Sales", "role": "champion" }, "contacts": null }], "pagination": { "page": 1, "limit": 50, "total": 1, "has_next": false } } ``` List responses include `contact_count` (live count from deal contacts) and `primary_contact` (resolves the deal's primary contact ID, falling back to the oldest linked contact). The full nested `contacts` list is intentionally `null` on list responses to keep payloads bounded — use `GET /deals/{deal_id}` for the full set. --- ## Create Deal `POST /deals` → `201 Created` Creates a new deal. If `pipeline_id` is not specified, the organization's default pipeline is used. If `stage_id` is not specified, the first stage in the pipeline is used. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | string | **Yes** | Deal name | | `company_id` | integer | No | Company to associate | | `description` | string | No | Deal description | | `amount` | number | No | Monetary value | | `currency` | string | No | Currency code (default: `USD`) | | `stage_id` | string | No | Stage ID — defaults to first stage of default pipeline | | `pipeline_id` | string | No | Pipeline ID — defaults to org default | | `close_date` | string | No | Expected close date (ISO 8601, e.g. `2026-09-30`) | | `owner_id` | string | No | Deal owner (user ID or email); omitted = un-owned | | `contact_ids` | array of integers | No | `mashup_contact_id`s to attach to the deal | `owner_id` is stored as-is (`String(255)`); whatever convention your org uses (user UUID or email) is preserved. Omitting it leaves the deal un-owned. `contact_ids` accepts canonical `mashup_contact_id`s — the same ID space as `GET /contacts/{id}`. Attachments are idempotent: already-linked contacts are skipped silently. When `contact_ids` is provided, the response includes the resolved `contacts` list, `contact_count`, and `primary_contact`. When omitted or empty, `contact_count` is `0`, `primary_contact` is `null`, and `contacts` is `null`. ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/deals" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Acme Q3", "amount": 50000, "currency": "USD", "company_id": 1, "close_date": "2026-09-30", "owner_id": "rep@x.com", "contact_ids": [5500429, 5500914] }' ``` Returns `201 Created` with the full DealResponse including resolved contacts when `contact_ids` are provided. --- ## Get Deal `GET /deals/{deal_id}` Returns a single deal by ID. This is the detail endpoint — it includes the full nested `contacts` list (omitted on list responses). `primary_contact` and `contact_count` mirror the list response. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `deal_id` | string | Deal UUID | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/deals/deal_uuid" \ -H "Authorization: Bearer $API_KEY" ``` ### Response The `contacts` array on the detail endpoint includes each linked contact with their role within the deal. ```json { "data": { "id": "deal_uuid", "name": "Acme Q3", "description": "...", "amount": 50000.0, "currency": "USD", "stage_id": "stage_uuid", "stage_name": "Discovery", "pipeline_id": "pipeline_uuid", "company_id": 1, "owner_id": "user_x", "close_date": "2026-09-30", "created_at": "...", "updated_at": "...", "contact_count": 2, "primary_contact": { "id": 5500429, "name": "Vince", "email": "v@example.com", "title": "VP Sales", "role": "champion" }, "contacts": [ { "id": 5500429, "name": "Vince", "email": "v@example.com", "title": "VP Sales", "role": "champion" }, { "id": 5500914, "name": "Dana", "email": "d@example.com", "title": "CTO", "role": "decision_maker" } ] } } ``` Contact roles can be: `champion`, `decision_maker`, `influencer`, `blocker`, `coach`, or `end_user`. --- ## Update Deal `PATCH /deals/{deal_id}` Partial update — send only the fields you want to change. Returns `400` only if no actionable fields and both contact delta lists are empty. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `deal_id` | string | Deal UUID | ### Request Body All fields are optional. | Field | Type | Description | |-------|------|-------------| | `name` | string | Deal name | | `description` | string | Deal description | | `amount` | number | Monetary value | | `currency` | string | Currency code | | `stage_id` | string | Stage ID | | `close_date` | string | Expected close date (ISO 8601) | | `owner_id` | string | Reassign owner; pass explicit `null` to clear; omit to leave unchanged | | `add_contact_ids` | array of integers | Contact IDs to link (delta, not replace-all; idempotent) | | `remove_contact_ids` | array of integers | Contact IDs to unlink (delta; idempotent; removes run before adds) | | `contact_roles` | object | Buying-committee role per contact, keyed by contact ID: `{"5500429": "champion"}`. `null` clears a role | `add_contact_ids` and `remove_contact_ids` are delta-style — an empty list is a no-op; omitting the field is also a no-op. The response includes `contact_count` and `primary_contact` post-update; `contacts` remains `null` (use `GET /deals/{deal_id}` for the full list). `contact_roles` records what each contact does in the deal. Valid roles are `champion`, `decision_maker`, `influencer`, `blocker`, `coach` and `end_user`; `null` clears a role, and anything else returns `422` listing the vocabulary. Keys are contact IDs — JSON object keys are strings, so send `"5500429"`, not `5500429`; a non-numeric key is `422`. Contact operations run in a fixed order inside one transaction: `remove_contact_ids`, then `add_contact_ids`, then `contact_roles`. So a contact you link in this request can be given a role in the same request. A contact named in `contact_roles` that is neither already on the deal nor being added returns `422` naming the ID, and the whole update is rolled back — it is never silently skipped. Omitting `contact_roles` leaves every existing role untouched. Read roles back from `GET /deals/{deal_id}` (`contacts[].role`). To set just one, use [Set Contact Role](#set-contact-role) below. ### Example **cURL** ```bash curl -X PATCH "https://be.graph8.com/api/v1/deals/deal_uuid" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "stage_id": "stage_uuid", "amount": 75000, "add_contact_ids": [5500999], "remove_contact_ids": [5500914], "contact_roles": { "5500999": "champion", "5500429": "decision_maker" } }' ``` --- ## Set Contact Role `PUT /deals/{deal_id}/contacts/{contact_id}` Set or clear one contact's buying-committee role on a deal. For a whole committee in one call, use `contact_roles` on [Update Deal](#update-deal) instead. This endpoint never creates a link — the contact must already be on the deal. Attach it first with `add_contact_ids` on `PATCH /deals/{deal_id}`. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `deal_id` | string | Deal UUID | | `contact_id` | integer | Contact ID, the same ID returned by `GET /contacts/{contact_id}` | ### Request Body | Field | Type | Description | |-------|------|-------------| | `role` | string or null | Required. One of `champion`, `decision_maker`, `influencer`, `blocker`, `coach`, `end_user`. Pass `null` to clear the role | `role` is required but nullable: `{"role": null}` clears the role, while an empty body `{}` returns `422`. Returns `404` if the deal is not in your organization or the contact is not linked to it, and `422` for a role outside the vocabulary. Re-sending a role the contact already has writes nothing. ### Example **cURL** ```bash curl -X PUT "https://be.graph8.com/api/v1/deals/deal_uuid/contacts/5500429" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "role": "champion" }' ``` ### Response ```json { "data": { "id": 5500429, "name": "Vince Reyes", "email": "vince@acme.com", "title": "VP Sales", "role": "champion" } } ``` --- ## Delete Deal `DELETE /deals/{deal_id}` ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `deal_id` | string | Deal UUID | ### Example **cURL** ```bash curl -X DELETE "https://be.graph8.com/api/v1/deals/deal_uuid" \ -H "Authorization: Bearer $API_KEY" ``` ### Response Returns `200` with `{"data": {"deleted": true}}` on success. --- ## Get Contact Deals `GET /contacts/{contact_id}/deals` Returns all deals associated with a specific contact. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `contact_id` | integer | Contact ID | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/contacts/5500429/deals" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": [{ "deal_id": "...", "name": "...", "stage": "...", "value": 50000.0, "currency": "USD", "role": "...", "pipeline_id": "...", "close_date": "...", "owner_id": "...", "created_at": "..." }] } ``` --- ## Get Company Deals `GET /companies/{company_id}/deals` Returns all deals associated with a specific company. Same response shape as `GET /contacts/{contact_id}/deals`. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `company_id` | integer | Company ID | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/companies/1/deals" \ -H "Authorization: Bearer $API_KEY" ``` --- # Destinations > Route events to data warehouses, analytics tools, and CRMs automatically Source: https://docs.graph8.com/developers/destinations/ graph8 stores all your event data automatically in its built-in CDP (ClickHouse + PostgreSQL). Contacts, companies, enrichment, sequences, and campaigns all work out of the box - no destination setup needed. **Destinations are optional** - for teams that also want a copy of their event data in their own warehouse or analytics tools. Configure once, every event flows to both graph8's CDP and your additional destinations. ## Supported Destinations ### Data Warehouses | Destination | What You Get | |-------------|-------------| | **BigQuery** | Auto-schema, streaming inserts, partitioned tables | | **Snowflake** | Auto-schema, bulk loading, warehouse auto-resume | | **ClickHouse** | Async inserts, optimized for high-volume streaming | | **Redshift** | Auto-schema, S3 staging for bulk loads | | **Postgres** | Direct inserts, auto-schema | | **MySQL** | Direct inserts, auto-schema | | **S3** | JSON/Parquet files, partitioned by date | ### Analytics & Marketing | Destination | What It Does | |-------------|-------------| | **Google Analytics 4** | Forward events as GA4 hits | | **Amplitude** | Send events and user properties | | **Mixpanel** | Track events and identify users | | **Facebook Conversion API** | Server-side conversion tracking | | **HubSpot** | Sync contacts and events | ### Custom | Destination | What It Does | |-------------|-------------| | **Webhook (HTTP)** | POST events to any URL | | **JavaScript Tag** | Load third-party scripts client-side | ## Adding a Destination 1. Go to **Connections > Destinations** in your graph8 dashboard 2. Click **+ Add Destination** 3. Select the destination type 4. Enter credentials (project ID, API key, connection string, etc.) 5. Click **Save** That's it. Events start flowing immediately to the new destination. ## How It Works ``` g8.track('signup', { plan: 'pro' }) │ ├──→ BigQuery (events.signup table, auto-created) ├──→ Google Analytics 4 (as GA4 event) ├──→ HubSpot (contact activity) └──→ graph8 CDP (contact enrichment + sequences) ``` - **No extra code** - the SDK sends to graph8, graph8 fans out to all destinations - **No data loss** - events are persisted before delivery, retried on failure - **Schema-free** - send any JSON, warehouses get auto-created tables and columns - **Real-time** - events arrive at destinations within seconds ## Filtering Events Per Destination Use [Functions](https://docs.graph8.com/developers/functions/) to control which events reach which destinations. For example, send only `purchase` events to Facebook Conversion API, or strip PII before sending to BigQuery. ## Verifying Delivery Open **Connections > Live Events** to see events flowing through the pipeline. Each event shows which destinations received it and delivery status. See [Live Events](https://docs.graph8.com/developers/live-events/) for details. > **Tip: Using the SDK?** > Events from `g8.track()`, `g8.identify()`, and `g8.page()` in the [JavaScript SDK](https://docs.graph8.com/developers/sdk/) automatically enter the pipeline. No additional setup needed beyond installing the SDK and configuring destinations. --- # Enrichment > Lookup contacts and companies, run waterfall enrichment, and verify emails Source: https://docs.graph8.com/developers/enrichment/ > **Tip: Using the SDK?** > The [JavaScript SDK](https://docs.graph8.com/developers/sdk/#enrichment) wraps these endpoints with TypeScript types: `g8.enrich.person()`, `g8.enrich.company()`, `g8.enrich.verifyEmail()`. The [CLI](https://docs.graph8.com/developers/cli/#g8-lookup-person) also supports `g8 lookup-person` and `g8 lookup-company`. The enrichment endpoints cover three capabilities: - **Lookup** — Find a single person or company from the open data index when you already know their email, LinkedIn URL, or domain. Returns full profile data instantly. - **Enrich** — Fill missing fields (emails, phone numbers) on contacts **you already have** in your workspace by running them through multiple data providers in sequence (waterfall enrichment). - **Verify** — Check if an email address is deliverable before sending. > **Tip: Lookup vs Search** > **Lookup** finds one specific person or company by a known identifier (email, LinkedIn URL, domain). If you want to find **multiple** contacts or companies matching criteria like job title, industry, or company size, use the [Search](https://docs.graph8.com/developers/search/) endpoints instead. ## Two billing buckets Enrichment endpoints split into two buckets that bill differently. The split matters because graph8 owns the cost on one side and pays a third-party provider on the other. ### Index Lookups - 2 credits each These endpoints hit graph8's owned indexes (OpenSearch + ClickHouse). Single-record, instant, no waterfall. A lookup is charged only when it finds a match; one that finds nothing (`found: false`) costs 0 credits. | Endpoint | PAYG | Platform | |---|---|---| | `POST /enrichment/lookup/person` | 2 credits per lookup | 2 credits per lookup | | `POST /enrichment/lookup/company` | 2 credits per lookup | 2 credits per lookup | | `POST /enrichment/verify-email` | 1 credit per verify | 1 credit per verify | ### Waterfall Enrichment - always credits These endpoints chain through external providers (Prospeo, Dropcontact, Cognism, and others). graph8 pays the provider per record - so credits apply on **both** PAYG and Platform. | Endpoint | PAYG | Platform | |---|---|---| | `POST /enrichment/enrich` (waterfall) | Variable per record | Variable per record | | External email verifiers (Kickbox, ZeroBounce) | Per-verification | Per-verification | See [Pricing](https://docs.graph8.com/developers/pricing/) for the full plan comparison. --- ## Person Lookup `POST /enrichment/lookup/person` Instant person lookup. Provide at least one of: `email`, `linkedin_url`, or `first_name` + `last_name` + `company_domain`. **Pricing:** Free on both PAYG and Platform within the 50 rps cap (graph8-owned index data). > **Note: Aliases and near misses** > `POST /enrichment/person` is an alias of this endpoint: same body, response, scope and pricing. Prefer `/enrichment/lookup/person` in new code. > There is no `/enrichment/contact`. To look up a person, use this endpoint. To enrich contacts already in your workspace by id, use [Start Enrichment Job](#start-enrichment-job) (`POST /enrichment/enrich`). A call to `/enrichment/contact` returns `404` with a `did_you_mean` list naming both. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `email` | string | No* | Work email address | | `linkedin_url` | string | No* | LinkedIn profile URL | | `first_name` | string | No* | First name (requires `last_name` + `company_domain`) | | `last_name` | string | No* | Last name (requires `first_name` + `company_domain`) | | `company_domain` | string | No* | Company domain (requires `first_name` + `last_name`) | *At least one lookup strategy is required. ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/enrichment/lookup/person" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"email": "jane@acme.com"}' ``` **Python** ```python response = requests.post( f"{BASE_URL}/enrichment/lookup/person", headers=HEADERS, json={"email": "jane@acme.com"} ) ``` ### Response ```json { "data": { "found": true, "confidence": 0.95, "data": { "first_name": "Jane", "last_name": "Doe", "job_title": "VP of Sales", "company": "Acme Inc", "linkedin_url": "https://linkedin.com/in/janedoe", "work_email": "jane@acme.com" } } } ``` --- ## Company Lookup `POST /enrichment/lookup/company` Instant company lookup by domain or name. **Pricing:** Free on both PAYG and Platform within the 50 rps cap (graph8-owned index data). ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `domain` | string | No* | Company domain | | `name` | string | No* | Company name | *At least one of `domain` or `name` is required. ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/enrichment/lookup/company" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"domain": "acme.com"}' ``` **Python** ```python response = requests.post( f"{BASE_URL}/enrichment/lookup/company", headers=HEADERS, json={"domain": "acme.com"} ) ``` ### Response ```json { "data": { "found": true, "confidence": 0.98, "data": { "name": "Acme Inc", "domain": "acme.com", "industry": "Technology", "employee_count": 500, "annual_revenue": "$50M-$100M" } } } ``` --- ## Start Enrichment Job `POST /enrichment/enrich` Start an async waterfall enrichment job. The job runs across multiple data providers and returns a `job_id` you can poll for results. Returns `202 Accepted`. **Pricing:** Credits apply on **both PAYG and Platform** - waterfall enrichment hits third-party providers (Prospeo, Dropcontact, Cognism, etc.) with per-record cost. Cost varies by which providers are configured in `fields_config`. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `contact_ids` | integer[] | Yes | Contact IDs to enrich | | `list_id` | integer | Yes | List ID the contacts belong to | | `fields_config` | object | No | Fields to enrich and provider order (see below) | **fields_config example:** ```json { "work_email": ["prospeo", "dropcontact"], "direct_phone": ["leadmagic", "prospeo"] } ``` If omitted, defaults to `{"work_email": ["prospeo", "dropcontact"]}`. **How the job runs (fixed October 16, 2026).** Until this date the job was accepted but never ran: it stayed `queued`, then returned `404`, and nothing was enriched or charged. It now runs, and **charges credits** for graph8-funded provider steps that find data: - Each key is a field (`work_email`, `email`, `direct_phone`, `phone`, `mobile_phone`, `linkedin_url`, or a column such as `CONTACT_WORK_EMAIL`). Its providers run in exactly the order you list, each with the right action for that field. An empty list runs every provider the field supports. - A provider with no step for the field returns `400 unsupported_provider_for_field` with the supported list (phones: `leadmagic`, `prospeo`, `apollo`). An unknown field returns `400 unsupported_field`. - Your own provider key is used when you saved one (free); otherwise graph8's key is used and credits apply. - A contact that already has the field is not enriched and not charged, however the value got there (an import, the app, an earlier run with other providers). Contacts enriched in the last 30 days are skipped too. - Your list's own pipelines are not changed or used. - Each field runs as its own job. `results` lists every field with its `status` (`queued`, `skipped` or `failed`), its `job_id` and how many contacts were skipped. `job_id` is the first queued job. - If some fields started and one failed, you get `202`: the started jobs are running. The failed field has an `attempted_job_id`; check it with `GET /enrichment/jobs/{id}` (a timed-out start may still run), then retry only that field with a new `Idempotency-Key`. - `502 enrichment_dispatch_failed` means no job was confirmed. Check each `attempted_job_id` before retrying, and retry with a new `Idempotency-Key` (the same key returns the same 502 for 15 minutes). - Send an `Idempotency-Key` header: a retry with the same key returns the same jobs and starts nothing new. A key is single-use per request: reusing a fixed key (for example `daily-enrich`) for a later run returns the earlier job, so send a new key for every new run. ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/enrichment/enrich" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "contact_ids": [101, 102, 103], "list_id": 5, "fields_config": {"work_email": ["prospeo", "dropcontact"]} }' ``` **Python** ```python response = requests.post( f"{BASE_URL}/enrichment/enrich", headers=HEADERS, json={ "contact_ids": [101, 102, 103], "list_id": 5, "fields_config": {"work_email": ["prospeo", "dropcontact"]} } ) job_id = response.json()["data"]["job_id"] ``` ### Response ```json { "data": { "job_id": "abc-123-def", "status": "queued", "total": null, "completed": null, "results": null } } ``` --- ## Poll Enrichment Job `GET /enrichment/jobs/{job_id}` Check the status of an enrichment job. Works for both `POST /enrichment/enrich` and `POST /enrichment/waterfall/enrich` jobs. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `job_id` | string | Job UUID returned by the enrich endpoint | ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `include_provider_errors` | boolean | `false` | When `true`, adds a `provider_errors` array with per-record, per-provider error details (one extra DB read) | > **Caution: `completed` does not mean data was written** > `status="completed"` means the pipeline ran to the end — not that records were enriched. Always check `successful_enrichments`: a value of `0` with `total_credits_used=0` almost always means a provider credential is missing, not a write-back bug. ### Status Values | Status | Description | |--------|-------------| | `queued` | Job is waiting to start | | `running` | Enrichment in progress | | `completed` | Pipeline ran to completion (check `successful_enrichments` for actual results) | | `failed` | Job failed | | `cancelled` | Job was cancelled | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/enrichment/jobs/abc-123-def" \ -H "Authorization: Bearer $API_KEY" # With provider error details curl "https://be.graph8.com/api/v1/enrichment/jobs/abc-123-def?include_provider_errors=true" \ -H "Authorization: Bearer $API_KEY" ``` **Python** ```python response = requests.get( f"{BASE_URL}/enrichment/jobs/{job_id}", headers=HEADERS, params={"include_provider_errors": True} ) data = response.json()["data"] ``` ### Response ```json { "data": { "job_id": "abc-123-def", "status": "completed", "total": 100, "completed": 100, "results": null, "successful_enrichments": 0, "failed_enrichments": 100, "total_credits_used": 0, "warnings": null, "provider_errors": null } } ``` `successful_enrichments`, `failed_enrichments`, and `total_credits_used` are populated only for terminal jobs (`completed`, `failed`, `cancelled`). They are `null` while a job is `queued` or `running`. When `include_provider_errors=true`, `provider_errors` contains entries like `{"record_id": 101, "provider": "hunter", "error": "hunter API key is not configured"}`. --- ## Verify Email `POST /enrichment/verify-email` Verify a single email address and check its deliverability status. **Pricing:** 1 credit per verify on both plans. If the verifier call fails, the response comes back with `status: "unknown"` and nothing is charged. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `email` | string | Yes | Email address to verify | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/enrichment/verify-email" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"email": "jane@acme.com"}' ``` **Python** ```python response = requests.post( f"{BASE_URL}/enrichment/verify-email", headers=HEADERS, json={"email": "jane@acme.com"} ) ``` ### Response ```json { "data": { "email": "jane@acme.com", "status": "valid", "sub_status": null, "is_valid": true } } ``` | Status | Description | |--------|-------------| | `valid` | Deliverable email address | | `invalid` | Undeliverable | | `catch-all` | Domain accepts all addresses | | `unknown` | Could not determine | --- ## List Waterfall Configs `GET /enrichment/waterfall/configs` Returns all waterfall enrichment pipelines configured for a list. Each config includes per-step credential information so you can tell which providers are ready to run. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `list_id` | integer | — | **Required.** List ID to fetch configs for | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/enrichment/waterfall/configs?list_id=12345" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": { "configs": [ { "column_id": "waterfall_xxx", "name": "Email lookup", "field_to_enrich": "work_email", "data_type": "text", "is_global": false, "is_active": true, "list_id": 12345, "step_count": 1, "providers": ["graph8"], "providers_detail": [ { "provider": "graph8", "is_system_provider": true, "credential_source": "system", "credential_ready": true } ], "missing_credentials": [] } ], "list_id": 12345, "count": 1 } } ``` `providers_detail` gives the per-step credential breakdown (system vs BYOK and readiness). `missing_credentials` lists providers whose BYOK step has no active org key — those steps will be skipped at run time. --- ## Create Waterfall Config `POST /enrichment/waterfall/configs` Creates (or reuses) a waterfall enrichment pipeline. Idempotent by `(list_id, name)` — calling it twice with the same name returns the existing config with `"created": false`. Returns `201 Created`. Three call styles are available, in increasing order of control: 1. **Bare** — pass only `list_id`. Server builds a single-step graph8 lookup (2 credits per row it finds, nothing on a miss). Pre-2026-06 contract. 2. **By providers** — pass `providers=["hunter","prospeo"]`. Server maps each to its default email-finder action in that order. Use this for "try these providers, in this order" pipelines. 3. **By steps** — pass `steps=[...]` with full step shapes. Server persists them verbatim. Use this for provider-specific config (e.g. Apollo `reveal_phone_numbers=true`). `providers` and `steps` are mutually exclusive — pass one or neither. `email_verification` may be combined with any call style. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `list_id` | integer | Yes | List ID to create the config for | | `name` | string | No | Pipeline name (default assigned if omitted) | | `field_to_enrich` | string | No | Field to fill in (default `work_email`) | | `skip_existing_values` | boolean | No | Skip records that already have a value (default `true`) | | `providers` | string[] | No | Ordered list of provider names — server builds default email-finder steps. Mutually exclusive with `steps`. | | `steps` | object[] | No | Full caller-supplied step objects (see below). Mutually exclusive with `providers`. | | `email_verification` | object | No | Post-enrichment email validation config (see below) | | `credential_mode` | string | No | `auto` \| `system` \| `byok` — controls how `providers[]` resolves credentials (default `auto`). Has no effect on `steps[]`. | **`steps[]` object fields:** | Field | Type | Description | |-------|------|-------------| | `provider` | string | Provider name (e.g. `hunter`) | | `action` | string | Provider action (e.g. `find_email`) | | `input_mapping` | object | Maps provider input keys to contact field tokens | | `output_field` | string | Contact field to write the result to (e.g. `contact.work_email`) | | `order` | integer | Step execution order (0-indexed) | | `is_system_provider` | boolean | `true` to use graph8's system key (charges credits); `false` to use the org's BYOK key | | `config` | object | Provider-specific flags (e.g. `{"reveal_phone_numbers": true}` for Apollo) | **`email_verification` object fields:** | Field | Type | Description | |-------|------|-------------| | `enabled` | boolean | Whether to run email verification after enrichment | | `provider` | string | Verifier to use: `zerobounce`, `hunter`, `icypeas`, or `leadmagic` | | `use_system_credentials` | boolean | Use graph8 system key (charges credits) | | `accept_catchall` | boolean | Treat catch-all addresses as valid | | `valid_statuses` | string[] | Statuses to accept (e.g. `["valid"]`) | **`credential_mode` values:** | Value | Behavior | |-------|----------| | `auto` | (Default, matches the UI) Use the org's BYOK key when configured (free); otherwise use graph8's system key for funded providers (charges credits). Unfunded providers (`lusha`, `rocketreach`) stay BYOK. | | `system` | Force graph8 system keys for funded providers (always charges credits). | | `byok` | Force the org's own keys for every non-graph8 provider. Steps silently skip when no key exists. | `graph8` is always system-funded regardless of mode. graph8 holds system keys for: `graph8`, `apollo`, `hunter`, `builtwith`, `dropcontact`, `prospeo`, `icypeas`, `zerobounce`, `leadmagic`, `emaillistverify`. **Allowed `providers[]` values:** `graph8`, `hunter`, `prospeo`, `dropcontact`, `icypeas`, `leadmagic`, `emaillistverify`, `apollo`, `lusha`, `rocketreach`. Unknown providers return 400 `unsupported_provider`. For other providers (e.g. `zerobounce` as a finder) use `steps[]`. ### Example **cURL** ```bash # By providers shortcut (recommended) curl -X POST "https://be.graph8.com/api/v1/enrichment/waterfall/configs" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "list_id": 12345, "name": "Email Pipeline", "providers": ["hunter", "prospeo"], "credential_mode": "auto", "email_verification": { "enabled": true, "provider": "zerobounce", "use_system_credentials": true, "accept_catchall": false, "valid_statuses": ["valid"] } }' # By steps (full control) curl -X POST "https://be.graph8.com/api/v1/enrichment/waterfall/configs" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "list_id": 12345, "name": "Apollo with phones", "steps": [{ "provider": "apollo", "action": "find_email", "input_mapping": {"first_name": "CONTACT_FIRST_NAME", "last_name": "CONTACT_LAST_NAME"}, "output_field": "contact.work_email", "order": 0, "is_system_provider": true, "config": {"reveal_phone_numbers": true} }] }' ``` ### Response ```json { "data": { "column_id": "waterfall_xxx", "config_id": "uuid", "name": "Email Pipeline", "list_id": 12345, "created": true, "providers": ["hunter", "prospeo"], "email_verification": { "enabled": true, "provider": "zerobounce", "use_system_credentials": true, "accept_catchall": false, "valid_statuses": ["valid"] }, "providers_detail": [ { "provider": "hunter", "is_system_provider": true, "credential_source": "system", "credential_ready": true }, { "provider": "prospeo", "is_system_provider": false, "credential_source": "byok", "credential_ready": false } ], "missing_credentials": ["prospeo"] } } ``` `missing_credentials` lists BYOK steps with no active org key — those steps will be skipped at run time. Call `GET /enrichment/providers` to see which providers need a BYOK key for your org. **Errors:** - `400` — `providers` and `steps` are mutually exclusive (pass one, not both) - `400 unsupported_provider` — `providers[]` contained a name without a default email-finder mapping --- ## Run Waterfall Enrichment `POST /enrichment/waterfall/enrich` Runs an existing waterfall pipeline against a set of records. Returns `202 Accepted` — poll `GET /enrichment/jobs/{job_id}` for the outcome. When a pipeline step is BYOK with no usable org key (or an unfunded provider like `lusha` or `rocketreach`), the 202 response carries a non-blocking `warnings` array naming those providers. The job still queues, but those steps will be skipped. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `column_id` | string | Yes | Waterfall config ID from `GET /enrichment/waterfall/configs` | | `list_id` | integer | Yes | List the records belong to | | `record_ids` | integer[] | Yes | Non-empty list of contact IDs to enrich | | `skip_existing_values` | boolean | No | Override the pipeline's `skip_existing_values` setting for this run | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/enrichment/waterfall/enrich" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "column_id": "waterfall_xxx", "list_id": 12345, "record_ids": [101, 102, 103], "skip_existing_values": true }' ``` ### Response ```json { "data": { "job_id": "abc-123-def", "status": "queued", "total": null, "completed": null, "results": null, "warnings": ["prospeo: no active org key configured — step will be skipped"] } } ``` `warnings` is a non-blocking array present when one or more pipeline steps are BYOK with no usable org key (or use an unfunded provider like `lusha` or `rocketreach`). The job still queues and runs; those steps are skipped. `warnings` is `null` when all configured steps have valid credentials. Poll `GET /enrichment/jobs/{job_id}` (optionally with `include_provider_errors=true`) to check `successful_enrichments` when the job reaches `completed`. **Errors:** - `422 waterfall_config_missing` — no active waterfall pipeline exists for the supplied `column_id`. Fix by calling `POST /enrichment/waterfall/configs` to create or restore the config. --- ## List Enrichment Providers `GET /enrichment/providers` Lists every enrichment provider available to the calling org, including its credential mode and per-action credit cost. This is the programmatic equivalent of the UI's provider picker — call it before building a pipeline to see which providers run on graph8 credits by default vs need your own API key. ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/enrichment/providers" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": { "providers": [ { "provider": "hunter", "system_funded": true, "byok_configured": false, "default_credential_source": "system", "credits_per_row": { "find_email": 2.0, "verify_email": 2.0, "domain_search": 2.0 }, "actions": ["domain_search", "find_email", "verify_email"] }, { "provider": "lusha", "system_funded": false, "byok_configured": false, "default_credential_source": "byok", "credits_per_row": { "enrich_person": 2.0, "enrich_company": 2.0 }, "actions": ["enrich_company", "enrich_person"] } ], "count": 2 } } ``` | Field | Description | |-------|-------------| | `system_funded` | graph8 holds a system key for this provider — works out of the box and charges credits | | `byok_configured` | The org has its own API key configured for this provider | | `default_credential_source` | What `credential_mode="auto"` resolves to right now (`system` or `byok`) | | `credits_per_row` | Per-action credit cost when running on graph8's system key | | `actions` | Available actions for this provider | --- ## List AI Enrichment Configs `GET /enrichment/ai/configs` Returns all AI enrichment configurations for a list. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `list_id` | integer | — | **Required.** List ID to fetch AI configs for | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/enrichment/ai/configs?list_id=12345" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": { "configs": [ { "group_id": "ai_enrich_xxx", "name": "Company research", "usecase": "web-research", "list_id": 12345, "is_global": false, "is_active": true } ], "list_id": 12345, "count": 1 } } ``` --- ## Run AI Enrichment `POST /enrichment/ai/enrich` Runs an AI enrichment config against a set of records. This endpoint is synchronous — it returns when the enrichment is complete (unlike `POST /enrichment/waterfall/enrich` which is async). ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `group_id` | string | Yes | AI enrichment config ID from `GET /enrichment/ai/configs` | | `list_id` | integer | Yes | List the records belong to | | `record_ids` | integer[] | Yes | Non-empty list of contact IDs to enrich | | `max_records` | integer | No | Maximum number of records to process in this call | | `skip_existing_values` | boolean | No | Skip records that already have a value | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/enrichment/ai/enrich" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "group_id": "ai_enrich_xxx", "list_id": 12345, "record_ids": [101, 102], "max_records": 50, "skip_existing_values": true }' ``` ### Response ```json { "data": { "job_id": "abc-456-ghi", "status": "completed", "total": 2, "completed": 2, "results": [ { "processed_records": 2, "successful_enrichments": 2, "failed_enrichments": 0 } ] } } ``` --- ## Waterfall Credit Matrix Waterfall enrichment (`POST /enrichment/enrich`) runs each contact / company through a sequence of 14 third-party providers. **Mostly, only successful provider hits charge credits.** Apollo also charges on a miss, and Icypeas does when it reports the call as debited. Each record is charged its **dearest** charged step, not the sum of every step that ran. Cached results (same record + field, within the 7-day cache TTL) also return for free. ### Per-provider hit cost | Provider | Returns | Email | Phone | Other | |---|---|---|---|---| | **Apollo** | Person or company enrich | 3 | +13 for `reveal_phone_numbers` (16 total) | +5 for `reveal_personal_emails` (8 total); company enrich 3 | | **Hunter** | Email, domain | 3 | — | Domain search 3, verify 2 | | **Prospeo** | Email, mobile | 3 | 20 (mobile) | Verify 3 | | **DropContact** | Work email, company from domain | 3 | — | Company 3 | | **Lusha** | Person or company enrich | 26 | — | Company 26 | | **Icypeas** | Email, profile URLs, people/company search | 2 | — | Profile URLs 2, find people/companies 11, verify 1 | | **RocketReach** | Email, phone, company | 37 | 37 | Company 37 | | **LeadMagic** | Email, mobile, profile, company, funding, technographics | 3 | 12 (mobile) | Personal email 12, job change 7, profile/company/funding/technographics 3, email validation 1 | | **Wiza** | Work email, phone | 3 | 8 | — | | **Clearbit** | Person or company enrich | — | — | 2 | | **BuiltWith** | Company tech stack, domain relationships | — | — | 2 per action | | **graph8 internal** | Mashup contacts + companies | — | — | 2 per row it finds (person or company lookup); nothing on a miss | Rates are `credits_per_row` on graph8's system keys. A step on your own provider key (BYOK) is not charged. `GET /enrichment/providers` returns the live per-action rates. ### Email verifier add-on cost Run after a provider returns an email; if invalid the waterfall continues to the next provider. The verify cost is **not added on top**: the record is charged its dearest step, so verification raises the record's cost only when it is dearer than the finder that ran. | Provider | Verify cost | Notes | |---|---|---| | **LeadMagic** | 1 (`email_validation`) | | | **EmailListVerify** | 1 | Dedicated verifier | | **ZeroBounce** | 2 (verify) / 3 (find_email) | The default verifier for `email_verification` and MCP `g8_enrich_contacts` | | **Icypeas** | 1 | Also a data-finding provider | | **Hunter** | 2 | | | **Prospeo** | 3 | | ### Default waterfall order **Email** (work_email / contact_email): Apollo → Hunter → Prospeo → DropContact → Lusha → Icypeas → RocketReach → LeadMagic → (optional verifier: EmailListVerify / ZeroBounce). **Direct phone**: Apollo → Prospeo → DropContact → Lusha. **Mobile phone**: Apollo → DropContact → Lusha → LeadMagic. **Company domain**: Clearbit → Apollo → Hunter. **Industry / size**: Clearbit only. Order is configurable per org via the in-app waterfall editor — `enrichment_configs` row drives the live sequence. ### When credits are (and aren't) charged | Scenario | Charged? | |---|---| | Provider returns a match | Yes — cost of that field for that provider | | Provider returns no match | No — waterfall moves on | | All providers exhausted, no match | **No charge at all** | | Cached result returned (same field, same record, within 7d TTL) | **Free** | | Provider call uses your own API key (configured in the org) | **Free** | | Record skipped because run conditions weren't met | **Free** | | Email verifier validates an email as valid | Verifier cost absorbed; no extra charge | | Email verifier rejects an email | Verifier cost charged; waterfall continues for free | ### Apollo phone-reveal flags Apollo offers two add-on flags that cost extra credits beyond the base hit: | Flag | Extra credits | |---|---| | `reveal_phone_numbers=true` | +3 to +15 (depends on number type) | | `reveal_personal_emails=true` | +1 to +7 | Both flags default to **off** to avoid surprise spend. Enable per call when you specifically need direct phone or personal email. ### Credit hold + batch charge For large enrichment jobs the system pre-holds a credit budget then charges in batches: 1. **Job start**: hold `total_records × max_per_record × 1.25` (25% safety buffer). 2. **Per record completion**: pending credits accumulate. 3. **Every 100 records**: pending → charged from the hold. 4. **Job complete**: remaining pending charged, unused hold released back to balance. Use `GET /enrichment/jobs/{job_id}` to monitor pending vs charged credits mid-run. ### Pricing examples | Scenario | Cost | |---|---| | Enrich a contact, Apollo finds work email on first hit | 3 credits | | Enrich a contact, no provider matches | 0 credits, except steps that bill on a miss (Apollo 3; Icypeas when debited) | | Enrich a contact with Apollo `reveal_phone_numbers=true` | 3 + 13 = 16 credits | | Hunter finds a work email (3), ZeroBounce verifies it (2) | 3 credits (dearest step, not 3 + 2) | | Re-enrich the same contact within 7 days | 0 credits (cache hit) | | Bulk enrich 1,000 contacts, ~70% find rate, avg 3 credits per hit | ~2,100 credits | --- # Errors > HTTP status codes and error response format Source: https://docs.graph8.com/developers/errors/ The API uses standard HTTP status codes to indicate success or failure. Error responses include a `detail` field with a human-readable message. --- ## Error Response Format ```json { "detail": "Company not found" } ``` For validation errors, the response includes field-level details: ```json { "detail": [ { "loc": ["body", "work_email"], "msg": "value is not a valid email address", "type": "value_error.email" } ] } ``` --- ## Status Codes ### Success | Code | Meaning | Used By | |------|---------|---------| | `200` | OK | GET, PATCH, DELETE | | `201` | Created | POST | ### Client Errors | Code | Meaning | Common Causes | |------|---------|---------------| | `400` | Bad Request | Missing required fields, invalid field values, no fields in update | | `401` | Unauthorized | Missing or invalid API key, expired API key, JWT token provided instead of API key. The `X-Error-Code` header (and the body's `code`) is `api_key_expired` for an expired key and `api_key_invalid` otherwise: rotate an expired key, do not retry it. | | `404` | Not Found | Resource does not exist or was deleted | | `422` | Unprocessable Entity | Request body fails validation (wrong types, invalid email format) | | `429` | Too Many Requests | Rate limit exceeded — see [Rate Limits](https://docs.graph8.com/developers/rate-limits/) | ### Server Errors | Code | Meaning | Action | |------|---------|--------| | `500` | Internal Server Error | Retry with backoff. If persistent, contact support. | --- ## Handling Errors **Python** ```python response = requests.get(f"{BASE_URL}/contacts/99999", headers=HEADERS) if response.status_code == 200: contact = response.json()["data"] elif response.status_code == 404: print("Contact not found") elif response.status_code == 401: print("Check your API key") elif response.status_code == 429: retry_after = int(response.headers.get("Retry-After", 5)) print(f"Rate limited. Retry after {retry_after}s") else: print(f"Error {response.status_code}: {response.json()}") ``` **TypeScript** ```typescript const response = await fetch(`${BASE_URL}/contacts/99999`, { headers: { Authorization: `Bearer ${API_KEY}` } }); if (response.ok) { const { data: contact } = await response.json(); } else if (response.status === 404) { console.error("Contact not found"); } else if (response.status === 401) { console.error("Check your API key"); } else if (response.status === 429) { const retryAfter = response.headers.get("Retry-After") || "5"; console.error(`Rate limited. Retry after ${retryAfter}s`); } else { const error = await response.json(); console.error(`Error ${response.status}:`, error); } ``` **cURL** ```bash # Use -w to print the HTTP status code curl -s -w "\nHTTP Status: %{http_code}\n" \ "https://be.graph8.com/api/v1/contacts/99999" \ -H "Authorization: Bearer $API_KEY" ``` --- ## Common Error Scenarios ### Authentication | Error | Cause | Fix | |-------|-------|-----| | `Missing Authorization header` | No `Authorization` header sent | Add `Authorization: Bearer ` | | `JWT token provided instead of API key` | Passed a browser session token | Use an org API key from Settings > API | | `API key not associated with an organization` | Key is malformed or revoked | Create a new key in Settings > API | ### Contacts | Error | Cause | Fix | |-------|-------|-----| | `list_id is required when creating contacts` | POST /contacts without `list_id` | Include `list_id` — every contact must belong to a list | | `Contact not found` | Contact ID doesn't exist or was deleted | Verify the contact ID | ### Companies | Error | Cause | Fix | |-------|-------|-----| | `Company not found` | Company ID doesn't exist or was deleted | Verify the company ID | | `No fields to update` | PATCH with empty body | Include at least one field to update | ### Lists | Error | Cause | Fix | |-------|-------|-----| | `Invalid list type` | POST /lists with unrecognized `type` | Use: `contacts`, `companies`, `suppressions`, `deals`, `leads` | | `List not found` | List ID doesn't exist or was deleted | Verify the list ID | --- # Fields > List, create, and write custom field definitions and values for contacts and companies Source: https://docs.graph8.com/developers/fields/ Fields are the column definitions for contacts and companies. Use these endpoints to discover available fields — both built-in and custom — and to create new custom columns, populate their values, and optionally provision enrichment pipelines that fill them automatically. --- ## List Contact Fields `GET /fields` Returns all contact field definitions, including base fields and any custom fields. Pass `list_id` to include list-specific custom fields. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `list_id` | integer | — | Include custom fields specific to this list | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/fields" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": [ { "id": 1, "title": "Job Level", "name": "job_level", "data_type": "text", "is_global": true } ] } ``` --- ## List Company Fields `GET /fields/companies` Returns all company field definitions, including base fields and any custom fields. Same response shape as `GET /fields`. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `list_id` | integer | — | Include custom fields specific to this list | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/fields/companies" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": [ { "id": 1, "title": "Company Name", "name": "COMPANY_NAME", "data_type": "text", "is_global": true } ] } ``` --- ## Create a Custom Field `POST /fields` → 201 Creates a single custom column for contacts or companies. Optionally provisions a waterfall enrichment pipeline alongside the column in the same request. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `title` | string | **R** | Display name for the column | | `data_type` | string | O | Column data type. Default `"text"` | | `list_id` | integer | O | Scope to a specific list. `null` or omitted = global. **Required when `enrichment` is set.** | | `entity` | string | O | `"contacts"` or `"companies"`. Default `"contacts"` | | `enrichment` | object | O | When present, provisions a waterfall pipeline alongside the column. See enrichment fields below. | #### `enrichment` object fields | Field | Type | Required | Description | |-------|------|----------|-------------| | `type` | string | O | Pipeline kind. Default `"waterfall"` (only supported kind today) | | `name` | string | O | Pipeline display name. Default `" Enrichment"` | | `field_to_enrich` | string | O | Column slug to target. Defaults to the new column's slug | | `skip_existing_values` | boolean | O | Skip contacts that already have a value. Default `true` | | `providers` | array of strings | O | Shorthand provider list. Mutually exclusive with `steps`. Allowed: `graph8`, `hunter`, `prospeo`, `dropcontact`, `icypeas`, `leadmagic`, `emaillistverify`, `apollo`, `lusha`, `rocketreach` | | `steps` | array of objects | O | Full step DTOs. Mutually exclusive with `providers` | | `email_verification` | object | O | Post-enrichment validation config. See below. | #### `email_verification` object fields | Field | Type | Description | |-------|------|-------------| | `enabled` | boolean | Enable email verification | | `provider` | string | `zerobounce`, `hunter`, `icypeas`, or `leadmagic` | | `use_system_credentials` | boolean | Use graph8's system credentials for the provider | | `accept_catchall` | boolean | Treat catch-all addresses as valid | | `valid_statuses` | array of strings | Which statuses to accept (e.g. `["valid"]`) | > **Note: Enrichment constraints** > - `list_id` is **required** when `enrichment` is set — waterfall pipelines are list-scoped. > - `providers` and `steps` are mutually exclusive. Pass one, neither, or `email_verification` alone. > - Unknown providers return `400` (`unsupported_provider`). > - Without `enrichment`, the column exists but has no enrichment plan — `POST /enrichment/waterfall/enrich` returns `422` (`waterfall_config_missing`) until you call `POST /enrichment/waterfall/configs`. ### Example **cURL** ```bash # Simple column (no enrichment) curl -X POST "https://be.graph8.com/api/v1/fields" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "title": "Job Level", "data_type": "text", "list_id": 12345, "entity": "contacts" }' # Column + enrichment pipeline curl -X POST "https://be.graph8.com/api/v1/fields" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "title": "Job Level", "data_type": "text", "list_id": 12345, "entity": "contacts", "enrichment": { "type": "waterfall", "name": "Job Level Enrichment", "field_to_enrich": "job_level", "skip_existing_values": true, "providers": ["hunter", "prospeo"], "email_verification": { "enabled": true, "provider": "zerobounce", "use_system_credentials": true, "accept_catchall": false, "valid_statuses": ["valid"] } } }' ``` ### Response (no enrichment) ```json { "data": { "id": 1, "title": "Job Level", "data_type": "text", "name": "job_level", "list_id": 12345, "is_global": false, "enrichment": null } } ``` ### Response (with enrichment) ```json { "data": { "id": 1, "title": "Job Level", "data_type": "text", "name": "job_level", "list_id": 12345, "is_global": false, "enrichment": { "column_id": "job_level", "config_id": "uuid", "name": "Job Level Enrichment", "type": "waterfall", "providers": ["hunter", "prospeo"], "email_verification": { "enabled": true, "provider": "zerobounce" } } } } ``` > **Tip: Using `enrichment.column_id`** > The `enrichment.column_id` in the response is the column's slug (e.g. `"job_level"`), not a `waterfall_<uuid>` handle. Pass it directly to `POST /enrichment/waterfall/enrich`. --- ## Create Multiple Custom Fields `POST /fields/batch` → 201 Creates many custom columns in a single request. All columns are created in a single DB transaction with a **single view rebuild** — far more efficient than calling `POST /fields` in a loop when setting up a list with many columns. `entity` and `list_id` are shared across the entire batch. `title`, `data_type`, and `enrichment` are per-column. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `entity` | string | O | `"contacts"` or `"companies"`. Applies to all columns. Default `"contacts"` | | `list_id` | integer | O | Shared list scope. **Required if any field has `enrichment` set.** | | `fields` | array of objects | **R** | 1–100 column definitions. Each item may have `title` (R), `data_type` (O), and `enrichment` (O, same shape as `POST /fields`) | > **Caution: Batch validation and atomicity** > - **Validation is fail-fast**: an invalid `data_type`, duplicate titles (case-insensitive) within the batch, an unsupported `enrichment.type`, `providers`+`steps` together, or a missing `list_id` when any field uses `enrichment` → **400, and nothing is created**. > - **Column creation is atomic** — all columns commit together or none do. > - **Per-column enrichment is best-effort**: if a column commits but its companion enrichment config fails, that column still exists and its `results[]` entry reports `ok: false` with an `error` message. The rest of the batch is unaffected. > - Maximum **100** columns per batch. ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/fields/batch" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "entity": "contacts", "list_id": 12345, "fields": [ { "title": "Persona" }, { "title": "Account Tier", "data_type": "text" }, { "title": "Work Email", "enrichment": { "type": "waterfall", "providers": ["graph8", "hunter", "prospeo"], "email_verification": { "enabled": true, "provider": "zerobounce" } } } ] }' ``` ### Response ```json { "data": { "entity": "contacts", "list_id": 12345, "total_requested": 3, "total_created": 3, "results": [ { "title": "Persona", "id": 10, "name": "udo_persona_1700000000", "ok": true, "error": null, "enrichment": null }, { "title": "Account Tier", "id": 11, "name": "udo_account_tier_1700000000", "ok": true, "error": null, "enrichment": null }, { "title": "Work Email", "id": 12, "name": "udo_work_email_1700000000", "ok": true, "error": null, "enrichment": { "column_id": "udo_work_email_1700000000", "config_id": "uuid", "name": "Work Email Enrichment", "type": "waterfall", "providers": ["graph8", "hunter", "prospeo"], "email_verification": { "enabled": true, "provider": "zerobounce" } } } ] } } ``` --- ## Write a Custom Field Value `PATCH /fields/{column_id}/values` Sets (or clears) the value of a custom column for a single contact or company record. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `column_id` | integer | The numeric ID of the custom field (from `GET /fields`) | ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `record_id` | integer | **R** | Contact or company ID | | `value` | string | O | New value. Pass `null` or omit to clear the field | | `entity` | string | O | `"contacts"` or `"companies"`. Default `"contacts"` | ### Example **cURL** ```bash # Set a value curl -X PATCH "https://be.graph8.com/api/v1/fields/1/values" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "record_id": 101, "value": "Director", "entity": "contacts" }' # Clear a value curl -X PATCH "https://be.graph8.com/api/v1/fields/1/values" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "record_id": 101, "value": null, "entity": "contacts" }' ``` ### Response ```json { "data": { "column_id": 1, "record_id": 101, "updated": true } } ``` --- ## Read a Custom Field Value `GET /fields/{column_id}/values` Reads the value of a custom column for a single contact or company record. > **Tip: Reading many values at once** > To pull custom values for a page of records in one call, pass `include_custom_fields=true` to `GET /contacts`, `GET /contacts/{id}`, `GET /companies`, or `GET /companies/{id}` instead of calling this endpoint per row. Each record then carries a `custom_fields` object keyed by column slug (only set values appear; unset rows return `{}`). This endpoint is best for reading a single known column on a single record. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `column_id` | integer | The numeric ID of the custom field (from `GET /fields`) | ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `record_id` | integer | — | **R** — Contact ID (`entity=contacts`) or company ID (`entity=companies`) | | `entity` | string | `contacts` | `contacts` or `companies` | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/fields/1/values?record_id=101&entity=contacts" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": { "column_id": 1, "record_id": 101, "entity": "contacts", "name": "udo_job_level_1712000000", "value": "Director" } } ``` `value` is `null` when the column has not been set for this record. Errors: `400` for an invalid `entity`; `404` when the column (for that entity) or the record does not exist. --- # Functions > Transform, filter, and enrich events with JavaScript before they reach destinations Source: https://docs.graph8.com/developers/functions/ Functions are JavaScript code that runs on every event in transit - between your app and destinations. Use them to filter unwanted events, transform data shapes, or enrich events with external data. ## What Functions Can Do | Capability | Example | |-----------|---------| | **Filter** | Drop bot traffic, test events, internal users | | **Transform** | Rename fields, change event structure, normalize data | | **Enrich** | Add geolocation, company data, or third-party API lookups | | **Route** | Send different events to different warehouse tables | ## Writing a Function Functions are JavaScript that receives an event and returns a modified event (or `"drop"` to filter it out). ### Basic Transform ```javascript // Rename event properties to match your warehouse schema function transform(event, ctx) { if (event.type === 'track') { event.properties.source = event.properties.utm_source; delete event.properties.utm_source; } return event; } ``` ### Filter Events ```javascript // Drop events from internal users function transform(event, ctx) { const internalDomains = ['graph8.com', 'yourcompany.com']; const email = event.traits?.email || event.properties?.email || ''; if (internalDomains.some(d => email.endsWith(`@${d}`))) { return 'drop'; // magic string — event is discarded } return event; } ``` ### Enrich with External API ```javascript // Add company data from a third-party API async function transform(event, ctx) { const email = event.traits?.email; if (!email) return event; const domain = email.split('@')[1]; const resp = await ctx.fetch(`https://api.example.com/company/${domain}`); if (resp.ok) { const company = await resp.json(); event.traits.company_name = company.name; event.traits.company_size = company.employee_count; event.traits.industry = company.industry; } return event; } ``` ### Strip PII ```javascript // Remove sensitive fields before sending to warehouse function transform(event, ctx) { delete event.traits?.phone; delete event.traits?.address; delete event.properties?.credit_card; // Hash email for analytics (keep original for CRM) if (event.traits?.email) { event.traits.email_hash = ctx.crypto.sha256(event.traits.email); } return event; } ``` ### Route to Custom Tables ```javascript // Send different events to different warehouse tables function transform(event, ctx) { if (event.event === 'purchase') { event.JITSU_TABLE_NAME = 'purchases'; } else if (event.event === 'page') { event.JITSU_TABLE_NAME = 'pageviews'; } return event; } ``` ## Available APIs Functions run in a sandboxed environment with these APIs: | API | Usage | Description | |-----|-------|-------------| | `ctx.fetch(url, options)` | HTTP requests | Call external APIs (enrichment, validation, webhooks) | | `ctx.store` | Persistent storage | Key-value store that persists between function calls | | `ctx.log` | Logging | Write to Live Events log for debugging | | `ctx.geo` | Geolocation | IP-based location data (city, country, region) | | `ctx.ua` | User agent | Parsed browser and device information | | `ctx.headers` | HTTP headers | Original request headers | | `ctx.getWarehouse` | Warehouse queries | Query your data warehouse from within a function | | `ctx.source` | Source metadata | Information about the originating stream | | `ctx.destination` | Destination metadata | Target destination details | ## Managing Functions 1. Go to **Connections > Functions** in your graph8 dashboard 2. Click **+ Create Function** 3. Write your JavaScript in the code editor 4. Test with sample events using the built-in debugger 5. Attach to a stream-destination link Functions include a built-in editor with syntax highlighting and a debugger that lets you test with sample event payloads. ## Return Values | Return | What Happens | |--------|-------------| | `event` (modified) | Modified event is sent to destination | | New object | Replaces the original event entirely | | `"drop"` | Event is discarded (not sent to any destination) | | Array of objects | One event becomes multiple events | | Nothing | Original event is sent unchanged | > **Tip: Pipeline Order** > Functions run after the [SDK](https://docs.graph8.com/developers/sdk/) sends events and before [Destinations](https://docs.graph8.com/developers/destinations/) receive them. Use [Live Events](https://docs.graph8.com/developers/live-events/) to see function execution results in real-time. --- # Getting Started > Choose how to integrate with graph8 - SDK, MCP, CLI, or REST API Source: https://docs.graph8.com/developers/getting-started/ Welcome to the graph8 developer platform. Four ways to integrate - pick the one that fits how you build. > **Tip: Choose your plan first** > Before you wire up code, decide how you want to pay. **PAYG** ($0.05/credit, 1,000 free credits, no card) is best for evaluators and low-volume scripts. **Platform** ($499/mo) gives you 75,000 included AI credits *plus* unlimited graph8 index lookups within a 50 rps cap. See **[Pricing](https://docs.graph8.com/developers/pricing/)** for the full comparison. ### Install & Embed **JavaScript SDK** 25+ modules - tracking, visitor intelligence, AI copilot, webchat, calendar, enrichment. TypeScript. React hooks. `npm install @graph8/sdk` - [Full reference →](https://docs.graph8.com/developers/sdk/) **MCP Server** 43 tools for AI agents. Claude.ai, Claude Desktop, Cursor, VS Code, ChatGPT. Just paste the URL. `https://be.graph8.com/mcp/` - [Setup guide →](https://docs.graph8.com/developers/mcp/) ### Script & Automate **CLI** 20+ terminal commands for contacts, prospecting, enrichment, sequences, campaigns. JSON output, scriptable. `pip install g8-mcp-server` - [View commands →](https://docs.graph8.com/developers/cli/) **REST API** 300+ endpoints. Contacts, companies, lists, enrichment, search, sequences, webhooks. Any language. `https://be.graph8.com/api/v1` - [API overview →](https://docs.graph8.com/developers/) --- ## Which should I use? | I'm building... | Use | Why | |-----------------|-----|-----| | A browser app (React, Next.js, Vue) | [JavaScript SDK](https://docs.graph8.com/developers/sdk/) | Typed, SSR-safe, React hooks, widgets | | A server script or backend | [REST API](https://docs.graph8.com/developers/) or [CLI](https://docs.graph8.com/developers/cli/) | Direct HTTP or terminal commands | | AI-powered workflows | [MCP Server](https://docs.graph8.com/developers/mcp/) | 43 tools, natural language | | A quick prototype | [CLI](https://docs.graph8.com/developers/cli/) | Fastest to test, JSON output | ## Authentication | Method | For | Where to get it | |--------|-----|----------------| | **Write key** | SDK (client-side) | Settings > MCP & API > tracking snippet | | **API key** | REST API, CLI, SDK (server-side) | Settings > MCP & API > API tab | | **OAuth** | MCP Server | Automatic - paste URL, sign in | See [Authentication](https://docs.graph8.com/developers/authentication/) for details. --- ## Common Workflows ### Find and reach new leads ``` g8.enrich.search(filters) // or POST /api/v1/search/contacts → g8.enrich.person(email) // or POST /api/v1/enrichment/lookup/person → g8.sequences.add(config) // or POST /api/v1/sequences/{id}/contacts ``` 1. **Search** the 700M+ contact / 100M+ company database with filters ([SDK](https://docs.graph8.com/developers/sdk/#enrichment) | [API](https://docs.graph8.com/developers/search/) | [CLI](https://docs.graph8.com/developers/cli/#g8-find-contacts)) 2. **Enrich** contacts to fill missing emails/phones ([SDK](https://docs.graph8.com/developers/sdk/#enrichment) | [API](https://docs.graph8.com/developers/enrichment/) | [CLI](https://docs.graph8.com/developers/cli/#g8-enrich)) 3. **Sequence** to enroll in outreach ([SDK](https://docs.graph8.com/developers/sdk/#sequences) | [API](https://docs.graph8.com/developers/sequences/) | [CLI](https://docs.graph8.com/developers/cli/#g8-add-to-sequence)) ### Embed graph8 in your product ``` g8.init({ writeKey }) g8.visitors.identify() // Know who's visiting g8.copilot.open() // AI assistant g8.calendar.show() // Booking widget g8.forms.lookup(email) // Smart forms ``` All via the [JavaScript SDK](https://docs.graph8.com/developers/sdk/) with a write key. No API key needed. ### Automate with AI Paste `https://be.graph8.com/mcp/` into [Claude.ai](https://docs.graph8.com/developers/mcp/), [Claude Desktop](https://docs.graph8.com/developers/mcp/), [Cursor](https://docs.graph8.com/developers/cursor-setup/), or [ChatGPT](https://docs.graph8.com/developers/mcp/). Then ask in natural language: > "Find 10 VPs of Sales at SaaS companies with 50-200 employees, save to a list, and enrich the ones missing emails." See [MCP Server](https://docs.graph8.com/developers/mcp/) and [What You Can Ask](https://docs.graph8.com/developers/what-you-can-ask/). ### Route data to your warehouse ``` g8.track() → Functions (transform/filter) → BigQuery / Snowflake / Postgres ``` Every event automatically routes to all connected [destinations](https://docs.graph8.com/developers/destinations/). Add BigQuery in the dashboard - every `g8.track()` call lands there. No extra code. - [Data Pipeline overview](https://docs.graph8.com/developers/data-pipeline/) - how events flow - [Destinations](https://docs.graph8.com/developers/destinations/) - BigQuery, Snowflake, ClickHouse, Redshift, Postgres, S3, GA4, HubSpot, Amplitude - [Functions](https://docs.graph8.com/developers/functions/) - JavaScript transforms (filter, enrich, PII strip) - [Live Events](https://docs.graph8.com/developers/live-events/) - real-time event debugger --- # GTM Campaigns > Create and manage AI Studio campaigns, documents, and sequence step catalogs Source: https://docs.graph8.com/developers/gtm-campaigns/ GTM Campaigns are graph8's AI-Studio campaign objects: a brief, a sequence (ordered step references), and a step catalog (the actual step definitions). Each campaign owns a set of generated documents (campaign brief, sequence doc, ad copy, landing page copy, etc.). These endpoints are the **org-scoped** flavor used by Studio, MCP, and Claude Desktop GTM mode. The same surface is also exposed under `/repos/{repo_id}/campaigns` for spine-installed CLI users; see the [CLI](https://docs.graph8.com/developers/cli/) reference for that variant. | Endpoint | Method | Description | |----------|--------|-------------| | `/campaigns` | GET | List campaigns | | `/campaigns` | POST | Create a campaign | | `/campaigns/ideas` | GET | List saved campaign ideas | | `/campaigns/{campaign_id}` | GET | Get a campaign with completed documents | | `/campaigns/{campaign_id}` | PATCH | Update campaign content fields | | `/campaigns/{campaign_id}/documents` | GET | List documents for a campaign | | `/campaigns/{campaign_id}/documents` | POST | Create a document | | `/campaigns/{campaign_id}/documents/{document_id}` | GET | Get full document content | | `/campaigns/{campaign_id}/documents/{document_id}` | PUT | Update document content | | `/campaigns/{campaign_id}/documents/{document_id}` | DELETE | Delete a document | | `/campaigns/{campaign_id}/sequence` | GET | Get sequence + step catalog docs | | `/campaigns/{campaign_id}/sequence` | PUT | Replace sequence step ordering | | `/campaigns/{campaign_id}/sequence/steps` | POST | Append a step | | `/campaigns/{campaign_id}/sequence/steps/{step_id}` | PUT | Update a step definition | | `/campaigns/{campaign_id}/sequence/steps/{step_id}` | DELETE | Remove a step | | `/campaigns/{campaign_id}/ad-images` | GET | List generated ad images | | `/campaigns/{campaign_id}/ad-images/{image_id}/hide` | POST | Mark an ad image hidden | | `/campaigns/{campaign_id}/launch` | POST | Launch campaign (real outreach) | | `/campaigns/{campaign_id}/metrics` | GET | Get campaign metrics | | `/campaigns/{campaign_id}/full` | GET | Full snapshot (campaign + audience + docs + sequence) | | `/campaigns/{campaign_id}/full` | PATCH | Deep bulk update in a single transaction | | `/campaigns/{campaign_id}/audience` | PUT | Attach or detach audience list | For machine-readable schemas see the [interactive API docs](https://be.graph8.com/api/v1/docs). --- > **Note: Ids in these examples** > The request examples use shell variables (`$CAMPAIGN_ID`, `$DOCUMENT_ID` and `$IMAGE_ID`) for ids. Set them from the `id` fields returned when you create or list campaigns, documents and ad images. Ids shown in sample responses (`cmp_abc`, `doc_001`) are illustrative only and do not exist. ## List Campaigns `GET /campaigns` Returns all non-archived campaigns for the org, paginated. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `page` | integer | 1 | Page number | | `limit` | integer | 50 | 1-200 | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/campaigns?limit=10" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": [ { "id": "cmp_abc", "name": "Q2 Outbound - VP Marketing", "slug": "q2-outbound-vp-marketing", "status": "draft", "category": "Outbound", "goal": "Book discovery calls", "target_persona": "VP Marketing at mid-market SaaS", "created_at": "2026-04-01T10:00:00Z" } ] } ``` --- ## Create Campaign `POST /campaigns` Create a new campaign. Slug is auto-generated from `name`. Campaign field constraints: `name ≤255`, `category ≤100`, `target_persona ≤200`, `goal ≤255`. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | string | **Yes** | Campaign name (max 255 chars) | | `category` | string | No (default `"Outbound"`) | Campaign category (max 100 chars) | | `brief` | string | No | Campaign brief (uncapped) | | `core_concept` | string | No | One-line concept | | `primary_hook` | string | No | Primary hook text | | `target_persona` | string | No | Target persona (max 200 chars) | | `goal` | string | No | Campaign goal (max 255 chars) | | `audience_list_id` | string | No | Attach a list at create time | | `target_channels` | string[] | No (default `["email"]`) | Channels to target | | `secondary_hooks` | string[] | No | Additional hook lines | | `auto_generate_documents` | boolean | No (default `false`) | When `true`, triggers full Studio bootstrap: creates paired campaign ideas row, metadata, sequence and step catalog stubs, and dispatches Celery doc generation. Sets `status="copy_in_progress"`. The MCP tool always sets this `true`; the launcher requires it. | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/campaigns" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Q2 Outbound - VP Marketing", "category": "Outbound", "primary_hook": "Cut SDR ramp time in half", "goal": "Book discovery calls", "audience_list_id": "12345", "target_channels": ["email"], "auto_generate_documents": true }' ``` ### Response ```json { "data": { "id": "cmp_abc", "name": "Q2 Outbound - VP Marketing", "slug": "q2-outbound-vp-marketing", "status": "copy_in_progress", "category": "Outbound", "audience_list_id": "12345", "campaign_idea_id": "idea_001", "target_channels": ["email"], "secondary_hooks": [], "generation_status": "in_progress", "total_documents": 15 } } ``` `generation_status` is `"in_progress"` when `auto_generate_documents` was `true`, `"skipped"` when it was `false`, or `"failed_to_dispatch"` if the Celery dispatch failed. `status` is `"copy_in_progress"` when auto-generation was requested, otherwise `"draft"`. --- ## List Campaign Ideas `GET /campaigns/ideas` Returns saved/AI-generated campaign ideas (pre-conversion). Use these to seed `POST /campaigns`. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `favorited` | boolean | - | Filter to favorited ideas only | | `limit` | integer | 50 | 1-200 | ### Response ```json { "data": [ { "id": "idea_001", "rank": 1, "campaign_name": "Sales Velocity Diagnostic", "campaign_category": "Outbound", "campaign_focus": "Mid-market RevOps", "core_concept": "...", "why_this_works": "...", "primary_hook": "Find your stuck deals in 5 minutes", "secondary_hooks": ["..."], "target_channels": ["email", "linkedin"], "required_assets": ["landing page", "demo video"], "execution_complexity": "medium", "expected_impact": "high", "proof_points": ["..."], "framework_inspiration": "Challenger", "source": "studio_generated", "favorited": true, "converted_to_campaign": false, "converted_campaign_id": null, "created_at": "2026-04-01T10:00:00Z", "updated_at": "2026-04-01T10:00:00Z" } ] } ``` --- ## Get Campaign `GET /campaigns/{campaign_id}` Returns campaign details plus a thumbnail list of completed documents. ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/campaigns/$CAMPAIGN_ID" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": { "id": "cmp_abc", "name": "Q2 Outbound - VP Marketing", "slug": "q2-outbound-vp-marketing", "status": "draft", "category": "Outbound", "goal": "Book discovery calls", "target_persona": "VP Marketing at mid-market SaaS", "brief": "...", "core_concept": "...", "primary_hook": "Cut SDR ramp time in half", "channels": ["email", "linkedin"], "documents": [ {"id": "doc_001", "name": "Campaign Brief", "type": "campaign_brief"}, {"id": "doc_002", "name": "Sequence", "type": "sequence"} ], "created_at": "2026-04-01T10:00:00Z", "updated_at": "2026-04-10T14:30:00Z" } } ``` ### Errors | Status | Meaning | |--------|---------| | `404` | Campaign not found | --- ## Update Campaign `PATCH /campaigns/{campaign_id}` Update content fields on a campaign. ### Request Body | Field | Type | Description | |-------|------|-------------| | `name` | string | Campaign name | | `brief` | string | Campaign brief | | `core_concept` | string | Core concept | | `primary_hook` | string | Primary hook text | | `target_persona` | string | Target persona | | `category` | string | Campaign category | | `goal` | string | Campaign goal | ### Errors | Status | Meaning | |--------|---------| | `400` | No fields provided | | `404` | Campaign not found | --- ## List Campaign Documents `GET /campaigns/{campaign_id}/documents` Returns metadata for every document associated with the campaign (no content payload). ### Response ```json { "data": { "campaign_id": "cmp_abc", "documents": [ { "id": "doc_001", "campaign_id": "cmp_abc", "display_name": "Campaign Brief", "name": "Campaign Brief", "file_type": "campaign_brief", "type": "campaign_brief", "folder_path": "/brief", "status": "completed", "version": 2, "current_version": 2, "created_at": "2026-04-01T10:00:00Z", "updated_at": "2026-04-05T11:00:00Z" } ], "count": 1 } } ``` --- ## Get Campaign Document `GET /campaigns/{campaign_id}/documents/{document_id}` Returns full document content. ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/campaigns/$CAMPAIGN_ID/documents/$DOCUMENT_ID" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": { "id": "doc_001", "campaign_id": "cmp_abc", "name": "Campaign Brief", "type": "campaign_brief", "status": "completed", "version": 2, "content": "# Campaign Brief\n\nGoal: ...", "created_at": "2026-04-01T10:00:00Z", "updated_at": "2026-04-05T11:00:00Z" } } ``` --- ## Create Campaign Document `POST /campaigns/{campaign_id}/documents` Create a new document attached to the campaign. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `file_type` | string | **Yes** | Document type (e.g. `campaign_brief`, `sequence`, etc.) | | `display_name` | string | **Yes** | Human-readable name | | `content` | string | No | Document content (default `""`) | | `folder_path` | string | No | Optional folder path (e.g. `"/briefs"`) | | `status` | string | No (default `"draft"`) | `"draft"` or `"completed"` | | `structured_data` | object | No | Optional structured payload stored in metadata | ### Response `201 Created` Returns the same document shape as `GET .../documents/{document_id}`. --- ## Update Campaign Document `PUT /campaigns/{campaign_id}/documents/{document_id}` Update content. Bumps `version` and `current_version` by 1, marks `completed=true` and `generating=false` in metadata. ### Request Body | Field | Type | Description | |-------|------|-------------| | `content` | string | New content (defaults to existing if empty) | | `status` | string | Optional status override | | `structured_data` | object | Optional structured payload stored in metadata | ### Errors | Status | Meaning | |--------|---------| | `404` | Document not found (or its parent campaign is archived) | --- ## Delete Campaign Document `DELETE /campaigns/{campaign_id}/documents/{document_id}` Hard delete. Returns `404` if the document or its parent campaign is missing. ### Response ```json { "data": { "status": "deleted", "document_id": "doc_001" } } ``` --- ## Get Sequence `GET /campaigns/{campaign_id}/sequence` Returns both the **sequence** document (ordered list of step references) and the **step catalog** document (step definitions keyed by `step_id`). ### Response ```json { "data": { "campaign_id": "cmp_abc", "sequence": { "steps": [ {"step_id": "step_1", "day": 0, "condition": null, "stop_on_reply": true}, {"step_id": "step_2", "day": 3, "condition": "no_reply", "stop_on_reply": true} ] }, "step_catalog": { "steps": { "step_1": {"name": "Intro email", "channel": "email", "mode": "send", ...}, "step_2": {"name": "LinkedIn follow-up", "channel": "linkedin", "mode": "send", ...} } }, "sequence_doc_id": "doc_seq_001", "catalog_doc_id": "doc_cat_001" } } ``` ### Errors | Status | Meaning | |--------|---------| | `404` | Campaign not found | --- ## Update Sequence `PUT /campaigns/{campaign_id}/sequence` Replace the ordered list of step references. Each `step_id` must exist in the step catalog. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `steps` | object[] | **Yes** | Ordered step references | Each `step` item: | Field | Type | Description | |-------|------|-------------| | `step_id` | string | Step ID (must exist in catalog) | | `day` | integer | Day offset from sequence start | | `condition` | string | Optional condition (`no_reply`, etc.) | | `stop_on_reply` | boolean | Default `true` | ### Errors | Status | Meaning | |--------|---------| | `400` | `step_id` not in catalog | | `404` | Sequence document not found | --- ## Create Sequence Step `POST /campaigns/{campaign_id}/sequence/steps` Create a new step **and** append it to the sequence in one call. The endpoint mints a fresh `step_id` (`step_N`). ### Channel → Mode Matrix The `channel` field determines which `mode` values are valid: | Channel | Valid modes | |---------|-------------| | `email` | `email` | | `phone` | `call`, `voicemail` | | `sms` | `sms` | | `social` | `connect_note`, `dm`, `comment`, `reply` | | `voice_ai` | `call` | ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | string | **Yes** | Step name | | `channel` | string | **Yes** | Channel type — see Channel → Mode Matrix above | | `mode` | string | **Yes** | Mode for the chosen channel — see Channel → Mode Matrix above | | `day` | integer | **Yes** | Day offset for sequence placement | | `platform` | string | No | Sub-channel platform (e.g. `"linkedin"`) | | `angle_id` | string | No (default `"angle_1"`) | Messaging angle reference | | `cta_type` | string | No (default `"soft_ask"`) | CTA type | | `personalization_level` | string | No (default `"medium"`) | Level of personalization | | `constraints` | object | No | Constraint dict | | `condition` | string | No | Sequence condition | | `stop_on_reply` | boolean | No (default `true`) | Stop the contact's enrollment on reply | ### Response `201 Created` ```json { "data": { "status": "created", "step_id": "step_3", "step": { "step_id": "step_3", "name": "Final follow-up", "channel": "email", "mode": "send", "platform": null, "angle_id": "angle_1", "cta_type": "soft_ask", "personalization_level": "medium", "constraints": {} }, "day": 7 } } ``` --- ## Update Sequence Step `PUT /campaigns/{campaign_id}/sequence/steps/{step_id}` Update a step definition in the catalog and (optionally) its placement in the sequence. All fields are optional - send only what you want to change. ### Request Body | Field | Type | Description | |-------|------|-------------| | `name` | string | Step name | | `channel` | string | Channel | | `mode` | string | Mode | | `platform` | string | Platform | | `angle_id` | string | Messaging angle | | `cta_type` | string | CTA type | | `personalization_level` | string | Level | | `constraints` | object | Constraint dict | | `do_not_send_rules` | string[] | Rules that should suppress sending | | `day` | integer | Sequence placement (updates the sequence doc, not the catalog) | | `condition` | string | Sequence placement condition | | `stop_on_reply` | boolean | Sequence placement override | `day`, `condition`, and `stop_on_reply` modify the sequence document; everything else modifies the catalog entry. ### Errors | Status | Meaning | |--------|---------| | `404` | Step or step catalog not found | --- ## Delete Sequence Step `DELETE /campaigns/{campaign_id}/sequence/steps/{step_id}` Remove a step from the catalog and any references in the sequence. ### Response ```json { "data": { "status": "deleted", "step_id": "step_3" } } ``` ### Errors | Status | Meaning | |--------|---------| | `404` | Step not found | --- ## Launch Campaign `POST /campaigns/{campaign_id}/launch` > **Caution:** > This begins **real outreach** to real contacts on linked channels. The request is irreversible once sequences start sending. Restricted API keys must include the `campaigns:launch` scope. This route enforces that scope even while other Developer API scopes are in soak mode. Legacy API keys created without scope restrictions remain unrestricted. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `validate` | boolean | `true` | Deprecated compatibility parameter. Readiness is always validated; `false` returns `400` before a sequence is created. | ### Request Body All fields are optional. An empty body is valid and keeps the 0.9.6 auto-pick contract. | Field | Type | Description | |-------|------|-------------| | `sender_mailbox_ids` | integer[] | Explicit sender mailbox IDs from `GET /mailboxes`. Each must be `connection_status="active"` and non-archived. Missing IDs return `400`. | | `disable_auto_pick` | boolean | Deprecated compatibility field. `false` is ignored; `true` returns `400` because a consequential launch cannot proceed without an executable sender. | | `textual_agent_name` | string | Since 0.9.7. Agent name from `GET /voice/dialer/agents` (the unified Agents+Twins picker). Stamped onto the sequence. Used for email and social steps. | | `voice_agent_name` | string | Since 0.9.7. Same picker; for phone and SMS steps. Set equal to `textual_agent_name` for the common "one agent, all channels" case. | | `schedule_id` | string | Since 0.9.7. UUID from `GET /schedules`. Stamped onto the sequence (send window + timezone). When omitted the sequencer falls back to its org-default send window. | | `appointment_id` | integer | Since 0.9.7. Integer from `GET /event-types`. Stamped onto the sequence so the booking-link rewrite kicks in during send. | **Sender selection precedence:** 1. If `sender_mailbox_ids` is provided → attach exactly those mailboxes. 2. Otherwise the bridge auto-picks the first active email mailbox (`ORDER BY id ASC`). 3. If no supported active sender exists, the request fails before sequence creation. Launch readiness is mandatory and requires a non-empty audience, completed campaign copy, at least one complete step, and every channel required by those steps. The one-step API launch currently supports email execution. For social, phone, SMS, or other multichannel work, create a Sequencer draft and configure the required channels before Go Live. ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/campaigns/$CAMPAIGN_ID/launch" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "sender_mailbox_ids": [42, 17], "textual_agent_name": "my-agent", "voice_agent_name": "my-agent", "schedule_id": "sched_uuid", "appointment_id": 1 }' ``` ### Response ```json { "data": { "status": "live", "campaign_id": "cmp_abc", "sequence_id": "seq_001", "sequence_name": "Q2 Outbound - VP Marketing", "steps_created": 3, "link_id": "link_001", "warnings": [], "channels_attached": [ { "mailbox_id": 42, "email": "sender@acme.com", "channel_type": "email", "auto_picked": false } ], "agent_assigned": { "textual_agent_name": "my-agent", "voice_agent_name": "my-agent" }, "schedule_id": "sched_uuid", "appointment_id": 1, "execution": { "status": "live", "contacts_total": 120, "orchestrator_version": "v2-sync" }, "message": "Sequence started successfully (V2)" } } ``` `channels_attached` lists the sender mailboxes the bridge attached. `auto_picked: true` means the bridge selected a mailbox automatically. `status` is truthful: `live` only after synchronous initialization completes, or `scheduling` with job and polling fields inside `execution` for background initialization. If the draft is created but activation fails, the endpoint returns `502`. `detail.status` is `draft_created_activation_failed` when the sequence was confirmed back in Draft, or `activation_state_uncertain` when the final state could not be proven. Follow the returned recovery instruction; do not repeat an uncertain launch. --- ## Get Ad Images `GET /campaigns/{campaign_id}/ad-images` Returns generated ad images for the campaign. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `campaign_id` | string | Campaign ID | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/campaigns/$CAMPAIGN_ID/ad-images" \ -H "Authorization: Bearer $API_KEY" ``` --- ## Hide Ad Image `POST /campaigns/{campaign_id}/ad-images/{image_id}/hide` Marks an ad image as hidden. No request body required. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `campaign_id` | string | Campaign ID | | `image_id` | string | Ad image ID | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/campaigns/$CAMPAIGN_ID/ad-images/$IMAGE_ID/hide" \ -H "Authorization: Bearer $API_KEY" ``` --- ## Get Campaign Metrics `GET /campaigns/{campaign_id}/metrics` Returns engagement metrics for the campaign over a rolling time window. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `campaign_id` | string | Campaign ID | ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `days` | integer | `30` | Rolling window in days (1–365) | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/campaigns/$CAMPAIGN_ID/metrics?days=30" \ -H "Authorization: Bearer $API_KEY" ``` --- ## Get Campaign Full Snapshot `GET /campaigns/{campaign_id}/full` Returns a full snapshot of the campaign including audience, all documents, and the sequence. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `campaign_id` | string | Campaign ID | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/campaigns/$CAMPAIGN_ID/full" \ -H "Authorization: Bearer $API_KEY" ``` --- ## Bulk Update Campaign `PATCH /campaigns/{campaign_id}/full` Deep bulk update executed in a single transaction. Updates campaign fields, patches or creates documents, reorders sequence steps, and links or unlinks existing sequences. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `campaign_id` | string | Campaign ID | ### Request Body All fields are optional. | Field | Type | Description | |-------|------|-------------| | `name` | string | Campaign name (max 255 chars) | | `brief` | string | Campaign brief | | `core_concept` | string | Core concept | | `primary_hook` | string | Primary hook | | `target_persona` | string | Target persona (max 200 chars) | | `category` | string | Campaign category (max 100 chars) | | `goal` | string | Campaign goal (max 255 chars) | | `audience_list_id` | string | Provide a list ID (or `""`) to set the audience. Use `detach_audience` to clear it. | | `detach_audience` | boolean | When `true`, explicitly sets `audience_list_id` to null | | `documents` | object[] | List of document patch/create objects (see below) | | `sequence_steps` | object[] | Replaces the full sequence ordering | | `linked_sequence_ids` | string[] | Link existing sequences (sets `status="linked"`) | | `unlink_sequence_ids` | string[] | Unlink sequences. Only deletes `status="linked"` rows; audit rows are untouched. | #### `documents` item fields | Field | Type | Description | |-------|------|-------------| | `id` | string | When present, updates an existing document. When absent, creates a new document (then `file_type` and `display_name` are required). | | `file_type` | string | Document type (required for create) | | `display_name` | string | Human-readable name (required for create) | | `folder_path` | string | Optional folder path | | `status` | string | `"draft"` or `"completed"` | | `content` | string | Document content | | `structured_data` | object | Optional structured payload | #### `sequence_steps` item fields | Field | Type | Description | |-------|------|-------------| | `step_id` | string | Step ID (must exist in catalog) | | `day` | integer | Day offset from sequence start | | `condition` | string | Optional condition string | | `stop_on_reply` | boolean | Stop enrollment on reply | ### Example **cURL** ```bash curl -X PATCH "https://be.graph8.com/api/v1/campaigns/$CAMPAIGN_ID/full" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Q3 Outbound - VP Engineering", "brief": "Updated brief text", "audience_list_id": "12345", "documents": [ { "id": "doc_uuid", "status": "completed", "content": "Updated brief content" }, { "file_type": "landing_page", "display_name": "Landing Page v2", "content": "New landing page copy", "structured_data": {} } ], "sequence_steps": [ { "step_id": "step_1", "day": 1, "condition": null, "stop_on_reply": true }, { "step_id": "step_2", "day": 3, "condition": null, "stop_on_reply": true } ], "linked_sequence_ids": ["seq_existing_001"], "unlink_sequence_ids": ["seq_old_002"] }' ``` --- ## Attach or Detach Audience `PUT /campaigns/{campaign_id}/audience` Attach an audience list to the campaign, or detach the current one by passing `null`. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `campaign_id` | string | Campaign ID | ### Request Body | Field | Type | Description | |-------|------|-------------| | `list_id` | string | Audience list ID to attach. Pass `null` to detach the current audience. | ### Example **cURL** ```bash # Attach an audience curl -X PUT "https://be.graph8.com/api/v1/campaigns/$CAMPAIGN_ID/audience" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "list_id": "12345" }' # Detach the audience curl -X PUT "https://be.graph8.com/api/v1/campaigns/$CAMPAIGN_ID/audience" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "list_id": null }' ``` --- # GTM Context > Read and update global context documents, personas, ICPs, mailboxes, intelligence data, research reports, and activities Source: https://docs.graph8.com/developers/gtm-context/ These endpoints expose the org's GTM context — the same data Studio uses to author campaigns. Use them from AI agents (MCP), CLI tools, or integrations to ground campaign generation in your specific brand, ICPs, and recent activity. | Endpoint | Method | Description | |----------|--------|-------------| | `/global-context/documents` | GET | List global context documents (44 per org — brand brief, value props, etc.) | | `/global-context/documents/{doc_id}` | PATCH | Update a Context-bucket document | | `/personas` | GET | List campaign-builder personas | | `/icps` | GET | List Ideal Customer Profiles | | `/mailboxes` | GET | List sending mailboxes | | `/mailboxes/{mailbox_id}/warmup` | GET | Live warmup analytics for a mailbox | | `/intelligence-data` | GET | List machine-generated intelligence documents (read-only) | | `/research-reports` | GET | List AI research reports | | `/research-reports/{report_id}` | PATCH | Update a research report | | `/activities` | GET | List recent CRM activities | For machine-readable schemas see the [interactive API docs](https://be.graph8.com/api/v1/docs). --- ## List Global Context Documents `GET /global-context/documents` Returns the org's global context documents. Each org has 44 of these total: 21 context docs (brand brief, value props, personas, ICPs, messaging house, etc.), 16 intelligence docs (website scrape, competitor data, keywords), 6 research reports, and 1 campaign intelligence brief. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `category` | string | — | Filter by context category | | `include_content` | boolean | `true` | Return the document `content` body. Pass `false` for a lighter metadata-only listing that omits the `content` key. | | `limit` | integer | `50` | 1–200 | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/global-context/documents?limit=10" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": [ { "id": "ctx_001", "display_name": "Brand Brief", "file_type": "brand_brief", "category": "brand", "folder_path": "/context/brand", "status": "completed", "priority": 100, "version": 3, "content": "# Acme brand brief\n\nMission: ...", "created_at": "2026-03-01T10:00:00Z", "updated_at": "2026-04-10T14:30:00Z" } ] } ``` Documents are sorted by `priority DESC, updated_at DESC`. --- ## Update Global Context Document `PATCH /global-context/documents/{doc_id}` Update a Context-bucket document. The request is partial — send at least one of `content` or `status`. Each save records a `DocumentVersion` snapshot (same as the in-app Studio editor, so history is consistent across UI, API, and MCP). ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `doc_id` | string | Document ID | ### Request Body | Field | Type | Description | |-------|------|-------------| | `content` | string | Updated document body (stored at `meta_data.content`) | | `status` | string | Document status, e.g. `"completed"` or `"draft"` | ### Example **cURL** ```bash curl -X PATCH "https://be.graph8.com/api/v1/global-context/documents/ctx_001" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "content": "Updated brand brief body...", "status": "completed" }' ``` ### Response ```json { "data": { "id": "ctx_001", "display_name": "Brand Brief", "file_type": "brand_brief", "category": "brand", "status": "completed", "version_number": 4, "content": "Updated brand brief body...", "updated_at": "2026-04-11T09:00:00Z" } } ``` ### Errors | Status | Meaning | |--------|---------| | `400` | Neither `content` nor `status` was provided | | `404` | Document not found in your org | --- ## List Personas `GET /personas` Returns campaign-builder personas (target buyer profiles with messaging signals). ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `status` | string | - | Filter by persona status | | `limit` | integer | 50 | 1-200 | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/personas" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": [ { "id": "per_001", "title": "VP of Marketing at mid-market SaaS", "priority": 1, "confidence": "high", "confidence_score": 0.9, "why_target": "Owns demand gen budget...", "key_signals": ["hiring SDRs", "expanding into NA"], "expected_receptivity": "warm", "campaign_approach": "Lead with peer proof", "recommended_goal": "Book discovery call", "status": "approved", "pinned": true, "source": "studio_generated", "created_at": "2026-03-15T10:00:00Z", "updated_at": "2026-04-01T14:30:00Z" } ] } ``` Sorted: pinned first, then by `priority ASC, updated_at DESC`. --- ## List ICPs `GET /icps` Returns Ideal Customer Profiles (firmographic + signal definitions used to find target accounts). ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `status` | string | - | Filter by ICP status | | `limit` | integer | 50 | 1-200 | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/icps" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": [ { "id": "icp_001", "name": "Mid-market SaaS - North America", "description": "Series B-D SaaS companies, 100-1000 FTE, US/CA HQ", "firmographics": {"employee_min": 100, "employee_max": 1000, "country": ["US", "CA"]}, "tech_stack": {"required": ["Salesforce"]}, "buying_signals": ["hiring SDRs", "raised funding"], "fit_score": 0.85, "opportunity_score": 0.7, "readiness_score": 0.8, "total_score": 0.78, "status": "approved", "pinned": false, "priority": 2, "created_at": "2026-03-15T10:00:00Z", "updated_at": "2026-04-01T14:30:00Z" } ] } ``` --- ## List Mailboxes `GET /mailboxes` Returns sending mailboxes connected to the org, including warmup status from the configured provider. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `limit` | integer | `100` | 1–500 | | `include_archived` | boolean | `false` | Include archived mailboxes | | `connection_status` | string | — | Filter by connection status (e.g. `"active"`) | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/mailboxes?limit=50" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": [ { "id": "mbx_001", "email": "rep@org.com", "display_name": "Rep Name", "provider": "gmail", "source": "oauth", "connection_status": "active", "is_archived": false, "shared_with_team": false, "team_id": null, "team_name": null, "daily_limit": 50, "created_by": "user_x", "created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-04-10T14:30:00Z", "warmup_provider": "smartlead", "warmup_status": "active", "smartlead_warmup_enabled": true } ] } ``` --- ## Get Mailbox Warmup `GET /mailboxes/{mailbox_id}/warmup` Live provider fetch for warmup analytics and settings for a specific mailbox. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `mailbox_id` | string | Mailbox ID | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/mailboxes/mbx_001/warmup" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "success": true, "warmup_enabled": true, "provider": "smartlead", "analytics": {}, "warmup_settings": {} } ``` --- ## List Intelligence Data `GET /intelligence-data` Returns machine-generated intelligence documents for the org. This bucket is intentionally read-only — it is machine-generated and has no manual-edit path. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `data_type` | string | — | Filter by type: `website_scrape`, `company_enrichment`, `competitor_discovery`, `company_keywords`, `product_inventory`, `organic_keywords`, `paid_keywords`, or others | | `include_content` | boolean | `false` | Include the full `data` payload in each row | | `limit` | integer | `50` | 1–200 | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/intelligence-data?data_type=website_scrape" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": [ { "id": "int_001", "website_url": "https://acme.com", "data_type": "website_scrape", "status": "completed", "data": {}, "collected_at": "2026-03-01T08:00:00Z", "created_at": "2026-03-01T08:05:00Z", "updated_at": "2026-03-01T08:05:00Z" } ] } ``` --- ## List Research Reports `GET /research-reports` Returns AI-generated research reports for the org (buyer psychology, competitive teardown, GTM channel analysis, etc.). ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `report_type` | string | — | Filter by type: `buyer_psychology`, `competitive_teardown`, `gtm_channel`, `industry_analyst`, `review_sentiment`, or `voice_of_customer` | | `include_content` | boolean | `false` | Include full `content` and `markdown_content` in each row | | `limit` | integer | `50` | 1–200 | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/research-reports?report_type=buyer_psychology" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": [ { "id": "rpt_001", "website_url": "https://acme.com", "report_type": "buyer_psychology", "report_number": 1, "status": "completed", "content": {}, "markdown_content": "# Buyer Psychology Report\n\n...", "created_at": "2026-03-05T10:00:00Z", "updated_at": "2026-04-01T14:30:00Z" } ] } ``` --- ## Update Research Report `PATCH /research-reports/{report_id}` Update a Research-bucket report. The request is partial — send at least one of `markdown_content` or `status`. Editing `markdown_content` records a `DocumentVersion` snapshot and re-indexes the report to RAG so search stays fresh. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `report_id` | string | Research report ID | ### Request Body | Field | Type | Description | |-------|------|-------------| | `markdown_content` | string | Updated report body — triggers RAG re-index | | `status` | string | Report status, e.g. `"approved"` or `"completed"` | > **Note:** > A report already in `completed` or `approved` status can only move between those two states; demotion is silently ignored. ### Example **cURL** ```bash curl -X PATCH "https://be.graph8.com/api/v1/research-reports/rpt_001" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "markdown_content": "# Updated Buyer Psychology Report\n\n...", "status": "approved" }' ``` ### Response ```json { "data": { "id": "rpt_001", "report_type": "buyer_psychology", "status": "approved", "version_number": 2, "markdown_content": "# Updated Buyer Psychology Report\n\n...", "updated_at": "2026-04-12T10:00:00Z" } } ``` `version_number` is `null` when only `status` changed (no content snapshot is recorded for status-only updates). ### Errors | Status | Meaning | |--------|---------| | `400` | Neither `markdown_content` nor `status` was provided | | `404` | Report not found in your org | --- ## List Activities `GET /activities` Returns recent CRM activities (calls, meetings, emails, notes) across the org. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `activity_type` | string | - | Filter by type | | `contact_id` | string | - | Filter to one contact | | `company_id` | string | - | Filter to one company | | `deal_id` | string | - | Filter to one deal | | `limit` | integer | 50 | 1-200 | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/activities?activity_type=meeting&limit=20" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": [ { "id": "act_001", "activity_type": "meeting", "activity_subtype": "discovery_call", "subject": "Discovery call with Acme", "description": "Walked through current outbound stack...", "company_id": "comp_42", "deal_id": "deal_7", "contact_id": "cont_101", "owner_id": "user_5", "owner_name": "Jane Rep", "activity_date": "2026-04-10T15:00:00Z", "duration_minutes": 30, "status": "completed", "outcome": "qualified", "next_steps": "Send pricing options by Friday", "extra_data": {}, "source": "calendar_sync", "external_id": "gcal_evt_xyz" } ] } ``` Sorted by `activity_date DESC`. --- # GTM Knowledge Base > Search and list org-scoped knowledge base documents used by GTM campaigns and AI agents Source: https://docs.graph8.com/developers/gtm-knowledge-base/ The GTM Knowledge Base stores structured documents that power your campaigns and AI agents. These endpoints let you search across KB content and list all available documents in your org's knowledge base. --- ## Search Knowledge Base `POST /kb/search` Performs a semantic search over your org's knowledge base and returns the most relevant document chunks ranked by score. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `query` | string | **Yes** | The search query string | | `limit` | integer | No | Maximum number of results to return. Range: 1–50. Default: `10` | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/kb/search" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "query": "pricing", "limit": 10 }' ``` ### Response ```json { "data": [ { "doc_path": "...", "section": "...", "title": "...", "content": "<500 chars>", "score": 0.87 } ] } ``` | Field | Type | Description | |-------|------|-------------| | `doc_path` | string | Path identifier of the source document | | `section` | string | Section within the document where the chunk was found | | `title` | string | Title of the document or section | | `content` | string | Matched content excerpt, up to 500 characters | | `score` | float | Relevance score for this result | --- ## List Knowledge Base Documents `GET /kb` Returns all documents in your org's knowledge base, including metadata about each document's structure and freshness. ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/kb" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": [ { "doc_path": "...", "section": "...", "title": "...", "chunks": 3, "last_updated": "..." } ] } ``` | Field | Type | Description | |-------|------|-------------| | `doc_path` | string | Path identifier of the document | | `section` | string | Section label within the document | | `title` | string | Human-readable document title | | `chunks` | integer | Number of indexed chunks in this document | | `last_updated` | string | ISO 8601 timestamp of the most recent update | --- # Idempotency > Safely retry create requests without duplicating data using Idempotency-Key Source: https://docs.graph8.com/developers/idempotency/ Network errors and timeouts are unavoidable — but retrying a `POST` shouldn't create the same record twice. Send an **`Idempotency-Key`** header and a safe retry of the same request returns the **first response** instead of creating a duplicate. This is the Stripe idempotency pattern: the first response (status + body) is cached for **24 hours** keyed to your organization and the key you chose; any retry with the same key replays that response. > **Note: Opt-in & backward-compatible** > The header is **optional**. Omit it and the endpoint behaves exactly as before. Idempotency only activates when you send a key. ## How it works 1. Generate a unique key per logical operation — a UUID v4 is ideal. 2. Send it as the `Idempotency-Key` header on your `POST`. 3. If the request succeeds, the response is cached for 24h. 4. Any retry with the **same key** returns the cached response, flagged with an `Idempotent-Replay: true` response header — no second record is created. **cURL** ```bash curl -X POST https://be.graph8.com/api/v1/contacts \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 5f9b8c1e-2a4d-4e7f-9c3b-1d2e3f4a5b6c" \ -d '{"email": "ada@example.com", "list_id": 1234}' # Retry the exact same request (same key) -> same response body, # with header: Idempotent-Replay: true ``` **Python** ```python import uuid import httpx key = str(uuid.uuid4()) # one key per logical create headers = { "Authorization": f"Bearer {API_KEY}", "Idempotency-Key": key, } payload = {"email": "ada@example.com", "list_id": 1234} # Safe to retry on timeout — the second call replays the first response for attempt in range(3): try: r = httpx.post("https://be.graph8.com/api/v1/contacts", json=payload, headers=headers) if r.headers.get("Idempotent-Replay") == "true": print("replayed first response, no duplicate created") break except httpx.TransportError: continue ``` **TypeScript** ```ts const key = crypto.randomUUID(); async function createContact() { return fetch("https://be.graph8.com/api/v1/contacts", { method: "POST", headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": key, // reuse the same key when retrying }, body: JSON.stringify({ email: "ada@example.com", list_id: 1234 }), }); } ``` ## Rules & limits | Rule | Detail | |------|--------| | Header name | `Idempotency-Key` | | Max length | 255 characters (a malformed/oversized key returns `400`) | | Cache window | 24 hours per key, scoped to your organization | | Replay marker | Replayed responses carry `Idempotent-Replay: true` | | Recommended value | A UUID v4, unique per logical operation | ## Supported endpoints Idempotency is rolling out across `POST` create endpoints, starting with: - `POST /api/v1/contacts` — create contacts More creators gain `Idempotency-Key` support each release. Sending the header to an endpoint that doesn't yet support it is harmless — it's simply ignored. > **Caution: Concurrency** > Idempotency protects **sequential retries** (the common case: a timeout followed by a retry). Two requests with the same key fired **simultaneously**, before the first response is cached, may both execute. Serialize retries of the same key rather than firing them in parallel. ## Best practices - **One key per logical operation** — not per process, not per day. Reuse the *same* key only when retrying the *same* request. - **Store the key** alongside the operation you're performing so a retry after a crash can reuse it. - **Don't reuse a key for a different payload** — the cached response is returned regardless of the new body, so a different payload under the same key won't take effect. --- # Inbox > Multi-channel inbox: list threads, generate AI drafts, send replies via email, SMS, or LinkedIn Source: https://docs.graph8.com/developers/inbox/ The Inbox API exposes graph8's unified inbox across **email**, **SMS**, and **LinkedIn** (HeyReach) in a single thread shape. Use it to build replies and assignments into your own surface, or to power AI agents that triage inbound messages. The `channel` query parameter or request field always uses the developer-facing values: | Channel value | Internal channel | |---------------|------------------| | `email` | email | | `sms` | sms | | `linkedin` | heyreach | Threads on the LinkedIn channel return `channel: "linkedin"` in responses. | Endpoint | Method | Description | |----------|--------|-------------| | `/inbox` | GET | List threads across channels | | `/inbox/{reply_id}` | GET | Get a single thread | | `/inbox/{reply_id}/assign` | POST | Assign a user to a thread | | `/inbox/{reply_id}/tag` | POST | Add tags to a thread | | `/inbox/{reply_id}/draft` | GET | Generate an AI draft (charges credits) | | `/inbox/{reply_id}/send` | POST | Send a reply on a channel | For machine-readable schemas see the [interactive API docs](https://be.graph8.com/api/v1/docs). > **Caution: Use a real thread id** > Every `{reply_id}` below is the `id` of a thread returned by [`GET /inbox`](#list-inbox-threads). Thread ids are opaque strings (a Gmail thread id, an email Message-ID, and so on), so there is no format to construct one from: always read it from the list. The examples store it in `$THREAD_ID` first. The ids in the sample responses on this page are illustrative only; sending one returns `422`. > > ```bash > THREAD_ID=$(curl -s "https://be.graph8.com/api/v1/inbox?channel=email&page_size=1" \ > -H "Authorization: Bearer $API_KEY" | jq -r '.data[0].id') > ``` --- ## List Inbox Threads `GET /inbox` Returns paginated threads across all channels (or a single channel via filter). ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `channel` | string | - | `email`, `sms`, or `linkedin`. Omit for all | | `sequence_id` | string | - | Filter by sequence (campaign) ID | | `status` | string | - | Filter by thread status | | `assignee` | string | - | Filter to threads assigned to this email | | `tag` | string | - | Filter by tag ID | | `exclude_tag_ids` | string[] | - | Array of tag IDs to exclude from results. Threads that carry any of the listed tags are hidden. Pass the parameter multiple times (or as a comma-separated list) to exclude several tags at once. | | `page` | integer | 1 | Page number (>= 1) | | `page_size` | integer | 50 | 1-100 | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/inbox?channel=email&page_size=20" \ -H "Authorization: Bearer $API_KEY" ``` **Python** ```python response = requests.get( f"{BASE_URL}/inbox", headers=HEADERS, params={"channel": "email", "page_size": 20}, ) threads = response.json()["data"] ``` ### Response ```json { "data": [ { "id": "<thread id: pass this as {reply_id}>", "channel": "email", "subject": "Re: Q2 outbound", "contact": {"email": "jane@acme.com", "name": "Jane Smith", "company": "Acme Corp"}, "messages": [ { "message_id": "msg_001", "from_address": "jane@acme.com", "to_addresses": ["rep@graph8.com"], "content": "Sounds great - let's chat Tuesday.", "responder": "OTHER", "date": "2026-04-15T14:30:00Z", "is_draft": false } ], "status": "open", "tags": [{"id": "tag_1", "name": "interested"}], "assignees": [{"email": "rep@graph8.com", "name": "Sales Rep"}], "created_at": "2026-04-15T14:30:00Z", "updated_at": "2026-04-15T14:30:00Z" } ], "pagination": {"page": 1, "limit": 20, "total": 1, "has_next": false} } ``` The `responder` field on a message is `USER` (a graph8 user replied), `AI` (graph8 sent an AI-drafted reply), or `OTHER` (the contact). `status` is `open`, `responded`, or `ai_responded`. > **Note: Sentinel tag IDs** > graph8 reserves a set of well-known UUIDs for internal system tags (Warmup, Bounced, Archived, Deleted). These are automatically excluded from the default Focused view via `exclude_tag_ids`. When building your own integrations, pass the org-specific tag IDs returned by your tags endpoint. If your org has not overridden them, the platform falls back to sentinel UUIDs of the form `00000000-0000-0000-0000-00000000000N`. --- ## Get Thread `GET /inbox/{reply_id}` Returns one thread by ID. Pass the matching `channel` query parameter so the right backing store is queried. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `channel` | string | `email` | `email`, `sms`, or `linkedin` | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/inbox/$THREAD_ID?channel=email" \ -H "Authorization: Bearer $API_KEY" ``` ### Errors | Status | Meaning | |--------|---------| | `400` | Invalid `reply_id` (null/undefined/empty rejected early) | | `422` | `reply_id` is an example id from this page, not a real thread | --- ## Assign Reply `POST /inbox/{reply_id}/assign` Assign a user (by email) to the thread. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `assignee_email` | string | **Yes** | Email of the user to assign | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/inbox/$THREAD_ID/assign?channel=email" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"assignee_email": "rep@graph8.com"}' ``` ### Response ```json { "data": { "assigned": true, "assignee": "rep@graph8.com", "count": 1 } } ``` --- ## Tag Reply `POST /inbox/{reply_id}/tag` Attach one or more tags to a thread. Tags must already exist in your workspace - this endpoint does not create them. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `channel` | string | `email` | `email`, `sms`, or `linkedin` | ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `tag_ids` | string[] | **Yes** | Tag IDs to associate | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/inbox/$THREAD_ID/tag?channel=email" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"tag_ids": ["tag_interested", "tag_q2"]}' ``` ### Response ```json { "data": { "tagged": true, "tag_count": 2 } } ``` --- ## Generate AI Draft `GET /inbox/{reply_id}/draft` Generate an AI-drafted reply for the thread. **This charges credits.** A `402 Payment Required` is returned when the org has insufficient credits. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `channel` | string | `email` | Channel of the source thread | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/inbox/$THREAD_ID/draft?channel=email" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": { "content": "<p>Tuesday at 10am works great...</p>", "plain_content": "Tuesday at 10am works great...", "credits_charged": 1 } } ``` ### Errors | Status | Meaning | |--------|---------| | `402` | Insufficient credits for draft generation | | `404` | No thread with this `reply_id` on this `channel`; no credits are charged | | `422` | `reply_id` is an example id from this page, not a real thread | --- ## Send Reply `POST /inbox/{reply_id}/send` Send a reply on the chosen channel. The required fields differ per channel. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `body` | string | **Yes** | Message body (HTML for email; plain text for SMS/LinkedIn) | | `channel` | string | **Yes** | `email`, `sms`, or `linkedin` | | `subject` | string | Email only | Email subject | | `to` | string | Email/SMS | Recipient address or phone number | | `from_address` | string | No | Email mailbox, Twilio number, or LinkedIn account ID | For LinkedIn, `from_address` must be a numeric LinkedIn account ID. `$THREAD_ID`, `$SMS_THREAD_ID` and `$LINKEDIN_THREAD_ID` are thread ids read from `GET /inbox` with `channel=email`, `sms` and `linkedin` respectively (see [Use a real thread id](#list-inbox-threads) above). ### Examples **Email** ```bash curl -X POST "https://be.graph8.com/api/v1/inbox/$THREAD_ID/send" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "channel": "email", "body": "<p>Tuesday at 10am works.</p>", "subject": "Re: Q2 outbound", "to": "jane@acme.com", "from_address": "rep@graph8.com" }' ``` **SMS** ```bash curl -X POST "https://be.graph8.com/api/v1/inbox/$SMS_THREAD_ID/send" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "channel": "sms", "body": "Confirmed for Tuesday 10am.", "to": "+15555550100", "from_address": "+15555550199" }' ``` **LinkedIn** ```bash curl -X POST "https://be.graph8.com/api/v1/inbox/$LINKEDIN_THREAD_ID/send" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "channel": "linkedin", "body": "Thanks for connecting!", "from_address": "12345" }' ``` ### Response ```json { "data": { "message_id": "msg_outbound_001", "status": "sent", "channel": "email" } } ``` ### Errors | Status | Meaning | |--------|---------| | `400` | Missing `to` or `from_address` for the channel; LinkedIn `from_address` not numeric; unsupported channel | | `403` | Your key may not send as `from_address` | | `422` | `reply_id` is an example id from this page, not a real thread; or the provider rejected a recipient address as malformed | --- # Intent & Signals > REST reference for graph8's intent tracking — keywords, page visitors, account-level intent search, signal scoring Source: https://docs.graph8.com/developers/intent-signals/ graph8's intent surface is two layers: 1. **`/api/v1/intent/*`** — full account-level intent tracking. Keyword groups, page visitor records, contact-level engagement, account-level intent search. 2. **`/api/v1/public/signals/company`** — a single endpoint that returns an aggregate intent score for one domain. Write-key safe (works from browser code). Use the public endpoint for quick lookups in a browser; use the server-side intent surface for prospecting / SDR workflows. > **Tip: Credits** > Most read endpoints are free. Intent signal **processing** (when a new visitor record arrives) consumes 1 credit per signal — that happens automatically when tracking events flow in; you don't trigger it from these endpoints. > **Note: Auth** > Every call needs `Authorization: Bearer $G8_API_KEY`. The `X-Org-Id` is bound to the key automatically. [Get a key in 30 seconds →](https://docs.graph8.com/developers/authentication/) ## Get Intent Stats `GET /intent/stats` Returns ClickHouse index stats for your org. ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/intent/stats" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": { "doc_count": 482000, "size_bytes": 1234567890 } } ``` --- ## List Pages by Domain `POST /intent/pages-by-domain` Returns tracked pages for a given domain. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `domain` | string | **Yes** | Domain to look up (3–253 chars, e.g. `"cognism.com"`) | | `limit` | integer | No | Max results (1–500, default `200`) | | `offset` | integer | No | Pagination offset (≥0, default `0`) | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/intent/pages-by-domain" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "domain": "cognism.com", "limit": 200, "offset": 0 }' ``` ### Response ```json { "data": { "domain": "cognism.com", "total_hits": 482, "took_ms": 120, "pages": [ { "url": "https://cognism.com/pricing", "domain_root": "cognism.com", "title": "Pricing", "summary": "...", "commercial_score": 0.8, "mention_score": null, "page_type": "pricing", "audience": "b2b" } ] } } ``` --- ## Search Pages `POST /intent/pages/search` Full-text, semantic, or hybrid search over tracked pages. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `query` | string | **Yes** | Search query (1–500 chars, e.g. `"site:cognism.com pricing"`) | | `limit` | integer | No | Max results (1–500) | | `offset` | integer | No | Pagination offset (≥0) | | `semantic_ratio` | float | No | Search mode — `0` = text, `1` = semantic, between = hybrid | | `filters` | object | No | Additional filters dict | | `sort` | array | No | Sort fields list | | `include_visitors` | boolean | No | Whether to include visitor data | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/intent/pages/search" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "query": "site:cognism.com pricing", "limit": 50, "offset": 0, "semantic_ratio": 0.0, "include_visitors": true }' ``` ### Response ```json { "data": { "query": "site:cognism.com pricing", "mode": "text", "total_hits": 0, "pages": [], "took_ms": 0 } } ``` --- ## Get Page Visitors `POST /intent/pages/visitors` Returns raw visitor objects for a single page URL. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `url` | string | **Yes** | Page URL without scheme or www (1–1024 chars, e.g. `"cognism.com/pricing"`) | | `limit` | integer | No | Max results (1–500, default `50`) | | `offset` | integer | No | Pagination offset (≥0) | | `mode` | string | No | Resolution mode — `"quick"` (~500 ms) or `"full"` (~2–3 s) | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/intent/pages/visitors" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "cognism.com/pricing", "limit": 50, "offset": 0, "mode": "quick" }' ``` Returns raw visitor objects from the resolver (HEM, profile fields, source tier). --- ## Get Page Contacts `POST /intent/pages/contacts` Returns deduplicated contacts across a set of page URLs. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `urls` | array | **Yes** | List of page URLs (1–200 items) | | `limit` | integer | No | Max results (1–500, default `100`) | | `offset` | integer | No | Pagination offset (≥0) | | `mode` | string | No | Resolution mode — `"quick"` or `"full"` | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/intent/pages/contacts" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "urls": ["cognism.com/pricing", "cognism.com/demo"], "limit": 100, "offset": 0, "mode": "quick" }' ``` --- ## Get Visitor Counts `POST /intent/pages/visitor-counts` Returns aggregate visitor counts for up to 15,000 URLs in a single call. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `urls` | array | **Yes** | List of page URLs (1–15,000 items) | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/intent/pages/visitor-counts" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "urls": ["cognism.com/pricing", "cognism.com/docs"] }' ``` ### Response ```json { "counts": { "cognism.com/pricing": 42, "cognism.com/docs": 13 } } ``` > **Note:** > This endpoint does not use the standard `{ "data": ... }` envelope — it returns the `counts` object directly. --- ## List Keywords `POST /intent/keywords/list` Returns a paginated list of tracked keyword groups. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `keyword_contains` | string | No | Case-insensitive substring filter on keyword name | | `page` | integer | No | Page number (default `1`) | | `limit` | integer | No | Items per page (1–500) | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/intent/keywords/list" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "keyword_contains": "cognism", "page": 1, "limit": 100 }' ``` ### Response Returns a list of keyword rows, each with `total_resolved_contacts` and `total_resolved_companies`. --- ## Add Keywords `POST /intent/keywords/add` Bulk-adds plain keyword phrases — the API twin of the web UI's Buyer Intent > Keywords "Add keywords" screen (same internal handler, identical behavior). Saves each phrase as a plain compete-tracking row; no search seeding. Set per-keyword filters afterwards with [Update Keyword Filters](#update-keyword-filters). ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `keywords` | string[] | Yes | 1–50 keyword phrases, each ≤500 chars. Trimmed and stored lowercased. | | `signal_type` | string | No | `intent` (default), `jobs`, or `job_changes` | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/intent/keywords/add" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "keywords": ["ai sales agents", "buyer intent data"] }' ``` ### Response ```json { "data": { "created": [ { "id": "…", "keyword": "ai sales agents", "signal_type": "intent", "created_at": "…" } ], "skipped": [ { "keyword": "buyer intent data", "reason": "already_exists" } ], "created_count": 1, "skipped_count": 1 } } ``` Empty / whitespace-only entries and in-request duplicates are skipped with reasons `empty` / `duplicate_in_request`; keywords already tracked for the org + `signal_type` are skipped with `already_exists` (idempotent across requests — existing rows are never replaced). For `signal_type: "intent"`, each newly created keyword seeds its URL set asynchronously in the background. An N-keyword batch counts as N request-rate units. --- ## Create Keyword Group from Domain `POST /intent/keywords/create-from-domain` Seeds a keyword group from every public page on a domain. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `domain` | string | **Yes** | Domain to seed from (3–253 chars) | | `page_limit` | integer | No | Max pages to index (1–500) | | `contact_limit` | integer | No | Max contacts to resolve (1–500) | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/intent/keywords/create-from-domain" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "domain": "cognism.com", "page_limit": 200, "contact_limit": 200 }' ``` ### Response ```json { "data": { "keyword_id": "kw_abc123", "keyword": "cognism.com", "pages_seeded": 197, "contacts_seeded": 184, "companies_seeded": 42, "off_domain_rejected": 3, "message": "Keyword group created successfully" } } ``` --- ## Get URLs for Keyword `POST /intent/keywords/{keyword_id}/urls` Returns a paginated list of URLs associated with a keyword group, with optional field-level filtering. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `keyword_id` | string | Keyword group ID | ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `page` | integer | No | Page number (default `1`) | | `limit` | integer | No | Items per page (default `100`) | | `conditions` | array | No | Filter conditions — each item is `` `{ "field": "...", "operator": "...", "value": "..." }` `` | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/intent/keywords/kw_abc123/urls" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "page": 1, "limit": 100, "conditions": [ { "field": "page_type", "operator": "eq", "value": "pricing" } ] }' ``` Returns a paginated URL list. --- ## Get Contacts for Keyword `POST /intent/keywords/{keyword_id}/contacts` Returns resolved contacts for a keyword group. Accepts the same body as the URLs endpoint. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `keyword_id` | string | Keyword group ID | ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `page` | integer | No | Page number (default `1`) | | `limit` | integer | No | Items per page (default `100`) | | `conditions` | array | No | Filter conditions — each item is `` `{ "field": "...", "operator": "...", "value": "..." }` `` | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/intent/keywords/kw_abc123/contacts" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "page": 1, "limit": 100 }' ``` --- ## Get Companies for Keyword `POST /intent/keywords/{keyword_id}/companies` Returns resolved companies for a keyword group. Accepts the same body as the URLs endpoint. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `keyword_id` | string | Keyword group ID | ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `page` | integer | No | Page number (default `1`) | | `limit` | integer | No | Items per page (default `100`) | | `conditions` | array | No | Filter conditions — each item is `` `{ "field": "...", "operator": "...", "value": "..." }` `` | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/intent/keywords/kw_abc123/companies" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "page": 1, "limit": 100 }' ``` --- ## Update Keyword Filters `POST /intent/keywords/{keyword_id}/filters` Replaces the search filters on a keyword group. All fields inside `search_filters` are optional; omitted fields retain their current value. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `keyword_id` | string | Keyword group ID | ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `search_filters` | object | **Yes** | Full replacement filter object (see sub-fields below) | #### `search_filters` fields | Field | Type | Description | |-------|------|-------------| | `audience` | string | Target audience, e.g. `"b2b"` | | `exclude_page_types` | array | Page type slugs to exclude, e.g. `["news"]` | | `min_commercial_intent_level` | string | Minimum commercial intent threshold | | `max_urls_per_domain` | integer | Cap on URLs indexed per domain | | `llm_triage` | object | LLM triage config (or `null` to disable) | | `exclude_domains` | array | List of domains to exclude | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/intent/keywords/kw_abc123/filters" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "search_filters": { "audience": "b2b", "exclude_page_types": ["news"], "min_commercial_intent_level": "medium", "max_urls_per_domain": 50, "llm_triage": null, "exclude_domains": [] } }' ``` ### Response ```json { "status": "success", "keyword_id": "kw_abc123" } ``` > **Note:** > This endpoint does not use the standard `{ "data": ... }` envelope — it returns `status` and `keyword_id` directly. --- ## Credit costs | Operation | Credits | |---|---| | Read endpoints (keywords, pages, visitors, stats) | Free | | Intent signal processing (background, per inbound event) | 1 credit per 10 events | | Keyword creation / deletion | Free | The 1-per-10-events rate is part of the platform-level event-stream pricing — see [Pricing](https://docs.graph8.com/developers/pricing/). ## Errors | Status | Cause | Fix | |---|---|---| | `401` | Missing or invalid `Authorization: Bearer` header | [Get a key](https://docs.graph8.com/developers/authentication/) | | `402` | Out of credits | Top up in **Settings → Billing**, or switch to Platform | | `404` | Resource ID doesn't exist | List first to verify the ID | | `422` | Validation error in request body | Inspect `error.message` + `error.field` in the response | | `429` | Rate limit (50 rps per org) | Backoff per `Retry-After` header. See [Rate Limits](https://docs.graph8.com/developers/rate-limits/) | | `5xx` | graph8 error | Retry with exponential backoff (5s → 30s → 120s) | The full error envelope shape: `{ "error": { "code": "...", "message": "...", "field": "...", "request_id": "..." } }`. Include the `request_id` in any support ticket. See [Errors](https://docs.graph8.com/developers/errors/) for the canonical reference. ## See also - [SDK intent methods](https://docs.graph8.com/developers/sdk/#intent-tracking) — same surface in TypeScript - [CLI intent commands](https://docs.graph8.com/developers/cli/#intent--visitor-tracking) - [MCP intent tools](https://docs.graph8.com/developers/mcp/#intent--signals--15-tools) - [Pricing](https://docs.graph8.com/developers/pricing/) — full credit matrix --- # Restricted Keys (Scopes) > Limit an API key to specific resource:action scopes Source: https://docs.graph8.com/developers/key-scopes/ Restrict an API key to a subset of the API with **scopes**. A key with **no scopes** keeps working on normal read and write routes, so every existing key keeps working and scopes are opt-in. It is **not** unrestricted everywhere, though: some billable, destructive and external routes already need an **explicit** scope, so an unscoped organization key gets a 403 there, and once strict scope enforcement is switched on (`enforcement.scopes_strict` in `/v1/me`) all of them do. An unscoped personal key is judged by its owner's role in graph8 instead (webhooks still need an explicit scope). ## Check what a key can do `GET /v1/me` describes the calling key. Read these fields before you rely on a key: | Field | Meaning | |-------|---------| | `scopes` | The key's scope list (empty = no list) | | `key_type` | `org` or `personal` | | `unscoped_access` | For a key with no scopes: `non_strict_only` (org key: normal reads and writes; billable, destructive and external calls are refused as `strict_requires_explicit` describes) or `owner_role` (personal key). `null` for a scoped key | | `strict_requires_explicit` | `true` when the strict gates need an explicit scope in `scopes` for this key. Some routes apply this now; all billable, destructive and external routes apply it once `enforcement.scopes_strict` is `true` | | `strict_tiers` | The tiers that rule covers | | `unrestricted` | Legacy: `true` when the key has no scope list. Prefer `unscoped_access` | | `enforcement` | Which scope gates are switched on right now | `npx @graph8/sdk doctor` reads these fields and fails when your blocks need a scope the key will be refused. | Scope | Grants | |-------|--------| | `*` | Everything | | `contacts:*` | Every action on contacts | | `contacts:read` | Exactly contacts read | ## Mint a restricted key Pass `scopes` to `POST /v1/api-keys`. The scopes are stored on the key (authoritative) and returned in the response. **curl** ```bash curl -X POST https://be.graph8.com/v1/api-keys \ -H "Authorization: Bearer $G8_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "read-only usage", "scopes": ["usage:read", "contacts:read"]}' # => { "api_key_id": "...", "api_key_token": "g8_live_...", "scopes": ["usage:read","contacts:read"], ... } ``` **Python** ```python import httpx r = httpx.post( "https://be.graph8.com/v1/api-keys", headers={"Authorization": f"Bearer {api_key}"}, json={"name": "read-only usage", "scopes": ["usage:read", "contacts:read"]}, ) ``` **TypeScript** ```ts await fetch("https://be.graph8.com/v1/api-keys", { method: "POST", headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" }, body: JSON.stringify({ name: "read-only usage", scopes: ["usage:read", "contacts:read"] }), }); ``` ## Enforcement A request to an endpoint whose required scope the key lacks returns **`403`** with `{ "type": "forbidden", "code": "403", ... }`. An unscoped key passes every check. > **Note: Soak rollout** > Scope enforcement ships in **soak mode** first — a missing scope is **logged** (server-side) but the request is **allowed**, so you can mint scoped keys and watch the logs before flipping on enforcement (`ENFORCE_DEVAPI_SCOPES=true`). The scope coverage of endpoints expands per release. --- # Landing Pages > REST reference for graph8 landing pages — clone from URL or homepage, edit, publish to Cloudflare Pages Source: https://docs.graph8.com/developers/landing-pages/ The Landing Pages API exposes graph8's clone-first page builder: clone any URL into a fully editable replica, modify text + images + design tokens via chat or direct edits, then publish to a Cloudflare Pages subdomain. > **Caution:** > `POST /publish` deploys to a public URL. Make sure your changes are ready before publishing. > **Note: Auth** > Every call needs `Authorization: Bearer $G8_API_KEY`. The `X-Org-Id` is bound to the key automatically. [Get a key in 30 seconds →](https://docs.graph8.com/developers/authentication/) ## 30-second start List your landing pages: **cURL** ```bash curl 'https://be.graph8.com/api/v1/pages?status=published' \ -H "Authorization: Bearer $G8_API_KEY" ``` **TypeScript** ```typescript import { g8 } from '@graph8/sdk'; g8.init({ apiKey: process.env.G8_API_KEY! }); const pages = await g8.pages.list({ status: 'published' }); console.log(pages); ``` **CLI** ```bash # Use the REST API directly — no dedicated CLI command yet curl 'https://be.graph8.com/api/v1/pages' \ -H "Authorization: Bearer $(g8 whoami | grep -oE '[a-f0-9]{40}' | head -1)" ``` **MCP** Ask your agent: *"List my landing pages"* → tool exists in MCP `all` mode; uses the same backend. ## Quick reference | Endpoint | Method | Purpose | |---|---|---| | `/pages` | GET | List landing pages | | `/pages/{id}` | GET | Get a page (HTML, design tokens, image refs, version) | | `/pages` | POST | Create from template / clone homepage / clone URL | | `/pages/{id}` | PATCH | Update page (HTML diff, brand overrides) | | `/pages/{id}` | DELETE | Delete | | `/pages/{id}/publish` | POST | Deploy to Cloudflare Pages | | `/pages/{id}/versions` | GET | List version history | | `/pages/{id}/versions/{version_id}` | GET | Restore / preview a previous version | | `/pages/{id}/chat` | POST | Send a chat message that edits the page | ## Three ways to create ### 1. From a template ```bash POST /api/v1/pages Content-Type: application/json { "template": "demo" | "lead_magnet" | "product_launch" | "event" | "free_trial", "title": "Q3 PLG Demo Page", "campaign_id": "cmp_abc123" } ``` Templates ship with sensible defaults — hero, value props, social proof, CTA. Customize via PATCH or the chat surface afterwards. ### 2. Clone your homepage ```bash POST /api/v1/pages { "clone_source": "homepage", "title": "Cloned home" } ``` Pulls the org's primary website via Firecrawl, extracts design tokens (colors, fonts, components), sanitizes the HTML, injects deterministic `data-g8-id` element IDs, and creates a fully editable replica. The clone preserves the visual identity so further pages live in the same brand system. ### 3. Clone from any URL ```bash POST /api/v1/pages { "clone_source": "url", "url": "https://competitor.com/pricing", "title": "Competitor pricing analog" } ``` Same pipeline as homepage clone, but from an arbitrary URL. Useful for matching a competitor's flow or replicating a hero page you like. ## Edit via chat ```bash POST /api/v1/pages/{page_id}/chat { "message": "Change the hero headline to 'Stop paying for stale data' and make the primary CTA pink." } ``` Returns the diff applied to the page plus a new version. The chat surface is conversational — it can target specific elements ("the second testimonial"), apply brand-token changes, and add or remove sections. ## Edit via PATCH ```bash PATCH /api/v1/pages/{page_id} { "html": "<...full HTML...>", "design_tokens": { "colors": { "primary": "#9C73FF", "secondary": "#00CAEB" }, "fonts": { "heading": "Aeonik", "body": "Inter" } } } ``` Use this for programmatic edits — e.g. propagating a brand-token change across multiple pages. ## Versions ```bash GET /api/v1/pages/{id}/versions # { data: [{ version_id, created_at, label, chat_message? }, ...] } GET /api/v1/pages/{id}/versions/{version_id} # Returns the full HTML snapshot of that version (for preview / restore) ``` Every chat edit creates a version. Restore by PATCHing the current page with a version's HTML. ## Publish ```bash POST /api/v1/pages/{page_id}/publish ``` Deploys the current version to Cloudflare Pages at `https://g8-lp-{org_sha256[:8]}.pages.dev/{slug}`. Returns the public URL. The CF Pages backend uses **Direct Upload** — no DNS / domain config needed. For a custom domain, attach via Cloudflare dashboard manually (custom-domain mapping API is not yet exposed). ## List + delete ```bash GET /api/v1/pages?campaign_id=cmp_abc123&status=published DELETE /api/v1/pages/{page_id} ``` ## Credit costs | Operation | Credits | |---|---| | List / get / versions | Free | | Create (template) | Free | | Create (clone) | Free — but Firecrawl + AI extraction may take 30-90 seconds | | Edit via PATCH | Free | | Edit via chat | **1 per 1,000 AI tokens** (the chat is LLM-powered) | | Publish | Free (Cloudflare Pages is bundled) | ## Errors | Status | Cause | Fix | |---|---|---| | `401` | Missing or invalid `Authorization: Bearer` header | [Get a key](https://docs.graph8.com/developers/authentication/) | | `402` | Out of credits (waterfall enrichment, AI generation, voice minutes) | Top up in **Settings → Billing**, or switch to Platform | | `404` | Resource ID doesn't exist | List first to verify the ID | | `422` | Validation error in request body | Inspect `error.message` + `error.field` in the response | | `429` | Rate limit (50 rps per org) | Backoff per `Retry-After` header. See [Rate Limits](https://docs.graph8.com/developers/rate-limits/) | | `5xx` | graph8 error | Retry with exponential backoff (5s → 30s → 120s) | The full error envelope shape: `{ "error": { "code": "...", "message": "...", "field": "...", "request_id": "..." } }`. Include the `request_id` in any support ticket. See [Errors](https://docs.graph8.com/developers/errors/) for the canonical reference. ## See also - [SDK pages methods](https://docs.graph8.com/developers/sdk/#landing-pages) - [Pricing](https://docs.graph8.com/developers/pricing/) — credit matrix --- # LangChain Setup > Connect LangChain agents to graph8 via MCP using langchain-mcp-adapters and LangGraph Source: https://docs.graph8.com/developers/langchain-setup/ Connect a LangChain or LangGraph agent to graph8's MCP server. Two transports are supported: hosted (remote OAuth) and self-hosted (local stdio). > **Tip:** > Try the developer tier free - 1,000 credits, no card required. [Get started](https://app.graph8.com/signup). ## Prerequisites - Python 3.10+ - `langchain-mcp-adapters`, `langgraph`, `langchain-anthropic` (or your provider of choice) - A graph8 account with a personal API key from **Profile -> Developer** (for stdio), or any active session (for OAuth) ```bash pip install langchain-mcp-adapters langgraph langchain-anthropic ``` ## Hosted MCP (remote OAuth) Use this when you want graph8 to handle auth via OAuth and you do not want to install the MCP server locally. The agent connects to `https://be.graph8.com/mcp/` over Streamable HTTP and prompts you to sign in once. ```python import asyncio import os from langchain_mcp_adapters.client import MultiServerMCPClient from langgraph.prebuilt import create_react_agent from langchain_anthropic import ChatAnthropic async def main() -> None: client = MultiServerMCPClient( { "graph8": { "transport": "streamable_http", "url": "https://be.graph8.com/mcp/", } } ) tools = await client.get_tools() model = ChatAnthropic( model="claude-sonnet-4", api_key=os.environ["ANTHROPIC_API_KEY"], ) agent = create_react_agent(model, tools) result = await agent.ainvoke( { "messages": [ ( "user", "Find 25 VP Engineering at Series B SaaS companies " "in the United States. Return name, company, and title.", ) ] } ) print(result["messages"][-1].content) if __name__ == "__main__": asyncio.run(main()) ``` The first run opens a browser window for OAuth. Subsequent runs reuse the token cached by the MCP client. ## Self-hosted MCP (stdio) Use this when you cannot rely on browser-based OAuth (CI, headless servers, air-gapped dev). Requires `uv` or Python on the host and a personal API key. ```bash export G8_API_KEY="g8_..." ``` ```python import asyncio import os from langchain_mcp_adapters.client import MultiServerMCPClient from langgraph.prebuilt import create_react_agent from langchain_anthropic import ChatAnthropic async def main() -> None: client = MultiServerMCPClient( { "graph8": { "transport": "stdio", "command": "uvx", "args": ["g8-mcp-server"], "env": { "G8_API_KEY": os.environ["G8_API_KEY"], "G8_MCP_MODE": "gtm", }, } } ) tools = await client.get_tools() model = ChatAnthropic(model="claude-sonnet-4") agent = create_react_agent(model, tools) result = await agent.ainvoke( {"messages": [("user", "Search my CRM for contacts at Stripe.")]} ) print(result["messages"][-1].content) if __name__ == "__main__": asyncio.run(main()) ``` Set `G8_MCP_MODE` to `dev`, `gtm`, or `all` to scope the tool set. See [Modes](https://docs.graph8.com/developers/mcp/#modes). ## Worked example: prospect, qualify, enroll End-to-end LangGraph agent that finds prospects, saves them to a list, and enrolls the list in a sequence. Confirm credit-charging steps before running. ```python prompt = ( "1. Use g8_find_contacts to preview 50 VP Engineering at Series B SaaS in the US.\n" "2. Show me the top 10 by company size and ask me to confirm.\n" "3. On confirmation, use g8_build_contact_list to save them to a new list " "named 'Series B SaaS VP Eng'.\n" "4. Then use g8_list_sequences to find a sequence named 'New SaaS Outreach'.\n" "5. Use g8_add_to_sequence to enroll the saved contacts. Ask me to confirm " "before enrolling." ) ``` graph8 enforces a confirmation rule for credit-charging tools (`g8_build_contact_list`, `g8_add_to_sequence`). The agent will pause and ask before any of these run. ## Related - [MCP Server overview](https://docs.graph8.com/developers/mcp/) - [MCP Tool Reference](https://docs.graph8.com/developers/mcp-tools/) - [MCP FAQ](https://docs.graph8.com/developers/mcp-faq/) - [End-to-end examples](https://docs.graph8.com/developers/mcp-examples/) --- # Launch Helpers > Discovery endpoints for the pre-launch fields used when launching campaigns and updating sequences Source: https://docs.graph8.com/developers/launch-helpers/ > **Note: Available since 0.9.7** > These endpoints were introduced in API version 0.9.7. All are read-only and org-scoped. These discovery endpoints return the values you need when filling in the four pre-launch fields on `POST /campaigns/{id}/launch` and on `PATCH /sequences/{id}`: - `sender_mailbox_ids` — mailbox IDs for sending - `textual_agent_name` — the `agent_name` from `GET /voice/dialer/agents` (unified agent picker, returns both agents and twins) - `voice_agent_name` — same source as `textual_agent_name` - `schedule_id` — the `id` from `GET /schedules` - `appointment_id` — the `id` from `GET /event-types` > **Tip: Agent picker** > The unified agent picker — the same backend behind the graph8 Agents tab — is available at `GET /voice/dialer/agents`. Despite the historical name it is not dialer-only; it returns both agents and twins. Use the row's `agent_name` as `textual_agent_name` or `voice_agent_name` on the launch body or PATCH sequence DTO. --- ## List Schedules `GET /schedules` Returns send-window schedules for your org. Pass the row's `id` back as `schedule_id` when launching a campaign or patching a sequence. Days that have no configured window are omitted from each row's `windows` array. Backed by the sequencer schedules service (`sequencer/utils/schedules`). ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `include_archived` | boolean | `false` | When `true`, archived schedules are included in the response | | `search` | string | — | Filter by schedule name (partial match) | All parameters are optional. ### Example **cURL** ```bash # All active schedules curl "https://be.graph8.com/api/v1/schedules" \ -H "Authorization: Bearer $API_KEY" # Include archived schedules curl "https://be.graph8.com/api/v1/schedules?include_archived=true" \ -H "Authorization: Bearer $API_KEY" # Search by name curl "https://be.graph8.com/api/v1/schedules?search=europe" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": [ { "id": "...", "name": "...", "description": "...", "timezone": "Europe/Madrid", "windows": [ { "day": "monday", "start": "09:00", "end": "17:00" }, { "day": "tuesday", "start": "09:00", "end": "17:00" }, { "day": "wednesday", "start": "09:00", "end": "17:00" }, { "day": "thursday", "start": "09:00", "end": "17:00" }, { "day": "friday", "start": "09:00", "end": "17:00" } ], "is_archived": false } ] } ``` --- ## List Event Types `GET /event-types` Returns bookable Cal.com event types (booking links) for your org. Pass the row's integer `id` back as `appointment_id` when launching a campaign or patching a sequence. The `slug` field is what appears in the public booking URL (`appointments/team/{org}/{slug}`). Backed by `EventTypesHandler.handle_list` — the same handler powering the in-app `/appointments/event-types` picker, so per-user visibility rules apply identically. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `include_hidden` | boolean | `false` | When `true`, hidden event types are included in the response | | `scheduling_type` | string | — | Filter by scheduling type. Allowed values: `managed`, `round_robin`, `collective` | All parameters are optional. ### Example **cURL** ```bash # All visible event types curl "https://be.graph8.com/api/v1/event-types" \ -H "Authorization: Bearer $API_KEY" # Only round-robin event types curl "https://be.graph8.com/api/v1/event-types?scheduling_type=round_robin" \ -H "Authorization: Bearer $API_KEY" # Include hidden event types curl "https://be.graph8.com/api/v1/event-types?include_hidden=true" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": [ { "id": 42, "title": "Intro call", "slug": "intro-call", "length": 30, "description": "...", "scheduling_type": "round_robin", "hidden": false, "schedule_id": 7 } ] } ``` --- # Lists > Create, manage, and populate contact lists Source: https://docs.graph8.com/developers/lists/ > **Tip: Using the CLI?** > The [CLI](https://docs.graph8.com/developers/cli/#g8-create-list) supports `g8 create-list`, `g8 add-to-list`, and `g8 build-list` (search + save in one step). Lists organize contacts into groups for campaigns, exports, and segmentation. Lists are also how data moves from the open data index into your workspace. When you call [`/search/contacts/save`](https://docs.graph8.com/developers/search/#save-contacts-to-list) or [`/search/companies/save`](https://docs.graph8.com/developers/search/#save-companies-to-list), graph8 creates a new list and populates it with matching records. Those contacts and companies then become part of your first-party data, accessible through the [`/contacts`](https://docs.graph8.com/developers/contacts/) and [`/companies`](https://docs.graph8.com/developers/companies/) endpoints. --- ## List All Lists `GET /lists` Returns a paginated list of all lists in your organization. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `page` | integer | `1` | Page number (1-indexed) | | `limit` | integer | `50` | Items per page (1-200) | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/lists?limit=20" \ -H "Authorization: Bearer $API_KEY" ``` **Python** ```python response = requests.get( f"{BASE_URL}/lists", headers=HEADERS, params={"limit": 20} ) lists = response.json() ``` **TypeScript** ```typescript const response = await fetch(`${BASE_URL}/lists?limit=20`, { headers: { Authorization: `Bearer ${API_KEY}` } }); const lists = await response.json(); ``` ### Response ```json { "data": [ { "id": 10, "title": "Enterprise Prospects Q1", "type": "contacts", "source": "graph8", "status": "completed", "total": 1250, "is_dynamic": false, "tags": ["enterprise", "q1-2026"], "created_at": "2026-01-15T10:00:00Z", "created_by": "user_abc123", "updated_at": "2026-02-20T14:30:00Z" } ], "pagination": { "page": 1, "limit": 20, "total": 8, "has_next": false } } ``` --- ## Create List `POST /lists` Create a new empty list. Add contacts to it afterward using the [Add Contacts](#add-contacts-to-list) endpoint. ### Request Body | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `title` | string | Yes | — | Name of the list | | `type` | string | No | `"contacts"` | List type: `contacts`, `companies`, `suppressions`, `deals`, `leads` | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/lists" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"title": "Enterprise Prospects Q1", "type": "contacts"}' ``` **Python** ```python response = requests.post( f"{BASE_URL}/lists", headers=HEADERS, json={"title": "Enterprise Prospects Q1", "type": "contacts"} ) new_list = response.json()["data"] ``` **TypeScript** ```typescript const response = await fetch(`${BASE_URL}/lists`, { method: "POST", headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json" }, body: JSON.stringify({ title: "Enterprise Prospects Q1", type: "contacts" }) }); const { data: newList } = await response.json(); ``` ### Response `201 Created` ```json { "data": { "id": 11, "title": "Enterprise Prospects Q1", "type": "contacts", "status": "completed", "total": 0 } } ``` ### Errors | Status | Meaning | |--------|---------| | `400` | Invalid list type | --- ## Delete List `DELETE /lists/{list_id}` Soft-delete a list. The list is marked as deleted but not permanently removed. Requires the `lists:delete` scope. Existing organization API keys that carry no scopes keep working. An API key that carries scopes must include `lists:delete`. A personal API key acts with its owner's role, so the owner must be allowed to delete lists in graph8 and to edit this list. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `list_id` | integer | List ID | ### Example **cURL** ```bash curl -X DELETE "https://be.graph8.com/api/v1/lists/10" \ -H "Authorization: Bearer $API_KEY" ``` **Python** ```python response = requests.delete(f"{BASE_URL}/lists/10", headers=HEADERS) ``` **TypeScript** ```typescript const response = await fetch(`${BASE_URL}/lists/10`, { method: "DELETE", headers: { Authorization: `Bearer ${API_KEY}` } }); ``` ### Response ```json { "data": { "deleted": true } } ``` ### Errors | Status | Meaning | |--------|---------| | `403` | The key carries scopes but not `lists:delete`, or the personal key's owner can't delete lists or can't edit this list | | `404` | List not found | > **Caution:** > Deleting a list does not delete its contacts. Contacts will remain in your workspace. --- ## Get List Contacts `GET /lists/{list_id}/contacts` Returns the contacts in a specific list. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `list_id` | integer | List ID | ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `page` | integer | `1` | Page number (1-indexed) | | `limit` | integer | `50` | Items per page (1-200) | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/lists/10/contacts?limit=25" \ -H "Authorization: Bearer $API_KEY" ``` **Python** ```python response = requests.get( f"{BASE_URL}/lists/10/contacts", headers=HEADERS, params={"limit": 25} ) contacts = response.json()["data"] ``` **TypeScript** ```typescript const response = await fetch(`${BASE_URL}/lists/10/contacts?limit=25`, { headers: { Authorization: `Bearer ${API_KEY}` } }); const { data: contacts } = await response.json(); ``` ### Response ```json { "data": [ { "id": 1, "first_name": "Jane", "last_name": "Smith", "full_name": "Jane Smith", "work_email": "jane@acme.com", "job_title": "VP of Engineering", "seniority_level": "VP", "direct_phone": "+1-555-0100", "linkedin_url": "https://linkedin.com/in/janesmith", "city": "San Francisco", "state": "CA", "country": "US" } ], "pagination": { "page": 1, "limit": 25, "total": 1250, "has_next": true } } ``` --- ## Add Contacts to List `POST /lists/{list_id}/contacts` Add one or more contacts to a list by their IDs. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `list_id` | integer | List ID | ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `contact_ids` | integer[] | Yes | Contact IDs to add | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/lists/10/contacts" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"contact_ids": [1, 2, 3, 4, 5]}' ``` **Python** ```python response = requests.post( f"{BASE_URL}/lists/10/contacts", headers=HEADERS, json={"contact_ids": [1, 2, 3, 4, 5]} ) result = response.json()["data"] ``` **TypeScript** ```typescript const response = await fetch(`${BASE_URL}/lists/10/contacts`, { method: "POST", headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json" }, body: JSON.stringify({ contact_ids: [1, 2, 3, 4, 5] }) }); const { data: result } = await response.json(); ``` ### Response `201 Created` ```json { "data": { "added": 5 } } ``` --- ## Remove Contacts from List `DELETE /lists/{list_id}/contacts` Remove one or more contacts from a list. The contacts are not deleted — they are only removed from this list. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `list_id` | integer | List ID | ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `contact_ids` | integer[] | Yes | Contact IDs to remove | ### Example **cURL** ```bash curl -X DELETE "https://be.graph8.com/api/v1/lists/10/contacts" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"contact_ids": [3, 4]}' ``` **Python** ```python response = requests.delete( f"{BASE_URL}/lists/10/contacts", headers=HEADERS, json={"contact_ids": [3, 4]} ) result = response.json()["data"] ``` **TypeScript** ```typescript const response = await fetch(`${BASE_URL}/lists/10/contacts`, { method: "DELETE", headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json" }, body: JSON.stringify({ contact_ids: [3, 4] }) }); const { data: result } = await response.json(); ``` ### Response ```json { "data": { "removed": 2 } } ``` > **Tip:** > To move contacts between lists, add them to the destination list first, then remove them from the source list. --- # Live Events > Real-time event debugger - see events flowing through your pipeline as they happen Source: https://docs.graph8.com/developers/live-events/ Live Events is graph8's real-time event debugger. After installing the [SDK](https://docs.graph8.com/developers/sdk/), open Live Events to see events arriving instantly - verify your integration works in seconds. ## Opening Live Events Go to **Connections > Live Events** in your graph8 dashboard. Events appear as they arrive. ## What You See Each event shows: | Field | Description | |-------|-------------| | **Timestamp** | When the event arrived | | **Type** | `page`, `track`, or `identify` | | **Event name** | For track events (e.g., "signup", "purchase") | | **Source** | Which stream/site sent it | | **Destination status** | Delivery status per destination | | **Function results** | Output from any functions that processed it | Click any event to expand the full JSON payload. ## Filtering Filter events by: - **Stream** - show events from a specific site/source - **Destination** - show events going to a specific destination - **Event type** - page views, track events, identify calls - **Date range** - historical events within a time window ## Verifying Your SDK Installation After adding the [JavaScript SDK](https://docs.graph8.com/developers/sdk/) to your app: 1. Open **Connections > Live Events** 2. Load your app in a browser 3. You should see a `page` event appear within seconds 4. Click a button that calls `g8.track()` - see the event arrive 5. Call `g8.identify()` - see the identify event with user traits If events don't appear: - Check that your **write key** matches the stream's browser key - Check that your **domain** is in the stream's allowed domains list - Open browser DevTools Network tab and look for requests to `t.graph8.com` ## Debugging Functions When [Functions](https://docs.graph8.com/developers/functions/) process an event, Live Events shows: - The original event (before function) - Function log output (`ctx.log` messages) - The transformed event (after function) - Whether the function dropped the event ## Debugging Destinations For each event, Live Events shows delivery status per [Destination](https://docs.graph8.com/developers/destinations/): - ✅ Delivered successfully - ⏳ Pending delivery - ❌ Delivery failed (with error details) > **Tip: First-time setup?** > The recommended workflow: install the [SDK](https://docs.graph8.com/developers/sdk/), open Live Events, verify events arrive, then add [Destinations](https://docs.graph8.com/developers/destinations/) and [Functions](https://docs.graph8.com/developers/functions/). --- # LlamaIndex Setup > Connect LlamaIndex agents to graph8 via MCP using llama-index-tools-mcp Source: https://docs.graph8.com/developers/llamaindex-setup/ Connect a LlamaIndex agent to graph8's MCP server. LlamaIndex's `llama-index-tools-mcp` package wraps MCP tools as native LlamaIndex `FunctionTool`s your agents can use. > **Tip:** > Try the developer tier free - 1,000 credits, no card required. [Get started](https://app.graph8.com/signup). ## Prerequisites - Python 3.10+ - `llama-index`, `llama-index-tools-mcp`, `llama-index-llms-anthropic` (or your model provider) ```bash pip install llama-index llama-index-tools-mcp llama-index-llms-anthropic ``` ## Hosted MCP (remote OAuth) ```python import asyncio import os from llama_index.core.agent import ReActAgent from llama_index.llms.anthropic import Anthropic from llama_index.tools.mcp import McpToolSpec, BasicMCPClient async def main() -> None: client = BasicMCPClient("https://be.graph8.com/mcp/") tool_spec = McpToolSpec(client=client) tools = await tool_spec.to_tool_list_async() llm = Anthropic( model="claude-sonnet-4", api_key=os.environ["ANTHROPIC_API_KEY"], ) agent = ReActAgent.from_tools(tools, llm=llm, verbose=True) response = await agent.achat( "Use g8_find_contacts to preview 10 VP Sales at fintech startups " "in New York. Return name, company, title." ) print(response) if __name__ == "__main__": asyncio.run(main()) ``` The first call opens a browser for OAuth. Subsequent calls reuse the cached token. ## Self-hosted MCP (stdio) For headless or CI use, run the MCP server locally with stdio and a personal API key. ```bash export G8_API_KEY="g8_..." ``` ```python import asyncio import os from llama_index.core.agent import ReActAgent from llama_index.llms.anthropic import Anthropic from llama_index.tools.mcp import McpToolSpec, BasicMCPClient async def main() -> None: client = BasicMCPClient( command_or_url="uvx", args=["g8-mcp-server"], env={ "G8_API_KEY": os.environ["G8_API_KEY"], "G8_MCP_MODE": "gtm", }, ) tool_spec = McpToolSpec(client=client) tools = await tool_spec.to_tool_list_async() llm = Anthropic(model="claude-sonnet-4") agent = ReActAgent.from_tools(tools, llm=llm, verbose=True) response = await agent.achat("Search my CRM for contacts at Stripe.") print(response) if __name__ == "__main__": asyncio.run(main()) ``` ## Worked example: SaaS prospecting workflow ```python prompt = ( "Find 25 VP Engineering at Series B SaaS in the US using g8_find_contacts. " "Pick the 10 with the highest signal score. Ask me to confirm before " "saving them to a new list via g8_build_contact_list." ) response = await agent.achat(prompt) ``` Credit-charging tools require explicit confirmation. The MCP server returns a structured prompt the agent surfaces to the user before any save. ## Related - [MCP Server overview](https://docs.graph8.com/developers/mcp/) - [MCP Tool Reference](https://docs.graph8.com/developers/mcp-tools/) - [MCP FAQ](https://docs.graph8.com/developers/mcp-faq/) - [End-to-end examples](https://docs.graph8.com/developers/mcp-examples/) --- # MCP Server > Connect AI coding agents to graph8 via the Model Context Protocol Source: https://docs.graph8.com/developers/mcp/ The graph8 MCP server connects AI coding agents (Claude Code, Cursor, Windsurf, Claude Desktop, ChatGPT) to your graph8 workspace. It exposes tools for contact and company search, sequences, inbox, campaigns, voice/dialer, workflow building, skill authoring, intent signals, quote-to-cash, pipelines, and developer install, all from within your IDE or chat client. > **Tip:** > Try the developer tier free - 1,000 credits, no card required. [Get started](https://app.graph8.com/signup). > > AI agents: fetch the full doc index at [https://docs.graph8.com/llms.txt](https://docs.graph8.com/llms.txt). ## Personal vs Admin Setup Most users should use **personal MCP setup from Profile**. You do not need org admin access. | Use Case | Where to set it up | Credential | |----------|---------------------|------------| | Personal MCP in Claude Desktop, Cursor, Windsurf, Claude Code | **Profile -> Developer** | Personal API key (for local) or OAuth (for remote) | | Shared org automation and admin-managed integrations | **Settings -> API** | Org API key | > **Tip:** > If you are a non-admin user and want MCP for yourself, use the personal flow in **Profile**. Do not use the admin-only org API settings. ## Setup There are two ways to connect: **Remote** (recommended) and **Local**. Remote uses OAuth — no API key or installation required. Local runs the server on your machine via stdio. ### Remote (OAuth) — Recommended Paste this config into your AI client. Your client will prompt you to sign in via OAuth when you first connect — no API key needed. **Claude.ai** 1. Go to **Customize > Connectors > Add custom connector** 2. Paste the server URL: `https://be.graph8.com/mcp/` 3. Click **Add**, then **Connect** 4. Authorize when prompted (one time) **Claude Desktop** 1. Go to **Settings > MCP Servers > Add** 2. Paste the server URL: `https://be.graph8.com/mcp/` 3. Authorize when prompted (one time) **Claude Code** Add to `~/.claude/mcp.json`: ```json { "mcpServers": { "graph8": { "url": "https://be.graph8.com/mcp/" } } } ``` **Cursor** Add to `.cursor/mcp.json` in your project root: ```json { "mcpServers": { "graph8": { "url": "https://be.graph8.com/mcp/" } } } ``` **Windsurf** Add to your Windsurf MCP config: ```json { "mcpServers": { "graph8": { "url": "https://be.graph8.com/mcp/" } } } ``` **ChatGPT** 1. Go to **Settings > Apps > Developer Mode** 2. Add MCP server and paste: `https://be.graph8.com/mcp/` 3. Authorize via OAuth > **Tip:** > Remote mode handles authentication automatically via OAuth. When you first use a graph8 tool, your client will open a browser window to sign in. No API key, no environment variables, no Python install needed. > > In graph8, active remote OAuth sessions appear in **Profile -> Connections**. Local Claude Desktop and stdio setups remain in **Profile -> Developer** because they use your personal API key instead of an OAuth session. ## Setup Guides Pick your client. Every guide covers both remote OAuth and local stdio variants. **Cursor** Cursor IDE with remote OAuth or local stdio. [Open guide](https://docs.graph8.com/developers/cursor-setup/) **Claude Desktop** Anthropic's desktop app with personal or org API key. [Open guide](https://docs.graph8.com/developers/claude-desktop-setup/) **Claude Code** Anthropic's terminal agent via `~/.claude/mcp.json`. [Open guide](https://docs.graph8.com/developers/claude-code-setup/) **VS Code** Native MCP via `.vscode/mcp.json` for Copilot Agent Mode. [Open guide](https://docs.graph8.com/developers/vscode-setup/) **n8n** Workflow automation via the MCP node or HTTP request. [Open guide](https://docs.graph8.com/developers/n8n-setup/) **LangChain** `langchain-mcp-adapters` + LangGraph for Python agents. [Open guide](https://docs.graph8.com/developers/langchain-setup/) **LlamaIndex** `llama-index-tools-mcp` for typed Python agents. [Open guide](https://docs.graph8.com/developers/llamaindex-setup/) **CrewAI** `MCPServerAdapter` for multi-agent crews. [Open guide](https://docs.graph8.com/developers/crewai-setup/) **Pydantic AI** Typed agent outputs with `MCPServerHTTP` and `MCPServerStdio`. [Open guide](https://docs.graph8.com/developers/pydantic-ai-setup/) **OpenAI Agents SDK** Native MCP via `MCPServerStreamableHttp`. [Open guide](https://docs.graph8.com/developers/openai-sdk-setup/) **End-to-end examples** Worked workflows chaining MCP tools. [Open guide](https://docs.graph8.com/developers/mcp-examples/) **What You Can Ask** Free-form prompt patterns and supported workflows. [Open guide](https://docs.graph8.com/developers/what-you-can-ask/) ### Local (stdio) If you prefer running the server locally (or your client doesn't support remote MCP), use the stdio transport. This requires Python 3.10+ and an API key. Replace `<your-api-key>` with: - your **personal API key** from **Profile -> Developer** (recommended for end users), or - an **org API key** from **Settings -> API** (admin/shared integrations). **Developer Mode** For developers in Cursor, Windsurf, or Claude Code: ```json { "mcpServers": { "graph8": { "command": "uvx", "args": ["g8-mcp-server"], "env": { "G8_API_KEY": "<your-api-key>", "G8_MCP_MODE": "dev" } } } } ``` **GTM Mode** For campaign managers in Claude Desktop: ```json { "mcpServers": { "graph8": { "command": "uvx", "args": ["g8-mcp-server"], "env": { "G8_API_KEY": "<your-api-key>", "G8_MCP_MODE": "gtm" } } } } ``` **All Mode** Loads every tool (developer + GTM + voice + workflow + skill authoring): ```json { "mcpServers": { "graph8": { "command": "uvx", "args": ["g8-mcp-server"], "env": { "G8_API_KEY": "<your-api-key>", "G8_MCP_MODE": "all" } } } } ``` `uvx` runs the server directly from PyPI without installing it. If you'd rather install permanently, use `pip install g8-mcp-server` — that ships both the MCP server and the [`g8` CLI](https://docs.graph8.com/developers/cli/). #### Environment Variables (local only) | Variable | Required | Default | Description | |----------|----------|---------|-------------| | `G8_API_KEY` | Yes | — | Your personal API key (Profile) or org API key (Settings -> API) ([details](https://docs.graph8.com/developers/authentication/)) | | `G8_MCP_MODE` | No | `all` | Tool set to load: `dev`, `gtm`, or `all` | | `G8_API_URL` | No | `https://be.graph8.com` | API base URL (for self-hosted or staging) | ## Modes The `G8_MCP_MODE` variable controls which tools are loaded. This applies to local (stdio) connections only — remote connections load all tools. | Mode | Tools | Best for | |------|------:|----------| | `dev` | **32** | Developers building graph8 into product code in Cursor / Windsurf / Claude Code (repo scan, install snippets, generate forms, CRM operations) | | `gtm` | **51** | Campaign managers and marketing ops in Claude Desktop (campaigns, mailbox warmup, pipelines, ICPs, personas, CRM operations) | | `all` | **114** | Maximum capability: dev + GTM + voice/dialer + workflow builder + skill authoring (default for remote OAuth) | Counts include the `g8_gtm_` / `g8_voice_` / `g8_workflow_` prefixed registrations used to avoid name collisions in `all` mode. Skill authoring tools are dual-registered under both `g8_skill_*` and `g8_workflow_skill_*` names, so the underlying handler count is ~100 distinct operations. ### Progressive tool discovery The remote MCP endpoint advertises only **~31 always-on tools** at connect time, plus the `g8_tool_search` meta-tool. The other 80+ tools (voice, workflows, skills, pipelines, quotes, intent, custom fields, etc.) are loaded on demand — when an agent calls `g8_tool_search(query="workflow")`, matching tools become visible on the next `tools/list` fetch via the standard `notifications/tools/list_changed` event. This keeps the steady-state context budget low while still giving agents access to the full 100+ tool surface when they need it. ### Tool-set scoping on the remote URL (coming soon) Today the remote OAuth endpoint `https://be.graph8.com/mcp/` loads every tool your role grants, with `g8_tool_search` for discovery. Per-mode endpoints are on the roadmap so different clients can pull only the tools they need: - `https://be.graph8.com/mcp/?mode=dev` - load the 32 developer tools only - `https://be.graph8.com/mcp/?mode=gtm` - load the 51 GTM tools only - `https://be.graph8.com/mcp/?groups=audience-sync,inbox` - load specific tool groups This is roadmap, not shipped. Use `G8_MCP_MODE` on stdio in the meantime. ## Available Tools For the complete per-tool reference (parameters, return shapes, JSON examples, danger warnings), see [MCP Tool Reference](https://docs.graph8.com/developers/mcp-tools/). The tables below are a quick index by category. ### Shared platform tools (all modes) Available in every mode — core CRM, prospecting, sequences, inbox, intent, sync, meetings, quotes, and custom fields. #### Contacts, lookup, search — 8 tools | Tool | Description | |------|-------------| | `g8_search_contacts` | Search contacts already in your CRM | | `g8_search_companies` | Search companies already in your CRM | | `g8_lookup_person` | Enrich a single person via the graph8 index (email / LinkedIn / name+domain) | | `g8_lookup_company` | Enrich a single company via the graph8 index (domain / name) | | `g8_find_contacts` | Search graph8's 700M+ contact index for new prospects | | `g8_find_companies` | Search graph8's 100M+ company index for new accounts | | `g8_create_contact` | Create a contact in your CRM | | `g8_enrich_contacts` | Run third-party waterfall enrichment on saved contacts; optional `providers[]` + `email_verification` overrides (charges credits) | #### Contact graph (read) — 5 tools | Tool | Description | |------|-------------| | `g8_get_contact_detail` | Full enriched details for a single contact | | `g8_get_contact_company` | Company record associated with a contact | | `g8_get_contact_deals` | Deals linked to a contact | | `g8_get_contact_activity` | Activity history for a contact | | `g8_get_company_contacts` | All contacts at a company | #### Lists — 3 tools | Tool | Description | |------|-------------| | `g8_get_lists` | List contact and company lists | | `g8_create_list` | Create a list (empty, from CRM contacts, explicit rows, or open-data search) | | `g8_add_to_list` | Add contacts to an existing list | #### Custom fields — 4 tools | Tool | Description | |------|-------------| | `g8_list_fields` | List field definitions (base + custom columns) | | `g8_create_field` | Create a custom column on contacts or companies. **STRONGLY RECOMMENDED** to pass `enrichment_providers` (+ optional `enrichment_email_verification`) in the same call when the column will hold enriched data — otherwise the UI **Enrich** button and `g8_enrich_contacts` will fail with `422 waterfall_config_missing` | | `g8_set_field_value` | Set a custom column value on a single record | | `g8_delete_field` | Soft-delete a custom column | #### Deals — 7 tools | Tool | Description | |------|-------------| | `g8_get_deals` | List deals with stage / pipeline / text filters | | `g8_get_deal` | Get a single deal | | `g8_get_pipeline` | List deal pipelines and stages | | `g8_create_deal` | Create a new deal | | `g8_update_deal` | Update a deal (partial; usually stage change) | | `g8_delete_deal` | Delete a deal | | `g8_get_company_deals` | All deals for a company | #### Tasks — 5 tools | Tool | Description | |------|-------------| | `g8_get_tasks` | List tasks with status / priority / assignee / search filters | | `g8_get_task` | Read a single task by ID | | `g8_create_task` | Create a task linked to a contact | | `g8_update_task` | Update a task (partial) | | `g8_delete_task` | Delete a task | #### Notes — 4 tools | Tool | Description | |------|-------------| | `g8_list_notes` | List notes on a contact | | `g8_create_note` | Add a note to a contact | | `g8_update_note` | Update a note | | `g8_delete_note` | Delete a note | #### Enrichment jobs — 1 tool | Tool | Description | |------|-------------| | `g8_get_enrichment_job` | Status + results of an async enrichment job | #### Sequences — 9 tools Full lifecycle control over outreach sequences. | Tool | Description | |------|-------------| | `g8_list_sequences` | List outbound sequences | | `g8_add_to_sequence` | Enroll contacts in a sequence (real sends) | | `g8_get_sequence_preview` | Preview steps and channels before launching | | `g8_get_sequence_analytics` | Performance metrics (reply rate, completion, engagement) | | `g8_create_sequence` | Create a new sequence with steps, channels, schedule | | `g8_update_sequence` | Update sequence name, description, or flags | | `g8_update_sequence_step` | Update a single step (template, timing, channel) | | `g8_pause_sequence` | Pause a live sequence | | `g8_resume_sequence` | Resume a paused sequence | | `g8_delete_sequence` | Archive a sequence (soft delete) | > **Caution:** > Pausing, resuming, and deleting sequences affect live outreach. These tools require confirmation before execution. #### Inbox & replies — 6 tools | Tool | Description | |------|-------------| | `g8_list_inbox` | List reply threads filterable by channel, sequence, status, assignee, or tag | | `g8_get_reply` | Get a single reply thread with full message history | | `g8_get_reply_draft` | Get the auto-generated draft reply (charges credits when generated) | | `g8_send_reply` | Send a reply via email, SMS, or LinkedIn | | `g8_assign_reply` | Assign a reply thread to a team member | | `g8_tag_reply` | Tag a reply thread for categorization | #### Activity stream — 3 tools | Tool | Description | |------|-------------| | `g8_get_activities` | Org-wide activity records (opens, clicks, form fills, etc.) | | `g8_get_activity_summary` | Aggregated activity summary | | `g8_get_contact_activity` | Activity history for one contact | #### Intent & signals — 15 tools | Tool | Description | |------|-------------| | `g8_intent_list_keywords` | List tracked keywords | | `g8_intent_add_keywords` | Bulk-add plain keyword phrases (web-UI Add keywords parity) | | `g8_intent_search_pages` | Search website pages by URL or keyword | | `g8_intent_domain_search` | Search visitor activity on a specific domain | | `g8_intent_search_url_companies` | Find companies whose users visited a URL | | `g8_intent_keyword_companies` | Companies showing interest in a keyword | | `g8_intent_keyword_contacts` | Contacts showing interest in a keyword | | `g8_intent_keyword_urls` | URLs associated with a keyword | | `g8_intent_pages_contacts` | Contacts who visited a specific page | | `g8_intent_pages_visitor_counts` | Visitor count aggregates by page | | `g8_intent_page_visitors` | Visitor records for a specific page | | `g8_intent_stats` | Org-level intent stats | | `g8_intent_create_from_domain` | Create a keyword group from a domain | | `g8_intent_update_keyword_filters` | Update keyword tracking rules | | `g8_intent_delete_keyword` | Stop tracking a keyword | #### Sync — 14 tools Push audiences to ad platforms, sync contacts to CRMs, monitor sync health. | Tool | Description | |------|-------------| | `g8_list_crm_syncs` | List connected CRM integrations (HubSpot, Salesforce, Pipedrive, Zoho, SugarCRM) | | `g8_get_crm_status` | CRM connection health and rate limits | | `g8_get_crm_fields` | Discover available field mappings | | `g8_push_to_crm_contact` | Push contacts to a CRM (may charge per integration) | | `g8_push_to_crm_company` | Push companies to a CRM (may charge per integration) | | `g8_push_to_crm_list` | Push list memberships to a CRM | | `g8_list_audience_syncs` | List configured audience syncs | | `g8_get_audience_sync` | Get one sync's config | | `g8_create_audience_sync` | Create a sync to Meta / LinkedIn / Google / X / CRM | | `g8_update_audience_sync` | Update cadence, mode, or suppression lists | | `g8_delete_audience_sync` | Remove a sync config | | `g8_trigger_audience_sync` | Manually trigger a sync run | | `g8_get_audience_sync_runs` | Sync run history with record counts | | `g8_get_audience_sync_errors` | Error logs for failed sync runs | #### Meetings — 2 tools | Tool | Description | |------|-------------| | `g8_list_meetings` | List meetings with optional filters | | `g8_get_meeting` | Full meeting details (transcript, attendees, recording link) | #### Quotes (quote-to-cash) — 12 tools | Tool | Description | |------|-------------| | `g8_list_quotes` | List quotes with optional filters | | `g8_get_quote` | Quote details (line items, values, recipient, status) | | `g8_create_quote` | Create a new quote from line items | | `g8_update_quote` | Update a draft quote | | `g8_duplicate_quote` | Duplicate an existing quote | | `g8_edit_quote_as_draft` | Convert signed/sent quote back to editable draft | | `g8_send_quote` | Send a quote via email (with signature + payment link) | | `g8_delete_quote` | Delete a draft quote | | `g8_list_quotable_products` | List products available for line items | | `g8_get_quote_settings` | Org-level quote settings | | `g8_get_company_quotes` | All quotes for a company | | `g8_get_contact_quotes` | All quotes for a contact | #### Discovery + playbooks — 3 tools | Tool | Description | |------|-------------| | `g8_tool_search` | Search the full tool catalog and activate matching tools for this session | | `g8_list_playbooks` | List available graph8 playbooks (workflow guides) | | `g8_load_playbook` | Load a playbook's full markdown content | ### Developer tools (`dev` mode) — 11 tools Repo-scoped tools for building graph8 into your codebase. | Tool | Description | |------|-------------| | `g8_connect_repo` | Connect a GitHub/GitLab repo to graph8 | | `g8_scan_repo` | Record scan results you produced by reading the code (graph8 does not scan server-side) | | `g8_get_scan_results` | Get scan results (frameworks, pages, components) | | `g8_status` | Check repo integration status | | `g8_doctor` | Report the graph8-side state of a repo: connection, write key, scan, patch set | | `g8_install_spine` | Generate tracking installation plan | | `g8_apply_install` | Apply the tracking installation | | `g8_list_campaigns` | List campaigns for a repo | | `g8_get_campaign` | Get campaign details | | `g8_get_tracking_snippet` | Get framework-specific tracking snippet (p.js) | | `g8_search_kb` | Search knowledge base articles | In `dev` mode `g8_get_tracking_snippet` and `g8_get_form_template` are also registered for use with the [installation flow](https://docs.graph8.com/developers/cli/#g8-snippet). ### GTM tools (`gtm` mode) — 30 tools Campaign builder, Studio, mailbox, and audience operations. #### Campaigns — 17 tools | Tool | Description | |------|-------------| | `g8_list_campaigns` | List campaigns in your org | | `g8_get_campaign` | Campaign details (summary or full) | | `g8_create_campaign` | Create a new campaign | | `g8_update_campaign` | Update campaign metadata | | `g8_patch_campaign_full` | Update full campaign state in one call | | `g8_launch_campaign` | Launch a campaign (sends real outreach) | | `g8_get_campaign_ideas` | AI-generated campaign ideas from org context | | `g8_get_campaign_metrics` | Open / click / reply / meeting metrics | | `g8_attach_audience` | Attach an audience (list) to a campaign | | `g8_list_campaign_documents` | List documents (copy, images, landing pages) | | `g8_get_campaign_document` | Get document content | | `g8_create_campaign_document` | Create a new document | | `g8_update_campaign_document` | Update document content | | `g8_delete_campaign_document` | Delete a document | | `g8_get_ad_images` | List ad image creatives | | `g8_hide_ad_image` | Hide an ad creative from the grid | | `g8_get_campaign_sequence` | Get sequence attached to a campaign | #### Campaign sequence steps — 4 tools | Tool | Description | |------|-------------| | `g8_update_campaign_sequence` | Update the sequence on a campaign | | `g8_create_campaign_step` | Add a step | | `g8_update_campaign_step` | Update a step | | `g8_delete_campaign_step` | Delete a step | #### Studio context — 5 tools | Tool | Description | |------|-------------| | `g8_get_global_context` | Org-level Studio documents (brand, value props, messaging) | | `g8_get_icps` | ICP definitions | | `g8_get_personas` | Buyer persona definitions | | `g8_list_intelligence_data` | Intelligence data (scrapes, enrichment, competitor research) | | `g8_list_research_reports` | AI research reports (buyer psychology, GTM channel analysis) | #### Mailbox & KB — 4 tools | Tool | Description | |------|-------------| | `g8_list_mailboxes` | Warmup-enabled mailboxes | | `g8_get_mailbox_warmup` | Warmup status + analytics | | `g8_list_kb_documents` | Knowledge Base documents | | `g8_search_kb` | Search the Knowledge Base | #### Pipelines (stage checklist v2) — 12 tools `gtm` mode unprefixed; `all` mode prefixed as `g8_gtm_*`. | Tool | Description | |------|-------------| | `g8_list_pipelines` | List deal pipelines with stages + evidence + channel scripts | | `g8_get_pipeline` | Get a pipeline by ID | | `g8_list_stage_evidence_library` | Get the 8 canonical evidence keys | | `g8_create_pipeline` | Create a new pipeline (default or blank) | | `g8_update_pipeline` | Update pipeline metadata | | `g8_create_stage` | Add a stage with evidence + scripts | | `g8_update_stage` | Update a stage | | `g8_reorder_stages` | Reorder stages | | `g8_delete_pipeline` | Delete a pipeline (when no deals exist) | | `g8_delete_stage` | Delete a stage | | `g8_suggest_pipeline_from_context` | AI-suggested pipeline from org context | | `g8_create_pipeline_from_suggestion` | Create pipeline from AI suggestion | ### `all` mode adds — voice, workflows, skills Available only in `all` mode (the default for remote OAuth). #### Voice & dialer — 13 tools (`g8_voice_*`) | Tool | Description | |------|-------------| | `g8_voice_list_dialer_sessions` | List parallel dialer sessions (status, SDR, campaign, metrics) | | `g8_voice_get_dialer_stats` | Aggregated dialer analytics (dials, connection rate, talk time) | | `g8_voice_list_numbers` | Phone numbers the org owns | | `g8_voice_list_agents` | Available voice agents | | `g8_voice_list_missed_callbacks` | Missed inbound callbacks / voicemails | | `g8_voice_list_calls_for_contact` | Call history for a contact | | `g8_voice_list_calls_for_sdr` | Call history for an SDR | | `g8_voice_get_call_transcript` | Transcript of a recorded call | | `g8_voice_get_call_grading` | AI grading + talk-track feedback | | `g8_voice_create_dialer_session` | Create a paused dialer session (SDR opens UI to dial) | | `g8_voice_pause_session` | Pause a dialer session | | `g8_voice_resume_session` | Resume a session and dial next batch (real calls) | | `g8_voice_stop_session` | Stop a session permanently | > **Caution:** > `g8_voice_resume_session` places real outbound calls. Voice minutes consume credits (20 per minute). Always confirm before resuming. #### Workflow builder — 25 tools (`g8_workflow_*`) | Tool | Description | |------|-------------| | `g8_workflow_list` | List workflows | | `g8_workflow_get` | Full workflow definition (nodes, connections, trigger, execution state) | | `g8_workflow_create` | Create a new workflow | | `g8_workflow_update` | Update workflow metadata | | `g8_workflow_delete` | Delete a workflow | | `g8_workflow_add_node` | Add a node (Action, Delay, Branch, Agent, Skill, Loop) | | `g8_workflow_update_node` | Update a node's configuration | | `g8_workflow_remove_node` | Remove a node | | `g8_workflow_connect_nodes` | Add an edge between two nodes | | `g8_workflow_list_node_types` | Available node types with schemas | | `g8_workflow_describe_node_type` | Detailed schema for a node type | | `g8_workflow_describe_form_fields` | Form-field schema for the config UI | | `g8_workflow_validate` | Validate a workflow (orphans, dangling connections, required fields) | | `g8_workflow_execute` | Execute immediately with trigger payload | | `g8_workflow_get_execution` | Status + output of an execution | | `g8_workflow_pause_execution` | Pause an in-flight execution | | `g8_workflow_resume_execution` | Resume a paused execution | | `g8_workflow_stop_execution` | Stop an execution (terminal) | | `g8_workflow_get_trigger_status` | Status of external trigger | | `g8_workflow_reset_trigger_cursor` | Reset trigger cursor (event-stream triggers) | | `g8_workflow_list_slack_users` | Slack workspace users | | `g8_workflow_list_slack_channels` | Slack workspace channels | | `g8_workflow_list_roam_users` | Roam (Copilot chat) users | | `g8_workflow_list_roam_groups` | Roam (Copilot chat) groups | | `g8_workflow_list_mcp_servers` | Available MCP servers (for Agent nodes) | | `g8_workflow_list_dispositions` | Available call dispositions (voice nodes) | #### Skill authoring — 14 tools (`g8_skill_*` + `g8_workflow_skill_*` alias) Each skill tool is registered under **two names** that point at the same handler — pick whichever matches the calling context (canonical `g8_skill_*` or workflow-namespaced `g8_workflow_skill_*`). | Tool | Description | |------|-------------| | `g8_skill_list` | List skills (LLM/API building blocks) | | `g8_skill_get` | Full skill definition (type, prompt/code, model, schemas) | | `g8_skill_create_llm` | Create an LLM skill (prompt template + model) | | `g8_skill_create_api` | Create an API skill (HTTP request config) | | `g8_skill_create_from_template` | Create a skill from a built-in template | | `g8_skill_create_from_node` | Lift a workflow node into a reusable skill | | `g8_skill_update_llm` | Update an LLM skill | | `g8_skill_update_api` | Update an API skill | | `g8_skill_delete` | Delete a skill | | `g8_skill_execute` | Execute a skill immediately (test / preview) | | `g8_skill_validate_template` | Validate a skill template | | `g8_skill_list_templates` | List available templates | | `g8_skill_list_models` | List available LLM models | | `g8_skill_extract_variables` | Extract `{variable}` placeholders from a prompt | ## Tracking Snippet — Server-Side vs Client-Side The graph8 tracking snippet (`p.js`) works differently depending on whether your framework renders on the server or the client. Understanding this is critical for correct installation. ### Server-Side Rendered (SSR) Frameworks: **Next.js**, **WordPress**, **Shopify**, **Webflow**, **HTML** The snippet is included in the server-rendered HTML layout (e.g., `app/layout.tsx`, `theme.liquid`, `functions.php`). It loads on every page from the first paint — no hydration delay. ```javascript // SSR — snippet is always available, call directly g8.identify("user-123", { email: "user@example.com" }); g8.track("page_view", { page: "/pricing" }); ``` ### Client-Side Rendered (CSR) Frameworks: **React**, **Vue** The snippet is injected via `useEffect` (React) or `onMounted` (Vue) after hydration. The script may not be ready immediately — use optional chaining to safely call tracking methods. ```javascript // CSR — use optional chaining since g8 loads after hydration window.g8?.identify("user-123", { email: "user@example.com" }); window.g8?.track("button_click", { button: "signup" }); ``` ### Privacy Options Add these data-attributes to the script tag to control privacy behavior: | Attribute | Values | Description | |-----------|--------|-------------| | `data-privacy-dont-send` | `"true"` | Disables cookies and event sending | | `data-privacy-user-ids` | `"true"` | Disables storing user identifiers | | `data-privacy-ip-policy` | `"keep"`, `"stripLastOctet"`, `"remove"` | Controls IP address handling | | `data-init-only` | `"true"` | Initializes the script without sending a page event | ### Verification After installing the snippet, verify it's working: 1. Open browser **DevTools -> Network** tab 2. Look for requests to your tracking host (`/p.js`) 3. In **Console**: `window.g8` should be defined 4. Call `g8.track('test_event')` — you should see a network request ## Resources MCP resources provide read-only context that agents can include in prompts. | Resource URI | Description | |-------------|-------------| | `g8://repos` | List of connected repositories | | `g8://repos/{repo_id}/scan` | Scan results for a repo | | `g8://repos/{repo_id}/kb` | Knowledge base documents for a repo | | `g8://repos/{repo_id}/kb/{doc_id}` | Single KB document content | | `g8://repos/{repo_id}/campaigns` | Campaigns for a repo | | `g8://repos/{repo_id}/campaigns/{campaign_id}/brief` | Campaign brief | | `g8://contacts` | Contact list summary | | `g8://companies` | Company list summary | ## Prompt Templates Pre-built prompts for common workflows. | Prompt | Arguments | Description | |--------|-----------|-------------| | `gtm_setup` | `repo_url` | Full setup workflow: connect, scan, install, launch | | `campaign_review` | `campaign_id` | Review and optimize a campaign | | `icp_refinement` | `feedback` | Refine ideal customer profile based on feedback | ## Getting Started A typical developer workflow with the MCP server: 1. **Configure** — Add the remote config to your AI client (no install needed) 2. **Connect** — Ask your agent to connect your repo: *"Connect my repo github.com/org/app"* 3. **Scan** — The agent scans your codebase for frameworks and integration points 4. **Install snippet** — *"Install the graph8 tracking snippet for Next.js"* 5. **Configure tracking** — Add `g8.identify()` on login and `g8.track()` on key actions 6. **Add forms** — *"Generate an embedded progressive form for React"* 7. **Launch campaigns** — *"Create a campaign targeting trial users"* For agents using progressive discovery: call `g8_tool_search(query="<topic>")` whenever the right tool isn't visible in the current `tools/list`. The matching tools become available on the next request. ## Need help? - [MCP FAQ](https://docs.graph8.com/developers/mcp-faq/) - common errors, auth, multi-org, security, rate limits - [MCP Tool Reference](https://docs.graph8.com/developers/mcp-tools/) - per-tool parameters and examples - [End-to-end examples](https://docs.graph8.com/developers/mcp-examples/) - worked workflows you can copy - [Interactive Swagger docs](https://be.graph8.com/api/v1/docs) - same surface, exposed as REST - [Pricing](https://docs.graph8.com/developers/pricing/) - free tier and credit costs --- # MCP Examples > End-to-end worked examples for graph8 MCP across prospecting, inbox, ads, tracking install, voice, and full GTM setup Source: https://docs.graph8.com/developers/mcp-examples/ Concrete agent workflows that chain graph8 MCP tools end to end. Each example shows the problem, the prompt you would send your agent, the tools used, and the expected outcome. Use these as starter prompts for Claude Code, Cursor, Claude Desktop, or any of the [framework integrations](https://docs.graph8.com/developers/mcp/). > **Tip:** > Try the developer tier free - 1,000 credits, no card required. [Get started](https://app.graph8.com/signup). ## Build a list of 50 VP Engineering at Series B SaaS **Problem.** You want to enroll 50 qualified VP Eng prospects at Series B SaaS companies in a new outreach sequence, without leaving your IDE. **Prompt:** ``` Use graph8 to find 50 VP Engineering at Series B SaaS startups in the US. Show me the top 25 by signal score and ask me to confirm before saving. On confirmation, save them to a new list named "Series B SaaS VP Eng". Then enroll the list in the sequence named "New SaaS Outreach". Ask me to confirm before enrolling. ``` **Tools used:** 1. [`g8_find_contacts`](https://docs.graph8.com/developers/mcp-tools/) - preview prospects (free) 2. [`g8_build_contact_list`](https://docs.graph8.com/developers/mcp-tools/) - save the list (charges credits) 3. [`g8_list_sequences`](https://docs.graph8.com/developers/mcp-tools/) - find the target sequence 4. [`g8_get_sequence_preview`](https://docs.graph8.com/developers/mcp-tools/) - confirm steps before enroll 5. [`g8_add_to_sequence`](https://docs.graph8.com/developers/mcp-tools/) - enroll contacts (sends real outreach) **Expected outcome.** A named list with 25 contacts and an enrollment confirmation, ready to begin outreach on the next sequence run. ## Reply triage in your IDE **Problem.** You start your morning and want to triage inbound replies across email, SMS, and LinkedIn from your terminal. **Prompt:** ``` Check my graph8 inbox for replies in the last 24 hours. For each warm reply (interested, asking for more info), pull the full thread, generate an AI draft reply, and show me the draft before sending. On approval, send the reply. Tag any reply that mentions pricing with "pricing-q". ``` **Tools used:** 1. [`g8_list_inbox`](https://docs.graph8.com/developers/mcp-tools/) - list reply threads with filters 2. [`g8_get_reply`](https://docs.graph8.com/developers/mcp-tools/) - get a single thread with full history 3. [`g8_get_reply_draft`](https://docs.graph8.com/developers/mcp-tools/) - AI-generated draft (charges credits) 4. [`g8_tag_reply`](https://docs.graph8.com/developers/mcp-tools/) - tag for categorization 5. [`g8_send_reply`](https://docs.graph8.com/developers/mcp-tools/) - send the reply (sends a real message) **Expected outcome.** All warm replies handled, drafts approved one by one, pricing-related threads tagged for later batch review. ## Push a list to Meta and LinkedIn ads **Problem.** You have a target-account list and want a matching audience syncing to both Meta and LinkedIn for an ABM campaign. **Prompt:** ``` List my graph8 contact lists and find the one named "Q1 ABM Targets". Create an audience sync to Meta Ads using that list, set to daily cadence. Create another sync to LinkedIn Ads with the same list. Trigger both syncs now and report the run status. ``` **Tools used:** 1. [`g8_get_lists`](https://docs.graph8.com/developers/mcp-tools/) - find the source list 2. [`g8_create_audience_sync`](https://docs.graph8.com/developers/mcp-tools/) - create the Meta sync 3. [`g8_create_audience_sync`](https://docs.graph8.com/developers/mcp-tools/) - create the LinkedIn sync 4. [`g8_trigger_audience_sync`](https://docs.graph8.com/developers/mcp-tools/) - run both syncs 5. [`g8_get_audience_sync_runs`](https://docs.graph8.com/developers/mcp-tools/) - check run status **Expected outcome.** Two new audience syncs (one per platform), both with a successful initial run pushing the list to the ad networks. ## Install graph8 tracking + progressive form on Next.js **Problem.** You want graph8 visitor identification and a progressive email-capture form live on your Next.js pricing page in under 10 minutes. **Prompt (requires `dev` mode):** ``` Connect this repo to graph8 and scan it. Once scanning completes, install the graph8 tracking snippet for Next.js in app/layout.tsx. Then generate an embedded progressive form for the pricing page and add it to app/pricing/page.tsx. Show me a diff before applying any edits. ``` **Tools used:** 1. [`g8_connect_repo`](https://docs.graph8.com/developers/mcp-tools/) - link the GitHub repo 2. Read the code, then [`g8_scan_repo`](https://docs.graph8.com/developers/mcp-tools/) - record the frameworks you found 3. [`g8_get_scan_results`](https://docs.graph8.com/developers/mcp-tools/) - read scan output 4. [`g8_get_tracking_snippet`](https://docs.graph8.com/developers/mcp-tools/) - get the Next.js snippet 5. [`g8_get_form_template`](https://docs.graph8.com/developers/mcp-tools/) - get the progressive form **Expected outcome.** Two edits applied (tracking snippet in layout, form on pricing page), verified by hitting the pricing page in dev and seeing `window.g8` defined plus a network request to `/p.js`. ## Voice dialer warmup for SDR start of day **Problem.** An SDR wants to start their morning by warming up a parallel dialer session on a fresh prospect list. **Prompt:** ``` List my graph8 contact lists and find "Today's Dial List". Find the from-number with the highest connect rate this week. Pick the voice agent named "Friendly Inbound SDR". Create a paused dialer session against that list, number, and agent. Tell me the session ID so I can open the dialer UI to start dialing. ``` **Tools used:** 1. [`g8_get_lists`](https://docs.graph8.com/developers/mcp-tools/) - find the list 2. [`g8_voice_list_numbers`](https://docs.graph8.com/developers/mcp-tools/) - pick a from-number 3. [`g8_voice_list_agents`](https://docs.graph8.com/developers/mcp-tools/) - pick a voice agent 4. [`g8_voice_create_dialer_session`](https://docs.graph8.com/developers/mcp-tools/) - create paused session (requires confirmation) **Expected outcome.** A dialer session in `paused` state, ready for the SDR to resume from the dialer UI. ## Full GTM setup walkthrough **Problem.** You just signed up and want to take a fresh repo from zero to live campaign in one prompt. **Prompt:** ``` Use the graph8 gtm_setup prompt template with repo_url=https://github.com/acme/app. ``` **Tools used:** The `gtm_setup` template orchestrates the full sequence: 1. [`g8_connect_repo`](https://docs.graph8.com/developers/mcp-tools/) - link the repo 2. Read the code, then [`g8_scan_repo`](https://docs.graph8.com/developers/mcp-tools/) - record the framework and pages you found 3. [`g8_install_spine`](https://docs.graph8.com/developers/mcp-tools/) - generate the tracking install plan 4. [`g8_apply_install`](https://docs.graph8.com/developers/mcp-tools/) - apply the install 5. [`g8_create_campaign`](https://docs.graph8.com/developers/mcp-tools/) - create a campaign 6. [`g8_launch_campaign`](https://docs.graph8.com/developers/mcp-tools/) - launch it (after confirmation) **Expected outcome.** Tracking installed in the repo, a campaign created in graph8, and either a dry-run report or a real launch depending on your confirmation. ## Where to go next **MCP Server overview** Setup, modes, and the full integration matrix. [Open](https://docs.graph8.com/developers/mcp/) **MCP Tool Reference** Every tool with parameters, return shapes, and credit costs. [Open](https://docs.graph8.com/developers/mcp-tools/) **MCP FAQ** Common errors, multi-org, security, and rate limits. [Open](https://docs.graph8.com/developers/mcp-faq/) **What You Can Ask** Free-form prompt patterns for graph8 MCP. [Open](https://docs.graph8.com/developers/what-you-can-ask/) --- # MCP FAQ > Answers to common questions about the graph8 MCP server - free tier, modes, errors, security, and multi-org Source: https://docs.graph8.com/developers/mcp-faq/ Common questions about connecting AI agents and IDEs to graph8 through the Model Context Protocol. If your question is not here, check the [MCP Server overview](https://docs.graph8.com/developers/mcp/) or file a bug in the GitHub repo for the relevant integration. > **Tip:** > Try the developer tier free - 1,000 credits, no card required. [Get started](https://app.graph8.com/signup). ## Plans and credits ### Is there a free tier for MCP? Yes. The Developer PAYG tier ships with 1,000 free credits on signup and no card required. MCP tool calls bill against that balance the same way SDK or REST calls do. **On both PAYG and Platform**, graph8-owned data tools — `g8_find_contacts`, `g8_find_companies`, all `g8_intent_*` reads, save-to-list — are **free within the 50 rps cap**. `g8_lookup_person` and `g8_lookup_company` are not free: each costs 2 credits per lookup, charged only when a match is found. Credits also deduct on AI tools (`g8_get_reply_draft`, copilot turns, skill execute with LLM type), waterfall enrichment (`g8_enrich_contacts`), voice minutes, meeting bookings, sequencer/campaign sends, and audience-sync pushes. See [Pricing](https://docs.graph8.com/developers/pricing/) for the full matrix. ### What is the price per tool call? Each credit is $0.05 on PAYG and $0.00666 on Platform overage. **graph8-owned data is free on both plans** within the 50 rps cap: first-party CRM (`/contacts`, `/companies`, `/deals`, etc.), B2B index search, intent reads, visitor reads, save-to-list. **Always-charging surfaces (both plans)**: single-record lookups (`/enrichment/lookup/person`, `/enrichment/lookup/company`: 2 credits per lookup, charged only when a match is found), email verify (`/enrichment/verify-email`: 1 credit per verify), waterfall enrichment through 3rd-party providers, copilot turns (per LLM tokens), AI reply drafts, skill execute with `type=llm`, voice minutes (20/min), meeting bookings (20 each), sequencer / campaign / newsletter sends, intent signal processing (background ingest, 1/10 events), audience-sync pushes, external email verifiers (Kickbox, ZeroBounce). Credit-charging tools are flagged in the [MCP Tool Reference](https://docs.graph8.com/developers/mcp-tools/) — look for the "credits" column. Full matrix in [Pricing](https://docs.graph8.com/developers/pricing/). ### How do I monitor my credit spend from MCP? Open **Profile -> Developer** in the graph8 app. The Usage panel shows credits consumed in the last 24 hours, 7 days, and billing month, broken down by source (MCP, SDK, REST, UI). Each tool call shows up with a timestamp and credit cost. ## Authentication ### Where do I get a personal API key vs an org API key? - **Personal API key**: **Profile -> Developer**. Scoped to your user inside the active org. Use this for local stdio MCP setups on your own machine. - **Org API key**: **Settings -> API**. Admin only. Scoped to the whole org for shared automation, service accounts, or production integrations. Most users want the personal key. See [Authentication](https://docs.graph8.com/developers/authentication/). ### What is the difference between remote OAuth and local stdio? | | Remote OAuth | Local stdio | |---|---|---| | Where the server runs | graph8 cloud | your machine | | How auth works | OAuth in browser, one-time | `G8_API_KEY` env var | | Install required | No | `uvx g8-mcp-server` | | Best for | Claude.ai, Claude Desktop, VS Code, hosted agents | Cursor, Windsurf, local dev, CI | | Tools loaded | All tools (mode filtering coming soon) | Filterable via `G8_MCP_MODE` | Remote OAuth is the default recommendation. See the [MCP setup matrix](https://docs.graph8.com/developers/mcp/) for which clients support which transport. ### Where does my API key actually live? For local stdio, the key lives in: - The `env` block of your MCP client config (`~/.cursor/mcp.json`, `~/.claude/mcp.json`, etc.), or - `~/.g8/credentials.json` if you ran `g8 login --api-key`. For remote OAuth there is no API key. graph8 issues an opaque session token tied to your PropelAuth identity. The token is held by your MCP client (Claude.ai, Claude Desktop, etc.) and rotated automatically. ### How do I revoke a session or key? - **OAuth sessions**: **Profile -> Connections** in the graph8 app. Click revoke next to the client name. - **Personal API keys**: **Profile -> Developer**. Delete the key. - **Org API keys**: **Settings -> API**. Admin only. Revocation is immediate. The client will get a 401 on the next tool call. ## Modes ### When do I use `dev` vs `gtm` vs `all` mode? | Mode | Audience | Tools | Set on | |---|---|---|---| | `dev` | Developers using Cursor, Windsurf, Claude Code | 17 - repo scan, tracking install, forms, KB | stdio only | | `gtm` | Campaign managers in Claude Desktop | 23 - campaigns, sequences, inbox, audience syncs | stdio only | | `all` | Default fallback | 35 - everything | stdio only | Mode is set via `G8_MCP_MODE` on stdio. Remote OAuth currently loads all tools. URL-based mode filtering for remote is on the roadmap (see [MCP Server overview](https://docs.graph8.com/developers/mcp/)). ### Why are some tools missing from my client? Three likely causes: 1. You are on stdio with `G8_MCP_MODE=dev` or `gtm` and the tool belongs to the other mode. Set `G8_MCP_MODE=all` or switch modes. 2. Your role does not grant access. Check **Settings -> Members** with an admin. 3. Your `g8-mcp-server` version is out of date. Run `uvx g8-mcp-server@latest` or `pip install -U g8-mcp-server`. ## Common errors ### `spawn uvx ENOENT` The `uvx` binary is not on your `PATH`. Install [uv](https://docs.astral.sh/uv/) (recommended) or replace `uvx` with the absolute path in your config: ```json { "command": "/Users/you/.local/bin/uvx", "args": ["g8-mcp-server"] } ``` ### `Authentication failed` or 401 The API key in your config is expired, revoked, or scoped to a different org. Regenerate the key from **Profile -> Developer** and update the `env` block in your MCP config. Restart the MCP client after changing config. For OAuth: revoke the session in **Profile -> Connections** and reconnect from your client. ### `Permission denied` The MCP can only do what your graph8 role can do. If a tool returns "permission denied", you do not have the underlying capability in the app either. Ask an admin to update your role in **Settings -> Members**. ### `Tool not found` Three likely causes: 1. Typo in the tool name. Tool names are case-sensitive and prefixed with `g8_`. 2. Tool belongs to a mode not loaded (see "Why are some tools missing"). 3. Your `g8-mcp-server` version is older than the tool. Update with `uvx g8-mcp-server@latest`. ### OAuth redirect loop Caused by stale browser cookies or a half-completed sign-in. Clear cookies for `be.graph8.com` and `app.graph8.com`, then reconnect. If you use a custom domain or self-hosted instance, make sure the `redirect_uri` registered in your client matches the canonical graph8 URL. ### `JSON parse error` on Claude Desktop startup The `claude_desktop_config.json` has invalid syntax. Common culprits: trailing comma, missing quote, smart quotes from a word processor. Paste it into a JSON validator like [jsonlint.com](https://jsonlint.com) and fix. ## Multi-org and multi-instance ### How do I switch graph8 orgs in my MCP client? Each MCP session is tied to one org. To switch: 1. Revoke the current session in **Profile -> Connections** (OAuth) or replace your API key (stdio). 2. Switch orgs in the graph8 app web UI. 3. Reconnect from your MCP client, or generate a new personal key under the new org. You can keep one config per org by adding multiple servers to `mcpServers`, e.g. `graph8-acme` and `graph8-staging`. ### Can I run multiple MCP instances in parallel? Yes. Each MCP client process is independent. You can have Cursor, Claude Desktop, and a CLI session all pointing at graph8 simultaneously. Credit billing is per call, not per session. For agent frameworks (LangChain, CrewAI, etc.) that spawn parallel tool calls, the server handles concurrent requests per session. Rate limits apply per org, not per session - see [Rate Limits](https://docs.graph8.com/developers/rate-limits/). ## Security ### What does the OAuth session see? The session sees exactly what your user account sees in the graph8 web app. Same role permissions, same org boundaries. The session does not see other orgs you are not a member of. ### Is my data sent to Anthropic / OpenAI / etc.? graph8 returns tool results to your MCP client. Your client (Claude, GPT, Cursor) then includes those results in the LLM prompt. graph8 does not push data to model providers directly. If you have a data residency requirement, set up a self-hosted MCP using stdio so the connection stays inside your machine. ### Can I scope an API key to specific tools? Not today. API keys carry the full permission set of the user or org they belong to. Tool-level scoping is on the roadmap. ## Timeouts and rate limits ### What is the rate limit? 50 requests per second + 1,000 requests per minute per org for all surfaces (REST, SDK, MCP). Burst above either cap returns HTTP 429 with a `Retry-After` header; responses also carry `X-RateLimit-*` headers for self-pacing. See [Rate Limits](https://docs.graph8.com/developers/rate-limits/). ### Why does my tool call time out? Long-running tools (`g8_build_contact_list`, `g8_enrich_contacts`) can take 30-90 seconds. Most MCP clients enforce a 60s timeout on tool responses by default. Workarounds: - Use the smaller-batch variant (e.g. `g8_find_contacts` first to preview, then `g8_build_contact_list` with a smaller `limit`). - For Claude Desktop, raise the timeout in app settings. - For programmatic agents, increase the per-tool timeout in your framework config. ## Preview tools ### Are there preview / dry-run tools? Yes. Use these before tools that cost credits or send real messages: - `g8_find_contacts` previews search results before `g8_build_contact_list` saves them. - `g8_get_sequence_preview` previews sequence steps and channels before launch. - `g8_lookup_person` / `g8_lookup_company` cost 2 credits per lookup (charged only when a match is found) but do not save - run them first if you want to check data quality before bulk enrichment. The graph8 MCP server enforces a "confirm before commit" rule for any tool that costs credits or sends outreach. Your client should prompt you each time. ## Filing bugs and requesting features - **Bugs**: open an issue against the relevant repo. For server bugs use the graph8 MCP server repo. For client bugs use the client repo. - **New tools**: open a discussion or contact your account manager. We prioritise tools by aggregate usage. - **Docs**: this site is open to PRs. Edit any `.mdx` file under `frontend/docs/src/content/docs/developers/`. ## Related - [MCP Server overview](https://docs.graph8.com/developers/mcp/) - [MCP Tool Reference](https://docs.graph8.com/developers/mcp-tools/) - [Pricing](https://docs.graph8.com/developers/pricing/) - [Rate Limits](https://docs.graph8.com/developers/rate-limits/) - [Authentication](https://docs.graph8.com/developers/authentication/) --- # Additional API operations through MCP > Complete inputs for CRM, app building, webhook, agent and developer operations. Source: https://docs.graph8.com/developers/mcp-reference/api-coverage/ These 145 tools extend progressive discovery: 117 arrived in MCP server 0.58.0, five additional reads require 0.59.0, five internal operator reports require 0.60.0, nine app-object operations require 0.61.0, and TLS readiness plus two public-widget calls require 0.62.0. Version 0.63.0 adds two protected credential exchanges, observed build reporting, and a read-only human-approval handoff. Version 0.64.0 adds the human-authorized decision relay and app-record change history. Availability requires that version or later and the appropriate organization permissions and feature enablement. Search with `g8_tool_search`, inspect the returned schema, and call the tool directly or through `g8_execute`. For changes and actions, the default call returns `confirmation_required` without sending an API request. Set `confirm` to `true` after reviewing the intended operation. API errors, ownership checks, key scopes and billing restrictions still apply. Reads preserve the full response, including pagination. For example, search for `rotate webhook secret` or the exact name `g8_webhook_rotate_webhook_secret`. Read the schema and obtain the webhook ID from a previous result. Rotating a secret changes webhook verification and requires an explicit confirmed call. App tools require the app builder to be enabled and the caller to own or administer the app. Sandbox tools require a test-mode key. Browser login, infrastructure control and human approval decisions are not exposed by this extension. ## g8_agency_get_agency_me > Get Agency Me. Uses the current organization and existing API permissions. API: GET /agency/me ### Inputs ```json { "properties": {}, "title": "g8_agency_get_agency_meArguments", "type": "object" } ``` ## g8_agency_list_agency_clients > List Agency Clients. Uses the current organization and existing API permissions. API: GET /agency/clients ### Inputs ```json { "properties": {}, "title": "g8_agency_list_agency_clientsArguments", "type": "object" } ``` ## g8_agent_cancel_agent_run > Cancel Agent Run. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /agent/runs/{run_id}/cancel ### Inputs ```json { "properties": { "run_id": { "description": "Run ID", "format": "uuid", "title": "Run Id", "type": "string" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "run_id" ], "title": "g8_agent_cancel_agent_runArguments", "type": "object" } ``` ## g8_agent_create_agent_run > Create Agent Run. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /agent/runs ### Inputs ```json { "$defs": { "ApiCreateAgentRunRequest": { "additionalProperties": false, "properties": { "agent_id": { "description": "Which agent drives the loop: 'onboarding', 'dialer_setup', 'sequencer_setup', 'campaign_run', 'voice_setup', 'marketplace', 'dev_setup', or a free-form value for an ad-hoc session.", "title": "Agent Id", "type": "string" }, "entry_point": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Entry Point" }, "goal": { "description": "Free text the planner works from.", "minLength": 1, "title": "Goal", "type": "string" }, "seed_message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "An opening user turn, rendered as the first message.", "title": "Seed Message" }, "session_title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Session Title" }, "skill_mode": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "'web_search' | 'deep_thinking' | 'research'. Unknown values are ignored.", "title": "Skill Mode" }, "subject_account_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Bind the run to a company.", "title": "Subject Account Id" }, "subject_campaign_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Bind the run to a campaign.", "title": "Subject Campaign Id" }, "title": { "maxLength": 500, "minLength": 1, "title": "Title", "type": "string" }, "visibility": { "default": "private", "description": "'private' (default) keeps the run yours. 'shared' lets the organization see it, though only you can cancel it.", "pattern": "^(shared|private)$", "title": "Visibility", "type": "string" } }, "required": [ "agent_id", "goal", "title" ], "title": "ApiCreateAgentRunRequest", "type": "object" } }, "properties": { "body": { "$ref": "#/$defs/ApiCreateAgentRunRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "body" ], "title": "g8_agent_create_agent_runArguments", "type": "object" } ``` ## g8_agent_get_agent_approval_receipts > Get Agent Approval Receipts. Uses the current organization and existing API permissions. API: GET /agent/approvals/{approval_id}/receipts ### Inputs ```json { "properties": { "approval_id": { "description": "Approval ID", "format": "uuid", "title": "Approval Id", "type": "string" } }, "required": [ "approval_id" ], "title": "g8_agent_get_agent_approval_receiptsArguments", "type": "object" } ``` ## g8_agent_get_agent_run > Get Agent Run. Uses the current organization and existing API permissions. API: GET /agent/runs/{run_id} ### Inputs ```json { "properties": { "run_id": { "description": "Run ID", "format": "uuid", "title": "Run Id", "type": "string" } }, "required": [ "run_id" ], "title": "g8_agent_get_agent_runArguments", "type": "object" } ``` ## g8_agent_list_agent_approval_history > List Agent Approval History. Uses the current organization and existing API permissions. API: GET /agent/approvals/history ### Inputs ```json { "properties": { "decision": { "anyOf": [ { "enum": [ "approve", "reject", "edit_and_approve" ], "type": "string" }, { "type": "null" } ], "default": null, "description": "Optional filter on the recorded decision. An unrecognised value is rejected with 422 rather than silently ignored.", "title": "Decision" } }, "title": "g8_agent_list_agent_approval_historyArguments", "type": "object" } ``` ## g8_agent_list_agent_approvals > List Agent Approvals. Uses the current organization and existing API permissions. API: GET /agent/approvals ### Inputs ```json { "properties": {}, "title": "g8_agent_list_agent_approvalsArguments", "type": "object" } ``` ## g8_agent_list_agent_runs > List Agent Runs. Uses the current organization and existing API permissions. API: GET /agent/runs ### Inputs ```json { "properties": { "statuses": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "description": "Optional repeatable status filter: ?statuses=running&statuses=queued", "title": "Statuses" }, "agent_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Agent Id" }, "include_shared": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "description": "When true (default), include org-wide shared runs. When false, return only runs this key owns.", "title": "Include Shared" }, "limit": { "anyOf": [ { "maximum": 500, "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "title": "Limit" }, "offset": { "anyOf": [ { "minimum": 0, "type": "integer" }, { "type": "null" } ], "default": null, "title": "Offset" } }, "title": "g8_agent_list_agent_runsArguments", "type": "object" } ``` ## g8_analytics_get_campaign_visitors > Get Campaign Visitors. Uses the current organization and existing API permissions. API: GET /analytics/campaign-visitors ### Inputs ```json { "properties": { "start_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Window start (inclusive), YYYY-MM-DD. If omitted, derived from `days`.", "title": "Start Date" }, "end_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Window end (inclusive), YYYY-MM-DD. If omitted, today.", "title": "End Date" }, "days": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "description": "Rolling 'last N days from today'. Ignored when start_date/end_date are supplied.", "title": "Days" }, "limit": { "anyOf": [ { "maximum": 200, "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "description": "Row cap for the steps + contacts tables", "title": "Limit" }, "include_contacts": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "description": "Include the Top contacts table (contact name + email \u2014 PII). Set false to omit it entirely for a privacy-lean response.", "title": "Include Contacts" } }, "title": "g8_analytics_get_campaign_visitorsArguments", "type": "object" } ``` ## g8_app_claim_app_domain > Claim App Domain. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /apps/{app_id}/domains ### Inputs ```json { "$defs": { "ApiClaimDomainRequest": { "additionalProperties": false, "properties": { "hostname": { "description": "The hostname to serve this app on. Normalized server-side: case, a trailing dot and unicode all resolve to one canonical form.", "maxLength": 253, "title": "Hostname", "type": "string" } }, "required": [ "hostname" ], "title": "ApiClaimDomainRequest", "type": "object" } }, "properties": { "app_id": { "title": "App Id", "type": "string" }, "body": { "$ref": "#/$defs/ApiClaimDomainRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "app_id", "body" ], "title": "g8_app_claim_app_domainArguments", "type": "object" } ``` ## g8_app_clear_app_limit > Clear App Limit. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: DELETE /apps/{app_id}/limit ### Inputs ```json { "properties": { "app_id": { "title": "App Id", "type": "string" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "app_id" ], "title": "g8_app_clear_app_limitArguments", "type": "object" } ``` ## g8_app_clear_app_source > Clear App Source. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: DELETE /apps/{app_id}/source ### Inputs ```json { "properties": { "app_id": { "title": "App Id", "type": "string" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "app_id" ], "title": "g8_app_clear_app_sourceArguments", "type": "object" } ``` ## g8_app_create_app_credential > Create App Credential. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /apps/{app_id}/credentials ### Inputs ```json { "$defs": { "ApiCreateCredentialRequest": { "additionalProperties": false, "properties": { "client_org_id": { "description": "The client organization this credential acts for. It must already have a consented installation of the app \u2014 a credential is bound to one tenant and cannot be pointed at another later.", "title": "Client Org Id", "type": "string" } }, "required": [ "client_org_id" ], "title": "ApiCreateCredentialRequest", "type": "object" } }, "properties": { "app_id": { "title": "App Id", "type": "string" }, "body": { "$ref": "#/$defs/ApiCreateCredentialRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "app_id", "body" ], "title": "g8_app_create_app_credentialArguments", "type": "object" } ``` ## g8_app_delete_app_secret > Delete App Secret. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: DELETE /apps/{app_id}/secrets/{secret_key} ### Inputs ```json { "properties": { "app_id": { "title": "App Id", "type": "string" }, "secret_key": { "title": "Secret Key", "type": "string" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "app_id", "secret_key" ], "title": "g8_app_delete_app_secretArguments", "type": "object" } ``` ## g8_app_get_active_deployment > Get Active Deployment. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. API: GET /apps/{app_id}/deployments/active ### Inputs ```json { "properties": { "app_id": { "title": "App Id", "type": "string" } }, "required": [ "app_id" ], "title": "g8_app_get_active_deploymentArguments", "type": "object" } ``` ## g8_app_get_app_limit > Get App Limit. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. API: GET /apps/{app_id}/limit ### Inputs ```json { "properties": { "app_id": { "title": "App Id", "type": "string" } }, "required": [ "app_id" ], "title": "g8_app_get_app_limitArguments", "type": "object" } ``` ## g8_app_get_app_logs > Get App Logs. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. API: GET /apps/{app_id}/logs ### Inputs ```json { "properties": { "app_id": { "title": "App Id", "type": "string" }, "tail_lines": { "anyOf": [ { "maximum": 2000, "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "description": "Lines per replica, newest last.", "title": "Tail Lines" } }, "required": [ "app_id" ], "title": "g8_app_get_app_logsArguments", "type": "object" } ``` ## g8_app_get_app_runtime > Get App Runtime. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. API: GET /apps/{app_id}/runtime ### Inputs ```json { "properties": { "app_id": { "title": "App Id", "type": "string" } }, "required": [ "app_id" ], "title": "g8_app_get_app_runtimeArguments", "type": "object" } ``` ## g8_app_get_app_usage > Get App Usage. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. API: GET /apps/{app_id}/usage ### Inputs ```json { "properties": { "app_id": { "title": "App Id", "type": "string" }, "period": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Calendar month `YYYY-MM`. Defaults to the current month.", "title": "Period" } }, "required": [ "app_id" ], "title": "g8_app_get_app_usageArguments", "type": "object" } ``` ## g8_app_get_deployment > Get Deployment. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. API: GET /apps/{app_id}/deployments/{deployment_id} ### Inputs ```json { "properties": { "app_id": { "title": "App Id", "type": "string" }, "deployment_id": { "title": "Deployment Id", "type": "string" } }, "required": [ "app_id", "deployment_id" ], "title": "g8_app_get_deploymentArguments", "type": "object" } ``` ## g8_app_get_deployment_logs > Get Deployment Logs. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. API: GET /apps/{app_id}/deployments/{deployment_id}/logs ### Inputs ```json { "properties": { "app_id": { "title": "App Id", "type": "string" }, "deployment_id": { "title": "Deployment Id", "type": "string" }, "tail_lines": { "anyOf": [ { "maximum": 2000, "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "description": "Lines per build step, newest last.", "title": "Tail Lines" } }, "required": [ "app_id", "deployment_id" ], "title": "g8_app_get_deployment_logsArguments", "type": "object" } ``` ## g8_app_get_installed_app > Get Installed App. Uses the current organization and existing API permissions. API: GET /installed-apps/{app_id} ### Inputs ```json { "properties": { "app_id": { "title": "App Id", "type": "string" } }, "required": [ "app_id" ], "title": "g8_app_get_installed_appArguments", "type": "object" } ``` ## g8_app_list_app_credentials > List App Credentials. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. API: GET /apps/{app_id}/credentials ### Inputs ```json { "properties": { "app_id": { "title": "App Id", "type": "string" } }, "required": [ "app_id" ], "title": "g8_app_list_app_credentialsArguments", "type": "object" } ``` ## g8_app_list_app_deployments > List App Deployments. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. API: GET /apps/{app_id}/deployments ### Inputs ```json { "properties": { "app_id": { "title": "App Id", "type": "string" } }, "required": [ "app_id" ], "title": "g8_app_list_app_deploymentsArguments", "type": "object" } ``` ## g8_app_list_app_domains > List App Domains. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. API: GET /apps/{app_id}/domains ### Inputs ```json { "properties": { "app_id": { "title": "App Id", "type": "string" } }, "required": [ "app_id" ], "title": "g8_app_list_app_domainsArguments", "type": "object" } ``` ## g8_app_list_app_installs > List App Installs. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. API: GET /apps/{app_id}/installs ### Inputs ```json { "properties": { "app_id": { "title": "App Id", "type": "string" } }, "required": [ "app_id" ], "title": "g8_app_list_app_installsArguments", "type": "object" } ``` ## g8_app_list_app_secrets > List App Secrets. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. API: GET /apps/{app_id}/secrets ### Inputs ```json { "properties": { "app_id": { "title": "App Id", "type": "string" } }, "required": [ "app_id" ], "title": "g8_app_list_app_secretsArguments", "type": "object" } ``` ## g8_app_list_installed_apps > List Installed Apps. Uses the current organization and existing API permissions. API: GET /installed-apps ### Inputs ```json { "properties": {}, "title": "g8_app_list_installed_appsArguments", "type": "object" } ``` ## g8_app_put_app_secret > Put App Secret. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: PUT /apps/{app_id}/secrets/{secret_key} ### Inputs ```json { "$defs": { "ApiPutAppSecretRequest": { "additionalProperties": false, "properties": { "provider_ref": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Secret-manager PATH for the value. Never the value itself.", "title": "Provider Ref" } }, "title": "ApiPutAppSecretRequest", "type": "object" } }, "properties": { "app_id": { "title": "App Id", "type": "string" }, "secret_key": { "title": "Secret Key", "type": "string" }, "body": { "$ref": "#/$defs/ApiPutAppSecretRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "app_id", "secret_key", "body" ], "title": "g8_app_put_app_secretArguments", "type": "object" } ``` ## g8_app_release_app_domain > Release App Domain. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: DELETE /apps/{app_id}/domains/{hostname} ### Inputs ```json { "properties": { "app_id": { "title": "App Id", "type": "string" }, "hostname": { "title": "Hostname", "type": "string" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "app_id", "hostname" ], "title": "g8_app_release_app_domainArguments", "type": "object" } ``` ## g8_app_revoke_app_credential > Revoke App Credential. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: DELETE /apps/{app_id}/credentials/{client_id} ### Inputs ```json { "properties": { "app_id": { "title": "App Id", "type": "string" }, "client_id": { "title": "Client Id", "type": "string" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "app_id", "client_id" ], "title": "g8_app_revoke_app_credentialArguments", "type": "object" } ``` ## g8_app_rollback_deployment > Rollback Deployment. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /apps/{app_id}/deployments/{deployment_id}/rollback ### Inputs ```json { "properties": { "app_id": { "title": "App Id", "type": "string" }, "deployment_id": { "title": "Deployment Id", "type": "string" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "app_id", "deployment_id" ], "title": "g8_app_rollback_deploymentArguments", "type": "object" } ``` ## g8_app_set_app_limit > Set App Limit. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: PUT /apps/{app_id}/limit ### Inputs ```json { "$defs": { "ApiSetAppLimitRequest": { "additionalProperties": false, "properties": { "credit_cap": { "description": "Hard cap in credits for the window. `0` means 'cap at zero', NOT 'uncapped' -- to remove a cap, DELETE the limit instead.", "minimum": 0.0, "title": "Credit Cap", "type": "integer" }, "window": { "default": "calendar_month", "description": "Cap window. `calendar_month` is the only value today.", "title": "Window", "type": "string" } }, "required": [ "credit_cap" ], "title": "ApiSetAppLimitRequest", "type": "object" } }, "properties": { "app_id": { "title": "App Id", "type": "string" }, "body": { "$ref": "#/$defs/ApiSetAppLimitRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "app_id", "body" ], "title": "g8_app_set_app_limitArguments", "type": "object" } ``` ## g8_app_set_app_source > Set App Source. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: PUT /apps/{app_id}/source ### Inputs ```json { "$defs": { "ApiAppSourceRequest": { "additionalProperties": false, "properties": { "credential_ref": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Secret-manager PATH for the fetch credential. Never the credential itself.", "title": "Credential Ref" }, "default_branch": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Defaults to the repository's own default when omitted.", "title": "Default Branch" }, "provider": { "description": "One of: github, gitlab, bitbucket.", "title": "Provider", "type": "string" }, "repo_url": { "description": "Clone URL.", "maxLength": 2000, "minLength": 1, "title": "Repo Url", "type": "string" } }, "required": [ "provider", "repo_url" ], "title": "ApiAppSourceRequest", "type": "object" } }, "properties": { "app_id": { "title": "App Id", "type": "string" }, "body": { "$ref": "#/$defs/ApiAppSourceRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "app_id", "body" ], "title": "g8_app_set_app_sourceArguments", "type": "object" } ``` ## g8_app_set_app_status > Set App Status. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /apps/{app_id}/status ### Inputs ```json { "$defs": { "ApiSetAppStatusRequest": { "additionalProperties": false, "properties": { "status": { "description": "`draft`, `published`, `suspended` or `archived`. `active` is accepted as the legacy spelling of `published`.", "title": "Status", "type": "string" } }, "required": [ "status" ], "title": "ApiSetAppStatusRequest", "type": "object" } }, "properties": { "app_id": { "title": "App Id", "type": "string" }, "body": { "$ref": "#/$defs/ApiSetAppStatusRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "app_id", "body" ], "title": "g8_app_set_app_statusArguments", "type": "object" } ``` ## g8_app_uninstall_app > Uninstall App. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: DELETE /installed-apps/{app_id} ### Inputs ```json { "properties": { "app_id": { "title": "App Id", "type": "string" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "app_id" ], "title": "g8_app_uninstall_appArguments", "type": "object" } ``` ## g8_app_verify_app_domain > Verify App Domain. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /apps/{app_id}/domains/{hostname}/verify ### Inputs ```json { "properties": { "app_id": { "title": "App Id", "type": "string" }, "hostname": { "title": "Hostname", "type": "string" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "app_id", "hostname" ], "title": "g8_app_verify_app_domainArguments", "type": "object" } ``` ## g8_appointments_update_ooo > Update an out-of-office entry. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: PATCH /appointments/out-of-office/{ooo_uuid} ### Inputs ```json { "$defs": { "ApiUpdateOutOfOfficeRequest": { "additionalProperties": false, "properties": { "end": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "End" }, "notes": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Notes" }, "public_note": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Public Note" }, "reason": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Reason" }, "start": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Start" }, "time_zone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Time Zone" }, "to_user_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "To User Id" } }, "title": "ApiUpdateOutOfOfficeRequest", "type": "object" } }, "properties": { "ooo_uuid": { "description": "UUID of the out-of-office entry to update.", "title": "Ooo Uuid", "type": "string" }, "body": { "$ref": "#/$defs/ApiUpdateOutOfOfficeRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "ooo_uuid", "body" ], "title": "g8_appointments_update_oooArguments", "type": "object" } ``` ## g8_audit_list_request_logs > List Request Logs. Uses the current organization and existing API permissions. API: GET /logs ### Inputs ```json { "properties": { "limit": { "anyOf": [ { "maximum": 200, "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "description": "Max entries to return (most recent first)", "title": "Limit" } }, "title": "g8_audit_list_request_logsArguments", "type": "object" } ``` ## g8_connection_describe_current_key > Describe Current Key. Uses the current organization and existing API permissions. API: GET /me ### Inputs ```json { "properties": {}, "title": "g8_connection_describe_current_keyArguments", "type": "object" } ``` ## g8_connection_get_mcp_connection > Get Mcp Connection. Uses the current organization and existing API permissions. API: GET /mcp/connection ### Inputs ```json { "properties": {}, "title": "g8_connection_get_mcp_connectionArguments", "type": "object" } ``` ## g8_crm_assert_companies_batch > Assert Companies Batch. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: PUT /companies/assert/batch ### Inputs ```json { "$defs": { "ApiBatchCompanyAssertRequest": { "additionalProperties": false, "properties": { "companies": { "description": "Up to 100 companies with domain or linkedin_url", "items": { "additionalProperties": true, "type": "object" }, "maxItems": 100, "title": "Companies", "type": "array" }, "list_id": { "description": "Target list ID", "title": "List Id", "type": "integer" } }, "required": [ "companies", "list_id" ], "title": "ApiBatchCompanyAssertRequest", "type": "object" } }, "properties": { "body": { "$ref": "#/$defs/ApiBatchCompanyAssertRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "body" ], "title": "g8_crm_assert_companies_batchArguments", "type": "object" } ``` ## g8_crm_assert_company > Assert Company. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: PUT /companies/assert ### Inputs ```json { "$defs": { "ApiCompanyAssertRequest": { "additionalProperties": false, "properties": { "annual_revenue": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Annual Revenue" }, "city": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "City" }, "country": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Country" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "domain": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Company domain (primary match key)", "title": "Domain" }, "employee_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Employee Count" }, "industry": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Industry" }, "linkedin_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "LinkedIn URL (fallback match key)", "title": "Linkedin Url" }, "list_id": { "description": "Target list ID", "title": "List Id", "type": "integer" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "website": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Website" } }, "required": [ "list_id" ], "title": "ApiCompanyAssertRequest", "type": "object" } }, "properties": { "body": { "$ref": "#/$defs/ApiCompanyAssertRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "body" ], "title": "g8_crm_assert_companyArguments", "type": "object" } ``` ## g8_crm_assert_contact > Assert Contact. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: PUT /contacts/assert ### Inputs ```json { "$defs": { "ApiContactAssertRequest": { "additionalProperties": false, "properties": { "city": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "City" }, "company_domain": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Company Domain" }, "country": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Country" }, "direct_phone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Direct Phone" }, "first_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "First Name" }, "job_department": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Job Department" }, "job_title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Job Title" }, "last_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Name" }, "linkedin_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "LinkedIn URL (fallback match key)", "title": "Linkedin Url" }, "list_id": { "description": "Target list ID", "title": "List Id", "type": "integer" }, "mobile_phone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Mobile Phone" }, "personal_emails": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Personal Emails" }, "seniority_level": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Seniority Level" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "work_email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Work email (primary match key)", "title": "Work Email" } }, "required": [ "list_id" ], "title": "ApiContactAssertRequest", "type": "object" } }, "properties": { "body": { "$ref": "#/$defs/ApiContactAssertRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "body" ], "title": "g8_crm_assert_contactArguments", "type": "object" } ``` ## g8_crm_assert_contacts_batch > Assert Contacts Batch. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: PUT /contacts/assert/batch ### Inputs ```json { "$defs": { "ApiBatchContactAssertRequest": { "additionalProperties": false, "properties": { "contacts": { "description": "Up to 100 contacts with work_email or linkedin_url", "items": { "additionalProperties": true, "type": "object" }, "maxItems": 100, "title": "Contacts", "type": "array" }, "list_id": { "description": "Target list ID", "title": "List Id", "type": "integer" } }, "required": [ "contacts", "list_id" ], "title": "ApiBatchContactAssertRequest", "type": "object" } }, "properties": { "body": { "$ref": "#/$defs/ApiBatchContactAssertRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "body" ], "title": "g8_crm_assert_contacts_batchArguments", "type": "object" } ``` ## g8_crm_assert_deal > Assert Deal. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: PUT /deals/assert ### Inputs ```json { "$defs": { "ApiDealAssertRequest": { "additionalProperties": false, "properties": { "amount": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Amount" }, "close_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Close Date" }, "company_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Company ID (match key, combined with name)", "title": "Company Id" }, "currency": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Currency" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "name": { "description": "Deal name (match key)", "title": "Name", "type": "string" }, "pipeline_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Pipeline Id" }, "stage_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Stage Id" } }, "required": [ "name" ], "title": "ApiDealAssertRequest", "type": "object" } }, "properties": { "body": { "$ref": "#/$defs/ApiDealAssertRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "body" ], "title": "g8_crm_assert_dealArguments", "type": "object" } ``` ## g8_crm_assert_deal_contact > Assert Deal Contact. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: PUT /contacts/{contact_id}/deals/assert ### Inputs ```json { "$defs": { "ApiDealContactAssertRequest": { "additionalProperties": false, "properties": { "deal_id": { "description": "Deal ID to associate", "title": "Deal Id", "type": "string" } }, "required": [ "deal_id" ], "title": "ApiDealContactAssertRequest", "type": "object" } }, "properties": { "contact_id": { "description": "Contact ID", "title": "Contact Id", "type": "integer" }, "body": { "$ref": "#/$defs/ApiDealContactAssertRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "contact_id", "body" ], "title": "g8_crm_assert_deal_contactArguments", "type": "object" } ``` ## g8_crm_create_company_column > Create Company Column. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /companies/columns/create ### Inputs ```json { "$defs": { "ApiCreateCompanyColumnRequest": { "additionalProperties": false, "properties": { "created_by": { "description": "Email of the user creating the column (for audit).", "format": "email", "title": "Created By", "type": "string" }, "data_type": { "default": "text", "description": "Column data type. Currently only 'text' is supported.", "enum": [ "text", "int", "numeric", "boolean" ], "title": "Data Type", "type": "string" }, "enrichment": { "anyOf": [ { "$ref": "#/$defs/ApiCreateFieldEnrichmentInput" }, { "type": "null" } ], "default": null, "description": "STRONGLY RECOMMENDED. Optional waterfall enrichment block. When supplied, a companion `enrichment_configs` row is created alongside the column so the UI 'Enrich' button works immediately. Without it the column exists but isn't enrichable until a pipeline is configured via POST /enrichment/waterfall/configs." }, "list_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "description": "If set, the column is list-scoped (only visible on that list). Required when `enrichment` is supplied \u2014 waterfall pipelines are list-scoped.", "title": "List Id" }, "title": { "description": "Column title shown in the UI (e.g. 'Tier')", "title": "Title", "type": "string" } }, "required": [ "created_by", "title" ], "title": "ApiCreateCompanyColumnRequest", "type": "object" }, "ApiCreateFieldEnrichmentInput": { "additionalProperties": false, "properties": { "email_verification": { "anyOf": [ { "$ref": "#/$defs/ApiEmailVerificationInput" }, { "type": "null" } ], "default": null, "description": "Optional post-enrichment email verification config." }, "field_to_enrich": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Target field identifier. Defaults to the new column's slug.", "title": "Field To Enrich" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Pipeline name. Defaults to `<column title> Enrichment` if omitted.", "title": "Name" }, "providers": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "description": "Ordered list of provider names. Without `target_field`, server maps each to its default email-finder action (legacy). With `target_field`, filters the column template to these providers (correct per-field action preserved). Mutually exclusive with `steps`.", "title": "Providers" }, "skip_existing_values": { "default": true, "description": "If true, skip rows where the target field is already populated.", "title": "Skip Existing Values", "type": "boolean" }, "steps": { "anyOf": [ { "items": { "$ref": "#/$defs/ApiWaterfallStepInput" }, "type": "array" }, { "type": "null" } ], "default": null, "description": "Full caller-supplied step list. Persisted verbatim. Mutually exclusive with `providers`.", "title": "Steps" }, "target_field": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Kind of data this column holds \u2014 CONTACT_DIRECT_PHONE, CONTACT_MOBILE_PHONE, CONTACT_WORK_EMAIL, CONTACT_LINKEDIN_URL, COMPANY_PHONE, ... or a friendly synonym ('phone', 'mobile', 'email', 'linkedin'). When set, the pipeline is built from the server's per-column TEMPLATE so each provider runs its CORRECT action for that data kind (e.g. leadmagic `mobile_finder` for a phone, NOT `email_finder`) \u2014 the same actions the web UI's 'Add column -> Waterfall' picks. Combine with `providers[]` to keep only those providers from the template. Mutually exclusive with `steps`. When omitted, the legacy `providers[]` email-finder shortcut is unchanged.", "title": "Target Field" }, "type": { "default": "waterfall", "description": "Pipeline kind. Only 'waterfall' is supported today.", "title": "Type", "type": "string" } }, "title": "ApiCreateFieldEnrichmentInput", "type": "object" }, "ApiEmailVerificationInput": { "additionalProperties": false, "properties": { "accept_catchall": { "default": false, "description": "Treat catch-all responses as valid (domains that accept any address).", "title": "Accept Catchall", "type": "boolean" }, "enabled": { "default": true, "description": "Enable post-enrichment email verification", "title": "Enabled", "type": "boolean" }, "provider": { "default": "zerobounce", "description": "Verification provider. Charges credits when use_system_credentials=True.", "enum": [ "zerobounce", "hunter", "icypeas", "leadmagic" ], "title": "Provider", "type": "string" }, "use_system_credentials": { "default": true, "description": "Use graph8-managed API keys (charges credits). False = use org's BYOK keys.", "title": "Use System Credentials", "type": "boolean" }, "valid_statuses": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "description": "Verification statuses to accept as valid. Typical: ['valid'] or ['valid','catch-all'].", "title": "Valid Statuses" } }, "title": "ApiEmailVerificationInput", "type": "object" }, "ApiWaterfallStepInput": { "additionalProperties": false, "properties": { "action": { "description": "Provider action: lookup_person, find_email, enrich_person, ...", "title": "Action", "type": "string" }, "action_display_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Human-readable label shown in UI. Auto-titled from `action` if omitted.", "title": "Action Display Name" }, "confidence_threshold": { "default": 0.0, "maximum": 1.0, "minimum": 0.0, "title": "Confidence Threshold", "type": "number" }, "config": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "description": "Provider-specific options (e.g. apollo `reveal_phone_numbers=true`).", "title": "Config" }, "enabled": { "default": true, "title": "Enabled", "type": "boolean" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Step identifier. Auto-generated (`step_<provider>_<uuid8>`) if omitted.", "title": "Id" }, "input_mapping": { "anyOf": [ { "additionalProperties": { "type": "string" }, "type": "object" }, { "type": "null" } ], "default": null, "description": "Maps provider input names to graph8 column identifiers (CONTACT_*).", "title": "Input Mapping" }, "is_system_provider": { "default": false, "description": "True = use graph8-managed credentials + charge credits. False = use org BYOK.", "title": "Is System Provider", "type": "boolean" }, "max_retries": { "default": 0, "maximum": 3.0, "minimum": 0.0, "title": "Max Retries", "type": "integer" }, "order": { "default": 0, "description": "0-indexed execution order.", "minimum": 0.0, "title": "Order", "type": "integer" }, "output_field": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Dotted path the worker writes into (e.g. 'contact.work_email').", "title": "Output Field" }, "output_mapping": { "anyOf": [ { "additionalProperties": { "type": "string" }, "type": "object" }, { "type": "null" } ], "default": null, "description": "Optional output field remap (deprecated \u2014 prefer `output_field`).", "title": "Output Mapping" }, "provider": { "description": "Provider name: graph8, hunter, prospeo, dropcontact, apollo, ...", "title": "Provider", "type": "string" }, "stop_on_success": { "default": true, "title": "Stop On Success", "type": "boolean" } }, "required": [ "action", "provider" ], "title": "ApiWaterfallStepInput", "type": "object" } }, "properties": { "body": { "$ref": "#/$defs/ApiCreateCompanyColumnRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "body" ], "title": "g8_crm_create_company_columnArguments", "type": "object" } ``` ## g8_crm_create_contact_column > Create Contact Column. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /contacts/columns/create ### Inputs ```json { "$defs": { "ApiCreateContactColumnRequest": { "additionalProperties": false, "properties": { "created_by": { "description": "Email of the user creating the column (for audit).", "format": "email", "title": "Created By", "type": "string" }, "data_type": { "default": "text", "description": "Column data type. Currently only 'text' is supported.", "enum": [ "text", "int", "numeric", "boolean" ], "title": "Data Type", "type": "string" }, "enrichment": { "anyOf": [ { "$ref": "#/$defs/ApiCreateFieldEnrichmentInput" }, { "type": "null" } ], "default": null, "description": "STRONGLY RECOMMENDED. Optional waterfall enrichment block. When supplied, a companion `enrichment_configs` row is created alongside the column so the UI 'Enrich' button works immediately. Without it the column exists but isn't enrichable until a pipeline is configured via POST /enrichment/waterfall/configs." }, "list_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "description": "If set, the column is list-scoped (only visible on that list). Required when `enrichment` is supplied \u2014 waterfall pipelines are list-scoped.", "title": "List Id" }, "title": { "description": "Column title shown in the UI (e.g. 'Lead Score')", "title": "Title", "type": "string" } }, "required": [ "created_by", "title" ], "title": "ApiCreateContactColumnRequest", "type": "object" }, "ApiCreateFieldEnrichmentInput": { "additionalProperties": false, "properties": { "email_verification": { "anyOf": [ { "$ref": "#/$defs/ApiEmailVerificationInput" }, { "type": "null" } ], "default": null, "description": "Optional post-enrichment email verification config." }, "field_to_enrich": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Target field identifier. Defaults to the new column's slug.", "title": "Field To Enrich" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Pipeline name. Defaults to `<column title> Enrichment` if omitted.", "title": "Name" }, "providers": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "description": "Ordered list of provider names. Without `target_field`, server maps each to its default email-finder action (legacy). With `target_field`, filters the column template to these providers (correct per-field action preserved). Mutually exclusive with `steps`.", "title": "Providers" }, "skip_existing_values": { "default": true, "description": "If true, skip rows where the target field is already populated.", "title": "Skip Existing Values", "type": "boolean" }, "steps": { "anyOf": [ { "items": { "$ref": "#/$defs/ApiWaterfallStepInput" }, "type": "array" }, { "type": "null" } ], "default": null, "description": "Full caller-supplied step list. Persisted verbatim. Mutually exclusive with `providers`.", "title": "Steps" }, "target_field": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Kind of data this column holds \u2014 CONTACT_DIRECT_PHONE, CONTACT_MOBILE_PHONE, CONTACT_WORK_EMAIL, CONTACT_LINKEDIN_URL, COMPANY_PHONE, ... or a friendly synonym ('phone', 'mobile', 'email', 'linkedin'). When set, the pipeline is built from the server's per-column TEMPLATE so each provider runs its CORRECT action for that data kind (e.g. leadmagic `mobile_finder` for a phone, NOT `email_finder`) \u2014 the same actions the web UI's 'Add column -> Waterfall' picks. Combine with `providers[]` to keep only those providers from the template. Mutually exclusive with `steps`. When omitted, the legacy `providers[]` email-finder shortcut is unchanged.", "title": "Target Field" }, "type": { "default": "waterfall", "description": "Pipeline kind. Only 'waterfall' is supported today.", "title": "Type", "type": "string" } }, "title": "ApiCreateFieldEnrichmentInput", "type": "object" }, "ApiEmailVerificationInput": { "additionalProperties": false, "properties": { "accept_catchall": { "default": false, "description": "Treat catch-all responses as valid (domains that accept any address).", "title": "Accept Catchall", "type": "boolean" }, "enabled": { "default": true, "description": "Enable post-enrichment email verification", "title": "Enabled", "type": "boolean" }, "provider": { "default": "zerobounce", "description": "Verification provider. Charges credits when use_system_credentials=True.", "enum": [ "zerobounce", "hunter", "icypeas", "leadmagic" ], "title": "Provider", "type": "string" }, "use_system_credentials": { "default": true, "description": "Use graph8-managed API keys (charges credits). False = use org's BYOK keys.", "title": "Use System Credentials", "type": "boolean" }, "valid_statuses": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "description": "Verification statuses to accept as valid. Typical: ['valid'] or ['valid','catch-all'].", "title": "Valid Statuses" } }, "title": "ApiEmailVerificationInput", "type": "object" }, "ApiWaterfallStepInput": { "additionalProperties": false, "properties": { "action": { "description": "Provider action: lookup_person, find_email, enrich_person, ...", "title": "Action", "type": "string" }, "action_display_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Human-readable label shown in UI. Auto-titled from `action` if omitted.", "title": "Action Display Name" }, "confidence_threshold": { "default": 0.0, "maximum": 1.0, "minimum": 0.0, "title": "Confidence Threshold", "type": "number" }, "config": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "description": "Provider-specific options (e.g. apollo `reveal_phone_numbers=true`).", "title": "Config" }, "enabled": { "default": true, "title": "Enabled", "type": "boolean" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Step identifier. Auto-generated (`step_<provider>_<uuid8>`) if omitted.", "title": "Id" }, "input_mapping": { "anyOf": [ { "additionalProperties": { "type": "string" }, "type": "object" }, { "type": "null" } ], "default": null, "description": "Maps provider input names to graph8 column identifiers (CONTACT_*).", "title": "Input Mapping" }, "is_system_provider": { "default": false, "description": "True = use graph8-managed credentials + charge credits. False = use org BYOK.", "title": "Is System Provider", "type": "boolean" }, "max_retries": { "default": 0, "maximum": 3.0, "minimum": 0.0, "title": "Max Retries", "type": "integer" }, "order": { "default": 0, "description": "0-indexed execution order.", "minimum": 0.0, "title": "Order", "type": "integer" }, "output_field": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Dotted path the worker writes into (e.g. 'contact.work_email').", "title": "Output Field" }, "output_mapping": { "anyOf": [ { "additionalProperties": { "type": "string" }, "type": "object" }, { "type": "null" } ], "default": null, "description": "Optional output field remap (deprecated \u2014 prefer `output_field`).", "title": "Output Mapping" }, "provider": { "description": "Provider name: graph8, hunter, prospeo, dropcontact, apollo, ...", "title": "Provider", "type": "string" }, "stop_on_success": { "default": true, "title": "Stop On Success", "type": "boolean" } }, "required": [ "action", "provider" ], "title": "ApiWaterfallStepInput", "type": "object" } }, "properties": { "body": { "$ref": "#/$defs/ApiCreateContactColumnRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "body" ], "title": "g8_crm_create_contact_columnArguments", "type": "object" } ``` ## g8_crm_delete_company > Delete Company. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: DELETE /companies/{company_id} ### Inputs ```json { "properties": { "company_id": { "description": "Company ID", "title": "Company Id", "type": "integer" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "company_id" ], "title": "g8_crm_delete_companyArguments", "type": "object" } ``` ## g8_crm_delete_company_column > Delete Company Column. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: DELETE /companies/columns/{column_id} ### Inputs ```json { "properties": { "column_id": { "description": "ID of the column to delete", "title": "Column Id", "type": "integer" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "column_id" ], "title": "g8_crm_delete_company_columnArguments", "type": "object" } ``` ## g8_crm_delete_contact > Delete Contact. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: DELETE /contacts/{contact_id} ### Inputs ```json { "properties": { "contact_id": { "description": "Contact ID", "title": "Contact Id", "type": "integer" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "contact_id" ], "title": "g8_crm_delete_contactArguments", "type": "object" } ``` ## g8_crm_delete_contact_column > Delete Contact Column. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: DELETE /contacts/columns/{column_id} ### Inputs ```json { "properties": { "column_id": { "description": "ID of the column to delete", "title": "Column Id", "type": "integer" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "column_id" ], "title": "g8_crm_delete_contact_columnArguments", "type": "object" } ``` ## g8_crm_delete_list > Delete List. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: DELETE /lists/{list_id} ### Inputs ```json { "properties": { "list_id": { "description": "List ID", "title": "List Id", "type": "integer" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "list_id" ], "title": "g8_crm_delete_listArguments", "type": "object" } ``` ## g8_crm_get_company > Get Company. Uses the current organization and existing API permissions. API: GET /companies/{company_id} ### Inputs ```json { "properties": { "company_id": { "description": "Company ID", "title": "Company Id", "type": "integer" }, "include_custom_fields": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "description": "When true, the response includes a `custom_fields` object of custom column values keyed by column slug. Default false omits it.", "title": "Include Custom Fields" } }, "required": [ "company_id" ], "title": "g8_crm_get_companyArguments", "type": "object" } ``` ## g8_crm_get_list_contacts > Get List Contacts. Uses the current organization and existing API permissions. API: GET /lists/{list_id}/contacts ### Inputs ```json { "properties": { "list_id": { "description": "List ID", "title": "List Id", "type": "integer" }, "page": { "anyOf": [ { "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "description": "Page number (1-indexed)", "title": "Page" }, "limit": { "anyOf": [ { "maximum": 200, "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "description": "Items per page (max 200)", "title": "Limit" } }, "required": [ "list_id" ], "title": "g8_crm_get_list_contactsArguments", "type": "object" } ``` ## g8_crm_list_company_columns > List Company Columns. Uses the current organization and existing API permissions. API: GET /companies/columns ### Inputs ```json { "properties": { "list_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "description": "Optional list ID. When provided, list-specific dynamic columns are included alongside global ones.", "title": "List Id" } }, "title": "g8_crm_list_company_columnsArguments", "type": "object" } ``` ## g8_crm_list_contact_columns > List Contact Columns. Uses the current organization and existing API permissions. API: GET /contacts/columns ### Inputs ```json { "properties": { "list_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "description": "Optional list ID. When provided, list-specific dynamic columns are included alongside global ones.", "title": "List Id" } }, "title": "g8_crm_list_contact_columnsArguments", "type": "object" } ``` ## g8_crm_list_contact_tasks > List Contact Tasks. Uses the current organization and existing API permissions. API: GET /contacts/{contact_id}/tasks ### Inputs ```json { "properties": { "contact_id": { "description": "Contact ID", "title": "Contact Id", "type": "integer" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Filter by status (open, completed)", "title": "Status" } }, "required": [ "contact_id" ], "title": "g8_crm_list_contact_tasksArguments", "type": "object" } ``` ## g8_crm_remove_contacts_from_list > Remove Contacts From List. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: DELETE /lists/{list_id}/contacts ### Inputs ```json { "$defs": { "ApiRemoveContactsFromListRequest": { "additionalProperties": false, "properties": { "contact_ids": { "description": "List of contact IDs to remove", "items": { "type": "integer" }, "title": "Contact Ids", "type": "array" } }, "required": [ "contact_ids" ], "title": "ApiRemoveContactsFromListRequest", "type": "object" } }, "properties": { "list_id": { "description": "List ID", "title": "List Id", "type": "integer" }, "body": { "$ref": "#/$defs/ApiRemoveContactsFromListRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "list_id", "body" ], "title": "g8_crm_remove_contacts_from_listArguments", "type": "object" } ``` ## g8_crm_update_company > Update Company. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: PATCH /companies/{company_id} ### Inputs ```json { "$defs": { "ApiCompanyUpdateRequest": { "additionalProperties": false, "properties": { "address": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Address" }, "city": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "City" }, "country": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Country" }, "domain": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Domain" }, "employee_count": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Employee Count" }, "industry": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Industry" }, "linkedin_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Linkedin Url" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "phone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Phone" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "website": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Website" }, "zip": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Zip" } }, "title": "ApiCompanyUpdateRequest", "type": "object" } }, "properties": { "company_id": { "description": "Company ID", "title": "Company Id", "type": "integer" }, "body": { "$ref": "#/$defs/ApiCompanyUpdateRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "company_id", "body" ], "title": "g8_crm_update_companyArguments", "type": "object" } ``` ## g8_crm_update_list > Update List. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: PATCH /lists/{list_id} ### Inputs ```json { "$defs": { "ApiListUpdateRequest": { "additionalProperties": false, "properties": { "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "New purpose note for the list (#16641). Omit to leave the current description unchanged; send an empty string to clear it.", "title": "Description" }, "title": { "description": "New name for the list", "minLength": 1, "title": "Title", "type": "string" } }, "required": [ "title" ], "title": "ApiListUpdateRequest", "type": "object" } }, "properties": { "list_id": { "description": "List ID", "title": "List Id", "type": "integer" }, "body": { "$ref": "#/$defs/ApiListUpdateRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "list_id", "body" ], "title": "g8_crm_update_listArguments", "type": "object" } ``` ## g8_crm_update_quote > Update Quote. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: PATCH /quotes/{quote_id} ### Inputs ```json { "$defs": { "ApiQuoteLineItemRequest": { "additionalProperties": false, "properties": { "billing_frequency": { "anyOf": [ { "enum": [ "one_time", "month", "year", "quarter" ], "type": "string" }, { "type": "null" } ], "default": null, "title": "Billing Frequency" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "discount_pct": { "default": 0, "maximum": 100.0, "minimum": 0.0, "title": "Discount Pct", "type": "number" }, "manual_product_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Manual Product Id" }, "product_name": { "maxLength": 255, "minLength": 1, "title": "Product Name", "type": "string" }, "quantity": { "default": 1, "maximum": 10000.0, "minimum": 1.0, "title": "Quantity", "type": "integer" }, "sort_order": { "default": 0, "title": "Sort Order", "type": "integer" }, "stripe_price_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Stripe Price Id" }, "stripe_product_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Stripe Product Id" }, "unit_amount": { "description": "In cents", "maximum": 1000000000.0, "minimum": 0.0, "title": "Unit Amount", "type": "integer" } }, "required": [ "product_name", "unit_amount" ], "title": "ApiQuoteLineItemRequest", "type": "object" }, "ApiQuoteUpdateRequest": { "additionalProperties": false, "properties": { "accepted_payment_methods": { "anyOf": [ { "items": { "enum": [ "ach", "credit_card", "wire" ], "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Accepted Payment Methods" }, "billing_address": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Billing Address" }, "billing_contact_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Billing Contact Id" }, "billing_email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Billing Email" }, "billing_legal_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Billing Legal Name" }, "billing_primary_email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Billing Primary Email" }, "contract_duration_unit": { "anyOf": [ { "enum": [ "months", "weeks", "days" ], "type": "string" }, { "type": "null" } ], "default": null, "title": "Contract Duration Unit" }, "contract_duration_value": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Contract Duration Value" }, "contract_end_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Contract End Date" }, "contract_months": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Contract Months" }, "contract_start_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Contract Start Date" }, "currency": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Currency" }, "deal_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Deal Id" }, "line_items": { "anyOf": [ { "items": { "$ref": "#/$defs/ApiQuoteLineItemRequest" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Line Items" }, "marketing_rights": { "anyOf": [ { "items": { "enum": [ "logo", "quote", "case_study" ], "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Marketing Rights" }, "mashup_company_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Mashup Company Id" }, "notes": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Notes" }, "owner_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Owner Id" }, "payment_terms": { "anyOf": [ { "enum": [ "due_on_receipt", "net_15", "net_30", "net_45", "net_60", "net_90" ], "type": "string" }, { "type": "null" } ], "default": null, "title": "Payment Terms" }, "privacy_content": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Privacy Content" }, "public_notes": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Public Notes" }, "signer_contact_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Signer Contact Id" }, "signer_email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Signer Email" }, "signer_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Signer Name" }, "tax_amount": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Tax Amount" }, "terms_content": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Terms Content" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "valid_until": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Valid Until" } }, "title": "ApiQuoteUpdateRequest", "type": "object" } }, "properties": { "quote_id": { "description": "Quote UUID", "title": "Quote Id", "type": "string" }, "body": { "$ref": "#/$defs/ApiQuoteUpdateRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "quote_id", "body" ], "title": "g8_crm_update_quoteArguments", "type": "object" } ``` ## g8_dashboard_chat_edit_app_page > Chat Edit App Page. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /app-pages/{page_id}/chat-edit ### Inputs ```json { "$defs": { "ApiAppPageChatEditRequest": { "additionalProperties": false, "properties": { "instruction": { "maxLength": 2000, "minLength": 1, "title": "Instruction", "type": "string" } }, "required": [ "instruction" ], "title": "ApiAppPageChatEditRequest", "type": "object" } }, "properties": { "page_id": { "title": "Page Id", "type": "string" }, "body": { "$ref": "#/$defs/ApiAppPageChatEditRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "page_id", "body" ], "title": "g8_dashboard_chat_edit_app_pageArguments", "type": "object" } ``` ## g8_dashboard_create_app_page > Create App Page. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /app-pages ### Inputs ```json { "$defs": { "ApiAppPageSourceDto": { "additionalProperties": false, "properties": { "alias": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Handle used inside the page. Auto-assigned from the kind when omitted.", "title": "Alias" }, "position": { "default": 0, "title": "Position", "type": "integer" }, "role": { "default": "data", "title": "Role", "type": "string" }, "source_id": { "description": "Opaque to everything but the connector that owns it", "title": "Source Id", "type": "string" }, "source_kind": { "description": "Connector family, e.g. staging-table or custom-object", "title": "Source Kind", "type": "string" } }, "required": [ "source_id", "source_kind" ], "title": "ApiAppPageSourceDto", "type": "object" }, "ApiCreateAppPageRequest": { "additionalProperties": false, "properties": { "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "name": { "title": "Name", "type": "string" }, "sources": { "description": "At least one. A page with no data source can never be generated.", "items": { "$ref": "#/$defs/ApiAppPageSourceDto" }, "minItems": 1, "title": "Sources", "type": "array" } }, "required": [ "name", "sources" ], "title": "ApiCreateAppPageRequest", "type": "object" } }, "properties": { "body": { "$ref": "#/$defs/ApiCreateAppPageRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "body" ], "title": "g8_dashboard_create_app_pageArguments", "type": "object" } ``` ## g8_dashboard_delete_app_page > Delete App Page. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: DELETE /app-pages/{page_id} ### Inputs ```json { "properties": { "page_id": { "title": "Page Id", "type": "string" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "page_id" ], "title": "g8_dashboard_delete_app_pageArguments", "type": "object" } ``` ## g8_dashboard_generate_app_page > Generate App Page. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /app-pages/{page_id}/generate ### Inputs ```json { "$defs": { "ApiGenerateAppPageRequest": { "additionalProperties": false, "properties": { "instruction": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "What the page should show. Free text; the generator composes every bound source.", "title": "Instruction" } }, "title": "ApiGenerateAppPageRequest", "type": "object" } }, "properties": { "page_id": { "title": "Page Id", "type": "string" }, "body": { "$ref": "#/$defs/ApiGenerateAppPageRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "page_id", "body" ], "title": "g8_dashboard_generate_app_pageArguments", "type": "object" } ``` ## g8_dashboard_get_app_page > Get App Page. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. API: GET /app-pages/{page_id} ### Inputs ```json { "properties": { "page_id": { "title": "Page Id", "type": "string" } }, "required": [ "page_id" ], "title": "g8_dashboard_get_app_pageArguments", "type": "object" } ``` ## g8_dashboard_get_app_page_artifact > Get App Page Artifact. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. API: GET /app-pages/{page_id}/artifact ### Inputs ```json { "properties": { "page_id": { "title": "Page Id", "type": "string" } }, "required": [ "page_id" ], "title": "g8_dashboard_get_app_page_artifactArguments", "type": "object" } ``` ## g8_dashboard_get_app_page_status > Get App Page Status. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. API: GET /app-pages/{page_id}/status ### Inputs ```json { "properties": { "page_id": { "title": "Page Id", "type": "string" } }, "required": [ "page_id" ], "title": "g8_dashboard_get_app_page_statusArguments", "type": "object" } ``` ## g8_dashboard_invoke_app_page_action > Invoke App Page Action. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /app-pages/{page_id}/actions/{alias}/{action_name} ### Inputs ```json { "$defs": { "ApiInvokeActionRequest": { "additionalProperties": false, "properties": { "args": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "description": "Action arguments. Validated against the object's own schema; an undeclared field is refused, not dropped.", "title": "Args" } }, "title": "ApiInvokeActionRequest", "type": "object" } }, "properties": { "page_id": { "title": "Page Id", "type": "string" }, "alias": { "title": "Alias", "type": "string" }, "action_name": { "title": "Action Name", "type": "string" }, "body": { "$ref": "#/$defs/ApiInvokeActionRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "page_id", "alias", "action_name", "body" ], "title": "g8_dashboard_invoke_app_page_actionArguments", "type": "object" } ``` ## g8_dashboard_list_app_page_actions > List App Page Actions. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. API: GET /app-pages/{page_id}/actions ### Inputs ```json { "properties": { "page_id": { "title": "Page Id", "type": "string" } }, "required": [ "page_id" ], "title": "g8_dashboard_list_app_page_actionsArguments", "type": "object" } ``` ## g8_dashboard_list_app_page_versions > List App Page Versions. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. API: GET /app-pages/{page_id}/versions ### Inputs ```json { "properties": { "page_id": { "title": "Page Id", "type": "string" } }, "required": [ "page_id" ], "title": "g8_dashboard_list_app_page_versionsArguments", "type": "object" } ``` ## g8_dashboard_list_app_pages > List App Pages. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. API: GET /app-pages ### Inputs ```json { "properties": {}, "title": "g8_dashboard_list_app_pagesArguments", "type": "object" } ``` ## g8_dashboard_mint_app_page_frame_session > Mint App Page Frame Session. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /app-pages/{page_id}/frame-session ### Inputs ```json { "properties": { "page_id": { "title": "Page Id", "type": "string" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "page_id" ], "title": "g8_dashboard_mint_app_page_frame_sessionArguments", "type": "object" } ``` ## g8_dashboard_restore_app_page_version > Restore App Page Version. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /app-pages/{page_id}/versions/{version_num}/restore ### Inputs ```json { "properties": { "page_id": { "title": "Page Id", "type": "string" }, "version_num": { "title": "Version Num", "type": "integer" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "page_id", "version_num" ], "title": "g8_dashboard_restore_app_page_versionArguments", "type": "object" } ``` ## g8_enrichment_list_ai_configs > List Ai Configs. Uses the current organization and existing API permissions. API: GET /enrichment/ai/configs ### Inputs ```json { "properties": { "list_id": { "description": "List ID whose saved AI configs to return", "title": "List Id", "type": "integer" } }, "required": [ "list_id" ], "title": "g8_enrichment_list_ai_configsArguments", "type": "object" } ``` ## g8_enrichment_start_enrichment > Start Enrichment. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /enrichment/enrich ### Inputs ```json { "$defs": { "ApiEnrichRequest": { "additionalProperties": false, "properties": { "contact_ids": { "description": "Contact IDs to enrich", "items": { "type": "integer" }, "title": "Contact Ids", "type": "array" }, "fields_config": { "anyOf": [ { "additionalProperties": { "items": { "type": "string" }, "type": "array" }, "type": "object" }, { "type": "null" } ], "default": null, "description": "Fields to enrich and provider order. Example: {\"work_email\": [\"prospeo\", \"dropcontact\"]}", "title": "Fields Config" }, "list_id": { "description": "List ID the contacts belong to", "title": "List Id", "type": "integer" } }, "required": [ "contact_ids", "list_id" ], "title": "ApiEnrichRequest", "type": "object" } }, "properties": { "body": { "$ref": "#/$defs/ApiEnrichRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "body" ], "title": "g8_enrichment_start_enrichmentArguments", "type": "object" } ``` ## g8_enrichment_verify_email > Verify Email. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /enrichment/verify-email ### Inputs ```json { "$defs": { "ApiVerifyEmailRequest": { "additionalProperties": false, "properties": { "email": { "description": "Email address to verify", "title": "Email", "type": "string" } }, "required": [ "email" ], "title": "ApiVerifyEmailRequest", "type": "object" } }, "properties": { "body": { "$ref": "#/$defs/ApiVerifyEmailRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "body" ], "title": "g8_enrichment_verify_emailArguments", "type": "object" } ``` ## g8_intent_keywords_create_from_search > Keywords Create From Search. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /intent/keywords/create-from-search ### Inputs ```json { "$defs": { "ApiKeywordsCreateFromSearchRequest": { "additionalProperties": false, "properties": { "contact_limit": { "default": 50, "description": "Max contacts to pre-resolve per keyword.", "maximum": 100.0, "minimum": 1.0, "title": "Contact Limit", "type": "integer" }, "keywords": { "description": "1-10 free-text keyword queries. Each is searched against the intent page index, contacts/companies are resolved, and a compete-tracking keyword row is saved. Duplicates within the request (case-insensitive, trimmed) are skipped, not re-saved.", "items": { "type": "string" }, "maxItems": 10, "minItems": 1, "title": "Keywords", "type": "array" }, "page_limit": { "default": 20, "description": "Max pages to seed per keyword. Deliberately smaller than create-from-domain's 200 default since this runs once per keyword.", "maximum": 50.0, "minimum": 1.0, "title": "Page Limit", "type": "integer" }, "search_filters": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "description": "Optional intent-search filters applied to EVERY keyword in the batch (audience, page_type, commercial_score_min, etc.). Stored verbatim on each saved keyword row so the daily resolver replays them.", "title": "Search Filters" }, "semantic_ratio": { "default": 0.0, "description": "0 = text search (default, cheapest), 1 = semantic, between = hybrid.", "maximum": 1.0, "minimum": 0.0, "title": "Semantic Ratio", "type": "number" } }, "required": [ "keywords" ], "title": "ApiKeywordsCreateFromSearchRequest", "type": "object" } }, "properties": { "body": { "$ref": "#/$defs/ApiKeywordsCreateFromSearchRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "body" ], "title": "g8_intent_keywords_create_from_searchArguments", "type": "object" } ``` ## g8_landing_page_create_landing_page_agent_session > Create Landing Page Agent Session. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /landing-pages/{page_id}/agent-sessions ### Inputs ```json { "$defs": { "ApiCreateAgentSessionRequest": { "additionalProperties": false, "properties": { "approval_mode": { "default": "review", "description": "'review' (default) stages each patch for a human to approve in Studio. 'auto_apply' writes straight to the draft. Bound to the session at creation, so a later patch cannot escalate itself.", "pattern": "^(review|auto_apply)$", "title": "Approval Mode", "type": "string" } }, "title": "ApiCreateAgentSessionRequest", "type": "object" } }, "properties": { "page_id": { "title": "Page Id", "type": "string" }, "body": { "anyOf": [ { "$ref": "#/$defs/ApiCreateAgentSessionRequest" }, { "type": "null" } ], "default": null }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "page_id" ], "title": "g8_landing_page_create_landing_page_agent_sessionArguments", "type": "object" } ``` ## g8_repo_create_campaign > Create Campaign. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /repos/{repo_id}/campaigns ### Inputs ```json { "$defs": { "Apideveloper_api__interfaces__v1__cli_campaigns_router__CampaignCreateRequest": { "additionalProperties": false, "properties": { "brief": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Campaign brief", "title": "Brief" }, "category": { "default": "Outbound", "description": "Campaign category", "title": "Category", "type": "string" }, "core_concept": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Core concept", "title": "Core Concept" }, "goal": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Campaign goal", "title": "Goal" }, "name": { "description": "Campaign name", "title": "Name", "type": "string" }, "primary_hook": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Primary hook text", "title": "Primary Hook" }, "target_persona": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Target persona", "title": "Target Persona" } }, "required": [ "name" ], "title": "Apideveloper_api__interfaces__v1__cli_campaigns_router__CampaignCreateRequest", "type": "object" } }, "properties": { "repo_id": { "description": "Repository ID", "title": "Repo Id", "type": "string" }, "body": { "$ref": "#/$defs/Apideveloper_api__interfaces__v1__cli_campaigns_router__CampaignCreateRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "repo_id", "body" ], "title": "g8_repo_create_campaignArguments", "type": "object" } ``` ## g8_repo_delete_repo > Delete Repo. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: DELETE /repos/{repo_id} ### Inputs ```json { "properties": { "repo_id": { "description": "Repository ID", "title": "Repo Id", "type": "string" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "repo_id" ], "title": "g8_repo_delete_repoArguments", "type": "object" } ``` ## g8_repo_get_campaign_document > Get Campaign Document. Uses the current organization and existing API permissions. API: GET /repos/{repo_id}/campaigns/{campaign_id}/documents/{document_id} ### Inputs ```json { "properties": { "repo_id": { "description": "Repository ID", "title": "Repo Id", "type": "string" }, "campaign_id": { "description": "Campaign ID", "title": "Campaign Id", "type": "string" }, "document_id": { "description": "Document ID", "title": "Document Id", "type": "string" } }, "required": [ "repo_id", "campaign_id", "document_id" ], "title": "g8_repo_get_campaign_documentArguments", "type": "object" } ``` ## g8_repo_launch_campaign > Launch Campaign. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /repos/{repo_id}/campaigns/{campaign_id}/launch ### Inputs ```json { "properties": { "repo_id": { "description": "Repository ID", "title": "Repo Id", "type": "string" }, "campaign_id": { "description": "Campaign ID", "title": "Campaign Id", "type": "string" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "repo_id", "campaign_id" ], "title": "g8_repo_launch_campaignArguments", "type": "object" } ``` ## g8_repo_list_repos > List Repos. Uses the current organization and existing API permissions. API: GET /repos ### Inputs ```json { "properties": { "page": { "anyOf": [ { "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "description": "Page number (1-indexed)", "title": "Page" }, "limit": { "anyOf": [ { "maximum": 200, "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "description": "Items per page (max 200)", "title": "Limit" } }, "title": "g8_repo_list_reposArguments", "type": "object" } ``` ## g8_repo_list_streams > List Streams. Uses the current organization and existing API permissions. API: GET /repos/{repo_id}/streams ### Inputs ```json { "properties": { "repo_id": { "description": "Repository ID", "title": "Repo Id", "type": "string" } }, "required": [ "repo_id" ], "title": "g8_repo_list_streamsArguments", "type": "object" } ``` ## g8_repo_rollback_install > Rollback Install. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /repos/{repo_id}/rollback ### Inputs ```json { "properties": { "repo_id": { "description": "Repository ID", "title": "Repo Id", "type": "string" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "repo_id" ], "title": "g8_repo_rollback_installArguments", "type": "object" } ``` ## g8_repo_update_campaign > Update Campaign. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: PATCH /repos/{repo_id}/campaigns/{campaign_id} ### Inputs ```json { "$defs": { "Apideveloper_api__interfaces__v1__cli_campaigns_router__CampaignUpdateRequest": { "additionalProperties": false, "properties": { "brief": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Campaign brief", "title": "Brief" }, "category": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Campaign category", "title": "Category" }, "core_concept": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Core concept", "title": "Core Concept" }, "goal": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Campaign goal", "title": "Goal" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Campaign name", "title": "Name" }, "primary_hook": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Primary hook text", "title": "Primary Hook" }, "target_persona": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Target persona", "title": "Target Persona" } }, "title": "Apideveloper_api__interfaces__v1__cli_campaigns_router__CampaignUpdateRequest", "type": "object" } }, "properties": { "repo_id": { "description": "Repository ID", "title": "Repo Id", "type": "string" }, "campaign_id": { "description": "Campaign ID", "title": "Campaign Id", "type": "string" }, "body": { "$ref": "#/$defs/Apideveloper_api__interfaces__v1__cli_campaigns_router__CampaignUpdateRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "repo_id", "campaign_id", "body" ], "title": "g8_repo_update_campaignArguments", "type": "object" } ``` ## g8_sandbox_sandbox_clear_failure_rules > Clear failure rules. Uses the current organization and existing API permissions. Test-mode sandbox only; a live key is rejected by the API. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: DELETE /sandbox/failure-injection ### Inputs ```json { "properties": { "channel": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Clear one channel only; omit for all", "title": "Channel" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "title": "g8_sandbox_sandbox_clear_failure_rulesArguments", "type": "object" } ``` ## g8_sandbox_sandbox_list_failure_rules > List failure rules. Uses the current organization and existing API permissions. Test-mode sandbox only; a live key is rejected by the API. API: GET /sandbox/failure-injection ### Inputs ```json { "properties": {}, "title": "g8_sandbox_sandbox_list_failure_rulesArguments", "type": "object" } ``` ## g8_sandbox_sandbox_outbox > List simulated outbox. Uses the current organization and existing API permissions. Test-mode sandbox only; a live key is rejected by the API. API: GET /sandbox/outbox ### Inputs ```json { "properties": { "channel": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Filter by channel", "title": "Channel" }, "limit": { "anyOf": [ { "maximum": 200, "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "title": "Limit" }, "offset": { "anyOf": [ { "minimum": 0, "type": "integer" }, { "type": "null" } ], "default": null, "title": "Offset" } }, "title": "g8_sandbox_sandbox_outboxArguments", "type": "object" } ``` ## g8_sandbox_sandbox_reset_fixtures > Reset fixture data. Uses the current organization and existing API permissions. Test-mode sandbox only; a live key is rejected by the API. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /sandbox/fixtures/reset ### Inputs ```json { "properties": { "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "title": "g8_sandbox_sandbox_reset_fixturesArguments", "type": "object" } ``` ## g8_sandbox_sandbox_restore > Restore a snapshot. Uses the current organization and existing API permissions. Test-mode sandbox only; a live key is rejected by the API. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /sandbox/fixtures/restore ### Inputs ```json { "properties": { "name": { "anyOf": [ { "maxLength": 64, "minLength": 1, "pattern": "^[A-Za-z0-9_\\-]+$", "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "title": "g8_sandbox_sandbox_restoreArguments", "type": "object" } ``` ## g8_sandbox_sandbox_seed_fixtures > Seed sample data. Uses the current organization and existing API permissions. Test-mode sandbox only; a live key is rejected by the API. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /sandbox/fixtures/seed ### Inputs ```json { "properties": { "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "title": "g8_sandbox_sandbox_seed_fixturesArguments", "type": "object" } ``` ## g8_sandbox_sandbox_set_failure_rule > Set a failure rule. Uses the current organization and existing API permissions. Test-mode sandbox only; a live key is rejected by the API. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: PUT /sandbox/failure-injection ### Inputs ```json { "$defs": { "ApiFailureRuleRequest": { "additionalProperties": false, "properties": { "channel": { "description": "Channel the injected failure applies to: email, sms, whatsapp, voice, linkedin, crm, ads, calendar, or '*' for every channel.", "title": "Channel", "type": "string" }, "failure_mode": { "description": "One of: hard_bounce, provider_error, timeout, rate_limited, invalid_recipient.", "title": "Failure Mode", "type": "string" }, "remaining": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "description": "Fail this many sends, then auto-clear. Omit to fail until cleared.", "title": "Remaining" } }, "required": [ "channel", "failure_mode" ], "title": "ApiFailureRuleRequest", "type": "object" } }, "properties": { "body": { "$ref": "#/$defs/ApiFailureRuleRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "body" ], "title": "g8_sandbox_sandbox_set_failure_ruleArguments", "type": "object" } ``` ## g8_sandbox_sandbox_snapshot > Snapshot fixture tables. Uses the current organization and existing API permissions. Test-mode sandbox only; a live key is rejected by the API. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /sandbox/fixtures/snapshot ### Inputs ```json { "properties": { "name": { "anyOf": [ { "maxLength": 64, "minLength": 1, "pattern": "^[A-Za-z0-9_\\-]+$", "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "title": "g8_sandbox_sandbox_snapshotArguments", "type": "object" } ``` ## g8_sandbox_sandbox_status > Sandbox status. Uses the current organization and existing API permissions. Test-mode sandbox only; a live key is rejected by the API. API: GET /sandbox/status ### Inputs ```json { "properties": {}, "title": "g8_sandbox_sandbox_statusArguments", "type": "object" } ``` ## g8_search_save_company_search > Save Company Search. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /search/companies/save ### Inputs ```json { "$defs": { "ApiSearchFilter": { "additionalProperties": false, "properties": { "field": { "description": "Field name to filter on (e.g., 'job_title', 'country', 'industry')", "title": "Field", "type": "string" }, "operator": { "description": "Filter operator", "enum": [ "any_of", "contains", "all_of", "none_of", "is_empty", "is_not_empty", "between", "exists" ], "title": "Operator", "type": "string" }, "value": { "anyOf": [ { "items": {}, "type": "array" }, { "type": "null" } ], "default": null, "description": "Filter values", "title": "Value" } }, "required": [ "field", "operator" ], "title": "ApiSearchFilter", "type": "object" }, "ApiSearchSaveRequest": { "additionalProperties": false, "properties": { "filters": { "anyOf": [ { "items": { "$ref": "#/$defs/ApiSearchFilter" }, "type": "array" }, { "type": "null" } ], "default": null, "description": "Filter conditions (AND logic)", "title": "Filters" }, "limit": { "default": 25, "description": "Results per page (max 100)", "maximum": 100.0, "minimum": 1.0, "title": "Limit", "type": "integer" }, "list_title": { "description": "Title for the new list", "title": "List Title", "type": "string" }, "max_results": { "default": 1000, "description": "Maximum results to save (max 10,000)", "maximum": 10000.0, "minimum": 1.0, "title": "Max Results", "type": "integer" }, "page": { "default": 1, "description": "Page number (1-indexed)", "maximum": 100.0, "minimum": 1.0, "title": "Page", "type": "integer" } }, "required": [ "list_title" ], "title": "ApiSearchSaveRequest", "type": "object" } }, "properties": { "body": { "$ref": "#/$defs/ApiSearchSaveRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "body" ], "title": "g8_search_save_company_searchArguments", "type": "object" } ``` ## g8_sequence_get_sequence > Get Sequence. Uses the current organization and existing API permissions. API: GET /sequences/{sequence_id} ### Inputs ```json { "properties": { "sequence_id": { "description": "Sequence ID", "title": "Sequence Id", "type": "string" } }, "required": [ "sequence_id" ], "title": "g8_sequence_get_sequenceArguments", "type": "object" } ``` ## g8_sequence_list_sequence_contacts > List Sequence Contacts. Uses the current organization and existing API permissions. API: GET /sequences/{sequence_id}/contacts ### Inputs ```json { "properties": { "sequence_id": { "description": "Sequence ID", "title": "Sequence Id", "type": "string" }, "page": { "anyOf": [ { "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "description": "Page number", "title": "Page" }, "limit": { "anyOf": [ { "maximum": 200, "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "description": "Items per page", "title": "Limit" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Filter by contact state", "title": "State" } }, "required": [ "sequence_id" ], "title": "g8_sequence_list_sequence_contactsArguments", "type": "object" } ``` ## g8_sequence_run_sequence > Run Sequence. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /sequences/{sequence_id}/run ### Inputs ```json { "properties": { "sequence_id": { "description": "Sequence ID", "title": "Sequence Id", "type": "string" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "sequence_id" ], "title": "g8_sequence_run_sequenceArguments", "type": "object" } ``` ## g8_snippet_get_form_template > Get Form Template. Uses the current organization and existing API permissions. API: GET /snippet/form ### Inputs ```json { "properties": {}, "title": "g8_snippet_get_form_templateArguments", "type": "object" } ``` ## g8_usage_get_usage > Get Usage. Uses the current organization and existing API permissions. API: GET /usage ### Inputs ```json { "properties": {}, "title": "g8_usage_get_usageArguments", "type": "object" } ``` ## g8_usage_list_usage_transactions > List Usage Transactions. Uses the current organization and existing API permissions. API: GET /usage/transactions ### Inputs ```json { "properties": { "page": { "anyOf": [ { "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "description": "Page number (ignored when `cursor` is supplied)", "title": "Page" }, "limit": { "anyOf": [ { "maximum": 200, "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "description": "Items per page", "title": "Limit" }, "cursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Opaque pagination cursor from a prior response's `next_cursor`. When supplied it takes precedence over `page`.", "title": "Cursor" } }, "title": "g8_usage_list_usage_transactionsArguments", "type": "object" } ``` ## g8_webhook_create_webhook > Create Webhook. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /webhooks ### Inputs ```json { "$defs": { "ApiWebhookCreateRequest": { "additionalProperties": false, "properties": { "events": { "description": "Event types to subscribe to", "items": { "type": "string" }, "title": "Events", "type": "array" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Human-readable name", "title": "Name" }, "url": { "description": "Target URL for webhook delivery", "title": "Url", "type": "string" } }, "required": [ "events", "url" ], "title": "ApiWebhookCreateRequest", "type": "object" } }, "properties": { "body": { "$ref": "#/$defs/ApiWebhookCreateRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "body" ], "title": "g8_webhook_create_webhookArguments", "type": "object" } ``` ## g8_webhook_delete_webhook > Delete Webhook. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: DELETE /webhooks/{webhook_id} ### Inputs ```json { "properties": { "webhook_id": { "description": "Webhook subscription ID", "title": "Webhook Id", "type": "string" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "webhook_id" ], "title": "g8_webhook_delete_webhookArguments", "type": "object" } ``` ## g8_webhook_get_webhook > Get Webhook. Uses the current organization and existing API permissions. API: GET /webhooks/{webhook_id} ### Inputs ```json { "properties": { "webhook_id": { "description": "Webhook subscription ID", "title": "Webhook Id", "type": "string" } }, "required": [ "webhook_id" ], "title": "g8_webhook_get_webhookArguments", "type": "object" } ``` ## g8_webhook_list_webhook_deliveries > List Webhook Deliveries. Uses the current organization and existing API permissions. API: GET /webhooks/{webhook_id}/deliveries ### Inputs ```json { "properties": { "webhook_id": { "description": "Webhook subscription ID", "title": "Webhook Id", "type": "string" }, "page": { "anyOf": [ { "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "description": "Page number", "title": "Page" }, "limit": { "anyOf": [ { "maximum": 200, "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "description": "Items per page", "title": "Limit" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Filter by delivery status", "title": "Status" } }, "required": [ "webhook_id" ], "title": "g8_webhook_list_webhook_deliveriesArguments", "type": "object" } ``` ## g8_webhook_list_webhook_events > List Webhook Events. Uses the current organization and existing API permissions. API: GET /webhooks/events ### Inputs ```json { "properties": {}, "title": "g8_webhook_list_webhook_eventsArguments", "type": "object" } ``` ## g8_webhook_list_webhooks > List Webhooks. Uses the current organization and existing API permissions. API: GET /webhooks ### Inputs ```json { "properties": { "is_active": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "description": "Filter by active status", "title": "Is Active" } }, "title": "g8_webhook_list_webhooksArguments", "type": "object" } ``` ## g8_webhook_rotate_webhook_secret > Rotate Webhook Secret. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /webhooks/{webhook_id}/rotate-secret ### Inputs ```json { "properties": { "webhook_id": { "description": "Webhook subscription ID", "title": "Webhook Id", "type": "string" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "webhook_id" ], "title": "g8_webhook_rotate_webhook_secretArguments", "type": "object" } ``` ## g8_webhook_update_webhook > Update Webhook. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: PATCH /webhooks/{webhook_id} ### Inputs ```json { "$defs": { "ApiWebhookUpdateRequest": { "additionalProperties": false, "properties": { "events": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "description": "Event types to subscribe to", "title": "Events" }, "is_active": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "description": "Whether the webhook is active", "title": "Is Active" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Human-readable name", "title": "Name" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Target URL for webhook delivery", "title": "Url" } }, "title": "ApiWebhookUpdateRequest", "type": "object" } }, "properties": { "webhook_id": { "description": "Webhook subscription ID", "title": "Webhook Id", "type": "string" }, "body": { "$ref": "#/$defs/ApiWebhookUpdateRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "webhook_id", "body" ], "title": "g8_webhook_update_webhookArguments", "type": "object" } ``` ## g8_workflow_list_workflow_executions > List workflow executions for the caller's org (recover a lost execution_id). Uses the current organization and existing API permissions. API: GET /workflows/executions ### Inputs ```json { "properties": { "action_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Filter to a single workflow's executions.", "title": "Action Id" }, "status_filter": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Filter by execution status (equality match \u2014 one status per call). The backend writes seven values: pending|running|completed|failed|paused|pending_approval|stopped. The four terminal-or-early states are pending, running, completed, failed; paused and pending_approval are resumable, stopped is terminal.", "title": "Status Filter" }, "triggered_by": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Filter by the identity that started the run (user email, agent id, 'api', ...).", "title": "Triggered By" }, "limit": { "anyOf": [ { "maximum": 200, "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "description": "Page size (1..200).", "title": "Limit" }, "offset": { "anyOf": [ { "minimum": 0, "type": "integer" }, { "type": "null" } ], "default": null, "description": "Rows to skip for pagination.", "title": "Offset" } }, "title": "g8_workflow_list_workflow_executionsArguments", "type": "object" } ``` ## g8_workflow_plan_inline_workflow > Preview a normalized execution plan for an inline workflow graph. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /workflows/plan ### Inputs ```json { "properties": { "body": { "additionalProperties": true, "title": "Body", "type": "object" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "body" ], "title": "g8_workflow_plan_inline_workflowArguments", "type": "object" } ``` ## g8_workflow_plan_workflow > Preview a normalized execution plan for a saved workflow. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /workflows/{action_id}/plan ### Inputs ```json { "properties": { "action_id": { "description": "Workflow action_id.", "minLength": 1, "title": "Action Id", "type": "string" }, "body": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Body" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "action_id" ], "title": "g8_workflow_plan_workflowArguments", "type": "object" } ``` ## Additional reads in 0.59.0 Visitor and company signal reads require an API key explicitly granting `intent:read`. Supply the visitor IPv4 or IPv6 address; the server does not infer it from its own connection. Existing browser SDK write-key calls remain supported by the API. Knowledge export requires the existing knowledge export and MCP access grants. The result is an embedded ZIP resource with a base64 `blob`; decode it to save the archive. Archives above 1 MiB return an error; use the authenticated API download for larger exports. ## g8_app_public_verification_keys > Read app-token public verification keys (JWKS), not private keys or tokens. Uses the configured graph8 API host. An empty keyset means the backend has no active verification keys; an app-platform gate may return 404. ### Inputs ```json { "properties": {}, "title": "g8_app_public_verification_keysArguments", "type": "object" } ``` ## g8_knowledge_export > Download authorized organization knowledge as a ZIP MCP embedded resource. Requires knowledge:export (or knowledge:admin) plus the existing knowledge read and MCP-channel gates. Contains only records authorized by the API. Maximum compressed archive size is 1 MiB; larger exports fail without truncation and must use the authenticated API download. No shared files or public download links are created. Decode resource.blob to save the ZIP. ### Inputs ```json { "properties": {}, "title": "g8_knowledge_exportArguments", "type": "object" } ``` ## g8_company_intent_signals > Read a company's buying signals from the authenticated organization's visitor events. Requires an API key with explicit intent:read. Supply a company domain such as example.com. No browser Origin or write-key impersonation is used. ### Inputs ```json { "properties": { "domain": { "maxLength": 253, "minLength": 1, "title": "Domain", "type": "string" } }, "required": [ "domain" ], "title": "g8_company_intent_signalsArguments", "type": "object" } ``` ## g8_visitor_company > Resolve an explicitly supplied visitor IP to company information. Requires an API key with explicit intent:read. Uses the supplied visitor IP, never the MCP server's outbound IP. Does not accept browser write keys. ### Inputs ```json { "properties": { "visitor_ip": { "format": "ipvanyaddress", "title": "Visitor Ip", "type": "string" } }, "required": [ "visitor_ip" ], "title": "g8_visitor_companyArguments", "type": "object" } ``` ## g8_visitor_score > Read engagement signals for a visitor IP within the authenticated organization. Requires an API key with explicit intent:read. Visitor events remain scoped to that organization; the IP does not select a different tenant. ### Inputs ```json { "properties": { "visitor_ip": { "format": "ipvanyaddress", "title": "Visitor Ip", "type": "string" } }, "required": [ "visitor_ip" ], "title": "g8_visitor_scoreArguments", "type": "object" } ``` ## Operator reports in 0.60.0 These five tools are for internal operators with an already-issued operator API key. Configure that key using your MCP client's credential settings and connect in developer or all-tools mode. Never paste the key into a tool argument. Search for the exact tool name with `g8_tool_search`, inspect its schema, then call it directly or with `g8_execute`. Ordinary customer keys, agency keys and Owner/Admin roles cannot grant operator access. The organization-user bridge refuses these reports and points to the separate MCP connection. If access is denied, verify that the configured key is an existing operator key for the intended backend; no tool creates or upgrades credentials. Start with `g8_ops_index`. For reports, use a known legacy organization ID or PropelAuth organization UUID. The target is explicit and every report read is audited by the API. Reports can include contact details and rendered messages. They do not send messages, change bindings or start sequences. All reports return JSON. Actions preserve pagination: use `page` and `limit` (maximum 200), and follow `pagination.has_next`. Missing LinkedIn tables yield empty reports. Null invitation notes are normal for bare invitations; `skipped_too_distant` describes eligibility, not an outage. Webhook summaries report counts and timing, not stored payloads. ## g8_ops_index > Describe the internal cross-organization LinkedIn operations reports. Requires an existing internally minted operator API key. Ordinary org keys, agency keys, and Owner/Admin roles do not grant this identity. Configure the credential on the MCP connection; never put it in tool arguments. These tools neither mint credentials nor send LinkedIn actions. ### Inputs ```json { "properties": {}, "title": "g8_ops_indexArguments", "type": "object" } ``` ## g8_ops_linkedin_actions > Read paginated LinkedIn action diagnostics for an explicitly selected organization. Internal operator key required; cross-organization reads are audited by the API. Returns JSON with pagination, including rendered messages and contact details. Use a known legacy org ID or PropelAuth UUID. since and until are ISO timestamps. A null note is normal for bare invitations; skipped_too_distant is an eligibility skip, not a provider outage. ### Inputs ```json { "properties": { "org_id": { "maxLength": 128, "pattern": "^(?:org_[A-Za-z0-9]+|[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})$", "title": "Org Id", "type": "string" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "kind": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Kind" }, "account_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Account Id" }, "sequence_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Sequence Id" }, "since": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Since" }, "until": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Until" }, "page": { "default": 1, "minimum": 1, "title": "Page", "type": "integer" }, "limit": { "default": 100, "maximum": 200, "minimum": 1, "title": "Limit", "type": "integer" } }, "required": [ "org_id" ], "title": "g8_ops_linkedin_actionsArguments", "type": "object" } ``` ## g8_ops_linkedin_actions_summary > Read LinkedIn action counts by sequence, sender, kind and status for one organization. Requires an internally minted operator key. Target organization is explicit and audited. Returns the API's JSON report without truncation. ### Inputs ```json { "properties": { "org_id": { "maxLength": 128, "pattern": "^(?:org_[A-Za-z0-9]+|[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})$", "title": "Org Id", "type": "string" } }, "required": [ "org_id" ], "title": "g8_ops_linkedin_actions_summaryArguments", "type": "object" } ``` ## g8_ops_linkedin_sequence_bindings > Read sequence-to-LinkedIn-sender bindings and unbound live sequences for one organization. Internal operator key required; target organization is explicit and audited. An unbound live sequence cannot send its LinkedIn steps. This report does not change bindings, start sequences or send messages. ### Inputs ```json { "properties": { "org_id": { "maxLength": 128, "pattern": "^(?:org_[A-Za-z0-9]+|[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})$", "title": "Org Id", "type": "string" } }, "required": [ "org_id" ], "title": "g8_ops_linkedin_sequence_bindingsArguments", "type": "object" } ``` ## g8_ops_linkedin_webhook_events > Read inbound LinkedIn webhook counts and first/last-seen times for one organization. Internal operator key required; target organization is audited. This is a deduplication ledger, not stored webhook payloads. lead.message_received means a tracked lead reply; inbox.message_received does not imply one. ### Inputs ```json { "properties": { "org_id": { "maxLength": 128, "pattern": "^(?:org_[A-Za-z0-9]+|[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})$", "title": "Org Id", "type": "string" } }, "required": [ "org_id" ], "title": "g8_ops_linkedin_webhook_eventsArguments", "type": "object" } ``` ## App-token connection Use the separate local `g8-app-mcp-server` command with an existing app token. This requires MCP package 0.61.0 or later. It uses stdio; the ordinary hosted MCP connection and organization-user bridge cannot execute these app-token tools. They will explain which connection is required. Configure the command in your MCP client's local-server settings. For example: ```json { "mcpServers": { "graph8-app-objects": { "command": "uvx", "args": ["--from", "g8-mcp-server==0.61.0", "g8-app-mcp-server"], "env": { "G8_API_URL": "https://be.graph8.com", "G8_APP_TOKEN": "<existing-app-token>" } } } } ``` Set the backend origin to the environment for that token. It must use HTTPS without an API path, query or embedded credentials. Keep the token in the client's credential configuration; never paste it into a tool argument. This connection does not use `G8_API_KEY` or saved organization login credentials and cannot create or refresh tokens. If a token expires or is revoked, obtain a replacement through the existing authorized app flow and restart the connection. The token determines the app and tenant. API checks for app scopes, installation, consent, tenant readiness, record references and app metering still apply. The connection exposes only the ten app-object tools, search and dispatch. It does not expose normal organization records, operator reports or credential issuance. Start with `g8_app_objects_list`, then read the chosen object's attributes. Use returned object slugs and record IDs. Record listing preserves pagination and accepts a returned cursor, which takes precedence over page numbers. History is newest first. For create, update and restore, the first call produces a review preview without an API request. Check the values and target, then set `confirm` to `true`. On update, omitted attributes remain unchanged and an explicit null clears an allowed nullable attribute. Supply `expected_revision` from a prior record to reject stale edits. Schema, uniqueness or reference errors remain visible instead of being silently dropped. ## g8_app_objects_list > List the objects declared by the authenticated app for its token's tenant. Requires the separate g8-app-mcp-server stdio connection and an existing app token with objects:read. Installation, consent, tenant readiness and metering stay enforced by the API. Ordinary org-owned objects are separate. ### Inputs ```json { "properties": {}, "title": "g8_app_objects_listArguments", "type": "object" } ``` ## g8_app_object_get > Get one app-owned object definition. Requires the app-token connection and objects:read. App and tenant are derived from the existing token, never tool arguments. ### Inputs ```json { "properties": { "object_slug": { "maxLength": 256, "minLength": 1, "pattern": "^[A-Za-z0-9_-]+$", "title": "Object Slug", "type": "string" } }, "required": [ "object_slug" ], "title": "g8_app_object_getArguments", "type": "object" } ``` ## g8_app_object_attributes > Read the app object's attribute schema before shaping record values. Requires the separate app-token connection and attributes:read. The API enforces the app's manifest, installation, consent and current schema. ### Inputs ```json { "properties": { "object_slug": { "maxLength": 256, "minLength": 1, "pattern": "^[A-Za-z0-9_-]+$", "title": "Object Slug", "type": "string" } }, "required": [ "object_slug" ], "title": "g8_app_object_attributesArguments", "type": "object" } ``` ## g8_app_object_records > List active app-owned records. Requires the app-token connection and records:read. Preserve pagination and use a cursor from the prior response when present; cursor takes precedence over page. App and tenant come only from the token. ### Inputs ```json { "properties": { "object_slug": { "maxLength": 256, "minLength": 1, "pattern": "^[A-Za-z0-9_-]+$", "title": "Object Slug", "type": "string" }, "page": { "default": 1, "minimum": 1, "title": "Page", "type": "integer" }, "limit": { "default": 50, "maximum": 200, "minimum": 1, "title": "Limit", "type": "integer" }, "cursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Cursor" } }, "required": [ "object_slug" ], "title": "g8_app_object_recordsArguments", "type": "object" } ``` ## g8_app_object_record_get > Read an app-owned record's current values. Requires the app-token connection and records:read. ### Inputs ```json { "properties": { "object_slug": { "maxLength": 256, "minLength": 1, "pattern": "^[A-Za-z0-9_-]+$", "title": "Object Slug", "type": "string" }, "record_id": { "maxLength": 256, "minLength": 1, "pattern": "^[A-Za-z0-9_-]+$", "title": "Record Id", "type": "string" } }, "required": [ "object_slug", "record_id" ], "title": "g8_app_object_record_getArguments", "type": "object" } ``` ## g8_app_object_record_history > Read an app-owned record's value history, newest first. Requires the app-token connection and records:read. ### Inputs ```json { "properties": { "object_slug": { "maxLength": 256, "minLength": 1, "pattern": "^[A-Za-z0-9_-]+$", "title": "Object Slug", "type": "string" }, "record_id": { "maxLength": 256, "minLength": 1, "pattern": "^[A-Za-z0-9_-]+$", "title": "Record Id", "type": "string" }, "limit": { "default": 100, "maximum": 500, "minimum": 1, "title": "Limit", "type": "integer" } }, "required": [ "object_slug", "record_id" ], "title": "g8_app_object_record_historyArguments", "type": "object" } ``` ## g8_app_object_record_update > Update supplied app-record attributes after review and confirm=true. Requires the app-token connection and records:write. Omitted attributes remain unchanged; explicit null clears an allowed nullable attribute. Supply expected_revision from the record to reject stale edits with 409. ### Inputs ```json { "properties": { "object_slug": { "maxLength": 256, "minLength": 1, "pattern": "^[A-Za-z0-9_-]+$", "title": "Object Slug", "type": "string" }, "record_id": { "maxLength": 256, "minLength": 1, "pattern": "^[A-Za-z0-9_-]+$", "title": "Record Id", "type": "string" }, "values": { "additionalProperties": true, "title": "Values", "type": "object" }, "expected_revision": { "anyOf": [ { "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "title": "Expected Revision" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "object_slug", "record_id", "values" ], "title": "g8_app_object_record_updateArguments", "type": "object" } ``` ## g8_app_object_record_create > Create an app-owned record after reviewing its values and setting confirm=true. Requires the separate app-token connection and records:write. Read the attribute schema first. The API rejects unknown attributes and invalid cross-app or cross-tenant references, and applies app metering. ### Inputs ```json { "properties": { "object_slug": { "maxLength": 256, "minLength": 1, "pattern": "^[A-Za-z0-9_-]+$", "title": "Object Slug", "type": "string" }, "values": { "additionalProperties": true, "title": "Values", "type": "object" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "object_slug", "values" ], "title": "g8_app_object_record_createArguments", "type": "object" } ``` ## g8_app_object_record_restore > Restore an archived app-owned record under its original ID after review and confirm=true. Requires the app-token connection and records:write. Current schema, uniqueness, reference, installation and consent checks remain authoritative. ### Inputs ```json { "properties": { "object_slug": { "maxLength": 256, "minLength": 1, "pattern": "^[A-Za-z0-9_-]+$", "title": "Object Slug", "type": "string" }, "record_id": { "maxLength": 256, "minLength": 1, "pattern": "^[A-Za-z0-9_-]+$", "title": "Record Id", "type": "string" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "object_slug", "record_id" ], "title": "g8_app_object_record_restoreArguments", "type": "object" } ``` ## Public-widget connection MCP package 0.62.0 or later includes `g8-public-mcp-server`, a separate local stdio connection for the public widget API. Configure an existing write key and the HTTPS origin of a site you are authorized to integrate. The backend checks the site's domain allowlist and selects the organization associated with the write key. An organization API key does not substitute for this connection. ```json { "mcpServers": { "graph8-public-widgets": { "command": "uvx", "args": ["--from", "g8-mcp-server==0.62.0", "g8-public-mcp-server"], "env": { "G8_API_URL": "https://be.graph8.com", "G8_PUBLIC_ORIGIN": "https://your-authorized-site.example", "G8_PUBLIC_WRITE_KEY": "YOUR_EXISTING_PUBLIC_WRITE_KEY" } } } } ``` Use the appropriate backend origin for your environment, without an API path. Keep credentials in your client's protected environment configuration. Neither credentials nor the site origin can be changed in tool arguments. Missing configuration stops startup; an expired or rejected key does not trigger credential exchange or an organization-key fallback. This connection exposes the two public tools below plus `g8_tool_search` and `g8_execute`. Both calls first return a preview. Review the target and potential usage costs, then call with `confirm: true`. The client makes one request and does not follow redirects or automatically retry. After an uncertain failure, check the result before repeating a potentially billable call. Existing API rate limits apply. Public enrichment returns fields from the public enrichment endpoint, not an organization CRM record. Public copilot uses the widget's non-streaming knowledge assistant. Its current API accepts but does not consume the `context` field; `session_id` is echoed rather than proof of persisted conversation history. TLS readiness is available on the ordinary developer/all MCP connection. It reports whether the public edge ask endpoint currently permits TLS for a domain. It does not issue a certificate, verify DNS, or prove an installed certificate is healthy. Unknown and unverified domains both return `tls_eligible: false`; other errors remain errors. ## g8_app_tls_readiness Check whether the configured edge currently permits TLS for a hostname. This read calls the public TLS ask endpoint. It does not issue a certificate, verify DNS or prove a deployed certificate is healthy. A 404 means not eligible, with unknown and unverified hosts kept indistinguishable. Infrastructure outages remain errors. ### Inputs ```json { "properties": { "domain": { "maxLength": 253, "minLength": 1, "title": "Domain", "type": "string" } }, "required": [ "domain" ], "title": "g8_app_tls_readinessArguments", "type": "object" } ``` ## g8_public_copilot_chat Ask the public widget copilot using the separate g8-public-mcp-server connection. Requires an existing public write key and a configured allowed site origin. The API selects the organization from the key; arguments cannot select it. Uses the public non-streaming knowledge assistant; context is forwarded as supplied but the current API does not consume it. Requires confirmation because model usage may be billable. No automatic retry is performed. ### Inputs ```json { "properties": { "message": { "minLength": 1, "title": "Message", "type": "string" }, "session_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Session Id" }, "context": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Context" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "message" ], "title": "g8_public_copilot_chatArguments", "type": "object" } ``` ## g8_public_enrich_lookup Look up known progressive-form contact fields through the public-widget API. Requires g8-public-mcp-server with an existing public write key and an allowed site origin. Returns the public endpoint's known/missing fields, not an organization CRM record. Confirm this potentially billable lookup. No automatic retry is performed; API authentication and rate limits apply. ### Inputs ```json { "properties": { "email": { "minLength": 1, "title": "Email", "type": "string" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "email" ], "title": "g8_public_enrich_lookupArguments", "type": "object" } ``` ## Trusted connections MCP package 0.63.0 adds credential-provider connections for app tokens and a controller connection for observed build reporting. Configure existing credentials through your MCP host's protected environment. Do not put secrets into tool arguments, prompts, screenshots, or shared configuration files. ### App credential providers Use `g8-browser-app-mcp-server` with an existing authenticated browser session, or `g8-service-app-mcp-server` with existing app client credentials. These are local stdio connections. They do not initiate browser login, create client credentials, grant new scopes, or silently fall back to an organization key. Both require the following fixed configuration: | Variable | Value | | --- | --- | | `G8_API_URL` | HTTPS backend origin for the environment, without an API path. | | `G8_APP_ID` | The intended app ID. | | `G8_APP_ORG_ID` | The intended organization ID. The browser API validates membership; service credentials remain tenant-bound. | | `G8_APP_AUDIENCE` | The app's exact token audience for this environment, from the app configuration. | | `G8_APP_SCOPES` | A nonempty JSON array of explicit approved app scopes, such as `["objects:read", "records:read"]`. Wildcards are refused. | For the browser provider, add `G8_BROWSER_SESSION_TOKEN`, supplied through a protected channel by an already authenticated client. For the service provider, add `G8_APP_CLIENT_ID` and `G8_APP_CLIENT_SECRET`. Configure only credentials you are authorized to use. A browser session expiring or losing access requires the existing sign-in flow; MCP cannot impersonate that session or bypass installation consent. A service connection can be configured as follows, with placeholders supplied from your protected credential configuration: ```json { "mcpServers": { "graph8-service-app": { "command": "uvx", "args": ["--from", "g8-mcp-server==0.63.0", "g8-service-app-mcp-server"], "env": { "G8_API_URL": "https://be.graph8.com", "G8_APP_ID": "YOUR_APP_ID", "G8_APP_ORG_ID": "YOUR_ORGANIZATION_ID", "G8_APP_AUDIENCE": "YOUR_APP_AUDIENCE", "G8_APP_SCOPES": "[\"objects:read\",\"records:read\"]", "G8_APP_CLIENT_ID": "YOUR_EXISTING_CLIENT_ID", "G8_APP_CLIENT_SECRET": "YOUR_EXISTING_CLIENT_SECRET" } } } } ``` For a browser connection, change the command argument to `g8-browser-app-mcp-server` and replace the two client-credential variables with `G8_BROWSER_SESSION_TOKEN`. Each connection exposes its single acquisition tool, ten app-object tools, search and dispatch. First call the acquisition tool without confirmation to review the fixed app, tenant, audience and scopes. Call again with `confirm: true` to acquire the token. Only grant metadata is returned. The token stays in memory and is used by app-object calls on the same connection; it is never returned to the agent or written to disk. API installation, consent, revocation, scope ceilings, rate limits and token verification remain authoritative. Unexpected token app, tenant, audience or scopes are refused. An exchange failure clears the prior token. Expiry does not trigger automatic acquisition; explicitly review a new acquisition. The existing `g8-app-mcp-server` remains available when an authorized caller already has an app token. ### Build-controller connection Run `g8-controller-mcp-server` as an isolated stdio process inside the authorized controller environment, with an explicit HTTPS `G8_API_URL` and existing `APP_BUILD_CONTROLLER_TOKEN` of at least 32 characters. It also requires the standard Kubernetes service-account token and CA mounts, and authorized read access to build Jobs. The cluster API address and credential mount locations are fixed; tools cannot supply replacement addresses or files. `g8_app_report_observed_build` accepts only app ID, deployment ID and confirmation. It derives the namespace and Job name from validated IDs and verifies the returned Job identity. A running Job reports `building`, a completed Job reports `promoting`, and a failed Job reports `failed`. Missing, unreadable, mismatched or contradictory evidence does not update progress. No image digest or user-supplied error text is invented. The API retains controller authentication, app/deployment binding and legal state transitions. A 409 is returned for an out-of-order report; the tool does not force intermediate transitions or retry. This operation does not promote an app to live traffic, create Jobs or configure cluster access. ### Human approval handoff Set `G8_APP_URL` on the ordinary MCP connection to the HTTPS frontend origin matching its backend environment. `g8_agent_human_approval_handoff` accepts an approval UUID and searches caller-visible pending approvals and the latest 100 decisions. An exact match produces a review link that selects only that approval in the existing authenticated human UI. Missing or older records are reported as unavailable; a different approval is never selected. The human review screen retains its access guard and existing decision controls. Expired approvals remain expired. Call the handoff tool again after review to inspect the recorded decision and available receipts. A decision is not proof that the action executed. The tool has no decision, edit, actor or confirmation argument and never sends the decision POST. MCP elicitation is not treated as proof of a human decision. ## g8_app_exchange_browser_session Acquire an app token from this connection's existing authenticated browser session. Requires g8-browser-app-mcp-server with a protected existing session token and fixed app, tenant, audience and scope configuration. This is not a login flow. The API validates user identity, organization membership, app installation and consent. Confirm the grant first. The issued token stays in connection memory for app-object calls. No secrets, tenant or scope overrides are accepted as arguments. ### Inputs ```json { "properties": { "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "title": "g8_app_exchange_browser_sessionArguments", "type": "object" } ``` ## g8_app_issue_service_token Acquire an app token with this connection's existing service client credentials. Requires g8-service-app-mcp-server with fixed app, tenant, audience and explicit scopes. Confirm the grant first. API credential validity, revocation, scope ceilings and tenant clamp remain authoritative. Returns grant metadata only; secrets and issued tokens remain inside the connection for app-object calls. No automatic retries, refresh or organization-key fallback. ### Inputs ```json { "properties": { "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "title": "g8_app_issue_service_tokenArguments", "type": "object" } ``` ## g8_app_report_observed_build Observe a real Kubernetes build Job and report its derived progress after confirm=true. Requires the isolated g8-controller-mcp-server connection with existing controller credentials and cluster service-account mounts. App/deployment identifiers select a Job whose identity is verified. Agents cannot supply status, image digest, error text, credentials or cluster addresses. Missing or unreadable evidence never updates progress. The API verifies the app binding and state transition; this never promotes an app to live traffic. ### Inputs ```json { "properties": { "app_id": { "pattern": "^dapp_[0-9a-f]{6,32}$", "title": "App Id", "type": "string" }, "deployment_id": { "pattern": "^ddep_[0-9a-f]{6,32}$", "title": "Deployment Id", "type": "string" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "app_id", "deployment_id" ], "title": "g8_app_report_observed_buildArguments", "type": "object" } ``` ## g8_agent_human_approval_handoff Open an exact approval for human review, or inspect a recorded decision. Requires G8_APP_URL configured to this connection's frontend origin. Reads caller-visible pending approvals and the most recent 100 decisions. A missing result does not prove deletion or denial. Follow the review URL in an authenticated browser; the human UI alone makes the decision. Call again after review to inspect recorded status and available receipts. This tool never approves, rejects, edits, or treats MCP elicitation as proof of a human decision. A decision does not prove downstream execution. ### Inputs ```json { "properties": { "approval_id": { "format": "uuid", "title": "Approval Id", "type": "string" } }, "required": [ "approval_id" ], "title": "g8_agent_human_approval_handoffArguments", "type": "object" } ``` ## Human-authorized decision relay `g8_agent_submit_human_decision` requires MCP server 0.64.0, a personal API key with `workflows:write` and approval permissions, and `G8_APP_URL` set to the graph8 HTTPS origin. Discover it with `g8_tool_search` or invoke it through `g8_execute` after checking its schema. | Input | Required | Type | Description | | --- | --- | --- | --- | | `approval_id` | Yes | UUID | Exact approval ID returned by the approval listing or handoff. | Call with the approval ID. If authorization is missing, the tool returns an exact review link. The human signs into graph8, reviews the action and explicitly authorizes approval, rejection or edited approval. Call the tool again within five minutes with the personal key belonging to that human. The one-use authorization remains on the server; do not copy a token into chat. The tool accepts no decision, edit, actor or confirmation arguments. It refuses expired, changed or already-consumed authorizations. Organization keys cannot substitute for a personal identity. On an uncertain response, inspect approval status before trying again. Success records a decision; it does not prove execution. The read-only `g8_agent_human_approval_handoff` remains available to inspect status and available receipts. ## App-record change history `g8_app_object_record_changes` reads mutation history through the separate app-token connection with `records:read`. Supply `object_slug` and `record_id` from prior app results. Optional `limit` is 1–200 (default 100); pass the API-returned `cursor` unchanged for the next page. App, tenant, installation and metering remain controlled by the API. This is separate from the existing attribute-value history call. See the [complete input schema](https://docs.graph8.com/developers/mcp-reference/crm/#g8_app_object_record_changes). --- # Campaigns MCP tools > Tool descriptions and complete input schemas for campaign operations. Source: https://docs.graph8.com/developers/mcp-reference/campaign/ Use this reference to find a tool and its inputs. [Connect an MCP client](https://docs.graph8.com/developers/mcp/) before calling tools. Tool visibility depends on discovery and permissions. For actions that send messages, spend credits, delete data, or start execution, review the intended records and confirm the action before proceeding. Obtain IDs from a prior result; do not invent them. A registered tool does not guarantee that its supporting service is available in every organization. ## g8_gtm_archive_campaign > Archive a campaign (soft delete). Use it to clean up test or unwanted campaigns. The campaign leaves list_campaigns and can no longer be read, edited or launched through these tools; nothing is dropped. It moves to the Archived view in Studio > Campaigns, where a user can restore it. Sequences that are already running are not stopped by this. Needs the campaigns:delete permission. Ask the user before archiving. Args: campaign_id: The campaign's id (from list_campaigns). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `campaign_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "campaign_id": { "title": "Campaign Id", "type": "string" } }, "required": [ "campaign_id" ], "title": "archive_campaignArguments", "type": "object" } ``` </details> ## g8_gtm_archive_campaign_ad > Permanently stop ONE ad. This cannot be undone. Archive, not hard delete: Google's remove and LinkedIn's archive are both one-way, so the ad is kept with status "archived" and the spend it already incurred stays attributable. It can never serve again - to stop an ad you may want back later, pause it with set_campaign_ad_status. Refused on a campaign's LAST remaining ad: a campaign serving nothing is not a state graph8 will leave you in. Pause or delete the campaign instead. Always confirm with the user before calling this. Args: campaign_id: ad campaign id from list_ad_campaigns. ad_id: graph8 ad id from list_campaign_ads, not platform_ad_id. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `campaign_id` | Yes | integer | See the schema below for constraints and defaults. | | `ad_id` | Yes | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "campaign_id": { "title": "Campaign Id", "type": "integer" }, "ad_id": { "title": "Ad Id", "type": "integer" } }, "required": [ "campaign_id", "ad_id" ], "title": "archive_campaign_adArguments", "type": "object" } ``` </details> ## g8_gtm_archive_icp > Archive a Studio ICP (soft delete - sets status to archived). The archived ICP drops out of the default get_icps listing but is reversible in the Studio UI. Use get_icps first to find the icp_id. Args: icp_id: ICP ID (UUID) from get_icps. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `icp_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "icp_id": { "title": "Icp Id", "type": "string" } }, "required": [ "icp_id" ], "title": "archive_icpArguments", "type": "object" } ``` </details> ## g8_gtm_archive_persona > Archive a Studio buyer persona (soft delete - sets status to archived). The archived persona drops out of the default get_personas listing but is reversible in the Studio UI. Use get_personas first to find the persona_id. Args: persona_id: Persona ID (UUID) from get_personas. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `persona_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "persona_id": { "title": "Persona Id", "type": "string" } }, "required": [ "persona_id" ], "title": "archive_personaArguments", "type": "object" } ``` </details> ## g8_gtm_attach_audience > Attach (or detach) a saved list to a campaign as its audience. Launch reads the campaign's audience to enroll contacts. Idempotent - attaching the same list twice is a no-op. Args: campaign_id: Campaign to update. list_id: List to attach (must exist in this org). Pass None to detach the current audience. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `campaign_id` | Yes | string | See the schema below for constraints and defaults. | | `list_id` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "campaign_id": { "title": "Campaign Id", "type": "string" }, "list_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "List Id" } }, "required": [ "campaign_id" ], "title": "attach_audienceArguments", "type": "object" } ``` </details> ## g8_gtm_create_campaign > Create a new campaign with full Studio "Convert to Campaign" parity. By default mirrors Studio's UI flow and dispatches a doc-generator so copy is ready in ~60-90s; the campaign starts in 'copy_in_progress' and flips to 'active' once docs generate. Sequence editing: the campaign owns a native sequence after create. To shape its steps, use the campaign-scoped step tools (g8_gtm_get_campaign_sequence, g8_gtm_create_campaign_step, g8_gtm_update_campaign_step, g8_gtm_delete_campaign_step). Do NOT call the standalone g8_create_sequence for a campaign you just created - that produces a free-floating sequence the launch endpoint never reads. To attach an audience after create, call g8_gtm_attach_audience. To launch, call g8_gtm_launch_campaign(campaign_id, dry_run=True) first. Args: name: Campaign name (required). Max 255 chars. category: Campaign category. Default "Outbound". Max 100 chars. brief: Campaign brief. core_concept: Core concept text. primary_hook: Primary hook / angle copy. target_persona: Target persona id or descriptor. Max 200 chars. goal: Campaign goal. Max 255 chars. audience_list_id: Optional list id to attach as the audience in the same call. Must already exist in this org or create fails 404. target_channels: REQUIRED when auto_generate_documents is True. Channels the campaign will use, e.g. ["email"], ["email","linkedin"]; drives which copy docs are generated. If omitted the tool creates NOTHING and returns action_required=true: ask the user which channels (email, linkedin, phone, sms, voice_ai), then call again. Never guess. secondary_hooks: Additional hook variations to seed the campaign idea with (used by the doc-generator for copy variants). auto_generate_documents: When True (default), runs the doc-generator and makes the campaign visible in the Studio workbench. Set False ONLY for a bare draft (no copy, not renderable in the Studio dashboard) - keep the default unless you must opt out. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `name` | Yes | string | See the schema below for constraints and defaults. | | `category` | No | string | See the schema below for constraints and defaults. | | `brief` | No | string / null | See the schema below for constraints and defaults. | | `core_concept` | No | string / null | See the schema below for constraints and defaults. | | `primary_hook` | No | string / null | See the schema below for constraints and defaults. | | `target_persona` | No | string / null | See the schema below for constraints and defaults. | | `goal` | No | string / null | See the schema below for constraints and defaults. | | `audience_list_id` | No | string / null | See the schema below for constraints and defaults. | | `target_channels` | No | array / null | See the schema below for constraints and defaults. | | `secondary_hooks` | No | array / null | See the schema below for constraints and defaults. | | `auto_generate_documents` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "name": { "title": "Name", "type": "string" }, "category": { "default": "Outbound", "title": "Category", "type": "string" }, "brief": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Brief" }, "core_concept": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Core Concept" }, "primary_hook": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Primary Hook" }, "target_persona": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Target Persona" }, "goal": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Goal" }, "audience_list_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Audience List Id" }, "target_channels": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Target Channels" }, "secondary_hooks": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Secondary Hooks" }, "auto_generate_documents": { "default": true, "title": "Auto Generate Documents", "type": "boolean" } }, "required": [ "name" ], "title": "create_campaignArguments", "type": "object" } ``` </details> ## g8_gtm_create_campaign_ads > Add one ad, or several at once, to a paid ad campaign. SPENDS REAL MONEY. On a campaign that is already launched the new ad goes live immediately and starts consuming that campaign's budget. On a draft campaign it is persisted and created when the campaign launches. Confirm with the user before calling this on a launched campaign. Partial success is expected on a fan-out: one creative rejected on policy does not stop its siblings. Read `results` per ad and check `status` ("ok" or "partial"); never report a clean success off the envelope alone. There is no per-ad budget or bid to set here: budget is campaign level. Args: campaign_id: ad campaign id from list_ad_campaigns. name, headline, primary_text, description, cta, destination_url, creative_image_url, creative_image_url_square, creative_image_url_portrait, logo_image_url, copy_assets, source_ad_creative_id: the single ad to add. copy_assets carries multi-asset copy, for example {"rsa_headlines": [...], "rsa_descriptions": [...]}. ads: add several at once, 1 to 20 entries, each a dict using the same field names as above. When given it REPLACES the flat single-ad fields rather than adding to them. Unknown keys are rejected by the API rather than silently ignored. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `campaign_id` | Yes | integer | See the schema below for constraints and defaults. | | `name` | No | string / null | See the schema below for constraints and defaults. | | `headline` | No | string / null | See the schema below for constraints and defaults. | | `primary_text` | No | string / null | See the schema below for constraints and defaults. | | `description` | No | string / null | See the schema below for constraints and defaults. | | `cta` | No | string / null | See the schema below for constraints and defaults. | | `destination_url` | No | string / null | See the schema below for constraints and defaults. | | `creative_image_url` | No | string / null | See the schema below for constraints and defaults. | | `creative_image_url_square` | No | string / null | See the schema below for constraints and defaults. | | `creative_image_url_portrait` | No | string / null | See the schema below for constraints and defaults. | | `logo_image_url` | No | string / null | See the schema below for constraints and defaults. | | `copy_assets` | No | object / null | See the schema below for constraints and defaults. | | `source_ad_creative_id` | No | string / null | See the schema below for constraints and defaults. | | `ads` | No | array / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "campaign_id": { "title": "Campaign Id", "type": "integer" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "headline": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Headline" }, "primary_text": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Primary Text" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "cta": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Cta" }, "destination_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Destination Url" }, "creative_image_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Creative Image Url" }, "creative_image_url_square": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Creative Image Url Square" }, "creative_image_url_portrait": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Creative Image Url Portrait" }, "logo_image_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Logo Image Url" }, "copy_assets": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Copy Assets" }, "source_ad_creative_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Source Ad Creative Id" }, "ads": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Ads" } }, "required": [ "campaign_id" ], "title": "create_campaign_adsArguments", "type": "object" } ``` </details> ## g8_gtm_create_campaign_document > Create a new campaign document. Default status is "completed" since MCP-authored docs are usually ready to use. The launch checklist counts only completed docs; "draft" keeps the doc out of the checklist until you flip it via update_campaign_document. Args: campaign_id: Campaign UUID. file_type: Document type (e.g. campaign_brief, sequence, email_copy). display_name: Human-readable document name. content: Markdown / text body. folder_path: Optional folder path for organizing. status: "completed" (default) or "draft". structured_data: Optional structured payload (e.g. parsed sequence steps, persona JSON). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `campaign_id` | Yes | string | See the schema below for constraints and defaults. | | `file_type` | Yes | string | See the schema below for constraints and defaults. | | `display_name` | Yes | string | See the schema below for constraints and defaults. | | `content` | No | string | See the schema below for constraints and defaults. | | `folder_path` | No | string / null | See the schema below for constraints and defaults. | | `status` | No | string | See the schema below for constraints and defaults. | | `structured_data` | No | object / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "campaign_id": { "title": "Campaign Id", "type": "string" }, "file_type": { "title": "File Type", "type": "string" }, "display_name": { "title": "Display Name", "type": "string" }, "content": { "default": "", "title": "Content", "type": "string" }, "folder_path": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Folder Path" }, "status": { "default": "completed", "title": "Status", "type": "string" }, "structured_data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Structured Data" } }, "required": [ "campaign_id", "file_type", "display_name" ], "title": "create_campaign_documentArguments", "type": "object" } ``` </details> ## g8_gtm_create_campaign_step > Add a step to a campaign sequence. The backend validates (channel, mode) against this matrix; a pair outside it returns 400 (no auto-coercion): - email -> email - phone -> call, voicemail - sms -> sms - social -> connect_note, dm, comment, reply - voice_ai -> call mode is the channel sub-type, not a manual/ai_generated toggle. Args: campaign_id: Campaign to attach the step to. Must already exist; the backend scaffolds the sequence on the first create_campaign_step. name: Human-readable step name (free text). channel: One of the keys in the matrix above. mode: A mode allowed for channel per the matrix above. day: Day offset from campaign start (integer, can be 0). platform: Optional sub-platform identifier (e.g. "linkedin" for channel='social'). angle_id: Messaging angle identifier. Default "angle_1". cta_type: Call-to-action category. Default "soft_ask". personalization_level: low \| medium \| high. Default "medium". constraints: Free-form dict; for email/sms steps this holds subject and body ({"subject": "...", "body": "..."}). condition: Optional sequence-flow condition (e.g. "no_reply"). stop_on_reply: Halt sequence for this contact on reply. Default True. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `campaign_id` | Yes | string | See the schema below for constraints and defaults. | | `name` | Yes | string | See the schema below for constraints and defaults. | | `channel` | Yes | string | See the schema below for constraints and defaults. | | `mode` | Yes | string | See the schema below for constraints and defaults. | | `day` | Yes | integer | See the schema below for constraints and defaults. | | `platform` | No | string / null | See the schema below for constraints and defaults. | | `angle_id` | No | string | See the schema below for constraints and defaults. | | `cta_type` | No | string | See the schema below for constraints and defaults. | | `personalization_level` | No | string | See the schema below for constraints and defaults. | | `constraints` | No | object / null | See the schema below for constraints and defaults. | | `condition` | No | string / null | See the schema below for constraints and defaults. | | `stop_on_reply` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "campaign_id": { "title": "Campaign Id", "type": "string" }, "name": { "title": "Name", "type": "string" }, "channel": { "title": "Channel", "type": "string" }, "mode": { "title": "Mode", "type": "string" }, "day": { "title": "Day", "type": "integer" }, "platform": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Platform" }, "angle_id": { "default": "angle_1", "title": "Angle Id", "type": "string" }, "cta_type": { "default": "soft_ask", "title": "Cta Type", "type": "string" }, "personalization_level": { "default": "medium", "title": "Personalization Level", "type": "string" }, "constraints": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Constraints" }, "condition": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Condition" }, "stop_on_reply": { "default": true, "title": "Stop On Reply", "type": "boolean" } }, "required": [ "campaign_id", "name", "channel", "mode", "day" ], "title": "create_campaign_stepArguments", "type": "object" } ``` </details> ## g8_gtm_create_icp > Create a Studio ICP (Ideal Customer Profile) manually. The write counterpart of get_icps. This is a manual create (no AI scoring); fit/opportunity/readiness scores stay empty until the ICP is regenerated in Studio. The new ICP is created with status "active". Args: name: ICP name/title. website_url: The org website this ICP belongs to. description: Freeform ICP description. Optional. firmographics: Firmographic criteria dict (industry, size, geo, ...). Optional. tech_stack: Technology-signal dict. Optional. buying_signals: List of buying-intent signals. Optional. estimated_market_size: Estimated addressable-market count. Optional. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `name` | Yes | string | See the schema below for constraints and defaults. | | `website_url` | Yes | string | See the schema below for constraints and defaults. | | `description` | No | string / null | See the schema below for constraints and defaults. | | `firmographics` | No | object / null | See the schema below for constraints and defaults. | | `tech_stack` | No | object / null | See the schema below for constraints and defaults. | | `buying_signals` | No | array / null | See the schema below for constraints and defaults. | | `estimated_market_size` | No | integer / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "name": { "title": "Name", "type": "string" }, "website_url": { "title": "Website Url", "type": "string" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "firmographics": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Firmographics" }, "tech_stack": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Tech Stack" }, "buying_signals": { "anyOf": [ { "items": {}, "type": "array" }, { "type": "null" } ], "default": null, "title": "Buying Signals" }, "estimated_market_size": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Estimated Market Size" } }, "required": [ "name", "website_url" ], "title": "create_icpArguments", "type": "object" } ``` </details> ## g8_gtm_create_persona > Create a Studio buyer persona manually. The write counterpart of get_personas. This is a manual create (no AI); the persona is created with status "active". Use for adding a persona you already know about without running Studio's AI persona generation. Args: title: Persona title (e.g. "Director of Marketing Operations"). website_url: The org website this persona belongs to. priority: Display priority (lower sorts first). Defaults to 1. confidence: Confidence label (e.g. "high"/"medium"/"low"). Defaults to "medium". why_target: Why this persona is worth targeting. Optional. key_signals: List of buying/intent signals. Optional. expected_receptivity: Expected-receptivity note. Optional. campaign_approach: Recommended campaign approach. Optional. recommended_goal: Recommended campaign goal. Optional. source: Origin label for the persona. Defaults to "manual". ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `title` | Yes | string | See the schema below for constraints and defaults. | | `website_url` | Yes | string | See the schema below for constraints and defaults. | | `priority` | No | integer / null | See the schema below for constraints and defaults. | | `confidence` | No | string / null | See the schema below for constraints and defaults. | | `why_target` | No | string / null | See the schema below for constraints and defaults. | | `key_signals` | No | array / null | See the schema below for constraints and defaults. | | `expected_receptivity` | No | string / null | See the schema below for constraints and defaults. | | `campaign_approach` | No | string / null | See the schema below for constraints and defaults. | | `recommended_goal` | No | string / null | See the schema below for constraints and defaults. | | `source` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "title": { "title": "Title", "type": "string" }, "website_url": { "title": "Website Url", "type": "string" }, "priority": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Priority" }, "confidence": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Confidence" }, "why_target": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Why Target" }, "key_signals": { "anyOf": [ { "items": {}, "type": "array" }, { "type": "null" } ], "default": null, "title": "Key Signals" }, "expected_receptivity": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Expected Receptivity" }, "campaign_approach": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Campaign Approach" }, "recommended_goal": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Recommended Goal" }, "source": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Source" } }, "required": [ "title", "website_url" ], "title": "create_personaArguments", "type": "object" } ``` </details> ## g8_gtm_create_pipeline > Create a new pipeline with default stages seeded for the target. For an AI-grounded pipeline based on the org's context, prefer suggest_pipeline_from_context then create_pipeline_from_suggestion. Args: name: Pipeline name. target: Default stage shape. One of: prospects_revenue (default, AE/new-logo), prospects_meeting (SDR meeting-booking), customers_revenue (CSM expansion/renewal). blank: When True, skip per-target defaults and seed 2 placeholder stages to customize manually (build-from-scratch path). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `name` | No | string | See the schema below for constraints and defaults. | | `target` | No | string | See the schema below for constraints and defaults. | | `blank` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "name": { "default": "Sales Pipeline", "title": "Name", "type": "string" }, "target": { "default": "prospects_revenue", "title": "Target", "type": "string" }, "blank": { "default": false, "title": "Blank", "type": "boolean" } }, "title": "create_pipelineArguments", "type": "object" } ``` </details> ## g8_gtm_create_pipeline_from_suggestion > Persist a PipelineSuggestion (possibly edited) as a real pipeline. Validates evidence keys, sanitizes channel scripts, and creates the pipeline + stages atomically. No dedup: every call creates a new pipeline, so do not post the same draft twice. Args: suggestion: Dict from suggest_pipeline_from_context (or equivalent). Shape: {name, rationale, context_sources: [...], stages: [ {name, probability, stage_type, color, required_elements, recommended_elements, channel_scripts, rationale}]}. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `suggestion` | Yes | object | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "suggestion": { "additionalProperties": true, "title": "Suggestion", "type": "object" } }, "required": [ "suggestion" ], "title": "create_pipeline_from_suggestionArguments", "type": "object" } ``` </details> ## g8_gtm_create_stage > Add a new stage to a pipeline. Args: pipeline_id: Pipeline to add the stage to. name: Stage name. probability: Win probability percent (0-100). stage_type: One of: open, won, lost. color: Hex color for the stage. required_elements: Evidence keys gating advance; subset of list_stage_evidence_library. Unknown keys dropped server-side. recommended_elements: Nice-to-have evidence keys; same vocabulary. channel_scripts: Optional dict with keys call/email/linkedin (any subset); each value is markdown talk track. Empty values dropped. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `pipeline_id` | Yes | string | See the schema below for constraints and defaults. | | `name` | Yes | string | See the schema below for constraints and defaults. | | `probability` | No | integer | See the schema below for constraints and defaults. | | `stage_type` | No | string | See the schema below for constraints and defaults. | | `color` | No | string | See the schema below for constraints and defaults. | | `required_elements` | No | array / null | See the schema below for constraints and defaults. | | `recommended_elements` | No | array / null | See the schema below for constraints and defaults. | | `channel_scripts` | No | object / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "pipeline_id": { "title": "Pipeline Id", "type": "string" }, "name": { "title": "Name", "type": "string" }, "probability": { "default": 0, "title": "Probability", "type": "integer" }, "stage_type": { "default": "open", "title": "Stage Type", "type": "string" }, "color": { "default": "#6B7280", "title": "Color", "type": "string" }, "required_elements": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Required Elements" }, "recommended_elements": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Recommended Elements" }, "channel_scripts": { "anyOf": [ { "additionalProperties": { "type": "string" }, "type": "object" }, { "type": "null" } ], "default": null, "title": "Channel Scripts" } }, "required": [ "pipeline_id", "name" ], "title": "create_stageArguments", "type": "object" } ``` </details> ## g8_gtm_delete_campaign_document > Delete a campaign document. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `campaign_id` | Yes | string | See the schema below for constraints and defaults. | | `document_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "campaign_id": { "title": "Campaign Id", "type": "string" }, "document_id": { "title": "Document Id", "type": "string" } }, "required": [ "campaign_id", "document_id" ], "title": "delete_campaign_documentArguments", "type": "object" } ``` </details> ## g8_gtm_delete_campaign_step > Remove a step from the campaign sequence. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `campaign_id` | Yes | string | See the schema below for constraints and defaults. | | `step_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "campaign_id": { "title": "Campaign Id", "type": "string" }, "step_id": { "title": "Step Id", "type": "string" } }, "required": [ "campaign_id", "step_id" ], "title": "delete_campaign_stepArguments", "type": "object" } ``` </details> ## g8_gtm_delete_pipeline > Delete a pipeline. Fails if the pipeline has deals or is the only one. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `pipeline_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "pipeline_id": { "title": "Pipeline Id", "type": "string" } }, "required": [ "pipeline_id" ], "title": "delete_pipelineArguments", "type": "object" } ``` </details> ## g8_gtm_delete_stage > Delete a stage. Fails if the stage has deals or the pipeline would drop below 2 stages. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `pipeline_id` | Yes | string | See the schema below for constraints and defaults. | | `stage_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "pipeline_id": { "title": "Pipeline Id", "type": "string" }, "stage_id": { "title": "Stage Id", "type": "string" } }, "required": [ "pipeline_id", "stage_id" ], "title": "delete_stageArguments", "type": "object" } ``` </details> ## g8_gtm_get_ad_images > List ad image creatives for one campaign. Returns every ad image that hasn't been marked hidden. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `campaign_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "campaign_id": { "title": "Campaign Id", "type": "string" } }, "required": [ "campaign_id" ], "title": "get_ad_imagesArguments", "type": "object" } ``` </details> ## g8_gtm_get_campaign > Get campaign details. Optionally fetch the full editable payload. Default (full=False): returns just the campaign row + documents. full=True: returns campaign + audience + ALL documents (with content) + sequence + step_catalog + linked_sequences + is_launched in one round trip. Use when about to edit a campaign and you need the full state. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `campaign_id` | Yes | string | See the schema below for constraints and defaults. | | `full` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "campaign_id": { "title": "Campaign Id", "type": "string" }, "full": { "default": false, "title": "Full", "type": "boolean" } }, "required": [ "campaign_id" ], "title": "get_campaignArguments", "type": "object" } ``` </details> ## g8_gtm_get_campaign_document > Get the full content of a campaign document. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `campaign_id` | Yes | string | See the schema below for constraints and defaults. | | `document_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "campaign_id": { "title": "Campaign Id", "type": "string" }, "document_id": { "title": "Document Id", "type": "string" } }, "required": [ "campaign_id", "document_id" ], "title": "get_campaign_documentArguments", "type": "object" } ``` </details> ## g8_gtm_get_campaign_ideas > List saved campaign ideas. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `favorited` | No | boolean / null | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "favorited": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Favorited" }, "limit": { "default": 50, "title": "Limit", "type": "integer" } }, "title": "get_campaign_ideasArguments", "type": "object" } ``` </details> ## g8_gtm_get_campaign_metrics > Get campaign execution metrics - sequence status + email counters. Returns email_metrics (sent/sequencer_sent/delivered/bounced/replied/ reply_rate), plus is_running and sequence_count. There is no open tracking: never report or estimate opens. Each campaign carries a ``metric_status``: - available: measured, reconciles with send receipts - zero: measured, both analytics and send receipts confirm nothing sent - stale: send receipts prove sends the analytics rollup hasn't counted yet (pipeline lag) - report the receipt count, do NOT call it an outage - unknown: analytics were unreachable - metrics are NOT zero - missing: campaign has no launched sequences yet Read ``reconciliation`` (rollup vs receipt counts + note) and ``measured_range`` before drawing any conclusion about delivery. Args: campaign_id: Campaign to read metrics for. days: Lookback window (1-365). Default 30. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `campaign_id` | Yes | string | See the schema below for constraints and defaults. | | `days` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "campaign_id": { "title": "Campaign Id", "type": "string" }, "days": { "default": 30, "title": "Days", "type": "integer" } }, "required": [ "campaign_id" ], "title": "get_campaign_metricsArguments", "type": "object" } ``` </details> ## g8_gtm_get_campaign_sequence > Get the campaign sequence and step catalog. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `campaign_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "campaign_id": { "title": "Campaign Id", "type": "string" } }, "required": [ "campaign_id" ], "title": "get_campaign_sequenceArguments", "type": "object" } ``` </details> ## g8_gtm_get_company_profile > Read the org's company profile - the source Studio generation reads. Check this FIRST when Studio documents describe the wrong company. Self-serve orgs that signed up without a domain carry graph8's own profile, which is why their ICP, personas and campaigns come out aimed at graph8's buyers. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "get_company_profileArguments", "type": "object" } ``` </details> ## g8_gtm_get_deliverability_metrics > Deliverability capacity / sends / placement / warmup for this org. Read-only fleet health snapshot - the same signals the internal admin Deliverability dashboard shows, scoped to your org: * fleet_usable_capacity / fleet_idle_capacity / fleet_idle_mailboxes / fleet_sends / utilization - ramp-aware sending capacity over the window, SPLIT into usable (mailboxes on a live sequence) vs idle (no live sequence). utilization = sends / usable capacity, so idle mailboxes don't inflate the denominator. * mailboxes_above_floor / below_floor / untested - honest per-mailbox inbox placement vs the 0.70 floor (the trustworthy placement signal; a raw org-average inbox% is misleading and intentionally not returned). * warmup_avg_health / warmup_stale_count - avg warmup health across freshly-synced, actively-warming mailboxes, plus how many have a stale/untracked health signal. Args: days: Trailing window in days (1-30, default 7). mailbox_id: Optional. When set, also returns a `mailbox` slice with that mailbox's daily_capacity, sends, inbox_rate, warmup_score, and lifecycle_state. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `days` | No | integer | See the schema below for constraints and defaults. | | `mailbox_id` | No | integer / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "days": { "default": 7, "title": "Days", "type": "integer" }, "mailbox_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Mailbox Id" } }, "title": "get_deliverability_metricsArguments", "type": "object" } ``` </details> ## g8_gtm_get_global_context > Read the org's Studio Global Context documents - the written GTM foundation. USE THIS TOOL to answer ANY question about what the organization has documented about itself. This is the single source of truth for the org's go-to-market context. Reach for it whenever the user asks about: - target audience / who we sell to / buyers -> category "audience" - ideal customer profile / ICP -> category "targeting" - personas -> category "targeting" - brand brief / brand voice / style / compliance -> category "brand" - pricing / offer / packaging / proof -> category "offer" - messaging / value props / elevator pitch / positioning / narrative -> category "messaging" - market insights / competitors / TAM research -> category "market" If you are unsure which category holds the answer, OMIT `category` to pull every document, then read the relevant ones. Each document carries `category`, `wiki_category` (the group the Studio Wiki shows it under - TAM / ICP / Persona Research are stored as market/audience but shown under "targeting"), `workflow`, `title`, `status`, `updated_at`, and (when include_content=True, the default) the full `content` body so you can answer what the document actually SAYS - not just list its title. The Studio Wiki also has Intelligence documents (website scrape, competitor discovery, organic/paid keywords, brand news, brand sentiment, ...) and Research reports. They are NOT returned unless you pass include_intelligence=True / include_research=True. To see everything the Wiki shows, pass both. Read an Intelligence doc's body with g8_gtm_list_intelligence_data(data_type=<doc_type>, include_content=True). Args: category: Filter to one category - one of brand, audience, messaging, market, offer, targeting, data. Omit to return every category (recommended when the user's question could span more than one). An unknown category returns an error that lists the valid ones. include_content: When True (default) each document includes its body text in `content` so you can read and quote it. Pass False only for a cheap title-and-status listing where the body is not needed. limit: Max documents to return (1-200) per bucket. Default 50. include_intelligence: Also list the Wiki's Intelligence documents (workflow "intelligence", category "data"). Default False. include_research: Also list the Wiki's Research reports (workflow "research", category "data"). Default False. For STRUCTURED, scored persona/ICP records (firmographics, fit scores, pinned flags) use g8_get_personas / g8_get_icps instead - those read the dedicated cb_personas / cb_icps tables rather than the narrative docs. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `category` | No | string / null | See the schema below for constraints and defaults. | | `include_content` | No | boolean | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | | `include_intelligence` | No | boolean | See the schema below for constraints and defaults. | | `include_research` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "category": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Category" }, "include_content": { "default": true, "title": "Include Content", "type": "boolean" }, "limit": { "default": 50, "title": "Limit", "type": "integer" }, "include_intelligence": { "default": false, "title": "Include Intelligence", "type": "boolean" }, "include_research": { "default": false, "title": "Include Research", "type": "boolean" } }, "title": "get_global_contextArguments", "type": "object" } ``` </details> ## g8_gtm_get_icps > List ICPs available for campaign targeting. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `status` | No | string / null | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "limit": { "default": 50, "title": "Limit", "type": "integer" } }, "title": "get_icpsArguments", "type": "object" } ``` </details> ## g8_gtm_get_mailbox_warmup > Get live warmup analytics for a single mailbox. Hits the configured warmup provider over HTTP - slow (~300-800ms). For a cheap bulk read of which mailboxes have warmup configured, use list_mailboxes and inspect warmup_status / warmup_provider first. Returns {success, warmup_enabled, provider, analytics, warmup_settings}. Provider-specific fields inside analytics: * Instantly: health_score, inbox_rate, daily_stats * SmartLead: reputation, daily_stats When warmup isn't configured, returns warmup_enabled=false, provider=null. Args: mailbox_id: Integer mailbox ID (from list_mailboxes). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `mailbox_id` | Yes | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "mailbox_id": { "title": "Mailbox Id", "type": "integer" } }, "required": [ "mailbox_id" ], "title": "get_mailbox_warmupArguments", "type": "object" } ``` </details> ## g8_gtm_get_personas > List personas available for campaign targeting. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `status` | No | string / null | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "limit": { "default": 50, "title": "Limit", "type": "integer" } }, "title": "get_personasArguments", "type": "object" } ``` </details> ## g8_gtm_get_pipeline > Get one pipeline by ID, including all stages and their checklist + scripts. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `pipeline_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "pipeline_id": { "title": "Pipeline Id", "type": "string" } }, "required": [ "pipeline_id" ], "title": "get_pipelineArguments", "type": "object" } ``` </details> ## g8_gtm_hide_ad_image > Hide one ad creative from the campaign's grid. Drops the image from future listings (data is retained). Idempotent - hiding an already-hidden image is a no-op that still succeeds. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `campaign_id` | Yes | string | See the schema below for constraints and defaults. | | `image_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "campaign_id": { "title": "Campaign Id", "type": "string" }, "image_id": { "title": "Image Id", "type": "string" } }, "required": [ "campaign_id", "image_id" ], "title": "hide_ad_imageArguments", "type": "object" } ``` </details> ## g8_gtm_launch_campaign > Launch a campaign to begin real outbound execution. Call with dry_run=True first to get a confirmation preview (audience size, active mailbox count, per-step snippets), then dry_run=False to send. A failed or incomplete preflight blocks confirmation. If no sender is given, the first active email mailbox is auto-attached; pass sender_mailbox_ids to choose explicitly. The response surfaces channels_attached listing which mailbox(es) were attached. The four pre-launch pickers (agents, schedule, appointment) are all optional. Args: campaign_id: Campaign to launch (from g8_gtm_list_campaigns). dry_run: When True, return a confirmation preview instead of launching. sender_mailbox_ids: Explicit sender mailbox IDs (from g8_gtm_list_mailboxes; each must be active + non-archived). Omit to auto-pick the first active email mailbox. textual_agent_name: Sender persona for email/social steps (from g8_list_agents). Pass the same value as voice_agent_name for the common one-agent-all-channels case. voice_agent_name: Sender persona for phone/SMS steps (from g8_list_agents). schedule_id: Send-window + timezone (from g8_list_schedules). appointment_id: Calendar booking link to attach (from g8_list_event_types). Approval: the dry_run=True preview returns a one-time approval_token (valid 15 min, single use, bound to these exact arguments). The real run (dry_run=False) after a preview must carry it: the user's Confirm click on the card sends it, or pass it yourself only after the user explicitly approves in chat. Without it the preview comes back (approval_required) and nothing runs. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `campaign_id` | Yes | string | See the schema below for constraints and defaults. | | `dry_run` | No | boolean | See the schema below for constraints and defaults. | | `sender_mailbox_ids` | No | array / null | See the schema below for constraints and defaults. | | `textual_agent_name` | No | string / null | See the schema below for constraints and defaults. | | `voice_agent_name` | No | string / null | See the schema below for constraints and defaults. | | `schedule_id` | No | string / null | See the schema below for constraints and defaults. | | `appointment_id` | No | integer / null | See the schema below for constraints and defaults. | | `approval_token` | No | string / null | One-time approval token from this tool's dry_run=True preview. The graph8 Confirm card sends it when the user clicks Confirm. Pass it yourself ONLY after the user has explicitly approved this exact preview in chat. Never pass it on your own initiative. Without it, a real run that follows a preview returns the preview again instead of executing. | <details> <summary>Complete input schema</summary> ```json { "properties": { "campaign_id": { "title": "Campaign Id", "type": "string" }, "dry_run": { "default": false, "title": "Dry Run", "type": "boolean" }, "sender_mailbox_ids": { "anyOf": [ { "items": { "type": "integer" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Sender Mailbox Ids" }, "textual_agent_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Textual Agent Name" }, "voice_agent_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Voice Agent Name" }, "schedule_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Schedule Id" }, "appointment_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Appointment Id" }, "approval_token": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "One-time approval token from this tool's dry_run=True preview. The graph8 Confirm card sends it when the user clicks Confirm. Pass it yourself ONLY after the user has explicitly approved this exact preview in chat. Never pass it on your own initiative. Without it, a real run that follows a preview returns the preview again instead of executing.", "title": "Approval Token" } }, "required": [ "campaign_id" ], "title": "launch_campaignArguments", "type": "object" } ``` </details> ## g8_gtm_list_ad_campaigns > List the org's PAID ad campaigns (Google, LinkedIn), or read one. Paid advertising, not outbound sequences: use list_campaigns for those. Each row is the budget and targeting unit, with `ad_count` creatives inside it. Creative text and images live on the ads, so read them with list_campaign_ads. Args: platform: optional filter: "google", "linkedin", "meta" (also "facebook") or "x". Any other value is rejected, so an empty result always means "no campaigns there". When a row's past_end_date is true, read its status_note: the stored status can be stale. campaign_id: optional. Read just this ad campaign. The result is a collection either way, holding one entry. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `platform` | No | string / null | See the schema below for constraints and defaults. | | `campaign_id` | No | integer / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "platform": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Platform" }, "campaign_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Campaign Id" } }, "title": "list_ad_campaignsArguments", "type": "object" } ``` </details> ## g8_gtm_list_campaign_ads > List the ads inside one paid ad campaign, or read a single ad. Always returns a collection, even for a campaign holding exactly one ad. A campaign launched before per-ad control existed returns its single implicit ad, promoted to a real slot on first read so you get a stable `id` to act on. Act on an ad by its graph8 `id`, never by `platform_ad_id`: the platform handle changes every time a creative is replaced, the graph8 id does not. Args: campaign_id: ad campaign id from list_ad_campaigns. ad_id: optional. Read just this ad, still as a one-item collection. include_archived: include ads already archived. Ignored with ad_id. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `campaign_id` | Yes | integer | See the schema below for constraints and defaults. | | `ad_id` | No | integer / null | See the schema below for constraints and defaults. | | `include_archived` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "campaign_id": { "title": "Campaign Id", "type": "integer" }, "ad_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Ad Id" }, "include_archived": { "default": false, "title": "Include Archived", "type": "boolean" } }, "required": [ "campaign_id" ], "title": "list_campaign_adsArguments", "type": "object" } ``` </details> ## g8_gtm_list_campaign_documents > List document metadata for a campaign. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `campaign_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "campaign_id": { "title": "Campaign Id", "type": "string" } }, "required": [ "campaign_id" ], "title": "list_campaign_documentsArguments", "type": "object" } ``` </details> ## g8_gtm_list_campaigns > List campaigns in your organization. Paged: read `has_next` and `total`, and call again with page + 1 until has_next is false. `status` is the stored campaign status; check `launch_state` / `is_running` to see whether a sequence is attached ("active" with launch_state "not_launched" means nothing is sending). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `page` | No | integer | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "page": { "default": 1, "title": "Page", "type": "integer" }, "limit": { "default": 50, "title": "Limit", "type": "integer" } }, "title": "list_campaignsArguments", "type": "object" } ``` </details> ## g8_gtm_list_intelligence_data > List documents in the Intelligence bucket of Studio Global Docs. ~16 doc types per org: website_scrape, company_enrichment, competitor_discovery, company_keywords, product_inventory, organic_keywords, paid_keywords, ... Args: data_type: Filter by data_type. Omit to return every type. include_content: When True, include the raw data blob per row (large; default False returns metadata only). limit: Max rows (1-200). Default 50. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `data_type` | No | string / null | See the schema below for constraints and defaults. | | `include_content` | No | boolean | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "data_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Data Type" }, "include_content": { "default": false, "title": "Include Content", "type": "boolean" }, "limit": { "default": 50, "title": "Limit", "type": "integer" } }, "title": "list_intelligence_dataArguments", "type": "object" } ``` </details> ## g8_gtm_list_kb_documents > List all documents in the GTM knowledge base. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "list_kb_documentsArguments", "type": "object" } ``` </details> ## g8_gtm_list_mailboxes > List sending mailboxes - or SENDING DOMAINS - for this org. Use before launching a campaign to confirm the org has at least one connection_status='active' mailbox. Three shapes: - default: every mailbox the caller can see. - ``mailbox_id``: just that one, as a one-entry list. The single read is the ONLY one that carries ``allows_nurture`` and ``warmup_enabled``; in exchange it omits source, team fields and the warmup_provider/warmup_status/smartlead_warmup_enabled trio the list carries. Read both when you need all of it. - ``by_domain=True``: the org's sending DOMAINS instead - the containers mailboxes live in - with ``provider_connected`` (the gate for buying mailboxes on a domain) and ``mailbox_count``. ``include_archived`` and ``connection_status`` do not apply to domains, and ``limit`` is clamped to 100 by that route. Args: limit: Max rows to return (1-500; domains clamp to 100). Default 100. include_archived: Include archived mailboxes. Default False. connection_status: Filter by status (active \| paused \| disconnected \| expired \| ...). mailbox_id: Read exactly this mailbox. by_domain: Return sending domains rather than mailboxes. page: 1-indexed page, domains only. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `limit` | No | integer | See the schema below for constraints and defaults. | | `include_archived` | No | boolean | See the schema below for constraints and defaults. | | `connection_status` | No | string / null | See the schema below for constraints and defaults. | | `mailbox_id` | No | integer / null | See the schema below for constraints and defaults. | | `by_domain` | No | boolean | See the schema below for constraints and defaults. | | `page` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "limit": { "default": 100, "title": "Limit", "type": "integer" }, "include_archived": { "default": false, "title": "Include Archived", "type": "boolean" }, "connection_status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Connection Status" }, "mailbox_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Mailbox Id" }, "by_domain": { "default": false, "title": "By Domain", "type": "boolean" }, "page": { "default": 1, "title": "Page", "type": "integer" } }, "title": "list_mailboxesArguments", "type": "object" } ``` </details> ## g8_gtm_list_pipelines > List all deal pipelines for your organization, with stages. Each stage includes required_elements (must-have evidence keys that gate advance), recommended_elements (nice-to-haves), and channel_scripts (markdown talk tracks per channel: call/email/linkedin). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "list_pipelinesArguments", "type": "object" } ``` </details> ## g8_gtm_list_research_reports > List documents in the Research bucket of Studio Global Docs. 6 report types per org: buyer_psychology, competitive_teardown, gtm_channel, industry_analyst, review_sentiment, voice_of_customer. Args: report_type: Filter by report_type. Omit to return every type. include_content: When True, include the structured content and rendered markdown (large; default False returns metadata only). limit: Max rows (1-200). Default 50. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `report_type` | No | string / null | See the schema below for constraints and defaults. | | `include_content` | No | boolean | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "report_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Report Type" }, "include_content": { "default": false, "title": "Include Content", "type": "boolean" }, "limit": { "default": 50, "title": "Limit", "type": "integer" } }, "title": "list_research_reportsArguments", "type": "object" } ``` </details> ## g8_gtm_list_stage_evidence_library > List the 8 canonical evidence keys + human-readable labels. Call before constructing required_elements / recommended_elements so you know the vocabulary. Keys not in this list are silently dropped at write time. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "list_stage_evidence_libraryArguments", "type": "object" } ``` </details> ## g8_gtm_patch_campaign_full > Atomic deep-update of a campaign in one round trip. Equivalent to update_campaign with deep-update kwargs. Exposed separately so agents searching "patch" or "deep update" discover it. Args: campaign_id: Campaign to patch. name/brief/core_concept/primary_hook/target_persona/category/goal: Campaign fields. Provided fields are updated; omit to leave unchanged. audience_list_id: Attach this list as the audience. detach_audience: Set True to clear the audience. Mutually exclusive with audience_list_id. documents: Per-document patches. Each item: {id?, file_type?, display_name?, folder_path?, status?, content?, structured_data?}. With id -> update; without id (and file_type + display_name present) -> create. sequence_steps: If provided, REORDERS / re-times the existing sequence. Each item {step_id, day, condition?, stop_on_reply?}; every step_id MUST already exist (use create_campaign_step to add, delete_campaign_step to remove). Omit to leave ordering alone; pass [] to clear the sequence (step bodies are retained). linked_sequence_ids: Existing sequence IDs to bind to this campaign. Skips already-linked IDs; does NOT modify launch audit rows. unlink_sequence_ids: Sequence IDs to unlink; audit rows are never touched. Returns the post-patch payload - same shape as get_campaign(full=True). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `campaign_id` | Yes | string | See the schema below for constraints and defaults. | | `name` | No | string / null | See the schema below for constraints and defaults. | | `brief` | No | string / null | See the schema below for constraints and defaults. | | `core_concept` | No | string / null | See the schema below for constraints and defaults. | | `primary_hook` | No | string / null | See the schema below for constraints and defaults. | | `target_persona` | No | string / null | See the schema below for constraints and defaults. | | `category` | No | string / null | See the schema below for constraints and defaults. | | `goal` | No | string / null | See the schema below for constraints and defaults. | | `audience_list_id` | No | string / null | See the schema below for constraints and defaults. | | `detach_audience` | No | boolean | See the schema below for constraints and defaults. | | `documents` | No | array / null | See the schema below for constraints and defaults. | | `sequence_steps` | No | array / null | See the schema below for constraints and defaults. | | `linked_sequence_ids` | No | array / null | See the schema below for constraints and defaults. | | `unlink_sequence_ids` | No | array / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "campaign_id": { "title": "Campaign Id", "type": "string" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "brief": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Brief" }, "core_concept": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Core Concept" }, "primary_hook": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Primary Hook" }, "target_persona": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Target Persona" }, "category": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Category" }, "goal": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Goal" }, "audience_list_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Audience List Id" }, "detach_audience": { "default": false, "title": "Detach Audience", "type": "boolean" }, "documents": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Documents" }, "sequence_steps": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Sequence Steps" }, "linked_sequence_ids": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Linked Sequence Ids" }, "unlink_sequence_ids": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Unlink Sequence Ids" } }, "required": [ "campaign_id" ], "title": "patch_campaign_fullArguments", "type": "object" } ``` </details> ## g8_gtm_provision_sending_domain > Set up a sending domain: register, connect, verify, add mailboxes. The flow, in order. Read the org's domains first with ``g8_gtm_list_mailboxes(by_domain=True)``. - ``action="check_availability"``: is one ``domain`` free to register, and what would it cost? Spends nothing and changes nothing. BUYING the domain is deliberately not on this tool - registering one files a legal registrant record in the customer's name - so the answer here is for the user to act on in the app, not a step this tool can finish. - ``action="add_external"``: register domains the org ALREADY owns elsewhere so graph8 can send from them. Creates a row only; the domain becomes eligible for the two steps below. Pass ``domains``. - ``action="connect_provider"``: connect one purchased domain to the managed mailbox-provisioning backend. Required once per domain before buying managed mailboxes on it. This REPOINTS THE DOMAIN'S NAMESERVERS at the registrar (skipped for domains added with add_external) and creates a vendor workspace for the org, so it needs ``confirm=True``. - ``action="check_propagation"``: has a connected domain's nameserver change taken effect yet? Mostly a read, but on the first check after connect_provider it can write the nameservers at the registrar, so it is not a read-only call. - ``action="buy_mailboxes"``: buy mailboxes on a connected domain. SPENDS REAL CREDITS and needs ``confirm=True``. Two shapes, because the two provisioning paths address a mailbox differently: for the MANAGED path pass ``mailbox_usernames`` (local parts - "sales" makes sales@thedomain) together with ``platform``; otherwise pass ``mailbox_emails`` (full addresses). ``last_name`` must be non-empty on the managed path or provisioning stalls upstream. Mailbox passwords are generated upstream; this tool takes none. Buying a NEW domain is deliberately not here - registering a domain files a legal registrant record in the customer's name. Args: action: check_availability \| add_external \| connect_provider \| check_propagation \| buy_mailboxes. domain_id: The domain to act on (all actions except check_availability and add_external). domain: One domain name, check_availability only. domains: Domains to register, add_external only. mailbox_usernames: Local parts to create on the managed path, buy_mailboxes only (max 5 per purchase). Needs `platform`. mailbox_emails: Full addresses to create on the default path, buy_mailboxes only. first_name, last_name: Owner name stamped on each new mailbox. platform: GOOGLE \| MICROSOFT. Required for a managed purchase. enable_warmup: Enroll new mailboxes in warmup immediately. confirm: Required for connect_provider and buy_mailboxes. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `action` | Yes | string | See the schema below for constraints and defaults. | | `domain_id` | No | integer / null | See the schema below for constraints and defaults. | | `domain` | No | string / null | See the schema below for constraints and defaults. | | `domains` | No | array / null | See the schema below for constraints and defaults. | | `mailbox_usernames` | No | array / null | See the schema below for constraints and defaults. | | `mailbox_emails` | No | array / null | See the schema below for constraints and defaults. | | `first_name` | No | string / null | See the schema below for constraints and defaults. | | `last_name` | No | string / null | See the schema below for constraints and defaults. | | `platform` | No | string / null | See the schema below for constraints and defaults. | | `enable_warmup` | No | boolean | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "action": { "title": "Action", "type": "string" }, "domain_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Domain Id" }, "domain": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Domain" }, "domains": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Domains" }, "mailbox_usernames": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Mailbox Usernames" }, "mailbox_emails": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Mailbox Emails" }, "first_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "First Name" }, "last_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Name" }, "platform": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Platform" }, "enable_warmup": { "default": true, "title": "Enable Warmup", "type": "boolean" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "action" ], "title": "provision_sending_domainArguments", "type": "object" } ``` </details> ## g8_gtm_regenerate_global_context > Regenerate Studio Global Context documents against a website. Studio docs are generated once at onboarding from the company profile's website. If that profile was wrong then - the common case for self-serve orgs, which seed graph8's own profile when no domain is supplied - every ICP, persona, TAM and campaign downstream describes the wrong company. Use this after correcting the company profile. The one-shot pipeline (`generate-all`) refuses to re-run once it has completed; this regenerates per document, which has no such lock. Requires intelligence for `website_url` to already exist - run the intelligence analyze step for a newly-changed domain first, or this returns 404 rather than quietly regenerating the same stale content. Args: website_url: The website to generate from. After a domain change this must be the NEW domain; intelligence is keyed by URL, so passing the old one reproduces the stale content. doc_types: Specific doc types to regenerate, e.g. ["icp_research", "persona_research"]. Omit for every document that already exists in the org. Regeneration is asynchronous: a queued task is not a regenerated document. Confirm with get_global_context and compare updated_at. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `website_url` | Yes | string | See the schema below for constraints and defaults. | | `doc_types` | No | array / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "website_url": { "title": "Website Url", "type": "string" }, "doc_types": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Doc Types" } }, "required": [ "website_url" ], "title": "regenerate_global_contextArguments", "type": "object" } ``` </details> ## g8_gtm_reorder_stages > Reorder stages in a pipeline. Pass the full ordered list of stage IDs. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `pipeline_id` | Yes | string | See the schema below for constraints and defaults. | | `stage_ids` | Yes | array | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "pipeline_id": { "title": "Pipeline Id", "type": "string" }, "stage_ids": { "items": { "type": "string" }, "title": "Stage Ids", "type": "array" } }, "required": [ "pipeline_id", "stage_ids" ], "title": "reorder_stagesArguments", "type": "object" } ``` </details> ## g8_gtm_search_kb > Search the GTM knowledge base. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `query` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "query": { "title": "Query", "type": "string" } }, "required": [ "query" ], "title": "search_kbArguments", "type": "object" } ``` </details> ## g8_gtm_set_campaign_ad_status > Pause or resume ONE ad, leaving its siblings and the campaign alone. Reversible, and it does not touch the creative, so nothing is replaced and neither policy review nor delivery learning restarts. Resuming an ad puts it back to serving against the campaign's budget. A LinkedIn creative still in policy review cannot be paused; that comes back as an actionable message rather than a raw platform error. To stop an ad for good use archive_campaign_ad. To stop the whole campaign, pause the campaign instead of pausing every ad. Args: campaign_id: ad campaign id from list_ad_campaigns. ad_id: graph8 ad id from list_campaign_ads, not platform_ad_id. status: "paused" or "active". ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `campaign_id` | Yes | integer | See the schema below for constraints and defaults. | | `ad_id` | Yes | integer | See the schema below for constraints and defaults. | | `status` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "campaign_id": { "title": "Campaign Id", "type": "integer" }, "ad_id": { "title": "Ad Id", "type": "integer" }, "status": { "title": "Status", "type": "string" } }, "required": [ "campaign_id", "ad_id", "status" ], "title": "set_campaign_ad_statusArguments", "type": "object" } ``` </details> ## g8_gtm_set_company_profile > Set the org's company profile (website + fields). This is the source of truth for Studio generation. Fixing it is step one when documents describe the wrong company; it does NOT rewrite existing documents, which were generated from the old site - follow with regenerate_global_context. Args: website_url: The client's real website. fields: Profile fields to save, e.g. {"company_name": "Acme", "industry": "Manufacturing"}. Replaces the stored field set. unapprove_if_approved: Approved profiles are immutable by design. Leave false and an approved profile returns 409. Set true only to deliberately unlock and overwrite a human-approved profile. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `website_url` | Yes | string | See the schema below for constraints and defaults. | | `fields` | Yes | object | See the schema below for constraints and defaults. | | `unapprove_if_approved` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "website_url": { "title": "Website Url", "type": "string" }, "fields": { "additionalProperties": true, "title": "Fields", "type": "object" }, "unapprove_if_approved": { "default": false, "title": "Unapprove If Approved", "type": "boolean" } }, "required": [ "website_url", "fields" ], "title": "set_company_profileArguments", "type": "object" } ``` </details> ## g8_gtm_suggest_pipeline_from_context > Generate an AI-suggested pipeline grounded in the org's GTM context. Reads the org's GTM context docs and returns a PipelineSuggestion draft whose stages carry required_elements, recommended_elements, and channel_scripts anchored to the org's actual language. Consumes credits. Returns a draft only - does NOT persist; pass the result to create_pipeline_from_suggestion (with optional edits) to commit. On API failure or empty context, returns a generic target-appropriate fallback. Args: target: Pipeline shape. One of: prospects_revenue (default, AE/new-logo), prospects_meeting (SDR meeting-booking), customers_revenue (CSM expansion/renewal). motion_hint: Optional freeform steer (e.g. "enterprise SaaS, annual contracts, 60-day cycle"). use_brief_only: When True (default), use the pre-extracted intelligence brief; set False to read source docs (more tokens). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `target` | No | string | See the schema below for constraints and defaults. | | `motion_hint` | No | string / null | See the schema below for constraints and defaults. | | `use_brief_only` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "target": { "default": "prospects_revenue", "title": "Target", "type": "string" }, "motion_hint": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Motion Hint" }, "use_brief_only": { "default": true, "title": "Use Brief Only", "type": "boolean" } }, "title": "suggest_pipeline_from_contextArguments", "type": "object" } ``` </details> ## g8_gtm_test_mailbox_connection > Re-test a mailbox's stored SMTP/IMAP credentials, now. This is what refreshes ``connection_status``: run it when ``g8_gtm_list_mailboxes`` shows a mailbox as disconnected or expired and you want to know whether it is healthy again before a launch. It dials the mail servers with the credentials already stored on the mailbox and writes the outcome back. A failure comes back as an error carrying the mail server's own message (bad credentials, TLS, timeout). That is a DIAGNOSTIC RESULT, not a tool malfunction - report it to the user rather than retrying, since a retry re-dials with the same credentials. The API also accepts host/port/username/password overrides to test credentials that are not stored yet. This tool does not expose them: they would let a caller dial an arbitrary host and port through the platform, and secrets do not belong in tool arguments. Args: mailbox_id: From ``g8_gtm_list_mailboxes``. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `mailbox_id` | Yes | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "mailbox_id": { "title": "Mailbox Id", "type": "integer" } }, "required": [ "mailbox_id" ], "title": "test_mailbox_connectionArguments", "type": "object" } ``` </details> ## g8_gtm_update_campaign > Update campaign strategy fields or metadata. Default (no deep-update kwargs): updates ONLY the campaign row. Deep-update mode (any of audience_list_id, detach_audience, documents, sequence_steps, linked_sequence_ids, unlink_sequence_ids provided): an atomic deep-merge of: 1. Campaign fields (name/brief/hook/etc.) 2. Audience attach (audience_list_id) or detach (detach_audience=True) 3. Documents - per-doc patches: update existing by id, or create by file_type + display_name. Each item: {id?, file_type?, display_name?, folder_path?, status?, content?, structured_data?}. 4. Sequence steps - full replacement. Each item: {step_id, day, condition?, stop_on_reply?}. 5. Linked sequences - linked_sequence_ids binds existing sequences to this campaign (skips already-linked IDs); unlink_sequence_ids unlinks them (launch audit rows are never touched). Use linking to bind a manually built sequence so metrics include it, or to pre-attach a sequence the launch flow should know about. target_channels replaces the campaign's channel list (email, linkedin, phone, sms, voice_ai), e.g. to drop a channel the brief no longer uses. Existing documents and steps are kept. A name must have visible text; renaming also updates the campaign's slug. Returns the deep-update payload in deep-update mode; plain ActionResult otherwise. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `campaign_id` | Yes | string | See the schema below for constraints and defaults. | | `name` | No | string / null | See the schema below for constraints and defaults. | | `brief` | No | string / null | See the schema below for constraints and defaults. | | `core_concept` | No | string / null | See the schema below for constraints and defaults. | | `primary_hook` | No | string / null | See the schema below for constraints and defaults. | | `target_persona` | No | string / null | See the schema below for constraints and defaults. | | `category` | No | string / null | See the schema below for constraints and defaults. | | `goal` | No | string / null | See the schema below for constraints and defaults. | | `audience_list_id` | No | string / null | See the schema below for constraints and defaults. | | `detach_audience` | No | boolean | See the schema below for constraints and defaults. | | `documents` | No | array / null | See the schema below for constraints and defaults. | | `sequence_steps` | No | array / null | See the schema below for constraints and defaults. | | `linked_sequence_ids` | No | array / null | See the schema below for constraints and defaults. | | `unlink_sequence_ids` | No | array / null | See the schema below for constraints and defaults. | | `target_channels` | No | array / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "campaign_id": { "title": "Campaign Id", "type": "string" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "brief": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Brief" }, "core_concept": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Core Concept" }, "primary_hook": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Primary Hook" }, "target_persona": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Target Persona" }, "category": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Category" }, "goal": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Goal" }, "audience_list_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Audience List Id" }, "detach_audience": { "default": false, "title": "Detach Audience", "type": "boolean" }, "documents": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Documents" }, "sequence_steps": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Sequence Steps" }, "linked_sequence_ids": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Linked Sequence Ids" }, "unlink_sequence_ids": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Unlink Sequence Ids" }, "target_channels": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Target Channels" } }, "required": [ "campaign_id" ], "title": "update_campaignArguments", "type": "object" } ``` </details> ## g8_gtm_update_campaign_ad > Edit one ad's creative. Omitted fields are left unchanged. On a live campaign this is applied as a REPLACE, because that is what the platforms permit: LinkedIn creatives are wholly immutable and on Google only Search/RSA is mutable in place. The platform ad is archived and a new one created, so POLICY REVIEW AND DELIVERY LEARNING START OVER for that ad and early performance will look unlike the old ad's. The graph8 ad id and its measurement history survive. Check `replaced_platform_ad` and `consequences` in the result rather than assuming, and warn the user before editing a well-performing live ad. There is no budget or bid to edit here: budget is campaign level. Args: campaign_id: ad campaign id from list_ad_campaigns. ad_id: graph8 ad id from list_campaign_ads, not platform_ad_id. name, headline, primary_text, description, cta, destination_url, creative_image_url, creative_image_url_square, creative_image_url_portrait, logo_image_url, copy_assets, source_ad_creative_id: the fields to change. Pass only what changes. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `campaign_id` | Yes | integer | See the schema below for constraints and defaults. | | `ad_id` | Yes | integer | See the schema below for constraints and defaults. | | `name` | No | string / null | See the schema below for constraints and defaults. | | `headline` | No | string / null | See the schema below for constraints and defaults. | | `primary_text` | No | string / null | See the schema below for constraints and defaults. | | `description` | No | string / null | See the schema below for constraints and defaults. | | `cta` | No | string / null | See the schema below for constraints and defaults. | | `destination_url` | No | string / null | See the schema below for constraints and defaults. | | `creative_image_url` | No | string / null | See the schema below for constraints and defaults. | | `creative_image_url_square` | No | string / null | See the schema below for constraints and defaults. | | `creative_image_url_portrait` | No | string / null | See the schema below for constraints and defaults. | | `logo_image_url` | No | string / null | See the schema below for constraints and defaults. | | `copy_assets` | No | object / null | See the schema below for constraints and defaults. | | `source_ad_creative_id` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "campaign_id": { "title": "Campaign Id", "type": "integer" }, "ad_id": { "title": "Ad Id", "type": "integer" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "headline": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Headline" }, "primary_text": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Primary Text" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "cta": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Cta" }, "destination_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Destination Url" }, "creative_image_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Creative Image Url" }, "creative_image_url_square": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Creative Image Url Square" }, "creative_image_url_portrait": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Creative Image Url Portrait" }, "logo_image_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Logo Image Url" }, "copy_assets": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Copy Assets" }, "source_ad_creative_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Source Ad Creative Id" } }, "required": [ "campaign_id", "ad_id" ], "title": "update_campaign_adArguments", "type": "object" } ``` </details> ## g8_gtm_update_campaign_document > Save edits back to a campaign document. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `campaign_id` | Yes | string | See the schema below for constraints and defaults. | | `document_id` | Yes | string | See the schema below for constraints and defaults. | | `content` | Yes | string | See the schema below for constraints and defaults. | | `status` | No | string / null | See the schema below for constraints and defaults. | | `structured_data` | No | object / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "campaign_id": { "title": "Campaign Id", "type": "string" }, "document_id": { "title": "Document Id", "type": "string" }, "content": { "title": "Content", "type": "string" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "structured_data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Structured Data" } }, "required": [ "campaign_id", "document_id", "content" ], "title": "update_campaign_documentArguments", "type": "object" } ``` </details> ## g8_gtm_update_campaign_sequence > Replace the campaign sequence ordering with a new step list. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `campaign_id` | Yes | string | See the schema below for constraints and defaults. | | `steps` | Yes | array | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "campaign_id": { "title": "Campaign Id", "type": "string" }, "steps": { "items": { "additionalProperties": true, "type": "object" }, "title": "Steps", "type": "array" } }, "required": [ "campaign_id", "steps" ], "title": "update_campaign_sequenceArguments", "type": "object" } ``` </details> ## g8_gtm_update_campaign_step > Update a campaign sequence step definition or placement. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `campaign_id` | Yes | string | See the schema below for constraints and defaults. | | `step_id` | Yes | string | See the schema below for constraints and defaults. | | `name` | No | string / null | See the schema below for constraints and defaults. | | `channel` | No | string / null | See the schema below for constraints and defaults. | | `mode` | No | string / null | See the schema below for constraints and defaults. | | `platform` | No | string / null | See the schema below for constraints and defaults. | | `angle_id` | No | string / null | See the schema below for constraints and defaults. | | `cta_type` | No | string / null | See the schema below for constraints and defaults. | | `personalization_level` | No | string / null | See the schema below for constraints and defaults. | | `constraints` | No | object / null | See the schema below for constraints and defaults. | | `do_not_send_rules` | No | array / null | See the schema below for constraints and defaults. | | `day` | No | integer / null | See the schema below for constraints and defaults. | | `condition` | No | string / null | See the schema below for constraints and defaults. | | `stop_on_reply` | No | boolean / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "campaign_id": { "title": "Campaign Id", "type": "string" }, "step_id": { "title": "Step Id", "type": "string" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "channel": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Channel" }, "mode": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Mode" }, "platform": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Platform" }, "angle_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Angle Id" }, "cta_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Cta Type" }, "personalization_level": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Personalization Level" }, "constraints": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Constraints" }, "do_not_send_rules": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Do Not Send Rules" }, "day": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Day" }, "condition": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Condition" }, "stop_on_reply": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Stop On Reply" } }, "required": [ "campaign_id", "step_id" ], "title": "update_campaign_stepArguments", "type": "object" } ``` </details> ## g8_gtm_update_global_context > Update a Studio Global Context document (Context bucket). The write counterpart of get_global_context. Each save creates a version snapshot. Use get_global_context first to find the doc_id. Args: doc_id: Global context document ID (UUID) from get_global_context. content: New document body. Omit to leave unchanged. status: New status (e.g. "completed", "draft"). Omit to leave unchanged. Pass at least one of content / status. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `doc_id` | Yes | string | See the schema below for constraints and defaults. | | `content` | No | string / null | See the schema below for constraints and defaults. | | `status` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "doc_id": { "title": "Doc Id", "type": "string" }, "content": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Content" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" } }, "required": [ "doc_id" ], "title": "update_global_contextArguments", "type": "object" } ``` </details> ## g8_gtm_update_icp > Update a Studio ICP's fields (partial update). The write counterpart of get_icps. Use get_icps first to find the icp_id. To archive an ICP use archive_icp instead. Pass at least one field to change. Args: icp_id: ICP ID (UUID) from get_icps. name: New ICP name. Omit to leave unchanged. description: New description. Omit to leave unchanged. firmographics: New firmographic criteria dict. Omit to leave unchanged. tech_stack: New technology-signal dict. Omit to leave unchanged. buying_signals: New list of buying-intent signals. Omit to leave unchanged. estimated_market_size: New estimated market-size count. Omit to leave unchanged. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `icp_id` | Yes | string | See the schema below for constraints and defaults. | | `name` | No | string / null | See the schema below for constraints and defaults. | | `description` | No | string / null | See the schema below for constraints and defaults. | | `firmographics` | No | object / null | See the schema below for constraints and defaults. | | `tech_stack` | No | object / null | See the schema below for constraints and defaults. | | `buying_signals` | No | array / null | See the schema below for constraints and defaults. | | `estimated_market_size` | No | integer / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "icp_id": { "title": "Icp Id", "type": "string" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "firmographics": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Firmographics" }, "tech_stack": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Tech Stack" }, "buying_signals": { "anyOf": [ { "items": {}, "type": "array" }, { "type": "null" } ], "default": null, "title": "Buying Signals" }, "estimated_market_size": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Estimated Market Size" } }, "required": [ "icp_id" ], "title": "update_icpArguments", "type": "object" } ``` </details> ## g8_gtm_update_mailbox > Change a mailbox's sending settings. Only supplied fields change. Reach for it to throttle a mailbox that is burning reputation (``daily_limit``), to keep nurture off a cold-outbound mailbox (``allows_nurture=False``), or to fix a signature. Credentials are deliberately NOT editable here. The API accepts SMTP/IMAP host, port, username and password on this route, but a password is a live secret and this surface never takes one as an argument; re-enter credentials in the app. Args: mailbox_id: From ``g8_gtm_list_mailboxes``. display_name: From-name shown on sends. daily_limit: Max sends per day (>= 1). allows_nurture: Whether nurture campaigns may use this mailbox. signature_html: HTML signature body. signature_plain: Plain-text signature body. use_html_signature: Send the HTML signature rather than the plain one. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `mailbox_id` | Yes | integer | See the schema below for constraints and defaults. | | `display_name` | No | string / null | See the schema below for constraints and defaults. | | `daily_limit` | No | integer / null | See the schema below for constraints and defaults. | | `allows_nurture` | No | boolean / null | See the schema below for constraints and defaults. | | `signature_html` | No | string / null | See the schema below for constraints and defaults. | | `signature_plain` | No | string / null | See the schema below for constraints and defaults. | | `use_html_signature` | No | boolean / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "mailbox_id": { "title": "Mailbox Id", "type": "integer" }, "display_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Display Name" }, "daily_limit": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Daily Limit" }, "allows_nurture": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Allows Nurture" }, "signature_html": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Signature Html" }, "signature_plain": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Signature Plain" }, "use_html_signature": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Use Html Signature" } }, "required": [ "mailbox_id" ], "title": "update_mailboxArguments", "type": "object" } ``` </details> ## g8_gtm_update_persona > Update a Studio persona's fields and/or status (partial update). The write counterpart of get_personas. Use get_personas first to find the persona_id. Passing status="archived" is equivalent to archive_persona. Pass at least one field to change. Args: persona_id: Persona ID (UUID) from get_personas. title: New persona title. Omit to leave unchanged. priority: New display priority. Omit to leave unchanged. confidence: New confidence label. Omit to leave unchanged. why_target: New "why target" text. Omit to leave unchanged. key_signals: New list of signals. Omit to leave unchanged. expected_receptivity: New receptivity note. Omit to leave unchanged. campaign_approach: New campaign approach. Omit to leave unchanged. recommended_goal: New recommended goal. Omit to leave unchanged. status: New status (e.g. "active", "archived"). Omit to leave unchanged. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `persona_id` | Yes | string | See the schema below for constraints and defaults. | | `title` | No | string / null | See the schema below for constraints and defaults. | | `priority` | No | integer / null | See the schema below for constraints and defaults. | | `confidence` | No | string / null | See the schema below for constraints and defaults. | | `why_target` | No | string / null | See the schema below for constraints and defaults. | | `key_signals` | No | array / null | See the schema below for constraints and defaults. | | `expected_receptivity` | No | string / null | See the schema below for constraints and defaults. | | `campaign_approach` | No | string / null | See the schema below for constraints and defaults. | | `recommended_goal` | No | string / null | See the schema below for constraints and defaults. | | `status` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "persona_id": { "title": "Persona Id", "type": "string" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "priority": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Priority" }, "confidence": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Confidence" }, "why_target": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Why Target" }, "key_signals": { "anyOf": [ { "items": {}, "type": "array" }, { "type": "null" } ], "default": null, "title": "Key Signals" }, "expected_receptivity": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Expected Receptivity" }, "campaign_approach": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Campaign Approach" }, "recommended_goal": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Recommended Goal" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" } }, "required": [ "persona_id" ], "title": "update_personaArguments", "type": "object" } ``` </details> ## g8_gtm_update_pipeline > Rename a pipeline and/or promote it to default. Args: pipeline_id: Pipeline to update. name: New name. Omit to leave unchanged. set_default: When True, leave this pipeline as the org's only default, atomically clearing the flag from any other pipeline that held it. Already the default is a no-op, not an error. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `pipeline_id` | Yes | string | See the schema below for constraints and defaults. | | `name` | No | string / null | See the schema below for constraints and defaults. | | `set_default` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "pipeline_id": { "title": "Pipeline Id", "type": "string" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "set_default": { "default": false, "title": "Set Default", "type": "boolean" } }, "required": [ "pipeline_id" ], "title": "update_pipelineArguments", "type": "object" } ``` </details> ## g8_gtm_update_research_report > Update a Studio Research report (Research bucket). The write counterpart of list_research_reports. Editing the markdown creates a version snapshot AND re-indexes the report to the RAG knowledge base. Use list_research_reports first to find the report_id. Args: report_id: Research report ID (UUID) from list_research_reports. markdown_content: New rendered markdown body. Omit to leave unchanged. Triggers a RAG re-index when provided. status: New status. A "completed"/"approved" report can only move between those two states. Omit to leave unchanged. Pass at least one of markdown_content / status. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `report_id` | Yes | string | See the schema below for constraints and defaults. | | `markdown_content` | No | string / null | See the schema below for constraints and defaults. | | `status` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "report_id": { "title": "Report Id", "type": "string" }, "markdown_content": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Markdown Content" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" } }, "required": [ "report_id" ], "title": "update_research_reportArguments", "type": "object" } ``` </details> ## g8_gtm_update_stage > Update a stage. All parameters optional - pass only what changes. Same vocabulary rules as create_stage for required_elements, recommended_elements, and channel_scripts (stage_type: open/won/lost). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `pipeline_id` | Yes | string | See the schema below for constraints and defaults. | | `stage_id` | Yes | string | See the schema below for constraints and defaults. | | `name` | No | string / null | See the schema below for constraints and defaults. | | `probability` | No | integer / null | See the schema below for constraints and defaults. | | `stage_type` | No | string / null | See the schema below for constraints and defaults. | | `color` | No | string / null | See the schema below for constraints and defaults. | | `required_elements` | No | array / null | See the schema below for constraints and defaults. | | `recommended_elements` | No | array / null | See the schema below for constraints and defaults. | | `channel_scripts` | No | object / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "pipeline_id": { "title": "Pipeline Id", "type": "string" }, "stage_id": { "title": "Stage Id", "type": "string" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "probability": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Probability" }, "stage_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Stage Type" }, "color": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Color" }, "required_elements": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Required Elements" }, "recommended_elements": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Recommended Elements" }, "channel_scripts": { "anyOf": [ { "additionalProperties": { "type": "string" }, "type": "object" }, { "type": "null" } ], "default": null, "title": "Channel Scripts" } }, "required": [ "pipeline_id", "stage_id" ], "title": "update_stageArguments", "type": "object" } ``` </details> ## g8_gtm_upload_enablement_asset > Upload an internal file to the org's Studio Enablement library. The asset is visible to authenticated users in the same organization under Studio > Content > Assets > Enablement. It is not published or shared externally. The decoded file limit is 50 MB. Supported file extensions are PDF, DOC/DOCX, TXT/MD/MDX, CSV/JSON, PNG/JPG/JPEG, XLS/XLSX, and PPT/PPTX. Args: file_name: Original filename including a supported extension. content_base64: Standard base64-encoded file bytes. mime_type: Optional MIME type such as application/pdf. folder: Optional Enablement subfolder, created on first upload. tags: Optional labels such as deck, one-pager, or battlecard. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `file_name` | Yes | string | See the schema below for constraints and defaults. | | `content_base64` | Yes | string | See the schema below for constraints and defaults. | | `mime_type` | No | string / null | See the schema below for constraints and defaults. | | `folder` | No | string / null | See the schema below for constraints and defaults. | | `tags` | No | array / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "file_name": { "title": "File Name", "type": "string" }, "content_base64": { "title": "Content Base64", "type": "string" }, "mime_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Mime Type" }, "folder": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Folder" }, "tags": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Tags" } }, "required": [ "file_name", "content_base64" ], "title": "upload_enablement_assetArguments", "type": "object" } ``` </details> --- # Clickhouse MCP tools > Tool descriptions and complete input schemas for clickhouse operations. Source: https://docs.graph8.com/developers/mcp-reference/clickhouse/ Use this reference to find a tool and its inputs. [Connect an MCP client](https://docs.graph8.com/developers/mcp/) before calling tools. Tool visibility depends on discovery and permissions. For actions that send messages, spend credits, delete data, or start execution, review the intended records and confirm the action before proceeding. Obtain IDs from a prior result; do not invent them. A registered tool does not guarantee that its supporting service is available in every organization. ## g8_clickhouse_mashup > Bulk enrichment lookup against the ClickHouse Cloud mashup tables. Use when you already have a PK (UP_ID from g8_opensearch_resolve_ids, a HEM from a visitor row, or a company domain) and want the canonical enrichment record. For free-form attribute search prefer g8_opensearch_search. Args: mode: "by_upid" (PK lookup), "by_hem" (resolves HEM then joins the profile), or "by_domain" (filter on company domain; b2b / joined only). values: Up to 100 input keys. Empty list is rejected (a PK filter is required). profile: "b2b" (default, work emails + LinkedIn), "b2c" (consumer), or "joined" (denormalized). limit: Max rows (server-capped at 1000). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `mode` | Yes | string | See the schema below for constraints and defaults. | | `values` | Yes | array | See the schema below for constraints and defaults. | | `profile` | No | string | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "mode": { "title": "Mode", "type": "string" }, "values": { "items": { "type": "string" }, "title": "Values", "type": "array" }, "profile": { "default": "b2b", "title": "Profile", "type": "string" }, "limit": { "default": 1000, "title": "Limit", "type": "integer" } }, "required": [ "mode", "values" ], "title": "g8_clickhouse_mashupArguments", "type": "object" } ``` </details> ## g8_clickhouse_org_analytics > Per-org visitor + intent analytics: who visited the site, which companies, which keywords resolved. Scoped to your authenticated org; you CANNOT query another tenant. Args: dataset: One of "people_visitors" (resolved-person visits), "company_visitors" (resolved-company visits), "5x5_visitors" (mashup-resolved visits keyed by HEM), or "intent_resolved" (intent keyword resolutions). since: ISO date or datetime - inclusive lower bound. until: ISO date or datetime - exclusive upper bound (defaults to now). Window is hard-capped at 90 days. filters: Optional column equality filters. Unknown columns are dropped server-side. Examples: {"contact_id": 12345}, {"keyword_id": ["k1", "k2"]}. aggregation: "raw" (matching rows, default), "daily_counts" (count per day), or "top_n" (count per the dataset's primary group key, descending). limit: Max rows (server-capped at 1000). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `dataset` | Yes | string | See the schema below for constraints and defaults. | | `since` | Yes | string | See the schema below for constraints and defaults. | | `until` | No | string / null | See the schema below for constraints and defaults. | | `filters` | No | object / null | See the schema below for constraints and defaults. | | `aggregation` | No | string | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "dataset": { "title": "Dataset", "type": "string" }, "since": { "title": "Since", "type": "string" }, "until": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Until" }, "filters": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Filters" }, "aggregation": { "default": "raw", "title": "Aggregation", "type": "string" }, "limit": { "default": 100, "title": "Limit", "type": "integer" } }, "required": [ "dataset", "since" ], "title": "g8_clickhouse_org_analyticsArguments", "type": "object" } ``` </details> --- # CRM records MCP tools > Tool descriptions and complete input schemas for crm operations. Source: https://docs.graph8.com/developers/mcp-reference/crm/ Use this reference to find a tool and its inputs. [Connect an MCP client](https://docs.graph8.com/developers/mcp/) before calling tools. Tool visibility depends on discovery and permissions. For actions that send messages, spend credits, delete data, or start execution, review the intended records and confirm the action before proceeding. Obtain IDs from a prior result; do not invent them. A registered tool does not guarantee that its supporting service is available in every organization. ## g8_add_to_list > Add contacts to an existing list, by id or by explicit records. Source modes (exactly one required): - contact_ids: list[int] - attach existing CRM contacts (free). - rows: list[dict] - import explicit records into the list (free; no enrichment credits). Accepted fields per row: first_name, last_name, work_email, job_title, company_domain, seniority_level, linkedin_url, city, state, country, personal_emails, direct_phone, mobile_phone, job_department. Searching by filters and merging into an existing list is not supported here in v1 - use g8_create_list(filters=...) to create a new list instead, or call g8_find_contacts and forward the result as `rows`. When `rows` is supplied, call with `dry_run=True` first to get a ConfirmationPreview, then re-call with `dry_run=False` to execute. Args: list_id: Target list id (from g8_create_list or g8_get_lists). contact_ids: Optional CRM contact ids to attach to the list. rows: Optional list of record dicts to import into the list. dry_run: When True, return a ConfirmationPreview instead of executing. org: Target organization (graph8 org id from g8_list_orgs) the user confirmed. Required for `rows` uploads when the user belongs to several orgs: ASK the user which org first. Approval: the dry_run=True preview returns a one-time approval_token (valid 15 min, single use, bound to these exact arguments). The real run (dry_run=False) after a preview must carry it: the user's Confirm click on the card sends it, or pass it yourself only after the user explicitly approves in chat. Without it the preview comes back (approval_required) and nothing runs. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `list_id` | Yes | integer | See the schema below for constraints and defaults. | | `contact_ids` | No | array / null | See the schema below for constraints and defaults. | | `rows` | No | array / null | See the schema below for constraints and defaults. | | `dry_run` | No | boolean | See the schema below for constraints and defaults. | | `org` | No | string / null | See the schema below for constraints and defaults. | | `approval_token` | No | string / null | One-time approval token from this tool's dry_run=True preview. The graph8 Confirm card sends it when the user clicks Confirm. Pass it yourself ONLY after the user has explicitly approved this exact preview in chat. Never pass it on your own initiative. Without it, a real run that follows a preview returns the preview again instead of executing. | <details> <summary>Complete input schema</summary> ```json { "properties": { "list_id": { "title": "List Id", "type": "integer" }, "contact_ids": { "anyOf": [ { "items": { "type": "integer" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Contact Ids" }, "rows": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Rows" }, "dry_run": { "default": false, "title": "Dry Run", "type": "boolean" }, "org": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Org" }, "approval_token": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "One-time approval token from this tool's dry_run=True preview. The graph8 Confirm card sends it when the user clicks Confirm. Pass it yourself ONLY after the user has explicitly approved this exact preview in chat. Never pass it on your own initiative. Without it, a real run that follows a preview returns the preview again instead of executing.", "title": "Approval Token" } }, "required": [ "list_id" ], "title": "g8_add_to_listArguments", "type": "object" } ``` </details> ## g8_add_to_sequence > Add contacts to an outbound sequence for automated outreach. IMPORTANT: This enrolls real contacts into an active sequence that will send emails/messages. Always confirm with the user before calling this tool. Recommended flow: call with `dry_run=True` first to render a confirmation widget, then again with `dry_run=False` after the user confirms. Use g8_list_sequences to find available sequence IDs. Contacts must exist in your CRM with a contact_id. If they came from prospecting (g8_lookup_person / g8_find_contacts), create them first with g8_create_contact. Args: sequence_id: Sequence ID (from g8_list_sequences) contact_ids: List of contact IDs to enroll list_id: Contact list ID the contacts belong to dry_run: When True, return a confirmation preview instead of executing. Approval: the dry_run=True preview returns a one-time approval_token (valid 15 min, single use, bound to these exact arguments). The real run (dry_run=False) after a preview must carry it: the user's Confirm click on the card sends it, or pass it yourself only after the user explicitly approves in chat. Without it the preview comes back (approval_required) and nothing runs. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `sequence_id` | Yes | string | See the schema below for constraints and defaults. | | `contact_ids` | Yes | array | See the schema below for constraints and defaults. | | `list_id` | Yes | integer | See the schema below for constraints and defaults. | | `dry_run` | No | boolean | See the schema below for constraints and defaults. | | `approval_token` | No | string / null | One-time approval token from this tool's dry_run=True preview. The graph8 Confirm card sends it when the user clicks Confirm. Pass it yourself ONLY after the user has explicitly approved this exact preview in chat. Never pass it on your own initiative. Without it, a real run that follows a preview returns the preview again instead of executing. | <details> <summary>Complete input schema</summary> ```json { "properties": { "sequence_id": { "title": "Sequence Id", "type": "string" }, "contact_ids": { "items": { "type": "integer" }, "title": "Contact Ids", "type": "array" }, "list_id": { "title": "List Id", "type": "integer" }, "dry_run": { "default": false, "title": "Dry Run", "type": "boolean" }, "approval_token": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "One-time approval token from this tool's dry_run=True preview. The graph8 Confirm card sends it when the user clicks Confirm. Pass it yourself ONLY after the user has explicitly approved this exact preview in chat. Never pass it on your own initiative. Without it, a real run that follows a preview returns the preview again instead of executing.", "title": "Approval Token" } }, "required": [ "sequence_id", "contact_ids", "list_id" ], "title": "g8_add_to_sequenceArguments", "type": "object" } ``` </details> ## g8_analytics_get_campaign_visitors > Read one graph8 analytics report. Uses the current organization and existing API permissions. `report` selects which report to read. Defaults to `campaign-visitors`, which is the Campaign Visitors report (click steps, email-link engagement and CTR, top contacts) this tool has always returned. Available reports: Campaign visitors: campaign-visitors Acquisition (where traffic comes from): acquisition/campaigns, acquisition/channels, acquisition/channels/trend, acquisition/referrals, acquisition/search, acquisition/social, acquisition/sources Audience (who the visitors are): audience/behavior, audience/demographics, audience/geo, audience/geo/cities, audience/languages, audience/overview, audience/technology, audience/trend Behavior (what they do on site): behavior/events, behavior/exit-pages, behavior/landing-pages, behavior/page-metrics, behavior/pages Conversions (forms, goals, funnel): conversions/forms, conversions/forms/analysis, conversions/forms/trend, conversions/funnel, conversions/goals, conversions/goals/trend, conversions/rate Dialer (call volume, quality, grading): dialer/grading/search, dialer/leaderboard, dialer/leaderboard/sdr, dialer/performance, dialer/quality, dialer/quality/leaderboard Engagement (depth and time on site): engagement/by-campaign, engagement/by-channel, engagement/by-landing-page, engagement/depth-distribution, engagement/overview, engagement/time-distribution, engagement/trend Marketing (campaigns, content, outbound bridge): marketing/campaigns, marketing/content, marketing/content/by-landing, marketing/outbound-bridge, marketing/outbound-bridge/by-campaign, marketing/outbound-bridge/by-channel, marketing/outbound-bridge/by-landing-page, marketing/outbound-bridge/top-sources, marketing/outbound-bridge/trend, marketing/overview Realtime (live visitors): realtime, realtime/locations, realtime/pages, realtime/sources SDR (rep activity and leaderboards): sdr/ai-agents/summary, sdr/detail, sdr/leaderboard, sdr/summary, sdr/trends Every report is read-only and runs on the `analytics:read` scope. Give it a window with `days` (rolling) or an explicit range: `start_date`/ `end_date` (YYYY-MM-DD) work on every report that takes a range, and `date_from`/`date_to` are accepted as the same thing. A malformed, impossible or inverted range is an error, never a silent default. Realtime, marketing and SDR reports take no explicit range (`days` only, or nothing for realtime) and refuse one. Realtime reports carry `data_as_of` / `data_lag_seconds`: events land in batches, so "the last 5 minutes" ends at the newest stored event. Other arguments a report does not declare are ignored by the API. Web visitor reports exclude bot / crawler traffic and internal traffic (the graph8 app, dev hosts, your own team) by default. Pass `include_bots=true` and/or `include_internal=true` for the raw counts. `sdr_email` is required for `sdr/detail` and `dialer/leaderboard/sdr`, where it is part of the URL. `path` is required for `behavior/page-metrics`. Everything else is optional. API: GET /analytics/campaign-visitors (and 59 sibling report operations under /analytics; see `report`). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `report` | No | string | Which analytics report to read. Defaults to campaign-visitors, the report this tool returned before the rest of the /analytics surface was folded in. | | `days` | No | integer / null | Rolling 'last N days from today', 1-365. Ignored when an explicit window is supplied. | | `start_date` | No | string / null | Window start (inclusive), YYYY-MM-DD. If omitted, derived from `days`. Refused by reports with no explicit range (realtime, marketing, sdr). | | `end_date` | No | string / null | Window end (inclusive), YYYY-MM-DD. If omitted, today. | | `date_from` | No | string / null | Same as start_date (YYYY-MM-DD). Send one or the other, not two different values. | | `date_to` | No | string / null | Same as end_date (YYYY-MM-DD). Send one or the other, not two different values. | | `limit` | No | integer / null | Row cap. The per-report maximum differs (50 to 500); the API rejects an over-large value. | | `sort_by` | No | string / null | Sort key. The accepted values differ per report; see the report's API documentation. | | `sort_order` | No | string / null | Sort order: asc or desc. Defaults to desc. | | `granularity` | No | string / null | Aggregation period for sdr/trends: day or week. | | `period` | No | string / null | Time period preset for dialer/leaderboard when no dates are given: today, week or month. | | `sdr_email` | No | string / null | SDR email address. REQUIRED for sdr/detail and dialer/leaderboard/sdr, where it is part of the URL; an optional filter on the other dialer reports. | | `campaign_id` | No | string / null | Campaign ID filter for dialer/grading/search. | | `path` | No | string / null | Exact context_page_path to roll up. REQUIRED for behavior/page-metrics. | | `page` | No | integer / null | Page number for dialer/grading/search, 1-based. | | `page_size` | No | integer / null | Results per page for dialer/grading/search, 1-100. | | `min_score` | No | integer / null | Minimum overall grading score, 0-40, for dialer/grading/search. | | `max_score` | No | integer / null | Maximum overall grading score, 0-40, for dialer/grading/search. | | `include_contacts` | No | boolean / null | campaign-visitors only: include the Top contacts table (contact name + email, so PII). Set false to omit it entirely for a privacy-lean response. | | `include_bots` | No | boolean / null | Web visitor reports only (audience, acquisition, behavior, conversions, engagement, marketing, realtime): also count bot and crawler traffic. Default false: crawlers, headless browsers and datacenter/cloud traffic are excluded. Set true for raw counts. | | `include_internal` | No | boolean / null | Web visitor reports only: also count internal traffic. Default false: the graph8 app (app.graph8.com), local dev hosts and your own team members are excluded. | <details> <summary>Complete input schema</summary> ```json { "properties": { "report": { "default": "campaign-visitors", "description": "Which analytics report to read. Defaults to campaign-visitors, the report this tool returned before the rest of the /analytics surface was folded in.", "enum": [ "campaign-visitors", "acquisition/campaigns", "acquisition/channels", "acquisition/channels/trend", "acquisition/referrals", "acquisition/search", "acquisition/social", "acquisition/sources", "audience/behavior", "audience/demographics", "audience/geo", "audience/geo/cities", "audience/languages", "audience/overview", "audience/technology", "audience/trend", "behavior/events", "behavior/exit-pages", "behavior/landing-pages", "behavior/page-metrics", "behavior/pages", "conversions/forms", "conversions/forms/analysis", "conversions/forms/trend", "conversions/funnel", "conversions/goals", "conversions/goals/trend", "conversions/rate", "dialer/grading/search", "dialer/leaderboard", "dialer/leaderboard/sdr", "dialer/performance", "dialer/quality", "dialer/quality/leaderboard", "engagement/by-campaign", "engagement/by-channel", "engagement/by-landing-page", "engagement/depth-distribution", "engagement/overview", "engagement/time-distribution", "engagement/trend", "marketing/campaigns", "marketing/content", "marketing/content/by-landing", "marketing/outbound-bridge", "marketing/outbound-bridge/by-campaign", "marketing/outbound-bridge/by-channel", "marketing/outbound-bridge/by-landing-page", "marketing/outbound-bridge/top-sources", "marketing/outbound-bridge/trend", "marketing/overview", "realtime", "realtime/locations", "realtime/pages", "realtime/sources", "sdr/ai-agents/summary", "sdr/detail", "sdr/leaderboard", "sdr/summary", "sdr/trends" ], "title": "Report", "type": "string" }, "days": { "anyOf": [ { "maximum": 365, "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "description": "Rolling 'last N days from today', 1-365. Ignored when an explicit window is supplied.", "title": "Days" }, "start_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Window start (inclusive), YYYY-MM-DD. If omitted, derived from `days`. Refused by reports with no explicit range (realtime, marketing, sdr).", "title": "Start Date" }, "end_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Window end (inclusive), YYYY-MM-DD. If omitted, today.", "title": "End Date" }, "date_from": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Same as start_date (YYYY-MM-DD). Send one or the other, not two different values.", "title": "Date From" }, "date_to": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Same as end_date (YYYY-MM-DD). Send one or the other, not two different values.", "title": "Date To" }, "limit": { "anyOf": [ { "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "description": "Row cap. The per-report maximum differs (50 to 500); the API rejects an over-large value.", "title": "Limit" }, "sort_by": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Sort key. The accepted values differ per report; see the report's API documentation.", "title": "Sort By" }, "sort_order": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Sort order: asc or desc. Defaults to desc.", "title": "Sort Order" }, "granularity": { "anyOf": [ { "enum": [ "day", "week" ], "type": "string" }, { "type": "null" } ], "default": null, "description": "Aggregation period for sdr/trends: day or week.", "title": "Granularity" }, "period": { "anyOf": [ { "enum": [ "today", "week", "month" ], "type": "string" }, { "type": "null" } ], "default": null, "description": "Time period preset for dialer/leaderboard when no dates are given: today, week or month.", "title": "Period" }, "sdr_email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "SDR email address. REQUIRED for sdr/detail and dialer/leaderboard/sdr, where it is part of the URL; an optional filter on the other dialer reports.", "title": "Sdr Email" }, "campaign_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Campaign ID filter for dialer/grading/search.", "title": "Campaign Id" }, "path": { "anyOf": [ { "maxLength": 1000, "minLength": 1, "type": "string" }, { "type": "null" } ], "default": null, "description": "Exact context_page_path to roll up. REQUIRED for behavior/page-metrics.", "title": "Path" }, "page": { "anyOf": [ { "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "description": "Page number for dialer/grading/search, 1-based.", "title": "Page" }, "page_size": { "anyOf": [ { "maximum": 100, "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "description": "Results per page for dialer/grading/search, 1-100.", "title": "Page Size" }, "min_score": { "anyOf": [ { "maximum": 40, "minimum": 0, "type": "integer" }, { "type": "null" } ], "default": null, "description": "Minimum overall grading score, 0-40, for dialer/grading/search.", "title": "Min Score" }, "max_score": { "anyOf": [ { "maximum": 40, "minimum": 0, "type": "integer" }, { "type": "null" } ], "default": null, "description": "Maximum overall grading score, 0-40, for dialer/grading/search.", "title": "Max Score" }, "include_contacts": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "description": "campaign-visitors only: include the Top contacts table (contact name + email, so PII). Set false to omit it entirely for a privacy-lean response.", "title": "Include Contacts" }, "include_bots": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "description": "Web visitor reports only (audience, acquisition, behavior, conversions, engagement, marketing, realtime): also count bot and crawler traffic. Default false: crawlers, headless browsers and datacenter/cloud traffic are excluded. Set true for raw counts.", "title": "Include Bots" }, "include_internal": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "description": "Web visitor reports only: also count internal traffic. Default false: the graph8 app (app.graph8.com), local dev hosts and your own team members are excluded.", "title": "Include Internal" } }, "title": "g8_analytics_get_campaign_visitorsArguments", "type": "object" } ``` </details> ## g8_app_apply_manifest > Apply a graph8.app.yaml to an app: publish its objects, store its build config. The objects half goes through the same publish path as g8_app_publish_schema, so it is idempotent on content and the tenant fan-out is failure-isolated. Refused when the manifest's app.slug names a different app than app_id, because a copied file would otherwise publish one app's schema into another app's tenants. A block the manifest omits is left alone rather than cleared, so applying a frontend-only manifest does not erase a stored backend. Args: app_id: The app id to apply this manifest to manifest: The full graph8.app.yaml document as text ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `app_id` | Yes | string | See the schema below for constraints and defaults. | | `manifest` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "app_id": { "title": "App Id", "type": "string" }, "manifest": { "title": "Manifest", "type": "string" } }, "required": [ "app_id", "manifest" ], "title": "g8_app_apply_manifestArguments", "type": "object" } ``` </details> ## g8_app_claim_app_domain > Claim App Domain. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /apps/{app_id}/domains ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `app_id` | Yes | string | See the schema below for constraints and defaults. | | `body` | Yes | #/$defs/ApiClaimDomainRequest | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "$defs": { "ApiClaimDomainRequest": { "additionalProperties": false, "properties": { "hostname": { "description": "The hostname to serve this app on. Normalized server-side: case, a trailing dot and unicode all resolve to one canonical form.", "maxLength": 253, "title": "Hostname", "type": "string" } }, "required": [ "hostname" ], "title": "ApiClaimDomainRequest", "type": "object" } }, "properties": { "app_id": { "title": "App Id", "type": "string" }, "body": { "$ref": "#/$defs/ApiClaimDomainRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "app_id", "body" ], "title": "g8_app_claim_app_domainArguments", "type": "object" } ``` </details> ## g8_app_clear_app_limit > Clear App Limit. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: DELETE /apps/{app_id}/limit ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `app_id` | Yes | string | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "app_id": { "title": "App Id", "type": "string" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "app_id" ], "title": "g8_app_clear_app_limitArguments", "type": "object" } ``` </details> ## g8_app_clear_app_source > Clear App Source. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: DELETE /apps/{app_id}/source ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `app_id` | Yes | string | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "app_id": { "title": "App Id", "type": "string" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "app_id" ], "title": "g8_app_clear_app_sourceArguments", "type": "object" } ``` </details> ## g8_app_create > Create an app you are building on graph8. An app is software your customers install into their own workspace, acting on their data with their consent. Distinct from your own workspace's data: an app declares its own objects. Requires your organization to be enabled as an app builder. Until then this answers 403 builder_not_allowlisted. Args: name: Human-readable name, shown to customers installing it slug: Short identifier, unique within your organization (e.g. invoicer) ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `name` | Yes | string | See the schema below for constraints and defaults. | | `slug` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "name": { "title": "Name", "type": "string" }, "slug": { "title": "Slug", "type": "string" } }, "required": [ "name", "slug" ], "title": "g8_app_createArguments", "type": "object" } ``` </details> ## g8_app_create_app_credential > Create App Credential. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /apps/{app_id}/credentials ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `app_id` | Yes | string | See the schema below for constraints and defaults. | | `body` | Yes | #/$defs/ApiCreateCredentialRequest | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "$defs": { "ApiCreateCredentialRequest": { "additionalProperties": false, "properties": { "client_org_id": { "description": "The client organization this credential acts for. It must already have a consented installation of the app - a credential is bound to one tenant and cannot be pointed at another later.", "title": "Client Org Id", "type": "string" } }, "required": [ "client_org_id" ], "title": "ApiCreateCredentialRequest", "type": "object" } }, "properties": { "app_id": { "title": "App Id", "type": "string" }, "body": { "$ref": "#/$defs/ApiCreateCredentialRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "app_id", "body" ], "title": "g8_app_create_app_credentialArguments", "type": "object" } ``` </details> ## g8_app_delete_app_secret > Delete App Secret. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: DELETE /apps/{app_id}/secrets/{secret_key} ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `app_id` | Yes | string | See the schema below for constraints and defaults. | | `secret_key` | Yes | string | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "app_id": { "title": "App Id", "type": "string" }, "secret_key": { "title": "Secret Key", "type": "string" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "app_id", "secret_key" ], "title": "g8_app_delete_app_secretArguments", "type": "object" } ``` </details> ## g8_app_deploy > Start a deployment of this app's runtime. A deployment begins queued. Promotion is a separate, deliberate step, so a build that passes its checks does not become live by default. Args: app_id: The app to deploy source_ref: REQUIRED. An immutable commit SHA, not a branch name. A branch moves, so a deployment recorded against one cannot answer "what is running right now" a week later - which is the question a rollback has to answer. schema_version_id: Schema version this build expects. Pin it so a rollback restores the matching schema, not merely the matching image. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `app_id` | Yes | string | See the schema below for constraints and defaults. | | `source_ref` | Yes | string | See the schema below for constraints and defaults. | | `schema_version_id` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "app_id": { "title": "App Id", "type": "string" }, "source_ref": { "title": "Source Ref", "type": "string" }, "schema_version_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Schema Version Id" } }, "required": [ "app_id", "source_ref" ], "title": "g8_app_deployArguments", "type": "object" } ``` </details> ## g8_app_get_active_deployment > Get Active Deployment. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. API: GET /apps/{app_id}/deployments/active ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `app_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "app_id": { "title": "App Id", "type": "string" } }, "required": [ "app_id" ], "title": "g8_app_get_active_deploymentArguments", "type": "object" } ``` </details> ## g8_app_get_app_limit > Get App Limit. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. API: GET /apps/{app_id}/limit ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `app_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "app_id": { "title": "App Id", "type": "string" } }, "required": [ "app_id" ], "title": "g8_app_get_app_limitArguments", "type": "object" } ``` </details> ## g8_app_get_app_logs > Get App Logs. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. API: GET /apps/{app_id}/logs ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `app_id` | Yes | string | See the schema below for constraints and defaults. | | `tail_lines` | No | integer / null | Lines per replica, newest last. | <details> <summary>Complete input schema</summary> ```json { "properties": { "app_id": { "title": "App Id", "type": "string" }, "tail_lines": { "anyOf": [ { "maximum": 2000, "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "description": "Lines per replica, newest last.", "title": "Tail Lines" } }, "required": [ "app_id" ], "title": "g8_app_get_app_logsArguments", "type": "object" } ``` </details> ## g8_app_get_app_runtime > Get App Runtime. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. API: GET /apps/{app_id}/runtime ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `app_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "app_id": { "title": "App Id", "type": "string" } }, "required": [ "app_id" ], "title": "g8_app_get_app_runtimeArguments", "type": "object" } ``` </details> ## g8_app_get_app_usage > Get App Usage. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. API: GET /apps/{app_id}/usage ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `app_id` | Yes | string | See the schema below for constraints and defaults. | | `period` | No | string / null | Calendar month `YYYY-MM`. Defaults to the current month. | <details> <summary>Complete input schema</summary> ```json { "properties": { "app_id": { "title": "App Id", "type": "string" }, "period": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Calendar month `YYYY-MM`. Defaults to the current month.", "title": "Period" } }, "required": [ "app_id" ], "title": "g8_app_get_app_usageArguments", "type": "object" } ``` </details> ## g8_app_get_deployment > Get Deployment. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. API: GET /apps/{app_id}/deployments/{deployment_id} ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `app_id` | Yes | string | See the schema below for constraints and defaults. | | `deployment_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "app_id": { "title": "App Id", "type": "string" }, "deployment_id": { "title": "Deployment Id", "type": "string" } }, "required": [ "app_id", "deployment_id" ], "title": "g8_app_get_deploymentArguments", "type": "object" } ``` </details> ## g8_app_get_deployment_logs > Get Deployment Logs. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. API: GET /apps/{app_id}/deployments/{deployment_id}/logs ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `app_id` | Yes | string | See the schema below for constraints and defaults. | | `deployment_id` | Yes | string | See the schema below for constraints and defaults. | | `tail_lines` | No | integer / null | Lines per build step, newest last. | <details> <summary>Complete input schema</summary> ```json { "properties": { "app_id": { "title": "App Id", "type": "string" }, "deployment_id": { "title": "Deployment Id", "type": "string" }, "tail_lines": { "anyOf": [ { "maximum": 2000, "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "description": "Lines per build step, newest last.", "title": "Tail Lines" } }, "required": [ "app_id", "deployment_id" ], "title": "g8_app_get_deployment_logsArguments", "type": "object" } ``` </details> ## g8_app_get_installed_app > Get Installed App. Uses the current organization and existing API permissions. API: GET /installed-apps/{app_id} ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `app_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "app_id": { "title": "App Id", "type": "string" } }, "required": [ "app_id" ], "title": "g8_app_get_installed_appArguments", "type": "object" } ``` </details> ## g8_app_install > Install an app into a client organization. The install starts unconsented. A tenant receives no object definitions until it consents, which is the security boundary rather than a filter. Args: app_id: The app to install client_org_id: The organization installing it ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `app_id` | Yes | string | See the schema below for constraints and defaults. | | `client_org_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "app_id": { "title": "App Id", "type": "string" }, "client_org_id": { "title": "Client Org Id", "type": "string" } }, "required": [ "app_id", "client_org_id" ], "title": "g8_app_installArguments", "type": "object" } ``` </details> ## g8_app_list > List the apps your organization is building, newest first. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_app_listArguments", "type": "object" } ``` </details> ## g8_app_list_app_credentials > List App Credentials. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. API: GET /apps/{app_id}/credentials ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `app_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "app_id": { "title": "App Id", "type": "string" } }, "required": [ "app_id" ], "title": "g8_app_list_app_credentialsArguments", "type": "object" } ``` </details> ## g8_app_list_app_deployments > List App Deployments. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. API: GET /apps/{app_id}/deployments ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `app_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "app_id": { "title": "App Id", "type": "string" } }, "required": [ "app_id" ], "title": "g8_app_list_app_deploymentsArguments", "type": "object" } ``` </details> ## g8_app_list_app_domains > List App Domains. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. API: GET /apps/{app_id}/domains ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `app_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "app_id": { "title": "App Id", "type": "string" } }, "required": [ "app_id" ], "title": "g8_app_list_app_domainsArguments", "type": "object" } ``` </details> ## g8_app_list_app_installs > List App Installs. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. API: GET /apps/{app_id}/installs ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `app_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "app_id": { "title": "App Id", "type": "string" } }, "required": [ "app_id" ], "title": "g8_app_list_app_installsArguments", "type": "object" } ``` </details> ## g8_app_list_app_secrets > List App Secrets. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. API: GET /apps/{app_id}/secrets ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `app_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "app_id": { "title": "App Id", "type": "string" } }, "required": [ "app_id" ], "title": "g8_app_list_app_secretsArguments", "type": "object" } ``` </details> ## g8_app_list_installed_apps > List Installed Apps. Uses the current organization and existing API permissions. API: GET /installed-apps ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_app_list_installed_appsArguments", "type": "object" } ``` </details> ## g8_app_list_schema_versions > List this app's published schema versions, oldest first. Args: app_id: The app id ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `app_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "app_id": { "title": "App Id", "type": "string" } }, "required": [ "app_id" ], "title": "g8_app_list_schema_versionsArguments", "type": "object" } ``` </details> ## g8_app_promote > Promote a deployment to live. Separate from deploying on purpose: promotion is what customers see, and it should be a decision rather than a side effect of a build finishing. Args: app_id: The app deployment_id: The deployment to promote (e.g. ddep_...) image_digest: REQUIRED. The built image digest being promoted, from g8_app_status. Recorded so a rollback targets an artifact rather than a rebuild - a rebuild of the same ref is not necessarily the same image. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `app_id` | Yes | string | See the schema below for constraints and defaults. | | `deployment_id` | Yes | string | See the schema below for constraints and defaults. | | `image_digest` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "app_id": { "title": "App Id", "type": "string" }, "deployment_id": { "title": "Deployment Id", "type": "string" }, "image_digest": { "title": "Image Digest", "type": "string" } }, "required": [ "app_id", "deployment_id", "image_digest" ], "title": "g8_app_promoteArguments", "type": "object" } ``` </details> ## g8_app_publish_schema > Publish this app's object schema and materialize it in every consented tenant. Idempotent on the content: republishing an identical schema resolves to the existing version rather than writing to every tenant again. The fan-out is failure-isolated, so one unreachable tenant does not fail the publish or stop the others. Only tenants that have CONSENTED receive anything. A pending or revoked install gets nothing. Args: app_id: The app id objects: Declared objects. Each needs slug, singular_noun, plural_noun and attributes; each attribute needs slug, title and type. Publishing an empty list is refused, because it would archive every object the app has ever declared. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `app_id` | Yes | string | See the schema below for constraints and defaults. | | `objects` | Yes | array | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "app_id": { "title": "App Id", "type": "string" }, "objects": { "items": { "additionalProperties": true, "type": "object" }, "title": "Objects", "type": "array" } }, "required": [ "app_id", "objects" ], "title": "g8_app_publish_schemaArguments", "type": "object" } ``` </details> ## g8_app_put_app_secret > Put App Secret. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: PUT /apps/{app_id}/secrets/{secret_key} ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `app_id` | Yes | string | See the schema below for constraints and defaults. | | `secret_key` | Yes | string | See the schema below for constraints and defaults. | | `body` | Yes | #/$defs/ApiPutAppSecretRequest | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "$defs": { "ApiPutAppSecretRequest": { "additionalProperties": false, "properties": { "provider_ref": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Secret-manager PATH for the value. Never the value itself.", "title": "Provider Ref" } }, "title": "ApiPutAppSecretRequest", "type": "object" } }, "properties": { "app_id": { "title": "App Id", "type": "string" }, "secret_key": { "title": "Secret Key", "type": "string" }, "body": { "$ref": "#/$defs/ApiPutAppSecretRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "app_id", "secret_key", "body" ], "title": "g8_app_put_app_secretArguments", "type": "object" } ``` </details> ## g8_app_release_app_domain > Release App Domain. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: DELETE /apps/{app_id}/domains/{hostname} ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `app_id` | Yes | string | See the schema below for constraints and defaults. | | `hostname` | Yes | string | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "app_id": { "title": "App Id", "type": "string" }, "hostname": { "title": "Hostname", "type": "string" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "app_id", "hostname" ], "title": "g8_app_release_app_domainArguments", "type": "object" } ``` </details> ## g8_app_report_observed_build > Observe a real Kubernetes build Job and report its derived progress after confirm=true. Requires the isolated g8-controller-mcp-server connection with existing controller credentials and cluster service-account mounts. App/deployment identifiers select a Job whose identity is verified. Agents cannot supply status, image digest, error text, credentials or cluster addresses. Missing or unreadable evidence never updates progress. The API verifies the app binding and state transition; this never promotes an app to live traffic. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `app_id` | Yes | string | See the schema below for constraints and defaults. | | `deployment_id` | Yes | string | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "app_id": { "pattern": "^dapp_[0-9a-f]{6,32}$", "title": "App Id", "type": "string" }, "deployment_id": { "pattern": "^ddep_[0-9a-f]{6,32}$", "title": "Deployment Id", "type": "string" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "app_id", "deployment_id" ], "title": "g8_app_report_observed_buildArguments", "type": "object" } ``` </details> ## g8_app_revoke_app_credential > Revoke App Credential. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: DELETE /apps/{app_id}/credentials/{client_id} ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `app_id` | Yes | string | See the schema below for constraints and defaults. | | `client_id` | Yes | string | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "app_id": { "title": "App Id", "type": "string" }, "client_id": { "title": "Client Id", "type": "string" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "app_id", "client_id" ], "title": "g8_app_revoke_app_credentialArguments", "type": "object" } ``` </details> ## g8_app_rollback_deployment > Rollback Deployment. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /apps/{app_id}/deployments/{deployment_id}/rollback ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `app_id` | Yes | string | See the schema below for constraints and defaults. | | `deployment_id` | Yes | string | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "app_id": { "title": "App Id", "type": "string" }, "deployment_id": { "title": "Deployment Id", "type": "string" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "app_id", "deployment_id" ], "title": "g8_app_rollback_deploymentArguments", "type": "object" } ``` </details> ## g8_app_set_app_limit > Set App Limit. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: PUT /apps/{app_id}/limit ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `app_id` | Yes | string | See the schema below for constraints and defaults. | | `body` | Yes | #/$defs/ApiSetAppLimitRequest | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "$defs": { "ApiSetAppLimitRequest": { "additionalProperties": false, "properties": { "credit_cap": { "description": "Hard cap in credits for the window. `0` means 'cap at zero', NOT 'uncapped' -- to remove a cap, DELETE the limit instead.", "minimum": 0.0, "title": "Credit Cap", "type": "integer" }, "window": { "default": "calendar_month", "description": "Cap window. `calendar_month` is the only value today.", "title": "Window", "type": "string" } }, "required": [ "credit_cap" ], "title": "ApiSetAppLimitRequest", "type": "object" } }, "properties": { "app_id": { "title": "App Id", "type": "string" }, "body": { "$ref": "#/$defs/ApiSetAppLimitRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "app_id", "body" ], "title": "g8_app_set_app_limitArguments", "type": "object" } ``` </details> ## g8_app_set_app_source > Set App Source. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: PUT /apps/{app_id}/source ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `app_id` | Yes | string | See the schema below for constraints and defaults. | | `body` | Yes | #/$defs/ApiAppSourceRequest | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "$defs": { "ApiAppSourceRequest": { "additionalProperties": false, "properties": { "credential_ref": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Secret-manager PATH for the fetch credential. Never the credential itself.", "title": "Credential Ref" }, "default_branch": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Defaults to the repository's own default when omitted.", "title": "Default Branch" }, "provider": { "description": "One of: github, gitlab, bitbucket.", "title": "Provider", "type": "string" }, "repo_url": { "description": "Clone URL.", "maxLength": 2000, "minLength": 1, "title": "Repo Url", "type": "string" } }, "required": [ "provider", "repo_url" ], "title": "ApiAppSourceRequest", "type": "object" } }, "properties": { "app_id": { "title": "App Id", "type": "string" }, "body": { "$ref": "#/$defs/ApiAppSourceRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "app_id", "body" ], "title": "g8_app_set_app_sourceArguments", "type": "object" } ``` </details> ## g8_app_set_app_status > Set App Status. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /apps/{app_id}/status ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `app_id` | Yes | string | See the schema below for constraints and defaults. | | `body` | Yes | #/$defs/ApiSetAppStatusRequest | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "$defs": { "ApiSetAppStatusRequest": { "additionalProperties": false, "properties": { "status": { "description": "`draft`, `published`, `suspended` or `archived`. `active` is accepted as the legacy spelling of `published`.", "title": "Status", "type": "string" } }, "required": [ "status" ], "title": "ApiSetAppStatusRequest", "type": "object" } }, "properties": { "app_id": { "title": "App Id", "type": "string" }, "body": { "$ref": "#/$defs/ApiSetAppStatusRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "app_id", "body" ], "title": "g8_app_set_app_statusArguments", "type": "object" } ``` </details> ## g8_app_status > Get one app: its lifecycle status, registered origins and hostname. Args: app_id: The app id (e.g. dapp_a1b2c3d4e5f6) ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `app_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "app_id": { "title": "App Id", "type": "string" } }, "required": [ "app_id" ], "title": "g8_app_statusArguments", "type": "object" } ``` </details> ## g8_app_uninstall_app > Uninstall App. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: DELETE /installed-apps/{app_id} ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `app_id` | Yes | string | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "app_id": { "title": "App Id", "type": "string" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "app_id" ], "title": "g8_app_uninstall_appArguments", "type": "object" } ``` </details> ## g8_app_validate_manifest > Check a graph8.app.yaml without applying it. No app id needed. Answers successfully whether or not the manifest is valid: `valid` is the answer and `issues` is why not, each with a path pointer, so a whole file is fixed in one pass rather than one error per attempt. Use this on a manifest you just wrote, before applying it. A manifest MUST declare a frontend and/or a backend build target. One that declares only objects is rejected with no_build_target. Args: manifest: The full graph8.app.yaml document as text, newlines and all. Rejected above 256 KiB. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `manifest` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "manifest": { "title": "Manifest", "type": "string" } }, "required": [ "manifest" ], "title": "g8_app_validate_manifestArguments", "type": "object" } ``` </details> ## g8_app_verify_app_domain > Verify App Domain. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /apps/{app_id}/domains/{hostname}/verify ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `app_id` | Yes | string | See the schema below for constraints and defaults. | | `hostname` | Yes | string | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "app_id": { "title": "App Id", "type": "string" }, "hostname": { "title": "Hostname", "type": "string" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "app_id", "hostname" ], "title": "g8_app_verify_app_domainArguments", "type": "object" } ``` </details> ## g8_apply_install > Apply the generated GTM patches to the repository. IMPORTANT: This modifies the codebase. Always confirm with the user first. Args: repo_id: Repository ID with a pending patch set ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `repo_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "repo_id": { "title": "Repo Id", "type": "string" } }, "required": [ "repo_id" ], "title": "g8_apply_installArguments", "type": "object" } ``` </details> ## g8_apply_landing_page_patch > Submit ordered exact replacements to a landing-page draft. First call g8_get_landing_page_source. Build replacements from its exact HTML and pass back its sha256. Every `old` string must occur exactly `expected_matches` times against the evolving document or nothing is written. Studio handoffs default to `pending_review`, where the user applies or discards the validated diff in graph8. If the user explicitly selected automatic updates, a successful call snapshots the previous HTML as a restorable graph8 version and applies immediately. Reuse the same idempotency key only when retrying the identical request. Args: edit_session: Opaque page-scoped session copied from graph8 Studio. base_sha256: sha256 returned by g8_get_landing_page_source. replacements: 1-20 ordered exact old/new replacements. idempotency_key: New stable retry key (8-128 URL-safe characters). summary: Concise human-readable description for version history. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `edit_session` | Yes | string | See the schema below for constraints and defaults. | | `base_sha256` | Yes | string | See the schema below for constraints and defaults. | | `replacements` | Yes | array | See the schema below for constraints and defaults. | | `idempotency_key` | Yes | string | See the schema below for constraints and defaults. | | `summary` | No | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "$defs": { "LandingPageReplacementInput": { "description": "One ordered exact-text replacement for a landing-page patch.", "properties": { "old": { "maxLength": 200000, "minLength": 1, "title": "Old", "type": "string" }, "new": { "maxLength": 500000, "title": "New", "type": "string" }, "expected_matches": { "default": 1, "maximum": 100, "minimum": 1, "title": "Expected Matches", "type": "integer" } }, "required": [ "old", "new" ], "title": "LandingPageReplacementInput", "type": "object" } }, "properties": { "edit_session": { "title": "Edit Session", "type": "string" }, "base_sha256": { "title": "Base Sha256", "type": "string" }, "replacements": { "items": { "$ref": "#/$defs/LandingPageReplacementInput" }, "title": "Replacements", "type": "array" }, "idempotency_key": { "title": "Idempotency Key", "type": "string" }, "summary": { "default": "External AI edit", "title": "Summary", "type": "string" } }, "required": [ "edit_session", "base_sha256", "replacements", "idempotency_key" ], "title": "g8_apply_landing_page_patchArguments", "type": "object" } ``` </details> ## g8_appointments_add_booking_guests > Add guest email addresses to an existing booking. Guests are extra attendees who get the same calendar invite and notifications; each new guest is emailed. Args: booking_uid: Booking UID to add guests to. guests: List of email addresses to invite as guests. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `booking_uid` | Yes | string | See the schema below for constraints and defaults. | | `guests` | Yes | array | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "booking_uid": { "title": "Booking Uid", "type": "string" }, "guests": { "items": { "type": "string" }, "title": "Guests", "type": "array" } }, "required": [ "booking_uid", "guests" ], "title": "g8_appointments_add_booking_guestsArguments", "type": "object" } ``` </details> ## g8_appointments_add_booking_note > Add an internal host-only note to a booking. Notes are not visible to attendees - only to org members in the booking detail view. Args: booking_uid: Booking UID to annotate. text: Note content (plain text). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `booking_uid` | Yes | string | See the schema below for constraints and defaults. | | `text` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "booking_uid": { "title": "Booking Uid", "type": "string" }, "text": { "title": "Text", "type": "string" } }, "required": [ "booking_uid", "text" ], "title": "g8_appointments_add_booking_noteArguments", "type": "object" } ``` </details> ## g8_appointments_cancel_booking > Cancel a booking and notify both parties. This sends cancellation emails and frees the slot - confirm with the user first. Args: booking_uid: Booking UID to cancel. cancellation_reason: Optional reason shown in the notification. cancel_subsequent: For recurring bookings, also cancel later occurrences (default False). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `booking_uid` | Yes | string | See the schema below for constraints and defaults. | | `cancellation_reason` | No | string / null | See the schema below for constraints and defaults. | | `cancel_subsequent` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "booking_uid": { "title": "Booking Uid", "type": "string" }, "cancellation_reason": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Cancellation Reason" }, "cancel_subsequent": { "default": false, "title": "Cancel Subsequent", "type": "boolean" } }, "required": [ "booking_uid" ], "title": "g8_appointments_cancel_bookingArguments", "type": "object" } ``` </details> ## g8_appointments_confirm_booking > Confirm or reject a pending booking that requires host approval. SENDS EMAILS to both parties - confirm intent with the user first. confirmed=True accepts (pending -> accepted); confirmed=False rejects and notifies the attendee with the optional reason. Args: booking_uid: Booking UID to confirm or reject. confirmed: True to accept, False to reject. reason: Optional message to the attendee (shown on rejection). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `booking_uid` | Yes | string | See the schema below for constraints and defaults. | | `confirmed` | Yes | boolean | See the schema below for constraints and defaults. | | `reason` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "booking_uid": { "title": "Booking Uid", "type": "string" }, "confirmed": { "title": "Confirmed", "type": "boolean" }, "reason": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Reason" } }, "required": [ "booking_uid", "confirmed" ], "title": "g8_appointments_confirm_bookingArguments", "type": "object" } ``` </details> ## g8_appointments_connect_conferencing > Connect a conferencing provider that does not need a browser OAuth flow. google_meet reuses your Google Calendar credential (must be connected first). roam/whereby/jitsi are link-based (optionally pass the URL). roam_auto creates a stub credential (OAuth completed once in the web app). For Zoom and Teams (office365_video) use the web app - they need browser OAuth. Args: provider: One of: google_meet, roam, roam_auto, whereby, jitsi. link: Optional meeting URL for roam, whereby, or jitsi providers. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `provider` | Yes | string | See the schema below for constraints and defaults. | | `link` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "provider": { "title": "Provider", "type": "string" }, "link": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Link" } }, "required": [ "provider" ], "title": "g8_appointments_connect_conferencingArguments", "type": "object" } ``` </details> ## g8_appointments_create_booking > Book an appointment. CHARGES CREDITS (~20) and sends calendar invites + confirmation emails to attendee and host. ALWAYS confirm event type, time, and attendee with the user first - billable, externally-visible. Resolve event_type_id via g8_appointments_list_event_types and inspect it with g8_appointments_get_event_type first (duration + required booking_fields). start_time must be an available slot; end_time derives from the event-type length when omitted. Args: event_type_id: The event type to book. start_time: ISO-8601 start (e.g. "2026-06-10T15:00:00Z"). attendee_name: Primary attendee's name. attendee_email: Primary attendee's email. attendee_time_zone: IANA tz for the attendee (e.g. "America/New_York"). end_time: ISO-8601 end. Omit to use the event-type duration. location: Location/conferencing key (e.g. "zoom", "google_meet"). guests: Additional guest email addresses to invite. responses: Answers to the event type's custom booking_fields, keyed by field id. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `event_type_id` | Yes | integer | See the schema below for constraints and defaults. | | `start_time` | Yes | string | See the schema below for constraints and defaults. | | `attendee_name` | Yes | string | See the schema below for constraints and defaults. | | `attendee_email` | Yes | string | See the schema below for constraints and defaults. | | `attendee_time_zone` | No | string / null | See the schema below for constraints and defaults. | | `end_time` | No | string / null | See the schema below for constraints and defaults. | | `location` | No | string / null | See the schema below for constraints and defaults. | | `guests` | No | array / null | See the schema below for constraints and defaults. | | `responses` | No | object / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "event_type_id": { "title": "Event Type Id", "type": "integer" }, "start_time": { "title": "Start Time", "type": "string" }, "attendee_name": { "title": "Attendee Name", "type": "string" }, "attendee_email": { "title": "Attendee Email", "type": "string" }, "attendee_time_zone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Attendee Time Zone" }, "end_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "End Time" }, "location": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Location" }, "guests": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Guests" }, "responses": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Responses" } }, "required": [ "event_type_id", "start_time", "attendee_name", "attendee_email" ], "title": "g8_appointments_create_bookingArguments", "type": "object" } ``` </details> ## g8_appointments_create_event_type > Create a bookable event type (what people book on your calendar). slug must be unique in the org (it becomes part of the public booking URL); collisions return an error. Args: title: Display name, e.g. "Discovery call". slug: URL slug, e.g. "discovery-call" (unique per org). length: Duration in minutes. description: Optional description shown on the booking page. scheduling_type: "collective" \| "round_robin" for team events; omit for 1:1. schedule_id: Availability schedule to attach (from g8_appointments_list_schedules). hidden: Create it hidden (not publicly bookable) when True. requires_confirmation: Require host approval before a booking is confirmed. minimum_booking_notice: Minutes of lead time required before a slot (default 120). locations: Location/conferencing config, e.g. [{"type": "integrations:google:meet"}]. booking_fields: Custom questions the attendee must answer. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `title` | Yes | string | See the schema below for constraints and defaults. | | `slug` | Yes | string | See the schema below for constraints and defaults. | | `length` | Yes | integer | See the schema below for constraints and defaults. | | `description` | No | string / null | See the schema below for constraints and defaults. | | `scheduling_type` | No | string / null | See the schema below for constraints and defaults. | | `schedule_id` | No | integer / null | See the schema below for constraints and defaults. | | `hidden` | No | boolean | See the schema below for constraints and defaults. | | `requires_confirmation` | No | boolean | See the schema below for constraints and defaults. | | `minimum_booking_notice` | No | integer / null | See the schema below for constraints and defaults. | | `locations` | No | array / null | See the schema below for constraints and defaults. | | `booking_fields` | No | array / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "title": { "title": "Title", "type": "string" }, "slug": { "title": "Slug", "type": "string" }, "length": { "title": "Length", "type": "integer" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "scheduling_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Scheduling Type" }, "schedule_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Schedule Id" }, "hidden": { "default": false, "title": "Hidden", "type": "boolean" }, "requires_confirmation": { "default": false, "title": "Requires Confirmation", "type": "boolean" }, "minimum_booking_notice": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Minimum Booking Notice" }, "locations": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Locations" }, "booking_fields": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Booking Fields" } }, "required": [ "title", "slug", "length" ], "title": "g8_appointments_create_event_typeArguments", "type": "object" } ``` </details> ## g8_appointments_create_ooo > Create an out-of-office entry for the calling user. While active, the user's slots are blocked so no new bookings land in the period. Args: start: ISO-8601 UTC start datetime, e.g. "2026-07-01T00:00:00Z". end: ISO-8601 UTC end datetime, e.g. "2026-07-08T00:00:00Z". notes: Optional free-text note visible to the host. to_user_id: Integer user id of a team member to redirect bookings to. reason: Optional short label, e.g. "vacation" or "conference". ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `start` | Yes | string | See the schema below for constraints and defaults. | | `end` | Yes | string | See the schema below for constraints and defaults. | | `notes` | No | string / null | See the schema below for constraints and defaults. | | `to_user_id` | No | integer / null | See the schema below for constraints and defaults. | | `reason` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "start": { "title": "Start", "type": "string" }, "end": { "title": "End", "type": "string" }, "notes": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Notes" }, "to_user_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "To User Id" }, "reason": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Reason" } }, "required": [ "start", "end" ], "title": "g8_appointments_create_oooArguments", "type": "object" } ``` </details> ## g8_appointments_create_routing_form > Create a new routing form (sequence of questions that route the visitor to an event type based on answers). Each field needs a label and type (select/text/email/phone); select fields need an options list of {label, value}. Routes need conditions (list of {field_id, operator, value} where field_id is the field's identifier slug) plus action_type + action_value (event-type slug to redirect to). Include one route with is_fallback=True as the catch-all. Args: name: Display name for the form. description: Optional description shown on the form page. disabled: Create the form in disabled state when True. fields: List of field dicts (label, type, required, options). routes: List of route dicts (conditions, action_type, action_value, is_fallback). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `name` | Yes | string | See the schema below for constraints and defaults. | | `description` | No | string / null | See the schema below for constraints and defaults. | | `disabled` | No | boolean | See the schema below for constraints and defaults. | | `fields` | No | array / null | See the schema below for constraints and defaults. | | `routes` | No | array / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "name": { "title": "Name", "type": "string" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "disabled": { "default": false, "title": "Disabled", "type": "boolean" }, "fields": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Fields" }, "routes": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Routes" } }, "required": [ "name" ], "title": "g8_appointments_create_routing_formArguments", "type": "object" } ``` </details> ## g8_appointments_create_schedule > Create a named availability schedule with weekly rules and optional date overrides. Args: name: Display name, e.g. "Working hours". time_zone: IANA timezone string, e.g. "America/New_York". is_default: Set as the caller's default schedule when True (used for slot generation when an event type has no schedule attached). availability: Weekly rules [{days: [1..7], start_time: "HH:MM", end_time: "HH:MM"}]. Days: 1=Monday ... 7=Sunday. Omit for Mon-Fri 09:00-17:00. overrides: Date-specific overrides [{date: "YYYY-MM-DD", start_time, end_time}]. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `name` | Yes | string | See the schema below for constraints and defaults. | | `time_zone` | Yes | string | See the schema below for constraints and defaults. | | `is_default` | No | boolean | See the schema below for constraints and defaults. | | `availability` | No | array / null | See the schema below for constraints and defaults. | | `overrides` | No | array / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "name": { "title": "Name", "type": "string" }, "time_zone": { "title": "Time Zone", "type": "string" }, "is_default": { "default": false, "title": "Is Default", "type": "boolean" }, "availability": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Availability" }, "overrides": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Overrides" } }, "required": [ "name", "time_zone" ], "title": "g8_appointments_create_scheduleArguments", "type": "object" } ``` </details> ## g8_appointments_create_workflow > Create a booking workflow that automates actions (emails, reminders) tied to booking lifecycle events. Must have at least one step. Args: name: Display name for the workflow. trigger: When the workflow fires. One of: "new_booking" (on creation), "before_event"/"after_event" (N time_units before/after; need time + time_unit), "booking_cancelled", "booking_rescheduled". steps: List of step dicts (at least one). Each step: action (required): "EMAIL_HOST" \| "EMAIL_ATTENDEE" \| "EMAIL_ADDRESS"; template (default "REMINDER"); email_subject; reminder_body; send_to (required when action is "EMAIL_ADDRESS"); sender; include_calendar_event (default False). time: For time-based triggers - how many time_unit intervals before/after. time_unit: "minutes" \| "hours" \| "days" \| "weeks" (required with time). is_active_on_all: Apply to all event types when True (default False). event_type_ids: Specific event types to attach this workflow to. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `name` | Yes | string | See the schema below for constraints and defaults. | | `trigger` | Yes | string | See the schema below for constraints and defaults. | | `steps` | Yes | array | See the schema below for constraints and defaults. | | `time` | No | integer / null | See the schema below for constraints and defaults. | | `time_unit` | No | string / null | See the schema below for constraints and defaults. | | `is_active_on_all` | No | boolean | See the schema below for constraints and defaults. | | `event_type_ids` | No | array / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "name": { "title": "Name", "type": "string" }, "trigger": { "title": "Trigger", "type": "string" }, "steps": { "items": { "additionalProperties": true, "type": "object" }, "title": "Steps", "type": "array" }, "time": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Time" }, "time_unit": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Time Unit" }, "is_active_on_all": { "default": false, "title": "Is Active On All", "type": "boolean" }, "event_type_ids": { "anyOf": [ { "items": { "type": "integer" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Event Type Ids" } }, "required": [ "name", "trigger", "steps" ], "title": "g8_appointments_create_workflowArguments", "type": "object" } ``` </details> ## g8_appointments_delete_event_type > Delete an event type permanently (irreversible). Removes its public booking page; existing bookings are not cancelled. Confirm with the user first. Requires Admin or Owner role - a non-admin key gets a permission error, in which case hide it instead via g8_appointments_update_event_type(hidden=True). Args: event_type_id: The event type to delete. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `event_type_id` | Yes | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "event_type_id": { "title": "Event Type Id", "type": "integer" } }, "required": [ "event_type_id" ], "title": "g8_appointments_delete_event_typeArguments", "type": "object" } ``` </details> ## g8_appointments_delete_ooo > Delete an out-of-office entry permanently. The user's slots in the previously-blocked period become bookable again after deletion. Confirm with the user before calling. Args: ooo_uuid: UUID of the out-of-office entry (from g8_appointments_list_ooo). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `ooo_uuid` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "ooo_uuid": { "title": "Ooo Uuid", "type": "string" } }, "required": [ "ooo_uuid" ], "title": "g8_appointments_delete_oooArguments", "type": "object" } ``` </details> ## g8_appointments_delete_routing_form > Delete a routing form permanently. Cascades to all fields, routes, and captured responses. Irreversible - confirm with the user first. Args: form_id: The routing form to delete. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `form_id` | Yes | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "form_id": { "title": "Form Id", "type": "integer" } }, "required": [ "form_id" ], "title": "g8_appointments_delete_routing_formArguments", "type": "object" } ``` </details> ## g8_appointments_delete_schedule > Delete an availability schedule permanently. Confirm with the user first - this removes the schedule from any linked event types. Returns 409 if it is the user's only remaining schedule (must keep at least one). Args: schedule_id: Schedule to delete. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `schedule_id` | Yes | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "schedule_id": { "title": "Schedule Id", "type": "integer" } }, "required": [ "schedule_id" ], "title": "g8_appointments_delete_scheduleArguments", "type": "object" } ``` </details> ## g8_appointments_delete_workflow > Delete a workflow permanently. Pending reminders are cancelled; existing bookings are unaffected. Confirm with the user before calling. Args: workflow_id: The workflow to delete. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `workflow_id` | Yes | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "workflow_id": { "title": "Workflow Id", "type": "integer" } }, "required": [ "workflow_id" ], "title": "g8_appointments_delete_workflowArguments", "type": "object" } ``` </details> ## g8_appointments_deselect_calendar > Remove a sub-calendar from conflict checking. The calendar account stays connected; only its conflict-check selection is removed. Args: credential_id: ID of the connected calendar account credential. integration: Provider type, e.g. "google_calendar". external_id: The calendar's external ID. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `credential_id` | Yes | integer | See the schema below for constraints and defaults. | | `integration` | Yes | string | See the schema below for constraints and defaults. | | `external_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "credential_id": { "title": "Credential Id", "type": "integer" }, "integration": { "title": "Integration", "type": "string" }, "external_id": { "title": "External Id", "type": "string" } }, "required": [ "credential_id", "integration", "external_id" ], "title": "g8_appointments_deselect_calendarArguments", "type": "object" } ``` </details> ## g8_appointments_disconnect_calendar > Disconnect a calendar account entirely: removes the credential, revokes the OAuth token (best-effort), and clears destination settings pointing at it. Existing bookings are unaffected. Confirm with the user before calling. Args: credential_id: ID of the calendar account credential to disconnect (from g8_appointments_list_calendars). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `credential_id` | Yes | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "credential_id": { "title": "Credential Id", "type": "integer" } }, "required": [ "credential_id" ], "title": "g8_appointments_disconnect_calendarArguments", "type": "object" } ``` </details> ## g8_appointments_disconnect_conferencing > Disconnect a conferencing provider. Real-credential providers (zoom, roam, whereby, jitsi, office365_video, roam_auto) have their credential deleted; Google Meet has no credential, so it is suppressed and cleared as default. Confirm with the user first - reconnecting OAuth providers (zoom, office365_video) requires the web app. Args: provider: The conferencing provider to disconnect. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `provider` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "provider": { "title": "Provider", "type": "string" } }, "required": [ "provider" ], "title": "g8_appointments_disconnect_conferencingArguments", "type": "object" } ``` </details> ## g8_appointments_duplicate_event_type > Duplicate an event type into a copy with a unique "{slug}-copy" slug and the same hosts/config. Args: event_type_id: The source event type to copy. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `event_type_id` | Yes | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "event_type_id": { "title": "Event Type Id", "type": "integer" } }, "required": [ "event_type_id" ], "title": "g8_appointments_duplicate_event_typeArguments", "type": "object" } ``` </details> ## g8_appointments_duplicate_routing_form > Duplicate a routing form with all its fields and routes. Creates a copy named "{name} (copy)" with a unique auto-generated slug. Captured responses are NOT copied. Args: form_id: The source routing form to copy. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `form_id` | Yes | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "form_id": { "title": "Form Id", "type": "integer" } }, "required": [ "form_id" ], "title": "g8_appointments_duplicate_routing_formArguments", "type": "object" } ``` </details> ## g8_appointments_get_availability > Get a schedule's availability list (recurring weekly rules with days 1=Mon..7=Sun, start_time, end_time) and overrides list (one-time overrides for specific dates). Args: schedule_id: Schedule integer ID. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `schedule_id` | Yes | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "schedule_id": { "title": "Schedule Id", "type": "integer" } }, "required": [ "schedule_id" ], "title": "g8_appointments_get_availabilityArguments", "type": "object" } ``` </details> ## g8_appointments_get_booking > Get a single booking by its UID. Returns the full detail: attendees, status, times, location, meeting_url, the attendee's form responses, and internal notes. Args: booking_uid: Booking UID (uuid string) from g8_appointments_list_bookings. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `booking_uid` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "booking_uid": { "title": "Booking Uid", "type": "string" } }, "required": [ "booking_uid" ], "title": "g8_appointments_get_bookingArguments", "type": "object" } ``` </details> ## g8_appointments_get_default_conferencing > Get the default conferencing provider used for new bookings when no explicit location is specified. Returns null provider when none is set. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_appointments_get_default_conferencingArguments", "type": "object" } ``` </details> ## g8_appointments_get_destination_calendar > Get the destination calendar where new booking events are pushed. Returns null when none is configured. Args: event_type_id: Optional event type ID for a per-event-type lookup; omit to get the user-level default. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `event_type_id` | No | integer / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "event_type_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Event Type Id" } }, "title": "g8_appointments_get_destination_calendarArguments", "type": "object" } ``` </details> ## g8_appointments_get_event_type > Get full detail for one bookable event type: title, slug, duration (length minutes), locations, hosts, schedule, buffers, minimum notice, and the booking_fields an attendee must answer. Call before g8_appointments_create_booking to learn required fields + slot length. Find event_type_id via g8_appointments_list_event_types. Args: event_type_id: Event-type integer ID. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `event_type_id` | Yes | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "event_type_id": { "title": "Event Type Id", "type": "integer" } }, "required": [ "event_type_id" ], "title": "g8_appointments_get_event_typeArguments", "type": "object" } ``` </details> ## g8_appointments_get_insights > Get aggregated booking insights (counts, show/no-show rates, per-event-type breakdowns) for the caller's org. Answers questions like booking volume, no-show rate, or most-booked event type. Args: date_from: ISO date lower bound, e.g. "2026-01-01". date_to: ISO date upper bound, e.g. "2026-06-30". event_type_ids: Comma-separated event-type IDs to filter by. member_user_id: Filter by a specific member (ignored for non-admin API callers). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `date_from` | No | string / null | See the schema below for constraints and defaults. | | `date_to` | No | string / null | See the schema below for constraints and defaults. | | `event_type_ids` | No | string / null | See the schema below for constraints and defaults. | | `member_user_id` | No | integer / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "date_from": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Date From" }, "date_to": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Date To" }, "event_type_ids": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Event Type Ids" }, "member_user_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Member User Id" } }, "title": "g8_appointments_get_insightsArguments", "type": "object" } ``` </details> ## g8_appointments_get_routing_form > Get a routing form by ID with its full fields, routes, and response_count. Each field has an identifier (slug used as the condition key), type (select/text/email/phone/number), label, required, and options (select fields). Routes map answer conditions to an event-type redirect; the fallback route has is_fallback=True. Conditions reference field_id = field.identifier (the slug, not the integer id). Args: form_id: The routing form integer ID. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `form_id` | Yes | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "form_id": { "title": "Form Id", "type": "integer" } }, "required": [ "form_id" ], "title": "g8_appointments_get_routing_formArguments", "type": "object" } ``` </details> ## g8_appointments_get_routing_form_responses > List captured responses for a routing form (paginated). Each response has the visitor's field answers (keyed by field identifier/slug), the event-type routed to, and the submission timestamp. Args: form_id: The routing form integer ID. skip: Number of responses to skip (default 0). take: Maximum responses to return, up to 500 (default 50). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `form_id` | Yes | integer | See the schema below for constraints and defaults. | | `skip` | No | integer | See the schema below for constraints and defaults. | | `take` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "form_id": { "title": "Form Id", "type": "integer" }, "skip": { "default": 0, "title": "Skip", "type": "integer" }, "take": { "default": 50, "title": "Take", "type": "integer" } }, "required": [ "form_id" ], "title": "g8_appointments_get_routing_form_responsesArguments", "type": "object" } ``` </details> ## g8_appointments_get_schedule > Get a single availability schedule with its weekly rules, date overrides, owner info, and linked event types. Returns 404 if it does not exist or belongs to another user. Args: schedule_id: Schedule integer ID (from g8_appointments_list_schedules or the schedule_id field on an event type). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `schedule_id` | Yes | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "schedule_id": { "title": "Schedule Id", "type": "integer" } }, "required": [ "schedule_id" ], "title": "g8_appointments_get_scheduleArguments", "type": "object" } ``` </details> ## g8_appointments_get_workflow > Get full details for a single workflow, including all steps and linked event types. Args: workflow_id: The workflow integer ID (from g8_appointments_list_workflows). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `workflow_id` | Yes | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "workflow_id": { "title": "Workflow Id", "type": "integer" } }, "required": [ "workflow_id" ], "title": "g8_appointments_get_workflowArguments", "type": "object" } ``` </details> ## g8_appointments_list_bookings > List the caller's appointment bookings (paginated). Shows bookings the user owns, hosts, or shared a link for - never org-wide. Args: status: Filter by status - accepted \| pending \| cancelled \| rejected. event_type_id: Only bookings for this event type. after_start: ISO-8601 lower bound on start_time (e.g. "2026-06-01T00:00:00Z"). before_end: ISO-8601 upper bound on end_time. attendee_email: Filter to bookings including this attendee. sort_by: start_time \| end_time \| created_at \| status (default start_time). sort_order: asc \| desc (default desc). skip: Pagination offset (default 0). take: Page size, 1-100 (default 20). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `status` | No | string / null | See the schema below for constraints and defaults. | | `event_type_id` | No | integer / null | See the schema below for constraints and defaults. | | `after_start` | No | string / null | See the schema below for constraints and defaults. | | `before_end` | No | string / null | See the schema below for constraints and defaults. | | `attendee_email` | No | string / null | See the schema below for constraints and defaults. | | `sort_by` | No | string | See the schema below for constraints and defaults. | | `sort_order` | No | string | See the schema below for constraints and defaults. | | `skip` | No | integer | See the schema below for constraints and defaults. | | `take` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "event_type_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Event Type Id" }, "after_start": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "After Start" }, "before_end": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Before End" }, "attendee_email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Attendee Email" }, "sort_by": { "default": "start_time", "title": "Sort By", "type": "string" }, "sort_order": { "default": "desc", "title": "Sort Order", "type": "string" }, "skip": { "default": 0, "title": "Skip", "type": "integer" }, "take": { "default": 20, "title": "Take", "type": "integer" } }, "title": "g8_appointments_list_bookingsArguments", "type": "object" } ``` </details> ## g8_appointments_list_calendars > List connected calendar accounts (Google Calendar, Outlook). Each account shows its sub-calendars and which are selected for conflict checking. Requires a calendar connected in the web app first. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_appointments_list_calendarsArguments", "type": "object" } ``` </details> ## g8_appointments_list_conferencing > List connected conferencing providers. Returns each provider's name, type, meeting link (if applicable), and whether it is the default. Google Meet appears when a Google Calendar credential exists and was not removed. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_appointments_list_conferencingArguments", "type": "object" } ``` </details> ## g8_appointments_list_event_types > List bookable calendar event types (Cal.com appointments). An event type is the "where do they book?" picker: a title, a slug, and a duration. Pass the row's integer id back as appointment_id on g8_gtm_launch_campaign to attach the booking link. Different from g8_list_meetings (the inbox of already-booked meetings); this is the picker for which booking template the sequence references. Args: include_hidden: Include event types marked hidden (not bookable publicly). Default False matches the in-app picker. scheduling_type: Filter by Cal.com scheduling type: managed \| round_robin \| collective. Omit to return all. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `include_hidden` | No | boolean | See the schema below for constraints and defaults. | | `scheduling_type` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "include_hidden": { "default": false, "title": "Include Hidden", "type": "boolean" }, "scheduling_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Scheduling Type" } }, "title": "list_event_typesArguments", "type": "object" } ``` </details> ## g8_appointments_list_ooo > List all out-of-office entries for the calling user. Each entry has a uuid, date range, optional notes, optional redirect-to user id, and reason. Use the uuid to delete an entry with g8_appointments_delete_ooo. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_appointments_list_oooArguments", "type": "object" } ``` </details> ## g8_appointments_list_routing_forms > List all routing forms owned by the current user. Returns each form's id, name, slug, description, disabled state, field count, route count, and total response_count. Use g8_appointments_get_routing_form for the full field/route definitions. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_appointments_list_routing_formsArguments", "type": "object" } ``` </details> ## g8_appointments_list_schedules > List the caller's appointment availability schedules. DISTINCT from g8_list_schedules (sequencer send-window schedules). Each entry has id, name, time_zone, weekly rules, and is_default; use an id with g8_appointments_get_schedule / _update_schedule / _set_default_schedule. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_appointments_list_schedulesArguments", "type": "object" } ``` </details> ## g8_appointments_list_workflows > List all booking workflows configured for the organisation. Returns every workflow with its trigger, active state, linked event types, and steps. Use this to discover existing workflows before creating new ones. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_appointments_list_workflowsArguments", "type": "object" } ``` </details> ## g8_appointments_mark_absent > Mark the host and/or attendees as no-show for a booking. Args: booking_uid: Booking UID to update. host: Set True to mark the host as no-show. attendees: List of attendee dicts (each needs at least "email"), e.g. [{"email": "jane@x.com"}], to mark as no-show. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `booking_uid` | Yes | string | See the schema below for constraints and defaults. | | `host` | No | boolean / null | See the schema below for constraints and defaults. | | `attendees` | No | array / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "booking_uid": { "title": "Booking Uid", "type": "string" }, "host": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Host" }, "attendees": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Attendees" } }, "required": [ "booking_uid" ], "title": "g8_appointments_mark_absentArguments", "type": "object" } ``` </details> ## g8_appointments_request_reschedule > Ask the attendee to pick a new time (host-initiated reschedule request). Sends a reschedule-request email with a booking-page link. Does NOT move or cancel the original booking - the attendee reschedules by clicking the link. Args: booking_uid: Booking UID for which to request a reschedule. reason: Optional message from host to the attendee. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `booking_uid` | Yes | string | See the schema below for constraints and defaults. | | `reason` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "booking_uid": { "title": "Booking Uid", "type": "string" }, "reason": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Reason" } }, "required": [ "booking_uid" ], "title": "g8_appointments_request_rescheduleArguments", "type": "object" } ``` </details> ## g8_appointments_reschedule_booking > Reschedule a booking to a new start time. Free (no extra credit charge); re-sends the updated calendar invite and notifies the attendee. Returns the NEW booking detail. Args: booking_uid: Booking UID to move. start_time: New ISO-8601 start time (must be an available slot). rescheduling_reason: Optional reason shown in the notification. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `booking_uid` | Yes | string | See the schema below for constraints and defaults. | | `start_time` | Yes | string | See the schema below for constraints and defaults. | | `rescheduling_reason` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "booking_uid": { "title": "Booking Uid", "type": "string" }, "start_time": { "title": "Start Time", "type": "string" }, "rescheduling_reason": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Rescheduling Reason" } }, "required": [ "booking_uid", "start_time" ], "title": "g8_appointments_reschedule_bookingArguments", "type": "object" } ``` </details> ## g8_appointments_select_calendar > Add a sub-calendar to conflict checking; its busy times are then checked before offering booking slots. Find all args via g8_appointments_list_calendars. Args: credential_id: ID of the connected calendar account credential. integration: Provider type, e.g. "google_calendar" or "office365_calendar". external_id: The calendar's external ID (from list_calendars sub_calendars). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `credential_id` | Yes | integer | See the schema below for constraints and defaults. | | `integration` | Yes | string | See the schema below for constraints and defaults. | | `external_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "credential_id": { "title": "Credential Id", "type": "integer" }, "integration": { "title": "Integration", "type": "string" }, "external_id": { "title": "External Id", "type": "string" } }, "required": [ "credential_id", "integration", "external_id" ], "title": "g8_appointments_select_calendarArguments", "type": "object" } ``` </details> ## g8_appointments_set_conferencing_link > Set or update the meeting URL for a link-based conferencing provider (roam, whereby, or jitsi). The provider must already be connected; the URL is validated against the provider's pattern. Args: provider: One of: roam, whereby, jitsi. link: The meeting URL (must match the provider's URL pattern). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `provider` | Yes | string | See the schema below for constraints and defaults. | | `link` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "provider": { "title": "Provider", "type": "string" }, "link": { "title": "Link", "type": "string" } }, "required": [ "provider", "link" ], "title": "g8_appointments_set_conferencing_linkArguments", "type": "object" } ``` </details> ## g8_appointments_set_default_conferencing > Set the default conferencing provider for new bookings when no explicit location is specified. Link-based providers (roam, whereby, jitsi) need a meeting URL - pass it here or set it first via g8_appointments_set_conferencing_link. Args: provider: The conferencing provider to set as default. credential_id: Optional credential ID for the provider. link: Optional meeting URL (required for roam/whereby/jitsi if not already stored). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `provider` | Yes | string | See the schema below for constraints and defaults. | | `credential_id` | No | integer / null | See the schema below for constraints and defaults. | | `link` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "provider": { "title": "Provider", "type": "string" }, "credential_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Credential Id" }, "link": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Link" } }, "required": [ "provider" ], "title": "g8_appointments_set_default_conferencingArguments", "type": "object" } ``` </details> ## g8_appointments_set_default_schedule > Set a schedule as the caller's default availability schedule, used for slot generation when an event type has no explicit schedule attached. Args: schedule_id: Schedule to make the default. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `schedule_id` | Yes | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "schedule_id": { "title": "Schedule Id", "type": "integer" } }, "required": [ "schedule_id" ], "title": "g8_appointments_set_default_scheduleArguments", "type": "object" } ``` </details> ## g8_appointments_set_destination_calendar > Set the destination calendar where new booking events are pushed. Find arg values via g8_appointments_list_calendars. Args: integration: Provider type, e.g. "google_calendar" or "office365_calendar". external_id: The calendar's external ID. credential_id: Optional credential ID for the calendar account. event_type_id: Optional event type ID for a per-event-type destination; omit to set the user-level default. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `integration` | Yes | string | See the schema below for constraints and defaults. | | `external_id` | Yes | string | See the schema below for constraints and defaults. | | `credential_id` | No | integer / null | See the schema below for constraints and defaults. | | `event_type_id` | No | integer / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "integration": { "title": "Integration", "type": "string" }, "external_id": { "title": "External Id", "type": "string" }, "credential_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Credential Id" }, "event_type_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Event Type Id" } }, "required": [ "integration", "external_id" ], "title": "g8_appointments_set_destination_calendarArguments", "type": "object" } ``` </details> ## g8_appointments_toggle_workflow > Activate or deactivate a workflow. Deactivating cancels all pending reminders so scheduled jobs never fire for a disabled workflow. Args: workflow_id: The workflow to toggle. active: True to activate, False to deactivate. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `workflow_id` | Yes | integer | See the schema below for constraints and defaults. | | `active` | Yes | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "workflow_id": { "title": "Workflow Id", "type": "integer" }, "active": { "title": "Active", "type": "boolean" } }, "required": [ "workflow_id", "active" ], "title": "g8_appointments_toggle_workflowArguments", "type": "object" } ``` </details> ## g8_appointments_update_availability > Replace weekly availability rules and/or date overrides for a schedule. Pass at least one. Each list REPLACES its counterpart entirely - pass all active entries, not just changed ones. Days: 1=Monday ... 7=Sunday. Args: schedule_id: Schedule to update. availability: Replacement weekly rules [{days: [1..7], start_time: "HH:MM", end_time: "HH:MM"}]. Omit to leave weekly rules unchanged. overrides: Replacement date overrides [{date: "YYYY-MM-DD", start_time: "HH:MM", end_time: "HH:MM"}]. Omit to leave overrides unchanged. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `schedule_id` | Yes | integer | See the schema below for constraints and defaults. | | `availability` | No | array / null | See the schema below for constraints and defaults. | | `overrides` | No | array / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "schedule_id": { "title": "Schedule Id", "type": "integer" }, "availability": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Availability" }, "overrides": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Overrides" } }, "required": [ "schedule_id" ], "title": "g8_appointments_update_availabilityArguments", "type": "object" } ``` </details> ## g8_appointments_update_booking_location > Change a booking's physical location and/or its conferencing. Updates the host's calendar event and emails the host (always) and, unless notify_attendees is false, the attendees. Pass only what changes. Args: booking_uid: Booking UID to update. selected_location: New physical location, e.g. {"kind": "in_person_host", "address": "1 Market St"}, {"kind": "phone_host", "phone": "+14155550100"} or {"kind": "custom", "display_label": "Lobby"}. selected_conferencing: New conferencing: {"kind": "link", "link": "https://..."} for a meeting URL, or {"kind": "conferencing", "provider": "zoom"} (also "roam_auto", "ms_teams", "google_meet"), which creates a NEW meeting room for this booking. clear_location: True to remove the physical location. clear_conferencing: True to remove the conferencing. notify_attendees: Email the attendees too (default true). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `booking_uid` | Yes | string | See the schema below for constraints and defaults. | | `selected_location` | No | object / null | See the schema below for constraints and defaults. | | `selected_conferencing` | No | object / null | See the schema below for constraints and defaults. | | `clear_location` | No | boolean | See the schema below for constraints and defaults. | | `clear_conferencing` | No | boolean | See the schema below for constraints and defaults. | | `notify_attendees` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "booking_uid": { "title": "Booking Uid", "type": "string" }, "selected_location": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Selected Location" }, "selected_conferencing": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Selected Conferencing" }, "clear_location": { "default": false, "title": "Clear Location", "type": "boolean" }, "clear_conferencing": { "default": false, "title": "Clear Conferencing", "type": "boolean" }, "notify_attendees": { "default": true, "title": "Notify Attendees", "type": "boolean" } }, "required": [ "booking_uid" ], "title": "g8_appointments_update_booking_locationArguments", "type": "object" } ``` </details> ## g8_appointments_update_booking_sequence > Associate a booking with a sequencer sequence, or detach it. CRM-only action: no notifications are sent. Pass null or omit to detach. Args: booking_uid: Booking UID to update. sequence_id: Sequence UUID to link (from g8_list_sequences), or null to detach. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `booking_uid` | Yes | string | See the schema below for constraints and defaults. | | `sequence_id` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "booking_uid": { "title": "Booking Uid", "type": "string" }, "sequence_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Sequence Id" } }, "required": [ "booking_uid" ], "title": "g8_appointments_update_booking_sequenceArguments", "type": "object" } ``` </details> ## g8_appointments_update_event_type > Update an event type. Only the fields you pass are changed; omit a field to leave it as-is. Args: event_type_id: The event type to update (from g8_appointments_list_event_types). title: New display name. slug: New URL slug (must stay unique in the org). length: New duration in minutes. description: New description. scheduling_type: "collective" \| "round_robin". schedule_id: Attach a different availability schedule. hidden: True hides the event type; False makes it publicly bookable. requires_confirmation: Toggle host-approval requirement. minimum_booking_notice: Minutes of lead time required before a slot. locations: Replace the location/conferencing config. booking_fields: Replace the custom booking questions. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `event_type_id` | Yes | integer | See the schema below for constraints and defaults. | | `title` | No | string / null | See the schema below for constraints and defaults. | | `slug` | No | string / null | See the schema below for constraints and defaults. | | `length` | No | integer / null | See the schema below for constraints and defaults. | | `description` | No | string / null | See the schema below for constraints and defaults. | | `scheduling_type` | No | string / null | See the schema below for constraints and defaults. | | `schedule_id` | No | integer / null | See the schema below for constraints and defaults. | | `hidden` | No | boolean / null | See the schema below for constraints and defaults. | | `requires_confirmation` | No | boolean / null | See the schema below for constraints and defaults. | | `minimum_booking_notice` | No | integer / null | See the schema below for constraints and defaults. | | `locations` | No | array / null | See the schema below for constraints and defaults. | | `booking_fields` | No | array / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "event_type_id": { "title": "Event Type Id", "type": "integer" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "slug": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Slug" }, "length": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Length" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "scheduling_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Scheduling Type" }, "schedule_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Schedule Id" }, "hidden": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Hidden" }, "requires_confirmation": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Requires Confirmation" }, "minimum_booking_notice": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Minimum Booking Notice" }, "locations": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Locations" }, "booking_fields": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Booking Fields" } }, "required": [ "event_type_id" ], "title": "g8_appointments_update_event_typeArguments", "type": "object" } ``` </details> ## g8_appointments_update_ooo > Update an out-of-office entry. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: PATCH /appointments/out-of-office/{ooo_uuid} ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `ooo_uuid` | Yes | string | UUID of the out-of-office entry to update. | | `body` | Yes | #/$defs/ApiUpdateOutOfOfficeRequest | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "$defs": { "ApiUpdateOutOfOfficeRequest": { "additionalProperties": false, "properties": { "end": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "End" }, "notes": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Notes" }, "public_note": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Public Note" }, "reason": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Reason" }, "start": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Start" }, "time_zone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Time Zone" }, "to_user_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "To User Id" } }, "title": "ApiUpdateOutOfOfficeRequest", "type": "object" } }, "properties": { "ooo_uuid": { "description": "UUID of the out-of-office entry to update.", "title": "Ooo Uuid", "type": "string" }, "body": { "$ref": "#/$defs/ApiUpdateOutOfOfficeRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "ooo_uuid", "body" ], "title": "g8_appointments_update_oooArguments", "type": "object" } ``` </details> ## g8_appointments_update_routing_form > Update a routing form. Only the fields you pass are changed; omit a parameter to leave it as-is. Passing fields or routes REPLACES the existing list atomically. slug must be unique in the org (409 on collision). disabled=True pauses the form without deleting it. Args: form_id: The routing form to update. name: New display name. slug: New URL slug (must be unique in the org). description: New description. disabled: True pauses the form; False re-enables it. fields: Replacement field list (replaces all existing fields). routes: Replacement route list (replaces all existing routes). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `form_id` | Yes | integer | See the schema below for constraints and defaults. | | `name` | No | string / null | See the schema below for constraints and defaults. | | `slug` | No | string / null | See the schema below for constraints and defaults. | | `description` | No | string / null | See the schema below for constraints and defaults. | | `disabled` | No | boolean / null | See the schema below for constraints and defaults. | | `fields` | No | array / null | See the schema below for constraints and defaults. | | `routes` | No | array / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "form_id": { "title": "Form Id", "type": "integer" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "slug": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Slug" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "disabled": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Disabled" }, "fields": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Fields" }, "routes": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Routes" } }, "required": [ "form_id" ], "title": "g8_appointments_update_routing_formArguments", "type": "object" } ``` </details> ## g8_appointments_update_schedule > Partial update of an availability schedule. Omit a field to leave it as-is. availability and overrides each REPLACE all entries when passed. Args: schedule_id: Schedule to update. name: New display name. time_zone: New IANA timezone string. is_default: True to set as the caller's default schedule. availability: Replacement weekly rules [{days, start_time, end_time}]. overrides: Replacement date overrides [{date, start_time, end_time}]. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `schedule_id` | Yes | integer | See the schema below for constraints and defaults. | | `name` | No | string / null | See the schema below for constraints and defaults. | | `time_zone` | No | string / null | See the schema below for constraints and defaults. | | `is_default` | No | boolean / null | See the schema below for constraints and defaults. | | `availability` | No | array / null | See the schema below for constraints and defaults. | | `overrides` | No | array / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "schedule_id": { "title": "Schedule Id", "type": "integer" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "time_zone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Time Zone" }, "is_default": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Default" }, "availability": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Availability" }, "overrides": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Overrides" } }, "required": [ "schedule_id" ], "title": "g8_appointments_update_scheduleArguments", "type": "object" } ``` </details> ## g8_appointments_update_workflow > Update a workflow. Only the fields you pass are changed; omit a field to leave it as-is. Passing steps replaces all existing steps; passing event_type_ids replaces all links. Args: workflow_id: The workflow to update (from g8_appointments_list_workflows). name: New display name. trigger: New trigger type. steps: New steps list (replaces all existing steps when provided). time: New time value for time-based triggers. time_unit: New time unit for time-based triggers. is_active_on_all: Toggle org-wide application. event_type_ids: New list of event type IDs to link (replaces existing links). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `workflow_id` | Yes | integer | See the schema below for constraints and defaults. | | `name` | No | string / null | See the schema below for constraints and defaults. | | `trigger` | No | string / null | See the schema below for constraints and defaults. | | `steps` | No | array / null | See the schema below for constraints and defaults. | | `time` | No | integer / null | See the schema below for constraints and defaults. | | `time_unit` | No | string / null | See the schema below for constraints and defaults. | | `is_active_on_all` | No | boolean / null | See the schema below for constraints and defaults. | | `event_type_ids` | No | array / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "workflow_id": { "title": "Workflow Id", "type": "integer" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "trigger": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Trigger" }, "steps": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Steps" }, "time": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Time" }, "time_unit": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Time Unit" }, "is_active_on_all": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Active On All" }, "event_type_ids": { "anyOf": [ { "items": { "type": "integer" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Event Type Ids" } }, "required": [ "workflow_id" ], "title": "g8_appointments_update_workflowArguments", "type": "object" } ``` </details> ## g8_assign_reply > Assign a user to an inbox thread. Args: reply_id: Thread/reply ID. assignee_email: Email of the user to assign. channel: email, sms, or linkedin (default email). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `reply_id` | Yes | string | See the schema below for constraints and defaults. | | `assignee_email` | Yes | string | See the schema below for constraints and defaults. | | `channel` | No | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "reply_id": { "title": "Reply Id", "type": "string" }, "assignee_email": { "title": "Assignee Email", "type": "string" }, "channel": { "default": "email", "title": "Channel", "type": "string" } }, "required": [ "reply_id", "assignee_email" ], "title": "g8_assign_replyArguments", "type": "object" } ``` </details> ## g8_assign_task_executor > Save an eligible Work agent assignment without starting execution. Keep the task's human assignee accountable. Omit agent_id to return to human execution. Read the task first and supply its updated_at value. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `task_id` | Yes | string | See the schema below for constraints and defaults. | | `expected_updated_at` | Yes | string | See the schema below for constraints and defaults. | | `agent_id` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "task_id": { "title": "Task Id", "type": "string" }, "expected_updated_at": { "title": "Expected Updated At", "type": "string" }, "agent_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Agent Id" } }, "required": [ "task_id", "expected_updated_at" ], "title": "g8_assign_task_executorArguments", "type": "object" } ``` </details> ## g8_audit_list_request_logs > List Request Logs. Uses the current organization and existing API permissions. API: GET /logs ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `limit` | No | integer / null | Max entries to return (most recent first) | <details> <summary>Complete input schema</summary> ```json { "properties": { "limit": { "anyOf": [ { "maximum": 200, "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "description": "Max entries to return (most recent first)", "title": "Limit" } }, "title": "g8_audit_list_request_logsArguments", "type": "object" } ``` </details> ## g8_cancel_booking > Cancel a booking and notify both parties. This sends cancellation emails and frees the slot - confirm with the user first. Args: booking_uid: Booking UID to cancel. cancellation_reason: Optional reason shown in the notification. cancel_subsequent: For recurring bookings, also cancel later occurrences (default False). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `booking_uid` | Yes | string | See the schema below for constraints and defaults. | | `cancellation_reason` | No | string / null | See the schema below for constraints and defaults. | | `cancel_subsequent` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "booking_uid": { "title": "Booking Uid", "type": "string" }, "cancellation_reason": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Cancellation Reason" }, "cancel_subsequent": { "default": false, "title": "Cancel Subsequent", "type": "boolean" } }, "required": [ "booking_uid" ], "title": "g8_appointments_cancel_bookingArguments", "type": "object" } ``` </details> ## g8_company_open_jobs > Find out what companies in your CRM are hiring for right now. A growth/buying signal: a company staffing up is a company with budget. Use it to prioritise accounts, time outreach, or open with a specific role they just posted. NOT related to g8_marketplace_create_job_post. That publishes an SDR role YOU are recruiting for onto the graph8 marketplace. This reads jobs OTHER companies are advertising. Same word, different data - if the user wants to hire someone, you want the marketplace tool, not this one. Pass CRM company ids (from g8_search_companies). Companies you don't have ids for yet: search first, then call this. Free - reads an already-ingested corpus, no credits. Results split into `items` (resolved) and `unresolved`. An unresolved company is normal - it means we couldn't match it to a LinkedIn company page, not that it isn't hiring. `open_jobs: 0` means resolved and genuinely hiring nobody. Read `lcid_confidence` before trusting a match: 1.0 is an exact LinkedIn-URL match, 0.7 is a domain fallback that can mismatch on shared or parked domains. And note `open_jobs` counts postings seen within `window_days` - the source has no open/closed state, so a filled role lingers until it ages out. Check `degraded` before reading low counts as "not hiring". It is true when a lookup failed (counts are then null) OR when the postings feed stopped or sharply slowed ingesting; `degraded_reasons` says which, and `feed` carries the last ingest time, the median days between a job being posted and graph8 seeing it, and a plain `message` to relay. Even on a healthy feed (`degraded` false) jobs are discovered weeks after posting, so windows under ~30 days undercount. `lcid_synthetic: true` means `linkedin_company_id` is a negative data-provider key, not a real LinkedIn id: the match is still exact, but do not build LinkedIn URLs from it. Args: company_ids: CRM company ids (max 200). NOT LinkedIn ids. window_days: How far back to look for postings (1-365, default 60). include_postings: False returns counts only - faster, use when you just need to rank accounts by hiring activity. limit_per_company: Max postings per company (1-50, default 25). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `company_ids` | Yes | array | See the schema below for constraints and defaults. | | `window_days` | No | integer | See the schema below for constraints and defaults. | | `include_postings` | No | boolean | See the schema below for constraints and defaults. | | `limit_per_company` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "company_ids": { "items": { "type": "integer" }, "title": "Company Ids", "type": "array" }, "window_days": { "default": 60, "title": "Window Days", "type": "integer" }, "include_postings": { "default": true, "title": "Include Postings", "type": "boolean" }, "limit_per_company": { "default": 25, "title": "Limit Per Company", "type": "integer" } }, "required": [ "company_ids" ], "title": "g8_company_open_jobsArguments", "type": "object" } ``` </details> ## g8_connect_repo > Connect a GitHub/GitLab repository to graph8 for GTM automation. After connecting, read the tech stack yourself and record it with g8_scan_repo. graph8 does not scan the repository for you. Args: repo_url: Full repository URL (e.g. https://github.com/org/repo) repo_name: Short name for the repo (e.g. my-saas-app) provider: Repository provider - github, gitlab, or local default_branch: Default branch name (usually main) ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `repo_url` | Yes | string | See the schema below for constraints and defaults. | | `repo_name` | Yes | string | See the schema below for constraints and defaults. | | `provider` | No | string | See the schema below for constraints and defaults. | | `default_branch` | No | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "repo_url": { "title": "Repo Url", "type": "string" }, "repo_name": { "title": "Repo Name", "type": "string" }, "provider": { "default": "github", "title": "Provider", "type": "string" }, "default_branch": { "default": "main", "title": "Default Branch", "type": "string" } }, "required": [ "repo_url", "repo_name" ], "title": "g8_connect_repoArguments", "type": "object" } ``` </details> ## g8_connection_describe_current_key > Describe the API connection this session is using. By default: who the key belongs to, the org it acts for, the scopes it carries and the operations those reach. That is the answer to "why did that call 403?" - compare the failing operation against what comes back here. Two opt-in extras, both reads: - `include_org_keys`: every API key in the org - id, name, mode, scopes, who created it and when it was last used. Secrets are never returned by any of these routes; a token is emitted only at creation and rotation, neither of which is available here. Use it to find a key nobody uses any more, or to see which key a teammate's integration is running on. - `include_scope_catalog`: the full scope vocabulary a key CAN be granted, plus the default grant. Wider than the scope list on this key, and wider than the operations above: several scopes exist only in this catalog because their routes gate themselves, so this is the only place to see them. Each extra costs one additional request, so leave them off unless the question needs them. API: GET /me (plus GET /api-keys and GET /api-keys/scopes when the flags are set). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `include_org_keys` | No | boolean | See the schema below for constraints and defaults. | | `include_scope_catalog` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "include_org_keys": { "default": false, "title": "Include Org Keys", "type": "boolean" }, "include_scope_catalog": { "default": false, "title": "Include Scope Catalog", "type": "boolean" } }, "title": "g8_connection_describe_current_keyArguments", "type": "object" } ``` </details> ## g8_connection_get_mcp_connection > Get Mcp Connection. Uses the current organization and existing API permissions. API: GET /mcp/connection ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_connection_get_mcp_connectionArguments", "type": "object" } ``` </details> ## g8_connection_revoke_api_key > Revoke one of the org's API keys, permanently. The key stops working immediately and its token cannot be recovered - anything running on it breaks until someone issues a new key in the app and redeploys. There is no un-revoke. Check `last_used_at` first with `g8_connection_describe_current_key(include_org_keys=True)`: a key used in the last few minutes is almost certainly load-bearing. Note the list does not say WHICH key this session is using, so revoking the wrong row can end the session's own access. A key id that is not in this org returns not-found, the same as one that was already revoked. Args: api_key_id: From `include_org_keys` above. confirm: Must be true. Without it no request is sent. API: DELETE /api-keys/{api_key_id} ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `api_key_id` | Yes | string | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "api_key_id": { "title": "Api Key Id", "type": "string" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "api_key_id" ], "title": "g8_connection_revoke_api_keyArguments", "type": "object" } ``` </details> ## g8_contact_support > Send the user's message to graph8's HUMAN support team. Use this when the user explicitly asks to talk to a human, contact graph8 support, report a problem you cannot solve, or file a complaint about the graph8 platform. Do NOT use it for questions you can answer yourself with the other tools. How the reply works: graph8 support is notified immediately and replies by EMAIL to the user's own address (or to contact_email when provided). After calling, tell the user which address the reply will go to - it is in the response. There is no live chat behind this tool. Duplicate handling: a repeat call within a few minutes returns status "already_received" - the team was already notified, so do NOT retry or call again for the same issue. Args: message: What the user needs help with, in their own words. Include relevant context (what they tried, error messages, which feature). 3 to 5000 characters. contact_email: Where support should reply. Usually omit it - the authenticated user's email is used. REQUIRED when the server says the API key has no user email (org-scoped machine key): ask the user for their email and retry with it. urgency: Optional free-form urgency hint, e.g. "normal", "high", "urgent". Urgent-flavored values are flagged to the team. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `message` | Yes | string | See the schema below for constraints and defaults. | | `contact_email` | No | string / null | See the schema below for constraints and defaults. | | `urgency` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "message": { "title": "Message", "type": "string" }, "contact_email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Contact Email" }, "urgency": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Urgency" } }, "required": [ "message" ], "title": "g8_contact_supportArguments", "type": "object" } ``` </details> ## g8_copy_task_to_people > Create a separate human task for each selected person; leave the source unchanged. Read the source first and pass its updated_at revision. request_id is a UUID that MUST be reused after any uncertain result or partial failure. Retry only failed recipients with the same request_id and revision. The response separates copied task IDs from recipient failures. Copies exclude execution/approval history, comments, subtasks and reminders. This does not start any agent execution. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `task_id` | Yes | string | See the schema below for constraints and defaults. | | `assignee_ids` | Yes | array | See the schema below for constraints and defaults. | | `expected_updated_at` | Yes | string | See the schema below for constraints and defaults. | | `request_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "task_id": { "title": "Task Id", "type": "string" }, "assignee_ids": { "items": { "type": "string" }, "title": "Assignee Ids", "type": "array" }, "expected_updated_at": { "title": "Expected Updated At", "type": "string" }, "request_id": { "title": "Request Id", "type": "string" } }, "required": [ "task_id", "assignee_ids", "expected_updated_at", "request_id" ], "title": "g8_copy_task_to_peopleArguments", "type": "object" } ``` </details> ## g8_create_audience_sync > Create an audience sync config: syncs a graph8 audience list to an ad platform for custom audience targeting. Returns 409 if a sync already exists for this audience + platform combo. Args: audience_id: Audience list ID to sync platform: Ad platform (meta, linkedin, google, x) mode: Sync mode - "mirror" (add+remove) or "append_only" (add only) refresh_cadence_hours: Hours between auto-syncs (default 24) platform_audience_name: Custom name for the audience on the platform ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `audience_id` | Yes | integer | See the schema below for constraints and defaults. | | `platform` | Yes | string | See the schema below for constraints and defaults. | | `mode` | No | string | See the schema below for constraints and defaults. | | `refresh_cadence_hours` | No | integer | See the schema below for constraints and defaults. | | `platform_audience_name` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "audience_id": { "title": "Audience Id", "type": "integer" }, "platform": { "title": "Platform", "type": "string" }, "mode": { "default": "mirror", "title": "Mode", "type": "string" }, "refresh_cadence_hours": { "default": 24, "title": "Refresh Cadence Hours", "type": "integer" }, "platform_audience_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Platform Audience Name" } }, "required": [ "audience_id", "platform" ], "title": "g8_create_audience_syncArguments", "type": "object" } ``` </details> ## g8_create_booking > Book an appointment. CHARGES CREDITS (~20) and sends calendar invites + confirmation emails to attendee and host. ALWAYS confirm event type, time, and attendee with the user first - billable, externally-visible. Resolve event_type_id via g8_appointments_list_event_types and inspect it with g8_appointments_get_event_type first (duration + required booking_fields). start_time must be an available slot; end_time derives from the event-type length when omitted. Args: event_type_id: The event type to book. start_time: ISO-8601 start (e.g. "2026-06-10T15:00:00Z"). attendee_name: Primary attendee's name. attendee_email: Primary attendee's email. attendee_time_zone: IANA tz for the attendee (e.g. "America/New_York"). end_time: ISO-8601 end. Omit to use the event-type duration. location: Location/conferencing key (e.g. "zoom", "google_meet"). guests: Additional guest email addresses to invite. responses: Answers to the event type's custom booking_fields, keyed by field id. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `event_type_id` | Yes | integer | See the schema below for constraints and defaults. | | `start_time` | Yes | string | See the schema below for constraints and defaults. | | `attendee_name` | Yes | string | See the schema below for constraints and defaults. | | `attendee_email` | Yes | string | See the schema below for constraints and defaults. | | `attendee_time_zone` | No | string / null | See the schema below for constraints and defaults. | | `end_time` | No | string / null | See the schema below for constraints and defaults. | | `location` | No | string / null | See the schema below for constraints and defaults. | | `guests` | No | array / null | See the schema below for constraints and defaults. | | `responses` | No | object / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "event_type_id": { "title": "Event Type Id", "type": "integer" }, "start_time": { "title": "Start Time", "type": "string" }, "attendee_name": { "title": "Attendee Name", "type": "string" }, "attendee_email": { "title": "Attendee Email", "type": "string" }, "attendee_time_zone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Attendee Time Zone" }, "end_time": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "End Time" }, "location": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Location" }, "guests": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Guests" }, "responses": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Responses" } }, "required": [ "event_type_id", "start_time", "attendee_name", "attendee_email" ], "title": "g8_appointments_create_bookingArguments", "type": "object" } ``` </details> ## g8_create_company > Create a company in your CRM. `domain` is required (e.g. "acme.com"); it drives dedup, so creating the same domain twice reuses the existing company rather than making a duplicate. Returns the new `company_id` (mashup_companies.id). Use this to add a company that isn't in your CRM yet and then attach it to a contact: create it here, then call g8_update_contact(contact_id=..., company_id=<company_id>). A deal can then derive its company from that contact. Args: domain: Company domain, e.g. "acme.com" (required). name: Company name. website, industry, linkedin_url, phone, city, state, country, employee_count: Optional company details. list_id: Optionally add the company to this list after creation. Backed by `POST /api/v1/companies`. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `domain` | Yes | string | See the schema below for constraints and defaults. | | `name` | No | string / null | See the schema below for constraints and defaults. | | `website` | No | string / null | See the schema below for constraints and defaults. | | `industry` | No | string / null | See the schema below for constraints and defaults. | | `linkedin_url` | No | string / null | See the schema below for constraints and defaults. | | `phone` | No | string / null | See the schema below for constraints and defaults. | | `city` | No | string / null | See the schema below for constraints and defaults. | | `state` | No | string / null | See the schema below for constraints and defaults. | | `country` | No | string / null | See the schema below for constraints and defaults. | | `employee_count` | No | string / null | See the schema below for constraints and defaults. | | `list_id` | No | integer / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "domain": { "title": "Domain", "type": "string" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "website": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Website" }, "industry": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Industry" }, "linkedin_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Linkedin Url" }, "phone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Phone" }, "city": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "City" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "country": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Country" }, "employee_count": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Employee Count" }, "list_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "List Id" } }, "required": [ "domain" ], "title": "g8_create_companyArguments", "type": "object" } ``` </details> ## g8_create_contact > Create a contact in your CRM database. Use this after g8_lookup_person to save a prospect you want to track, or to manually add a contact you know about. Optionally add them directly to a list by providing list_id. Returns the new contact_id (needed for g8_add_to_sequence and g8_add_to_list). Args: work_email: Contact's work email (required) first_name: First name last_name: Last name job_title: Job title company_domain: Company domain (e.g. acme.com) to resolve-or-create company_id: Existing company id (mashup_companies.id) to attach; wins over company_domain linkedin_url: LinkedIn profile URL direct_phone: Direct phone number mobile_phone: Mobile phone number seniority_level: Seniority level (C-Suite, VP, Director, Manager, etc.) city: City state: State/province country: Country list_id: Optionally add to this list immediately (from g8_create_list) ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `work_email` | Yes | string | See the schema below for constraints and defaults. | | `first_name` | No | string / null | See the schema below for constraints and defaults. | | `last_name` | No | string / null | See the schema below for constraints and defaults. | | `job_title` | No | string / null | See the schema below for constraints and defaults. | | `company_domain` | No | string / null | See the schema below for constraints and defaults. | | `company_id` | No | integer / null | See the schema below for constraints and defaults. | | `linkedin_url` | No | string / null | See the schema below for constraints and defaults. | | `direct_phone` | No | string / null | See the schema below for constraints and defaults. | | `mobile_phone` | No | string / null | See the schema below for constraints and defaults. | | `seniority_level` | No | string / null | See the schema below for constraints and defaults. | | `city` | No | string / null | See the schema below for constraints and defaults. | | `state` | No | string / null | See the schema below for constraints and defaults. | | `country` | No | string / null | See the schema below for constraints and defaults. | | `list_id` | No | integer / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "work_email": { "title": "Work Email", "type": "string" }, "first_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "First Name" }, "last_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Name" }, "job_title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Job Title" }, "company_domain": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Company Domain" }, "company_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Company Id" }, "linkedin_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Linkedin Url" }, "direct_phone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Direct Phone" }, "mobile_phone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Mobile Phone" }, "seniority_level": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Seniority Level" }, "city": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "City" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "country": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Country" }, "list_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "List Id" } }, "required": [ "work_email" ], "title": "g8_create_contactArguments", "type": "object" } ``` </details> ## g8_create_deal > Create a new CRM deal. Requires an owner and at least one contact. A deal is always tied to a company, and the company is DERIVED from the contacts (every contact must belong to the same company, which becomes the deal's company). You do not pass a company id. Defaults to the org's default pipeline and its first non-closed stage when those are not provided. Backed by `POST /api/v1/deals`. By default this rejects (409) creating a second deal for a company that already has one. If you get that error and still want another deal for the company, retry with allow_duplicate=true. Closed-stage rules (400 on violation, detail prefixed with a marker): creating a deal directly in a Closed Won stage requires close_date AND amount; Closed Lost requires close_date; either requires an owner. Markers: close_date_required_for_won_stage, close_date_required_for_lost_stage, amount_required_for_won_stage, owner_required_for_closed_stage. Past close dates are allowed (backdating is a supported workflow). Args: name: Deal name (required). owner_id: Deal owner, user id or email (required). contact_ids: mashup_contact_ids to link (required, at least one). All must belong to the same company; that company becomes the deal's company. Same id space as g8_search_contacts.id. Idempotent: already-linked contacts are skipped. company_id: Deprecated. The company is derived from the contacts. If provided it must match the contacts' company, else omit it. description: Free-text description. amount: Monetary value. Required when creating directly in a Closed Won stage (0 is a valid amount). currency: Currency code (defaults to USD server-side). stage_id: Pipeline stage UUID (see g8_get_pipeline). Must exist (422 otherwise) and belong to pipeline_id when both are sent. pipeline_id: Pipeline UUID (defaults to org default). Must exist (422 otherwise). close_date: Expected close date (ISO 8601). Required when creating directly in a Closed Won or Closed Lost stage. allow_duplicate: Set true to create a deal even when one already exists for the company (default false). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `name` | Yes | string | See the schema below for constraints and defaults. | | `owner_id` | Yes | string | See the schema below for constraints and defaults. | | `contact_ids` | Yes | array | See the schema below for constraints and defaults. | | `company_id` | No | integer / null | See the schema below for constraints and defaults. | | `description` | No | string / null | See the schema below for constraints and defaults. | | `amount` | No | number / null | See the schema below for constraints and defaults. | | `currency` | No | string / null | See the schema below for constraints and defaults. | | `stage_id` | No | string / null | See the schema below for constraints and defaults. | | `pipeline_id` | No | string / null | See the schema below for constraints and defaults. | | `close_date` | No | string / null | See the schema below for constraints and defaults. | | `allow_duplicate` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "name": { "title": "Name", "type": "string" }, "owner_id": { "title": "Owner Id", "type": "string" }, "contact_ids": { "items": { "type": "integer" }, "title": "Contact Ids", "type": "array" }, "company_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Company Id" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "amount": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Amount" }, "currency": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Currency" }, "stage_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Stage Id" }, "pipeline_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Pipeline Id" }, "close_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Close Date" }, "allow_duplicate": { "default": false, "title": "Allow Duplicate", "type": "boolean" } }, "required": [ "name", "owner_id", "contact_ids" ], "title": "g8_create_dealArguments", "type": "object" } ``` </details> ## g8_create_desk_card > Put one planned outreach touch (an email, LinkedIn or call step for one contact) on a rep's desk with its why, facts and draft. The rep edits and sends it; nothing sends by itself. owner_id must be the caller's own email unless the key is an org or machine key: a personal key or an OAuth connection is a person and gets 403 owner_not_self for anyone else. Posting the same external_key again updates the open card instead of adding a second one. Needs deals:write and a key allowed to see the contact. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /desk/cards ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `body` | Yes | #/$defs/ApiDeskCardCreateRequest | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "$defs": { "ApiDeskCardContext": { "additionalProperties": false, "properties": { "facts": { "anyOf": [ { "items": { "type": "string" }, "maxItems": 20, "type": "array" }, { "type": "null" } ], "default": null, "description": "Bullets under what we know. Empty ones are dropped.", "title": "Facts" }, "fields": { "anyOf": [ { "additionalProperties": { "type": "string" }, "type": "object" }, { "type": "null" } ], "default": null, "description": "The columns the card was built from, shown as chips.", "title": "Fields" }, "persona": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Persona" }, "why": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "The one-line reason, shown at the top of the card.", "title": "Why" } }, "title": "ApiDeskCardContext", "type": "object" }, "ApiDeskCardCreateRequest": { "additionalProperties": false, "properties": { "account_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "description": "The contact's company, when it is not the contact's own.", "title": "Account Id" }, "channel": { "description": "email, linkedin or call.", "maxLength": 32, "minLength": 1, "title": "Channel", "type": "string" }, "contact_id": { "description": "The contact the touch is for.", "exclusiveMinimum": 0.0, "maximum": 9223372036854775807, "title": "Contact Id", "type": "integer" }, "context": { "anyOf": [ { "$ref": "#/$defs/ApiDeskCardContext" }, { "type": "null" } ], "default": null }, "draft": { "anyOf": [ { "$ref": "#/$defs/ApiDeskCardDraft" }, { "type": "null" } ], "default": null }, "due_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "When the touch is due. It orders the queue and never hides the card.", "title": "Due At" }, "external_key": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Your own key for this touch. Posting it again updates the unsent card. Keys are kept apart by `origin.kind`; with no `origin.kind` the key must be unique across the org.", "title": "External Key" }, "list_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "List Id" }, "origin": { "anyOf": [ { "$ref": "#/$defs/ApiDeskCardOrigin" }, { "type": "null" } ], "default": null }, "owner_id": { "description": "Whose desk: a team member's email, id or user id. With a personal key or an OAuth connection this must be the caller's own email (the key is a person; anyone else is 403 owner_not_self). Only an org or machine key may name another rep.", "maxLength": 320, "minLength": 1, "title": "Owner Id", "type": "string" }, "priority": { "default": "normal", "description": "low, normal, high or urgent.", "maxLength": 16, "title": "Priority", "type": "string" }, "sender": { "anyOf": [ { "$ref": "#/$defs/ApiDeskCardSender" }, { "type": "null" } ], "default": null }, "touch": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "description": "The touch number in the list's cadence.", "title": "Touch" } }, "required": [ "channel", "contact_id", "owner_id" ], "title": "ApiDeskCardCreateRequest", "type": "object" }, "ApiDeskCardDraft": { "additionalProperties": false, "properties": { "body": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Body" }, "handoff_line": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Handoff Line" }, "kind": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Kind" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "A connect note is at most 300 characters.", "title": "Message" }, "questions": { "anyOf": [ { "items": { "type": "string" }, "maxItems": 20, "type": "array" }, { "type": "null" } ], "default": null, "title": "Questions" }, "subject": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Subject" }, "talking_points": { "anyOf": [ { "items": { "type": "string" }, "maxItems": 20, "type": "array" }, { "type": "null" } ], "default": null, "title": "Talking Points" } }, "title": "ApiDeskCardDraft", "type": "object" }, "ApiDeskCardOrigin": { "additionalProperties": false, "properties": { "kind": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Who planned it, e.g. \"list_plan\" or \"workflow\".", "title": "Kind" }, "mapping": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Mapping" } }, "title": "ApiDeskCardOrigin", "type": "object" }, "ApiDeskCardSender": { "additionalProperties": false, "properties": { "linkedin_seat_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Not used: LinkedIn sends from the rep's own connected account.", "title": "Linkedin Seat Id" }, "mailbox": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Email address the card sends from. Checked against the rep's send rights when the rep sends.", "title": "Mailbox" }, "mailbox_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Not used: email sends from `mailbox`, else the rep's own mailbox.", "title": "Mailbox Id" } }, "title": "ApiDeskCardSender", "type": "object" } }, "properties": { "body": { "$ref": "#/$defs/ApiDeskCardCreateRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "body" ], "title": "g8_create_desk_cardArguments", "type": "object" } ``` </details> ## g8_create_field > Create a SINGLE custom column on contacts or companies. DEPRECATED - prefer `g8_create_fields`, which creates one OR many columns in one call. STRONGLY RECOMMENDED: pass `enrichment_providers=[...]` (and `enrichment_email_verification=True` for ZeroBounce email verification). Without them the column has NO enrichment pipeline, so the UI "Enrich" button and `g8_enrich_contacts` (waterfall mode) both fail with 422 until a pipeline is provisioned separately. Default to passing `enrichment_providers` unless the caller wants a placeholder column. When `list_id` is set the column is list-scoped; when omitted it is global across the org. `list_id` is REQUIRED when supplying `enrichment_providers` (waterfall pipelines are list-scoped). `data_type` is "text" (default), "int" (whole number), "numeric" (decimal) or "boolean" (yes/no); unknown values 400. `entity` must be "contacts" or "companies"; anything else is rejected (400) and nothing is created. If a column with this name already exists in the same scope, it is returned with `already_existed: true` (no new column); asking for a different `data_type` than the existing column's is rejected with 409. `warnings` flags a same-named column in the other scope (e.g. an org-wide 'Tier' that also shows on this list). Allowed providers: graph8, hunter, prospeo, dropcontact, icypeas, leadmagic, emaillistverify, apollo, lusha, rocketreach. Response on success includes an `enrichment` block with the `column_id` slug to pass to `g8_enrich_contacts` (waterfall mode). Args: title: Column title shown in the UI (e.g. "Work Email") entity: Target entity - "contacts" or "companies" (default: contacts) list_id: List ID to scope the column. Required when supplying `enrichment_providers`. data_type: Column data type (default: "text") enrichment_providers: STRONGLY RECOMMENDED. Ordered list of provider names. When supplied, the new column ships with a runnable waterfall pipeline so the UI "Enrich" button works immediately. `list_id` becomes required. enrichment_email_verification: When True, the auto-provisioned pipeline bolts ZeroBounce email verification on after enrichment. Recommended for any email-finder column. Ignored unless a pipeline is provisioned. enrichment_name: Optional pipeline name. Defaults to `"<title> Enrichment"`. enrichment_target_field: The KIND of data this column holds - "phone" / "mobile" / "email" / "linkedin" or a canonical CONTACT_*/COMPANY_* column. Pass this whenever the column is for a phone/mobile/etc. so each provider runs the CORRECT action (phone -> leadmagic `mobile_finder`, not `email_finder`) - the same actions the UI's "Add column -> Waterfall" picks. Combine with `enrichment_providers` to filter to those providers. Without it, `enrichment_providers` uses the legacy email-finder mapping. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `title` | Yes | string | See the schema below for constraints and defaults. | | `entity` | No | string | See the schema below for constraints and defaults. | | `list_id` | No | integer / null | See the schema below for constraints and defaults. | | `data_type` | No | string | See the schema below for constraints and defaults. | | `enrichment_providers` | No | array / null | See the schema below for constraints and defaults. | | `enrichment_email_verification` | No | boolean | See the schema below for constraints and defaults. | | `enrichment_name` | No | string / null | See the schema below for constraints and defaults. | | `enrichment_target_field` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "title": { "title": "Title", "type": "string" }, "entity": { "default": "contacts", "title": "Entity", "type": "string" }, "list_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "List Id" }, "data_type": { "default": "text", "title": "Data Type", "type": "string" }, "enrichment_providers": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Enrichment Providers" }, "enrichment_email_verification": { "default": false, "title": "Enrichment Email Verification", "type": "boolean" }, "enrichment_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Enrichment Name" }, "enrichment_target_field": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Enrichment Target Field" } }, "required": [ "title" ], "title": "g8_create_fieldArguments", "type": "object" } ``` </details> ## g8_create_fields > Create one OR many custom columns on contacts or companies in a single call. The default field-creation tool (prefer over deprecated `g8_create_field`). `fields` is a list: one column or a batch, all created in one transaction with a single view rebuild. `entity` and `list_id` are SHARED across every column. Each entry is a dict: { "title": "Phone Number", # required "data_type": "text", # optional, default "text" "enrichment_target_field": "phone", # RECOMMENDED for phone/email/etc. "enrichment_providers": ["leadmagic"], # optional waterfall (filters template) "enrichment_email_verification": true, # optional, ZeroBounce (email cols) "enrichment_name": "Phone Enrichment" # optional pipeline name } Per-column enrichment works like `g8_create_field`. Ship a runnable pipeline (else the UI "Enrich" button 422s) by passing either `enrichment_target_field` (RECOMMENDED - "phone"/"mobile"/"email"/ "linkedin" or a CONTACT_*/COMPANY_* column, so each provider runs the CORRECT action: phone -> leadmagic `mobile_finder`, not `email_finder`) and/or `enrichment_providers`. `list_id` is REQUIRED whenever ANY column carries a pipeline. For email columns also set `enrichment_email_verification: true`. Allowed providers: graph8, hunter, prospeo, dropcontact, icypeas, leadmagic, emaillistverify, apollo, lusha, rocketreach. Validation is fail-fast: invalid data_type, an entity other than "contacts"/"companies", duplicate titles, a bad enrichment block, or a name that already exists with a DIFFERENT type (409) rejects the whole request and creates NOTHING. On success the response includes `total_created`, `total_already_existed` and a `results[]` array with each column's `id`, `name`, `already_existed` (true = an existing column was returned, not a new one), `warnings`, and (if requested) its `enrichment` block carrying the `column_id` for `g8_enrich_contacts`. Args: fields: List of column specs (1-100). Each is a dict as shown above. entity: Target entity for ALL columns - "contacts" or "companies". list_id: Shared list scope. REQUIRED if any field uses enrichment. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `fields` | Yes | array | See the schema below for constraints and defaults. | | `entity` | No | string | See the schema below for constraints and defaults. | | `list_id` | No | integer / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "fields": { "items": { "additionalProperties": true, "type": "object" }, "title": "Fields", "type": "array" }, "entity": { "default": "contacts", "title": "Entity", "type": "string" }, "list_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "List Id" } }, "required": [ "fields" ], "title": "g8_create_fieldsArguments", "type": "object" } ``` </details> ## g8_create_list > Create a contact or company list, optionally pre-populated. Source modes (pass at most one; default = empty list): - contact_ids: list[int] - add existing CRM contacts to the new list (free) - rows: list[dict] - import explicit records (free; no enrichment). Each row may contain any of: first_name, last_name, work_email, job_title, company_domain, seniority_level, linkedin_url, city, state, country, personal_emails, direct_phone, mobile_phone, job_department. - filters: list[dict] - search + save from open data (free, no credits). Same shape as g8_find_contacts; max_results caps how many are saved. When `filters` or `rows` is supplied, call with `dry_run=True` first to get a ConfirmationPreview. Re-call with `dry_run=False` to execute. For `contact_ids` and the empty-list mode the cost is zero, but `dry_run=True` is still honored if the caller wants a preview. Returns the new list's id on success. Backward compatible: with no source params this is identical to the original empty-list creator. Args: title: List name (e.g. "Enterprise Q2 Targets") type: List type - "contacts" or "companies" (default: contacts) contact_ids: Optional CRM contact ids to attach to the new list. rows: Optional list of record dicts to import into the list. filters: Optional g8_find_contacts-style filters to search and save. max_results: Cap on records saved when `filters` is used (default 100). dry_run: When True, return a ConfirmationPreview instead of executing. org: Target organization (graph8 org id from g8_list_orgs) the user confirmed. Required for `rows` / `filters` uploads when the user belongs to several orgs: ASK the user which org first. Approval: the dry_run=True preview returns a one-time approval_token (valid 15 min, single use, bound to these exact arguments). The real run (dry_run=False) after a preview must carry it: the user's Confirm click on the card sends it, or pass it yourself only after the user explicitly approves in chat. Without it the preview comes back (approval_required) and nothing runs. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `title` | Yes | string | See the schema below for constraints and defaults. | | `type` | No | string | See the schema below for constraints and defaults. | | `contact_ids` | No | array / null | See the schema below for constraints and defaults. | | `rows` | No | array / null | See the schema below for constraints and defaults. | | `filters` | No | array / null | See the schema below for constraints and defaults. | | `max_results` | No | integer | See the schema below for constraints and defaults. | | `dry_run` | No | boolean | See the schema below for constraints and defaults. | | `org` | No | string / null | See the schema below for constraints and defaults. | | `approval_token` | No | string / null | One-time approval token from this tool's dry_run=True preview. The graph8 Confirm card sends it when the user clicks Confirm. Pass it yourself ONLY after the user has explicitly approved this exact preview in chat. Never pass it on your own initiative. Without it, a real run that follows a preview returns the preview again instead of executing. | <details> <summary>Complete input schema</summary> ```json { "properties": { "title": { "title": "Title", "type": "string" }, "type": { "default": "contacts", "title": "Type", "type": "string" }, "contact_ids": { "anyOf": [ { "items": { "type": "integer" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Contact Ids" }, "rows": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Rows" }, "filters": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Filters" }, "max_results": { "default": 100, "title": "Max Results", "type": "integer" }, "dry_run": { "default": false, "title": "Dry Run", "type": "boolean" }, "org": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Org" }, "approval_token": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "One-time approval token from this tool's dry_run=True preview. The graph8 Confirm card sends it when the user clicks Confirm. Pass it yourself ONLY after the user has explicitly approved this exact preview in chat. Never pass it on your own initiative. Without it, a real run that follows a preview returns the preview again instead of executing.", "title": "Approval Token" } }, "required": [ "title" ], "title": "g8_create_listArguments", "type": "object" } ``` </details> ## g8_create_note > Add a note to a CRM contact, COMPANY or DEAL. Notes are polymorphic in graph8: contacts, companies and deals all file notes into the same store, so this one tool covers all three. Pass exactly ONE of contact_id / company_id / deal_id - whichever record the note belongs to. Use `deal_id` for deal-room context (budget, next steps, decision criteria); there is no separate "deal note" tool to look for. Backed by `POST /api/v1/{contacts\|companies\|deals}/{id}/notes`. Args: content: Note body (plain text or markdown). An "@name" inside the body is parsed as a mention and NOTIFIES that teammate, so avoid stray "@" text you did not mean as a ping. contact_id: Integer contact ID from g8_find_contacts. company_id: Integer company ID from g8_find_companies. deal_id: Deal UUID from g8_get_deals / g8_create_deal. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `content` | Yes | string | See the schema below for constraints and defaults. | | `contact_id` | No | integer / null | See the schema below for constraints and defaults. | | `company_id` | No | integer / null | See the schema below for constraints and defaults. | | `deal_id` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "content": { "title": "Content", "type": "string" }, "contact_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Contact Id" }, "company_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Company Id" }, "deal_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Deal Id" } }, "required": [ "content" ], "title": "g8_create_noteArguments", "type": "object" } ``` </details> ## g8_create_quote > Create a new quote in `draft` status. Always creates, never sends. At least one of signer_contact_id or signer_email is required. line_items must be non-empty. Each line-item dict (see g8_list_quotable_products): stripe_product_id + stripe_price_id, OR manual_product_id, OR neither (custom one-off); plus product_name (required), description, quantity, unit_amount (cents), discount_pct, billing_frequency, start_month, billing_start_date, sort_order. Recurring billing starts at contract start by default (start_month=1). Use start_month=3 for after 2 months, up to 120, or billing_start_date (YYYY-MM-DD) for an exact date, keeping start_month=1. Dates must be within the contract. The two start options cannot be combined. Args: title: Quote title (1-500 chars). line_items: Non-empty list of line-item dicts. signer_contact_id: Contact id of the e-signature recipient. signer_email: Direct signer email (used if signer_contact_id is omitted). signer_name: Display name to render on the quote. billing_contact_id: Optional separate billing contact id. billing_legal_name: Customer legal entity name shown on the quote. billing_primary_email: Billing contact email. billing_email: Secondary billing email. billing_address: Billing address (max 2000 chars). owner_id: Quote owner/AE (user id or email). Defaults to the API caller; set it to attribute the quote to a real rep. mashup_company_id: Company the quote is for (defaults to the deal's company). deal_id: REQUIRED. UUID of an existing deal to link the quote to; every new quote must be linked to a deal (422 otherwise). template_id: Optional quote-settings template id for branding. currency: 3-letter ISO currency, default USD. payment_terms: One of due_on_receipt / net_15 / net_30 / net_45 / net_60 / net_90. valid_until: ISO date for quote expiration. terms_content: Inline terms markdown (max 50k chars). privacy_content: Inline privacy markdown (max 50k chars). notes: Private notes (max 10k chars). public_notes: Public notes shown to signer (max 10k chars). tax_amount: Total tax in cents (default 0). contract_months: Month-equivalent for reporting/MRR, accepts fractions. contract_duration_value / contract_duration_unit: Exact term, e.g. 6 + weeks or 42 + days. contract_start_date / contract_end_date: ISO dates for contract window. accepted_payment_methods: Any subset of [ach, credit_card, wire]. marketing_rights: Any subset of [logo, quote, case_study]. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `title` | Yes | string | See the schema below for constraints and defaults. | | `line_items` | Yes | array | See the schema below for constraints and defaults. | | `signer_contact_id` | No | integer / null | See the schema below for constraints and defaults. | | `signer_email` | No | string / null | See the schema below for constraints and defaults. | | `signer_name` | No | string / null | See the schema below for constraints and defaults. | | `billing_contact_id` | No | integer / null | See the schema below for constraints and defaults. | | `billing_legal_name` | No | string / null | See the schema below for constraints and defaults. | | `billing_primary_email` | No | string / null | See the schema below for constraints and defaults. | | `billing_email` | No | string / null | See the schema below for constraints and defaults. | | `billing_address` | No | string / null | See the schema below for constraints and defaults. | | `mashup_company_id` | No | integer / null | See the schema below for constraints and defaults. | | `deal_id` | No | string / null | See the schema below for constraints and defaults. | | `owner_id` | No | string / null | See the schema below for constraints and defaults. | | `template_id` | No | string / null | See the schema below for constraints and defaults. | | `currency` | No | string | See the schema below for constraints and defaults. | | `payment_terms` | No | string | See the schema below for constraints and defaults. | | `valid_until` | No | string / null | See the schema below for constraints and defaults. | | `terms_content` | No | string / null | See the schema below for constraints and defaults. | | `privacy_content` | No | string / null | See the schema below for constraints and defaults. | | `notes` | No | string / null | See the schema below for constraints and defaults. | | `public_notes` | No | string / null | See the schema below for constraints and defaults. | | `tax_amount` | No | integer | See the schema below for constraints and defaults. | | `contract_months` | No | number / null | See the schema below for constraints and defaults. | | `contract_duration_value` | No | number / null | See the schema below for constraints and defaults. | | `contract_duration_unit` | No | string / null | See the schema below for constraints and defaults. | | `contract_start_date` | No | string / null | See the schema below for constraints and defaults. | | `contract_end_date` | No | string / null | See the schema below for constraints and defaults. | | `accepted_payment_methods` | No | array / null | See the schema below for constraints and defaults. | | `marketing_rights` | No | array / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "title": { "title": "Title", "type": "string" }, "line_items": { "items": { "additionalProperties": true, "type": "object" }, "title": "Line Items", "type": "array" }, "signer_contact_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Signer Contact Id" }, "signer_email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Signer Email" }, "signer_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Signer Name" }, "billing_contact_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Billing Contact Id" }, "billing_legal_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Billing Legal Name" }, "billing_primary_email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Billing Primary Email" }, "billing_email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Billing Email" }, "billing_address": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Billing Address" }, "mashup_company_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Mashup Company Id" }, "deal_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Deal Id" }, "owner_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Owner Id" }, "template_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Template Id" }, "currency": { "default": "USD", "title": "Currency", "type": "string" }, "payment_terms": { "default": "net_30", "title": "Payment Terms", "type": "string" }, "valid_until": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Valid Until" }, "terms_content": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Terms Content" }, "privacy_content": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Privacy Content" }, "notes": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Notes" }, "public_notes": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Public Notes" }, "tax_amount": { "default": 0, "title": "Tax Amount", "type": "integer" }, "contract_months": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Contract Months" }, "contract_duration_value": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Contract Duration Value" }, "contract_duration_unit": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Contract Duration Unit" }, "contract_start_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Contract Start Date" }, "contract_end_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Contract End Date" }, "accepted_payment_methods": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Accepted Payment Methods" }, "marketing_rights": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Marketing Rights" } }, "required": [ "title", "line_items" ], "title": "g8_create_quoteArguments", "type": "object" } ``` </details> ## g8_create_sequence > Create a new outbound sequence (drafted state). Confirm with the user before calling - this creates a real sequence. A "nurture" is a sequence with sequence_kind="nurture": same step types and structure, but pinned to one mailbox (pinned_mailbox_id) with a higher daily email cap. There is no separate nurture tool - pass sequence_kind="nurture" plus pinned_mailbox_id here. Args: name: Sequence name user_email: Owner email. Leave it out to make the person who created this API key the owner. Never invent an address. description: Optional description finish_on_reply: Stop contacting on reply (default true) send_in_same_thread: Send follow-ups in same email thread wait_for_new_contacts: Keep sequence open for late-arriving contacts associated_list_id: Contact list ID to attach to the sequence (use g8_get_lists to find) steps: Sequence steps. Each item is a dict with: - step_order (int, 1-based) - step_type (str): one of EMAIL, PHONE, SMS, WHATSAPP, HEYREACH, MANUAL_DIALER. Note: LinkedIn outreach uses HEYREACH, not LINKEDIN. Case-insensitive. - input_type (str, default ON_DEMAND): one of ON_DEMAND, MANUAL_TEMPLATE, AI_GENERATED_TEMPLATE. Case-insensitive (e.g. 'template' -> MANUAL_TEMPLATE). - time_interval (int, seconds delay after previous step) - step_data (dict): EMAIL needs {subject, body}; SMS/WHATSAPP need {message_body}; HEYREACH needs {linkedin_action_type, ...}; ON_DEMAND can use {instructions}. channels: Channels to attach. Each item is a dict with: - channel_id (int): mailbox/phone resource ID - channel_value (str): email address, phone number, etc. - channel_type (str): one of SMTP, GMAIL, INBOXKIT, PHONE, LINKEDIN, SMS, WHATSAPP. campaign_id: AI Studio campaign ID to link (optional) sequence_kind: Sequence kind, 'cold_outbound' (default) or 'nurture'. Use 'nurture' for high-volume, single-mailbox nurture sends. Immutable after creation. pinned_mailbox_id: Mailbox ID to pin for nurture sequences. REQUIRED when sequence_kind='nurture'; all email channels must use this mailbox. Ignored for cold_outbound. (Nurture is gated by the ENABLE_NURTURE feature flag.) Example minimal email sequence: steps=[ {"step_order": 1, "step_type": "EMAIL", "input_type": "MANUAL_TEMPLATE", "time_interval": 0, "step_data": {"subject": "Hi {{first_name}}", "body": "Quick intro..."}}, {"step_order": 2, "step_type": "EMAIL", "input_type": "MANUAL_TEMPLATE", "time_interval": 259200, "step_data": {"subject": "Re: Hi {{first_name}}", "body": "Bumping this..."}}, ] Example nurture sequence (same step format, just pinned + kind): sequence_kind="nurture", pinned_mailbox_id=12345, steps=[...same shape as above...] ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `name` | Yes | string | See the schema below for constraints and defaults. | | `user_email` | No | string / null | See the schema below for constraints and defaults. | | `description` | No | string / null | See the schema below for constraints and defaults. | | `finish_on_reply` | No | boolean | See the schema below for constraints and defaults. | | `send_in_same_thread` | No | boolean | See the schema below for constraints and defaults. | | `wait_for_new_contacts` | No | boolean | See the schema below for constraints and defaults. | | `associated_list_id` | No | integer / null | See the schema below for constraints and defaults. | | `steps` | No | array / null | See the schema below for constraints and defaults. | | `channels` | No | array / null | See the schema below for constraints and defaults. | | `campaign_id` | No | string / null | See the schema below for constraints and defaults. | | `sequence_kind` | No | string | See the schema below for constraints and defaults. | | `pinned_mailbox_id` | No | integer / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "name": { "title": "Name", "type": "string" }, "user_email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "User Email" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "finish_on_reply": { "default": true, "title": "Finish On Reply", "type": "boolean" }, "send_in_same_thread": { "default": false, "title": "Send In Same Thread", "type": "boolean" }, "wait_for_new_contacts": { "default": false, "title": "Wait For New Contacts", "type": "boolean" }, "associated_list_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Associated List Id" }, "steps": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Steps" }, "channels": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Channels" }, "campaign_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Campaign Id" }, "sequence_kind": { "default": "cold_outbound", "title": "Sequence Kind", "type": "string" }, "pinned_mailbox_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Pinned Mailbox Id" } }, "required": [ "name" ], "title": "g8_create_sequenceArguments", "type": "object" } ``` </details> ## g8_create_task > Create a task linked to a contact or multiple typed business records. Args: tags: Task labels (up to 20, each 1-50 characters); [] clears labels on update. contact_id: Optional CRM contact. Otherwise supply records or entity_type/entity_id. records: Related records with entity_type and entity_id (contact, company, deal, lead, application, team_member). entity_type: Primary record type when records is omitted. entity_id: Primary record ID when records is omitted. source_url: Exact originating source URL, when known. task_type: Optional activity type, such as call or meeting. title: Task title. description: Optional long-form description. due_date: ISO 8601 due date. assignee_id: User ID to assign the task to. priority: 0=none, 1=high, 2=medium, 3=low; historical 4=low is preserved. parent_task_id: Optional parent task UUID. When set, this task is created as a subtask of the parent. Subtasks cannot themselves have subtasks (one level deep). subtask_sort_order: Optional sort position within the parent's subtasks. Omit to append to the end. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `title` | Yes | string | See the schema below for constraints and defaults. | | `contact_id` | No | integer / null | See the schema below for constraints and defaults. | | `description` | No | string / null | See the schema below for constraints and defaults. | | `tags` | No | array / null | See the schema below for constraints and defaults. | | `due_date` | No | string / null | See the schema below for constraints and defaults. | | `assignee_id` | No | string / null | See the schema below for constraints and defaults. | | `priority` | No | integer / null | See the schema below for constraints and defaults. | | `parent_task_id` | No | string / null | See the schema below for constraints and defaults. | | `subtask_sort_order` | No | integer / null | See the schema below for constraints and defaults. | | `records` | No | array / null | See the schema below for constraints and defaults. | | `entity_type` | No | string / null | See the schema below for constraints and defaults. | | `entity_id` | No | string / null | See the schema below for constraints and defaults. | | `source_url` | No | string / null | See the schema below for constraints and defaults. | | `task_type` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "title": { "title": "Title", "type": "string" }, "contact_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Contact Id" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "tags": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Tags" }, "due_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Due Date" }, "assignee_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Assignee Id" }, "priority": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Priority" }, "parent_task_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Parent Task Id" }, "subtask_sort_order": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Subtask Sort Order" }, "records": { "anyOf": [ { "items": { "additionalProperties": { "type": "string" }, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Records" }, "entity_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Entity Type" }, "entity_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Entity Id" }, "source_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Source Url" }, "task_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Task Type" } }, "required": [ "title" ], "title": "g8_create_taskArguments", "type": "object" } ``` </details> ## g8_crm_assert_companies_batch > Assert Companies Batch. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: PUT /companies/assert/batch ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `body` | Yes | #/$defs/ApiBatchCompanyAssertRequest | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "$defs": { "ApiBatchCompanyAssertRequest": { "additionalProperties": false, "properties": { "companies": { "description": "Up to 100 companies with domain or linkedin_url", "items": { "additionalProperties": true, "type": "object" }, "maxItems": 100, "title": "Companies", "type": "array" }, "create_missing_fields": { "default": false, "description": "When true, any key in `custom_fields` that matches no existing column is created as a NEW list-scoped custom column (max 100 per request) and then written. Default false preserves the previous behaviour of not creating columns implicitly.", "title": "Create Missing Fields", "type": "boolean" }, "list_id": { "description": "Target list ID", "title": "List Id", "type": "integer" } }, "required": [ "companies", "list_id" ], "title": "ApiBatchCompanyAssertRequest", "type": "object" } }, "properties": { "body": { "$ref": "#/$defs/ApiBatchCompanyAssertRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "body" ], "title": "g8_crm_assert_companies_batchArguments", "type": "object" } ``` </details> ## g8_crm_assert_company > Assert Company. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: PUT /companies/assert ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `body` | Yes | #/$defs/ApiCompanyAssertRequest | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "$defs": { "ApiCompanyAssertRequest": { "additionalProperties": false, "properties": { "annual_revenue": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Annual Revenue" }, "city": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "City" }, "country": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Country" }, "create_missing_fields": { "default": false, "description": "When true, any key in `custom_fields` that matches no existing column is created as a NEW list-scoped custom column (max 100 per request) and then written. Default false preserves the previous behaviour of not creating columns implicitly.", "title": "Create Missing Fields", "type": "boolean" }, "custom_fields": { "anyOf": [ { "additionalProperties": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "type": "object" }, { "type": "null" } ], "default": null, "description": "Custom column values to write on this record, keyed by the column's display title (e.g. 'Followup 1'), its slug (e.g. 'udo_followup_1_ab12cd34'), or a case/space variant of the title. Keys that match no existing column are reported back in `ignored_fields` unless `create_missing_fields` is true.", "title": "Custom Fields" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "domain": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Company domain (primary match key)", "title": "Domain" }, "employee_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Employee Count" }, "industry": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Industry" }, "linkedin_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "LinkedIn URL (fallback match key)", "title": "Linkedin Url" }, "list_id": { "description": "Target list ID", "title": "List Id", "type": "integer" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "website": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Website" } }, "required": [ "list_id" ], "title": "ApiCompanyAssertRequest", "type": "object" } }, "properties": { "body": { "$ref": "#/$defs/ApiCompanyAssertRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "body" ], "title": "g8_crm_assert_companyArguments", "type": "object" } ``` </details> ## g8_crm_assert_contact > Assert Contact. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: PUT /contacts/assert ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `body` | Yes | #/$defs/ApiContactAssertRequest | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "$defs": { "ApiContactAssertRequest": { "additionalProperties": false, "properties": { "city": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "City" }, "company_domain": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Company Domain" }, "country": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Country" }, "create_missing_fields": { "default": false, "description": "When true, any key in `custom_fields` that matches no existing column is created as a NEW list-scoped custom column (max 100 per request) and then written. Default false preserves the previous behaviour of not creating columns implicitly.", "title": "Create Missing Fields", "type": "boolean" }, "custom_fields": { "anyOf": [ { "additionalProperties": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "type": "object" }, { "type": "null" } ], "default": null, "description": "Custom column values to write on this record, keyed by the column's display title (e.g. 'Followup 1'), its slug (e.g. 'udo_followup_1_ab12cd34'), or a case/space variant of the title. Keys that match no existing column are reported back in `ignored_fields` unless `create_missing_fields` is true.", "title": "Custom Fields" }, "direct_phone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Direct Phone" }, "first_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "First Name" }, "job_department": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Job Department" }, "job_title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Job Title" }, "last_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Name" }, "linkedin_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "LinkedIn URL (fallback match key)", "title": "Linkedin Url" }, "list_id": { "description": "Target list ID", "title": "List Id", "type": "integer" }, "mobile_phone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Mobile Phone" }, "personal_emails": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Personal Emails" }, "seniority_level": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Seniority Level" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "work_email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Work email (primary match key)", "title": "Work Email" } }, "required": [ "list_id" ], "title": "ApiContactAssertRequest", "type": "object" } }, "properties": { "body": { "$ref": "#/$defs/ApiContactAssertRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "body" ], "title": "g8_crm_assert_contactArguments", "type": "object" } ``` </details> ## g8_crm_assert_contacts_batch > Assert Contacts Batch. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: PUT /contacts/assert/batch ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `body` | Yes | #/$defs/ApiBatchContactAssertRequest | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "$defs": { "ApiBatchContactAssertRequest": { "additionalProperties": false, "properties": { "contacts": { "description": "Up to 100 contacts with work_email or linkedin_url", "items": { "additionalProperties": true, "type": "object" }, "maxItems": 100, "title": "Contacts", "type": "array" }, "create_missing_fields": { "default": false, "description": "When true, any key in `custom_fields` that matches no existing column is created as a NEW list-scoped custom column (max 100 per request) and then written. Default false preserves the previous behaviour of not creating columns implicitly.", "title": "Create Missing Fields", "type": "boolean" }, "list_id": { "description": "Target list ID", "title": "List Id", "type": "integer" } }, "required": [ "contacts", "list_id" ], "title": "ApiBatchContactAssertRequest", "type": "object" } }, "properties": { "body": { "$ref": "#/$defs/ApiBatchContactAssertRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "body" ], "title": "g8_crm_assert_contacts_batchArguments", "type": "object" } ``` </details> ## g8_crm_assert_deal > Assert Deal. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: PUT /deals/assert ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `body` | Yes | #/$defs/ApiDealAssertRequest | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "$defs": { "ApiDealAssertRequest": { "additionalProperties": false, "properties": { "amount": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Amount" }, "close_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Close Date" }, "company_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Company ID (match key, combined with name)", "title": "Company Id" }, "currency": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Currency" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "name": { "description": "Deal name (match key)", "title": "Name", "type": "string" }, "pipeline_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Pipeline Id" }, "stage_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Stage Id" } }, "required": [ "name" ], "title": "ApiDealAssertRequest", "type": "object" } }, "properties": { "body": { "$ref": "#/$defs/ApiDealAssertRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "body" ], "title": "g8_crm_assert_dealArguments", "type": "object" } ``` </details> ## g8_crm_assert_deal_contact > Assert Deal Contact. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: PUT /contacts/{contact_id}/deals/assert ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `contact_id` | Yes | integer | Contact ID | | `body` | Yes | #/$defs/ApiDealContactAssertRequest | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "$defs": { "ApiDealContactAssertRequest": { "additionalProperties": false, "properties": { "deal_id": { "description": "Deal ID to associate", "title": "Deal Id", "type": "string" } }, "required": [ "deal_id" ], "title": "ApiDealContactAssertRequest", "type": "object" } }, "properties": { "contact_id": { "description": "Contact ID", "title": "Contact Id", "type": "integer" }, "body": { "$ref": "#/$defs/ApiDealContactAssertRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "contact_id", "body" ], "title": "g8_crm_assert_deal_contactArguments", "type": "object" } ``` </details> ## g8_crm_create_company_column > Create Company Column. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /companies/columns/create ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `body` | Yes | #/$defs/ApiCreateCompanyColumnRequest | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "$defs": { "ApiCreateCompanyColumnRequest": { "additionalProperties": false, "properties": { "created_by": { "description": "Email of the user creating the column (for audit).", "format": "email", "title": "Created By", "type": "string" }, "data_type": { "default": "text", "description": "Column data type: 'text', 'int' (whole number), 'numeric' (decimal) or 'boolean' (yes/no). Other values 422.", "enum": [ "text", "int", "numeric", "boolean" ], "title": "Data Type", "type": "string" }, "enrichment": { "anyOf": [ { "$ref": "#/$defs/ApiCreateFieldEnrichmentInput" }, { "type": "null" } ], "default": null, "description": "STRONGLY RECOMMENDED. Optional waterfall enrichment block. When supplied, a companion `enrichment_configs` row is created alongside the column so the UI 'Enrich' button works immediately. Without it the column exists but isn't enrichable until a pipeline is configured via POST /enrichment/waterfall/configs." }, "list_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "description": "If set, the column is list-scoped (only visible on that list). Required when `enrichment` is supplied - waterfall pipelines are list-scoped.", "title": "List Id" }, "title": { "description": "Column title shown in the UI (e.g. 'Tier')", "title": "Title", "type": "string" } }, "required": [ "created_by", "title" ], "title": "ApiCreateCompanyColumnRequest", "type": "object" }, "ApiCreateFieldEnrichmentInput": { "additionalProperties": false, "properties": { "email_verification": { "anyOf": [ { "$ref": "#/$defs/ApiEmailVerificationInput" }, { "type": "null" } ], "default": null, "description": "Optional post-enrichment email verification config." }, "field_to_enrich": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Target field identifier. Defaults to the new column's slug.", "title": "Field To Enrich" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Pipeline name. Defaults to `<column title> Enrichment` if omitted.", "title": "Name" }, "providers": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "description": "Ordered list of provider names. Without `target_field`, server maps each to its default email-finder action (legacy). With `target_field`, filters the column template to these providers (correct per-field action preserved). Mutually exclusive with `steps`.", "title": "Providers" }, "skip_existing_values": { "default": true, "description": "If true, skip rows where the target field is already populated.", "title": "Skip Existing Values", "type": "boolean" }, "steps": { "anyOf": [ { "items": { "$ref": "#/$defs/ApiWaterfallStepInput" }, "type": "array" }, { "type": "null" } ], "default": null, "description": "Full caller-supplied step list. Persisted verbatim. Mutually exclusive with `providers`.", "title": "Steps" }, "target_field": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Kind of data this column holds - CONTACT_DIRECT_PHONE, CONTACT_MOBILE_PHONE, CONTACT_WORK_EMAIL, CONTACT_LINKEDIN_URL, COMPANY_PHONE, ... or a friendly synonym ('phone', 'mobile', 'email', 'linkedin'). When set, the pipeline is built from the server's per-column TEMPLATE so each provider runs its CORRECT action for that data kind (e.g. leadmagic `mobile_finder` for a phone, NOT `email_finder`) - the same actions the web UI's 'Add column -> Waterfall' picks. Combine with `providers[]` to keep only those providers from the template. Mutually exclusive with `steps`. When omitted, the legacy `providers[]` email-finder shortcut is unchanged.", "title": "Target Field" }, "type": { "default": "waterfall", "description": "Pipeline kind. Only 'waterfall' is supported today.", "title": "Type", "type": "string" } }, "title": "ApiCreateFieldEnrichmentInput", "type": "object" }, "ApiEmailVerificationInput": { "additionalProperties": false, "properties": { "accept_catchall": { "default": false, "description": "Treat catch-all responses as valid (domains that accept any address).", "title": "Accept Catchall", "type": "boolean" }, "enabled": { "default": true, "description": "Enable post-enrichment email verification", "title": "Enabled", "type": "boolean" }, "provider": { "default": "zerobounce", "description": "Verification provider. Charges credits when use_system_credentials=True.", "enum": [ "zerobounce", "hunter", "icypeas", "leadmagic" ], "title": "Provider", "type": "string" }, "use_system_credentials": { "default": true, "description": "Use graph8-managed API keys (charges credits). False = use org's BYOK keys.", "title": "Use System Credentials", "type": "boolean" }, "valid_statuses": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "description": "Verification statuses to accept as valid. Typical: ['valid'] or ['valid','catch-all'].", "title": "Valid Statuses" } }, "title": "ApiEmailVerificationInput", "type": "object" }, "ApiWaterfallStepInput": { "additionalProperties": false, "properties": { "action": { "description": "Provider action: lookup_person, find_email, enrich_person, ...", "title": "Action", "type": "string" }, "action_display_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Human-readable label shown in UI. Auto-titled from `action` if omitted.", "title": "Action Display Name" }, "confidence_threshold": { "default": 0.0, "maximum": 1.0, "minimum": 0.0, "title": "Confidence Threshold", "type": "number" }, "config": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "description": "Provider-specific options (e.g. apollo `reveal_phone_numbers=true`).", "title": "Config" }, "enabled": { "default": true, "title": "Enabled", "type": "boolean" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Step identifier. Auto-generated (`step_<provider>_<uuid8>`) if omitted.", "title": "Id" }, "input_mapping": { "anyOf": [ { "additionalProperties": { "type": "string" }, "type": "object" }, { "type": "null" } ], "default": null, "description": "Maps provider input names to graph8 column identifiers (CONTACT_*).", "title": "Input Mapping" }, "is_system_provider": { "default": false, "description": "True = use graph8-managed credentials + charge credits. False = use org BYOK.", "title": "Is System Provider", "type": "boolean" }, "max_retries": { "default": 0, "maximum": 3.0, "minimum": 0.0, "title": "Max Retries", "type": "integer" }, "order": { "default": 0, "description": "0-indexed execution order.", "minimum": 0.0, "title": "Order", "type": "integer" }, "output_field": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Dotted path the worker writes into (e.g. 'contact.work_email').", "title": "Output Field" }, "output_mapping": { "anyOf": [ { "additionalProperties": { "type": "string" }, "type": "object" }, { "type": "null" } ], "default": null, "description": "Optional output field remap (deprecated - prefer `output_field`).", "title": "Output Mapping" }, "provider": { "description": "Provider name: graph8, hunter, prospeo, dropcontact, apollo, ...", "title": "Provider", "type": "string" }, "stop_on_success": { "default": true, "title": "Stop On Success", "type": "boolean" } }, "required": [ "action", "provider" ], "title": "ApiWaterfallStepInput", "type": "object" } }, "properties": { "body": { "$ref": "#/$defs/ApiCreateCompanyColumnRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "body" ], "title": "g8_crm_create_company_columnArguments", "type": "object" } ``` </details> ## g8_crm_create_contact_column > Create Contact Column. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /contacts/columns/create ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `body` | Yes | #/$defs/ApiCreateContactColumnRequest | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "$defs": { "ApiCreateContactColumnRequest": { "additionalProperties": false, "properties": { "created_by": { "description": "Email of the user creating the column (for audit).", "format": "email", "title": "Created By", "type": "string" }, "data_type": { "default": "text", "description": "Column data type: 'text', 'int' (whole number), 'numeric' (decimal) or 'boolean' (yes/no). Other values 422.", "enum": [ "text", "int", "numeric", "boolean" ], "title": "Data Type", "type": "string" }, "enrichment": { "anyOf": [ { "$ref": "#/$defs/ApiCreateFieldEnrichmentInput" }, { "type": "null" } ], "default": null, "description": "STRONGLY RECOMMENDED. Optional waterfall enrichment block. When supplied, a companion `enrichment_configs` row is created alongside the column so the UI 'Enrich' button works immediately. Without it the column exists but isn't enrichable until a pipeline is configured via POST /enrichment/waterfall/configs." }, "list_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "description": "If set, the column is list-scoped (only visible on that list). Required when `enrichment` is supplied - waterfall pipelines are list-scoped.", "title": "List Id" }, "title": { "description": "Column title shown in the UI (e.g. 'Lead Score')", "title": "Title", "type": "string" } }, "required": [ "created_by", "title" ], "title": "ApiCreateContactColumnRequest", "type": "object" }, "ApiCreateFieldEnrichmentInput": { "additionalProperties": false, "properties": { "email_verification": { "anyOf": [ { "$ref": "#/$defs/ApiEmailVerificationInput" }, { "type": "null" } ], "default": null, "description": "Optional post-enrichment email verification config." }, "field_to_enrich": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Target field identifier. Defaults to the new column's slug.", "title": "Field To Enrich" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Pipeline name. Defaults to `<column title> Enrichment` if omitted.", "title": "Name" }, "providers": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "description": "Ordered list of provider names. Without `target_field`, server maps each to its default email-finder action (legacy). With `target_field`, filters the column template to these providers (correct per-field action preserved). Mutually exclusive with `steps`.", "title": "Providers" }, "skip_existing_values": { "default": true, "description": "If true, skip rows where the target field is already populated.", "title": "Skip Existing Values", "type": "boolean" }, "steps": { "anyOf": [ { "items": { "$ref": "#/$defs/ApiWaterfallStepInput" }, "type": "array" }, { "type": "null" } ], "default": null, "description": "Full caller-supplied step list. Persisted verbatim. Mutually exclusive with `providers`.", "title": "Steps" }, "target_field": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Kind of data this column holds - CONTACT_DIRECT_PHONE, CONTACT_MOBILE_PHONE, CONTACT_WORK_EMAIL, CONTACT_LINKEDIN_URL, COMPANY_PHONE, ... or a friendly synonym ('phone', 'mobile', 'email', 'linkedin'). When set, the pipeline is built from the server's per-column TEMPLATE so each provider runs its CORRECT action for that data kind (e.g. leadmagic `mobile_finder` for a phone, NOT `email_finder`) - the same actions the web UI's 'Add column -> Waterfall' picks. Combine with `providers[]` to keep only those providers from the template. Mutually exclusive with `steps`. When omitted, the legacy `providers[]` email-finder shortcut is unchanged.", "title": "Target Field" }, "type": { "default": "waterfall", "description": "Pipeline kind. Only 'waterfall' is supported today.", "title": "Type", "type": "string" } }, "title": "ApiCreateFieldEnrichmentInput", "type": "object" }, "ApiEmailVerificationInput": { "additionalProperties": false, "properties": { "accept_catchall": { "default": false, "description": "Treat catch-all responses as valid (domains that accept any address).", "title": "Accept Catchall", "type": "boolean" }, "enabled": { "default": true, "description": "Enable post-enrichment email verification", "title": "Enabled", "type": "boolean" }, "provider": { "default": "zerobounce", "description": "Verification provider. Charges credits when use_system_credentials=True.", "enum": [ "zerobounce", "hunter", "icypeas", "leadmagic" ], "title": "Provider", "type": "string" }, "use_system_credentials": { "default": true, "description": "Use graph8-managed API keys (charges credits). False = use org's BYOK keys.", "title": "Use System Credentials", "type": "boolean" }, "valid_statuses": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "description": "Verification statuses to accept as valid. Typical: ['valid'] or ['valid','catch-all'].", "title": "Valid Statuses" } }, "title": "ApiEmailVerificationInput", "type": "object" }, "ApiWaterfallStepInput": { "additionalProperties": false, "properties": { "action": { "description": "Provider action: lookup_person, find_email, enrich_person, ...", "title": "Action", "type": "string" }, "action_display_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Human-readable label shown in UI. Auto-titled from `action` if omitted.", "title": "Action Display Name" }, "confidence_threshold": { "default": 0.0, "maximum": 1.0, "minimum": 0.0, "title": "Confidence Threshold", "type": "number" }, "config": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "description": "Provider-specific options (e.g. apollo `reveal_phone_numbers=true`).", "title": "Config" }, "enabled": { "default": true, "title": "Enabled", "type": "boolean" }, "id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Step identifier. Auto-generated (`step_<provider>_<uuid8>`) if omitted.", "title": "Id" }, "input_mapping": { "anyOf": [ { "additionalProperties": { "type": "string" }, "type": "object" }, { "type": "null" } ], "default": null, "description": "Maps provider input names to graph8 column identifiers (CONTACT_*).", "title": "Input Mapping" }, "is_system_provider": { "default": false, "description": "True = use graph8-managed credentials + charge credits. False = use org BYOK.", "title": "Is System Provider", "type": "boolean" }, "max_retries": { "default": 0, "maximum": 3.0, "minimum": 0.0, "title": "Max Retries", "type": "integer" }, "order": { "default": 0, "description": "0-indexed execution order.", "minimum": 0.0, "title": "Order", "type": "integer" }, "output_field": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Dotted path the worker writes into (e.g. 'contact.work_email').", "title": "Output Field" }, "output_mapping": { "anyOf": [ { "additionalProperties": { "type": "string" }, "type": "object" }, { "type": "null" } ], "default": null, "description": "Optional output field remap (deprecated - prefer `output_field`).", "title": "Output Mapping" }, "provider": { "description": "Provider name: graph8, hunter, prospeo, dropcontact, apollo, ...", "title": "Provider", "type": "string" }, "stop_on_success": { "default": true, "title": "Stop On Success", "type": "boolean" } }, "required": [ "action", "provider" ], "title": "ApiWaterfallStepInput", "type": "object" } }, "properties": { "body": { "$ref": "#/$defs/ApiCreateContactColumnRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "body" ], "title": "g8_crm_create_contact_columnArguments", "type": "object" } ``` </details> ## g8_crm_delete_company > Delete Company. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: DELETE /companies/{company_id} ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `company_id` | Yes | integer | Company ID | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "company_id": { "description": "Company ID", "title": "Company Id", "type": "integer" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "company_id" ], "title": "g8_crm_delete_companyArguments", "type": "object" } ``` </details> ## g8_crm_delete_company_column > Delete Company Column. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: DELETE /companies/columns/{column_id} ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `column_id` | Yes | integer | ID of the column to delete | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "column_id": { "description": "ID of the column to delete", "title": "Column Id", "type": "integer" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "column_id" ], "title": "g8_crm_delete_company_columnArguments", "type": "object" } ``` </details> ## g8_crm_delete_contact > Delete Contact. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise nothing is changed and the preview's `target` names the record that would be affected (masked if locked). API: DELETE /contacts/{contact_id} ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `contact_id` | Yes | integer | Contact ID | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "contact_id": { "description": "Contact ID", "title": "Contact Id", "type": "integer" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "contact_id" ], "title": "g8_crm_delete_contactArguments", "type": "object" } ``` </details> ## g8_crm_delete_contact_column > Delete Contact Column. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: DELETE /contacts/columns/{column_id} ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `column_id` | Yes | integer | ID of the column to delete | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "column_id": { "description": "ID of the column to delete", "title": "Column Id", "type": "integer" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "column_id" ], "title": "g8_crm_delete_contact_columnArguments", "type": "object" } ``` </details> ## g8_crm_delete_list > Delete List. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: DELETE /lists/{list_id} ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `list_id` | Yes | integer | List ID | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "list_id": { "description": "List ID", "title": "List Id", "type": "integer" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "list_id" ], "title": "g8_crm_delete_listArguments", "type": "object" } ``` </details> ## g8_crm_get_company > Get Company. Uses the current organization and existing API permissions. API: GET /companies/{company_id} ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `company_id` | Yes | integer | Company ID | | `include_custom_fields` | No | boolean / null | When true, the response includes a `custom_fields` object of custom column values keyed by column slug. Default false omits it. | <details> <summary>Complete input schema</summary> ```json { "properties": { "company_id": { "description": "Company ID", "title": "Company Id", "type": "integer" }, "include_custom_fields": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "description": "When true, the response includes a `custom_fields` object of custom column values keyed by column slug. Default false omits it.", "title": "Include Custom Fields" } }, "required": [ "company_id" ], "title": "g8_crm_get_companyArguments", "type": "object" } ``` </details> ## g8_crm_get_list_contacts > Get List Contacts. Uses the current organization and existing API permissions. API: GET /lists/{list_id}/contacts ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `list_id` | Yes | integer | List ID | | `page` | No | integer / null | Page number (1-indexed) | | `limit` | No | integer / null | Items per page (max 200) | <details> <summary>Complete input schema</summary> ```json { "properties": { "list_id": { "description": "List ID", "title": "List Id", "type": "integer" }, "page": { "anyOf": [ { "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "description": "Page number (1-indexed)", "title": "Page" }, "limit": { "anyOf": [ { "maximum": 200, "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "description": "Items per page (max 200)", "title": "Limit" } }, "required": [ "list_id" ], "title": "g8_crm_get_list_contactsArguments", "type": "object" } ``` </details> ## g8_crm_list_company_columns > List Company Columns. Uses the current organization and existing API permissions. API: GET /companies/columns ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `list_id` | No | integer / null | Optional list ID. When provided, list-specific dynamic columns are included alongside global ones. | <details> <summary>Complete input schema</summary> ```json { "properties": { "list_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "description": "Optional list ID. When provided, list-specific dynamic columns are included alongside global ones.", "title": "List Id" } }, "title": "g8_crm_list_company_columnsArguments", "type": "object" } ``` </details> ## g8_crm_list_contact_columns > List Contact Columns. Uses the current organization and existing API permissions. API: GET /contacts/columns ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `list_id` | No | integer / null | Optional list ID. When provided, list-specific dynamic columns are included alongside global ones. | <details> <summary>Complete input schema</summary> ```json { "properties": { "list_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "description": "Optional list ID. When provided, list-specific dynamic columns are included alongside global ones.", "title": "List Id" } }, "title": "g8_crm_list_contact_columnsArguments", "type": "object" } ``` </details> ## g8_crm_list_contact_tasks > List Contact Tasks. Uses the current organization and existing API permissions. API: GET /contacts/{contact_id}/tasks ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `contact_id` | Yes | integer | Contact ID | | `status` | No | string / null | Filter by status (open, completed) | <details> <summary>Complete input schema</summary> ```json { "properties": { "contact_id": { "description": "Contact ID", "title": "Contact Id", "type": "integer" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Filter by status (open, completed)", "title": "Status" } }, "required": [ "contact_id" ], "title": "g8_crm_list_contact_tasksArguments", "type": "object" } ``` </details> ## g8_crm_list_duplicates > Find duplicate records, and review merges that already happened. Four views over one surface: - `suggestions` (default): candidate duplicate PAIRS awaiting a decision, with their confidence and the fields that matched. Each carries a suggestion id - that is what `g8_crm_resolve_duplicate` takes. - `counts`: how many pairs are pending. Cheap; use it to answer "do we have a duplicates problem?" without pulling rows. - `recent_merges`: merges already applied, each with `can_undo`. `can_undo` false means the 30-day undo window has closed or the merge is not reversible - do not offer an undo it will refuse. - `merge_status`: progress of one bulk-merge run. Needs `run_id`, which comes from a run started in the app's duplicate review screen; this surface does not start merge runs. Rows are already narrowed to what the calling key may see, so an empty result means "none visible to you", which is not always the same as "none exist". Args: entity_type: contact \| company \| deal. Required for suggestions and counts. Note deals can be SUGGESTED and rejected but never merged. view: suggestions \| counts \| recent_merges \| merge_status. status: Suggestion status filter, default "pending". search: Substring match on the records in the pair. confidence: Confidence band filter. owner_id: Only pairs owned by this user. lifecycle_stage: Comma-separated lifecycle stages to filter by. run_id: The bulk-merge run to poll, merge_status only. include_shadow: recent_merges only - include shadow merges. cursor: Opaque page cursor from a previous suggestions page. limit: Rows per page (1-100, default 25). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `entity_type` | No | string | See the schema below for constraints and defaults. | | `view` | No | string | See the schema below for constraints and defaults. | | `status` | No | string | See the schema below for constraints and defaults. | | `search` | No | string / null | See the schema below for constraints and defaults. | | `confidence` | No | string / null | See the schema below for constraints and defaults. | | `owner_id` | No | string / null | See the schema below for constraints and defaults. | | `lifecycle_stage` | No | string / null | See the schema below for constraints and defaults. | | `run_id` | No | string / null | See the schema below for constraints and defaults. | | `include_shadow` | No | boolean | See the schema below for constraints and defaults. | | `cursor` | No | string / null | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "entity_type": { "default": "contact", "title": "Entity Type", "type": "string" }, "view": { "default": "suggestions", "title": "View", "type": "string" }, "status": { "default": "pending", "title": "Status", "type": "string" }, "search": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Search" }, "confidence": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Confidence" }, "owner_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Owner Id" }, "lifecycle_stage": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Lifecycle Stage" }, "run_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Run Id" }, "include_shadow": { "default": false, "title": "Include Shadow", "type": "boolean" }, "cursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Cursor" }, "limit": { "default": 25, "title": "Limit", "type": "integer" } }, "title": "g8_crm_list_duplicatesArguments", "type": "object" } ``` </details> ## g8_crm_list_suppressions > List the org's do-not-contact ledger - who may NOT be contacted. This is the same ledger every send path checks (sequencer, newsletter, audience sync), so a contact listed here will not be reached by any of them. Use it to reconcile an external suppression list, to audit what the org is blocking and why, or to answer "how many people have opted out". To check ONE contact, use `g8_get_contact_detail(include_suppression=True)` instead of paging this. To suppress or reinstate, use `g8_update_contact`. Rows carry NO contact PII - no email, phone or name, only `contact_id`. Resolve a person with `g8_get_contact_detail`. `category` says who set the block, and whether it may be lifted: - `contact_initiated`: they asked (an unsubscribe). NEVER lift it. - `org_initiated`: your own operator block. Reversible by you. - `system`: a hard bounce, complaint or DNC-registry hit. Args: channel: all \| email \| phone \| linkedin. Filters the way the SEND gate filters: `email` also returns the umbrella `all` rows, because a contact blocked on every channel is blocked for email too. Pass `all` to see only the umbrella rows. Omit it for the whole ledger. category: contact_initiated \| org_initiated \| system. Exact match - unlike channel, there is no umbrella category. limit: Rows per page (1-500, default 50). offset: Rows to skip, for paging. Only ACTIVE (uncleared) rows are returned, so a lifted suppression simply disappears rather than showing as cleared. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `channel` | No | string / null | See the schema below for constraints and defaults. | | `category` | No | string / null | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | | `offset` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "channel": { "anyOf": [ { "enum": [ "all", "email", "phone", "linkedin" ], "type": "string" }, { "type": "null" } ], "default": null, "title": "Channel" }, "category": { "anyOf": [ { "enum": [ "contact_initiated", "org_initiated", "system" ], "type": "string" }, { "type": "null" } ], "default": null, "title": "Category" }, "limit": { "default": 50, "title": "Limit", "type": "integer" }, "offset": { "default": 0, "title": "Offset", "type": "integer" } }, "title": "g8_crm_list_suppressionsArguments", "type": "object" } ``` </details> ## g8_crm_remove_contacts_from_list > Remove Contacts From List. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: DELETE /lists/{list_id}/contacts ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `list_id` | Yes | integer | List ID | | `body` | Yes | #/$defs/ApiRemoveContactsFromListRequest | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "$defs": { "ApiRemoveContactsFromListRequest": { "additionalProperties": false, "properties": { "contact_ids": { "description": "List of contact IDs to remove", "items": { "type": "integer" }, "title": "Contact Ids", "type": "array" } }, "required": [ "contact_ids" ], "title": "ApiRemoveContactsFromListRequest", "type": "object" } }, "properties": { "list_id": { "description": "List ID", "title": "List Id", "type": "integer" }, "body": { "$ref": "#/$defs/ApiRemoveContactsFromListRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "list_id", "body" ], "title": "g8_crm_remove_contacts_from_listArguments", "type": "object" } ``` </details> ## g8_crm_resolve_duplicate > Dismiss a suggested duplicate pair, or undo a merge. Two decisions, one per id: - `suggestion_id` (from `g8_crm_list_duplicates`): the pair is NOT a duplicate. This is permanent - there is no un-reject, and the pair will not be suggested again. It changes no contact, company or deal record; only the suggestion is marked. - `merge_history_id` (from the `recent_merges` view, where `can_undo` is true): reverse a merge that already happened, restoring the record that was merged away. Only within its 30-day window, and only for contacts and companies. MERGING pairs is deliberately not available here: choosing which record survives a merge is a decision a person makes, and this surface offers no way to show them the outcome first. Point the user at the duplicate review screen for that, then use `view="merge_status"` and `view="recent_merges"` to follow it. Args: suggestion_id: Pair to dismiss permanently. merge_history_id: Merge to reverse. reason: Why, recorded on the dismissal. Optional. confirm: Required. Both actions are consequential and neither has a preview. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `suggestion_id` | No | string / null | See the schema below for constraints and defaults. | | `merge_history_id` | No | string / null | See the schema below for constraints and defaults. | | `reason` | No | string / null | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "suggestion_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Suggestion Id" }, "merge_history_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Merge History Id" }, "reason": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Reason" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "title": "g8_crm_resolve_duplicateArguments", "type": "object" } ``` </details> ## g8_crm_update_company > Update Company. Uses the current organization and existing API permissions. `body` edits the company's own fields. `lifecycle_stage` moves it through the revenue lifecycle (Suspects -> Leads -> Trial -> Customers -> Churned, plus any custom stages), which writes the revenue overlay rather than the company record and leaves an audit entry. Pass either, or both. Moving to `customers` is refused unless the company has a live subscription - the same guard the app enforces, so a manual "mark them a customer" cannot silently disagree with billing. Moving to `churned` is refused unless the company is or was a customer: Churned is for ex-customers only. With both, the stage move is checked first (a dry run that writes nothing), so a bad stage or a refused move fails the whole call before the field edit is saved. Then the field edit runs, then the move. Any `warnings` from the field edit are returned alongside the move result. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: PATCH /companies/{company_id} and POST /companies/{company_id}/lifecycle-stage ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `company_id` | Yes | integer | Company ID | | `body` | No | #/$defs/ApiCompanyUpdateRequest / null | See the schema below for constraints and defaults. | | `lifecycle_stage` | No | string / null | Move the company to this revenue lifecycle stage. Use a `slug` from g8_get_pipeline(include_lifecycle=True); an unknown slug is rejected with the list of valid ones. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "$defs": { "ApiCompanyUpdateRequest": { "additionalProperties": false, "properties": { "address": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Address" }, "city": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "City" }, "country": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Country" }, "domain": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Domain" }, "employee_count": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Employee Count" }, "industry": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Industry" }, "linkedin_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Linkedin Url" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "phone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Phone" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "website": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Website" }, "zip": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Zip" } }, "title": "ApiCompanyUpdateRequest", "type": "object" } }, "properties": { "company_id": { "description": "Company ID", "title": "Company Id", "type": "integer" }, "body": { "anyOf": [ { "$ref": "#/$defs/ApiCompanyUpdateRequest" }, { "type": "null" } ], "default": null }, "lifecycle_stage": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Move the company to this revenue lifecycle stage. Use a `slug` from g8_get_pipeline(include_lifecycle=True); an unknown slug is rejected with the list of valid ones.", "title": "Lifecycle Stage" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "company_id" ], "title": "g8_crm_update_companyArguments", "type": "object" } ``` </details> ## g8_crm_update_list > Update List. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: PATCH /lists/{list_id} ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `list_id` | Yes | integer | List ID | | `body` | Yes | #/$defs/ApiListUpdateRequest | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "$defs": { "ApiListUpdateRequest": { "additionalProperties": false, "properties": { "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "New purpose note for the list (#16641). Omit to leave the current description unchanged; send an empty string to clear it.", "title": "Description" }, "title": { "description": "New name for the list", "minLength": 1, "title": "Title", "type": "string" } }, "required": [ "title" ], "title": "ApiListUpdateRequest", "type": "object" } }, "properties": { "list_id": { "description": "List ID", "title": "List Id", "type": "integer" }, "body": { "$ref": "#/$defs/ApiListUpdateRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "list_id", "body" ], "title": "g8_crm_update_listArguments", "type": "object" } ``` </details> ## g8_crm_update_quote > Update Quote. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: PATCH /quotes/{quote_id} ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `quote_id` | Yes | string | Quote UUID | | `body` | Yes | #/$defs/ApiQuoteUpdateRequest | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "$defs": { "ApiQuoteLineItemRequest": { "additionalProperties": false, "properties": { "billing_frequency": { "anyOf": [ { "enum": [ "one_time", "month", "year", "quarter" ], "type": "string" }, { "type": "null" } ], "default": null, "title": "Billing Frequency" }, "billing_start_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Exact recurring billing start date within the contract; start_month must remain 1 when supplied.", "title": "Billing Start Date" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "discount_pct": { "default": 0, "maximum": 100.0, "minimum": 0.0, "title": "Discount Pct", "type": "number" }, "manual_product_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Manual Product Id" }, "product_name": { "maxLength": 255, "minLength": 1, "title": "Product Name", "type": "string" }, "quantity": { "default": 1, "maximum": 10000.0, "minimum": 1.0, "title": "Quantity", "type": "integer" }, "sort_order": { "default": 0, "title": "Sort Order", "type": "integer" }, "start_month": { "default": 1, "description": "Contract month when recurring billing begins; 1 means at contract start. Use billing_start_date instead for an exact date.", "maximum": 120.0, "minimum": 1.0, "title": "Start Month", "type": "integer" }, "stripe_price_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Stripe Price Id" }, "stripe_product_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Stripe Product Id" }, "unit_amount": { "description": "In cents", "maximum": 1000000000.0, "minimum": 0.0, "title": "Unit Amount", "type": "integer" } }, "required": [ "product_name", "unit_amount" ], "title": "ApiQuoteLineItemRequest", "type": "object" }, "ApiQuoteUpdateRequest": { "additionalProperties": false, "properties": { "accepted_payment_methods": { "anyOf": [ { "items": { "enum": [ "ach", "credit_card", "wire" ], "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Accepted Payment Methods" }, "billing_address": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Billing Address" }, "billing_contact_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Billing Contact Id" }, "billing_email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Billing Email" }, "billing_legal_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Billing Legal Name" }, "billing_primary_email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Billing Primary Email" }, "contract_duration_unit": { "anyOf": [ { "enum": [ "months", "weeks", "days" ], "type": "string" }, { "type": "null" } ], "default": null, "title": "Contract Duration Unit" }, "contract_duration_value": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Contract Duration Value" }, "contract_end_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Contract End Date" }, "contract_months": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Contract Months" }, "contract_start_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Contract Start Date" }, "currency": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Currency" }, "deal_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Deal Id" }, "line_items": { "anyOf": [ { "items": { "$ref": "#/$defs/ApiQuoteLineItemRequest" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Line Items" }, "marketing_rights": { "anyOf": [ { "items": { "enum": [ "logo", "quote", "case_study" ], "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Marketing Rights" }, "mashup_company_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Mashup Company Id" }, "notes": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Notes" }, "owner_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Owner Id" }, "payment_terms": { "anyOf": [ { "enum": [ "due_on_receipt", "net_15", "net_30", "net_45", "net_60", "net_90" ], "type": "string" }, { "type": "null" } ], "default": null, "title": "Payment Terms" }, "privacy_content": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Privacy Content" }, "public_notes": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Public Notes" }, "signer_contact_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Signer Contact Id" }, "signer_email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Signer Email" }, "signer_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Signer Name" }, "tax_amount": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Tax Amount" }, "terms_content": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Terms Content" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "valid_until": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Valid Until" } }, "title": "ApiQuoteUpdateRequest", "type": "object" } }, "properties": { "quote_id": { "description": "Quote UUID", "title": "Quote Id", "type": "string" }, "body": { "$ref": "#/$defs/ApiQuoteUpdateRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "quote_id", "body" ], "title": "g8_crm_update_quoteArguments", "type": "object" } ``` </details> ## g8_current_org > Report which organization your MCP actions currently run against. Call when unsure which org is active before reading or writing org data. SURFACE THE RESULT TO THE USER, do not treat it as silent setup. When you call this at session start, briefly tell the user which org they are acting in, and if the result carries an `activity` brief, present it: for mode 'resume' show the "pick up where you left off" items (name + type) so they can continue prior work; for 'onboarding' offer the first setup steps; for 'suggest' offer the next actions. One short line or list is enough - the point is the user should SEE where to pick up, not have to ask. For mode 'provisioning' the workspace is still being set up: say so, note it usually takes under a minute, and offer to check again shortly. Re-call this tool when the user asks - a provisioning brief does not consume the once-per-session brief, so re-calling is correct here (and only here). If the brief carries first_connect=true, welcome the user to graph8, offer the get_started prompt for a guided walkthrough, and mention they can open their workspace in the browser via activity.app_url. Call this ONCE at session start and not again unless the user switches org (or you are re-checking a 'provisioning' brief). If `action_required` is true the user belongs to multiple orgs and has not chosen one yet - list orgs with g8_list_orgs and ASK which to use before any write. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_current_orgArguments", "type": "object" } ``` </details> ## g8_dashboard_chat_edit_app_page > Chat Edit App Page. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /app-pages/{page_id}/chat-edit ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `page_id` | Yes | string | See the schema below for constraints and defaults. | | `body` | Yes | #/$defs/ApiAppPageChatEditRequest | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "$defs": { "ApiAppPageChatEditRequest": { "additionalProperties": false, "properties": { "instruction": { "maxLength": 2000, "minLength": 1, "title": "Instruction", "type": "string" } }, "required": [ "instruction" ], "title": "ApiAppPageChatEditRequest", "type": "object" } }, "properties": { "page_id": { "title": "Page Id", "type": "string" }, "body": { "$ref": "#/$defs/ApiAppPageChatEditRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "page_id", "body" ], "title": "g8_dashboard_chat_edit_app_pageArguments", "type": "object" } ``` </details> ## g8_dashboard_create_app_page > Create App Page. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /app-pages ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `body` | Yes | #/$defs/ApiCreateAppPageRequest | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "$defs": { "ApiAppPageSourceDto": { "additionalProperties": false, "properties": { "alias": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Handle used inside the page. Auto-assigned from the kind when omitted.", "title": "Alias" }, "position": { "default": 0, "title": "Position", "type": "integer" }, "role": { "default": "data", "title": "Role", "type": "string" }, "source_id": { "description": "Opaque to everything but the connector that owns it", "title": "Source Id", "type": "string" }, "source_kind": { "description": "Connector family, e.g. staging-table or custom-object", "title": "Source Kind", "type": "string" } }, "required": [ "source_id", "source_kind" ], "title": "ApiAppPageSourceDto", "type": "object" }, "ApiCreateAppPageRequest": { "additionalProperties": false, "properties": { "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "name": { "title": "Name", "type": "string" }, "sources": { "description": "At least one. A page with no data source can never be generated.", "items": { "$ref": "#/$defs/ApiAppPageSourceDto" }, "minItems": 1, "title": "Sources", "type": "array" } }, "required": [ "name", "sources" ], "title": "ApiCreateAppPageRequest", "type": "object" } }, "properties": { "body": { "$ref": "#/$defs/ApiCreateAppPageRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "body" ], "title": "g8_dashboard_create_app_pageArguments", "type": "object" } ``` </details> ## g8_dashboard_delete_app_page > Delete App Page. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: DELETE /app-pages/{page_id} ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `page_id` | Yes | string | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "page_id": { "title": "Page Id", "type": "string" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "page_id" ], "title": "g8_dashboard_delete_app_pageArguments", "type": "object" } ``` </details> ## g8_dashboard_generate_app_page > Generate App Page. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /app-pages/{page_id}/generate ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `page_id` | Yes | string | See the schema below for constraints and defaults. | | `body` | Yes | #/$defs/ApiGenerateAppPageRequest | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "$defs": { "ApiGenerateAppPageRequest": { "additionalProperties": false, "properties": { "instruction": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "What the page should show. Free text; the generator composes every bound source.", "title": "Instruction" } }, "title": "ApiGenerateAppPageRequest", "type": "object" } }, "properties": { "page_id": { "title": "Page Id", "type": "string" }, "body": { "$ref": "#/$defs/ApiGenerateAppPageRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "page_id", "body" ], "title": "g8_dashboard_generate_app_pageArguments", "type": "object" } ``` </details> ## g8_dashboard_get_app_page > Get App Page. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. API: GET /app-pages/{page_id} ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `page_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "page_id": { "title": "Page Id", "type": "string" } }, "required": [ "page_id" ], "title": "g8_dashboard_get_app_pageArguments", "type": "object" } ``` </details> ## g8_dashboard_get_app_page_artifact > Get App Page Artifact. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. API: GET /app-pages/{page_id}/artifact ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `page_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "page_id": { "title": "Page Id", "type": "string" } }, "required": [ "page_id" ], "title": "g8_dashboard_get_app_page_artifactArguments", "type": "object" } ``` </details> ## g8_dashboard_get_app_page_status > Get App Page Status. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. API: GET /app-pages/{page_id}/status ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `page_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "page_id": { "title": "Page Id", "type": "string" } }, "required": [ "page_id" ], "title": "g8_dashboard_get_app_page_statusArguments", "type": "object" } ``` </details> ## g8_dashboard_invoke_app_page_action > Invoke App Page Action. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /app-pages/{page_id}/actions/{alias}/{action_name} ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `page_id` | Yes | string | See the schema below for constraints and defaults. | | `alias` | Yes | string | See the schema below for constraints and defaults. | | `action_name` | Yes | string | See the schema below for constraints and defaults. | | `body` | Yes | #/$defs/ApiInvokeActionRequest | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "$defs": { "ApiInvokeActionRequest": { "additionalProperties": false, "properties": { "args": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "description": "Action arguments. Validated against the object's own schema; an undeclared field is refused, not dropped.", "title": "Args" } }, "title": "ApiInvokeActionRequest", "type": "object" } }, "properties": { "page_id": { "title": "Page Id", "type": "string" }, "alias": { "title": "Alias", "type": "string" }, "action_name": { "title": "Action Name", "type": "string" }, "body": { "$ref": "#/$defs/ApiInvokeActionRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "page_id", "alias", "action_name", "body" ], "title": "g8_dashboard_invoke_app_page_actionArguments", "type": "object" } ``` </details> ## g8_dashboard_list_app_page_actions > List App Page Actions. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. API: GET /app-pages/{page_id}/actions ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `page_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "page_id": { "title": "Page Id", "type": "string" } }, "required": [ "page_id" ], "title": "g8_dashboard_list_app_page_actionsArguments", "type": "object" } ``` </details> ## g8_dashboard_list_app_page_versions > List App Page Versions. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. API: GET /app-pages/{page_id}/versions ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `page_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "page_id": { "title": "Page Id", "type": "string" } }, "required": [ "page_id" ], "title": "g8_dashboard_list_app_page_versionsArguments", "type": "object" } ``` </details> ## g8_dashboard_list_app_pages > List App Pages. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. API: GET /app-pages ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_dashboard_list_app_pagesArguments", "type": "object" } ``` </details> ## g8_dashboard_mint_app_page_frame_session > Mint App Page Frame Session. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /app-pages/{page_id}/frame-session ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `page_id` | Yes | string | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "page_id": { "title": "Page Id", "type": "string" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "page_id" ], "title": "g8_dashboard_mint_app_page_frame_sessionArguments", "type": "object" } ``` </details> ## g8_dashboard_restore_app_page_version > Restore App Page Version. Uses the current organization and existing API permissions. App-builder enablement and ownership gates apply. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /app-pages/{page_id}/versions/{version_num}/restore ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `page_id` | Yes | string | See the schema below for constraints and defaults. | | `version_num` | Yes | integer | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "page_id": { "title": "Page Id", "type": "string" }, "version_num": { "title": "Version Num", "type": "integer" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "page_id", "version_num" ], "title": "g8_dashboard_restore_app_page_versionArguments", "type": "object" } ``` </details> ## g8_data_list_destinations > List Destinations. Uses the current organization and existing API permissions. API: GET /data-pipelines/destinations ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_data_list_destinationsArguments", "type": "object" } ``` </details> ## g8_data_list_functions > List Functions. Uses the current organization and existing API permissions. API: GET /data-pipelines/functions ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_data_list_functionsArguments", "type": "object" } ``` </details> ## g8_data_list_live_events > List Stream Events. Uses the current organization and existing API permissions. API: GET /data-pipelines/streams/{stream_id}/events ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `stream_id` | Yes | string | Stream id (from GET /streams). | | `limit` | No | integer / null | Newest events to return, 1-200. | | `start` | No | string / null | Only events at or after (ISO 8601). | | `end` | No | string / null | Only events before (ISO 8601). | | `levels` | No | string / null | Only entries at this log level (e.g. error = rejected events). | <details> <summary>Complete input schema</summary> ```json { "properties": { "stream_id": { "description": "Stream id (from GET /streams).", "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$", "title": "Stream Id", "type": "string" }, "limit": { "anyOf": [ { "maximum": 200, "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "description": "Newest events to return, 1-200.", "title": "Limit" }, "start": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Only events at or after (ISO 8601).", "title": "Start" }, "end": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Only events before (ISO 8601).", "title": "End" }, "levels": { "anyOf": [ { "enum": [ "error", "warn", "info", "debug" ], "type": "string" }, { "type": "null" } ], "default": null, "description": "Only entries at this log level (e.g. error = rejected events).", "title": "Levels" } }, "required": [ "stream_id" ], "title": "g8_data_list_live_eventsArguments", "type": "object" } ``` </details> ## g8_data_list_sync_runs > List Sync Tasks. Uses the current organization and existing API permissions. API: GET /data-pipelines/syncs/{sync_id}/tasks ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `sync_id` | Yes | string | Sync id (from GET /data-pipelines/syncs). | | `status` | No | string / null | Only runs in this status (e.g. SUCCESS, FAILED, RUNNING). | | `started_after` | No | string / null | Only runs started at or after (ISO 8601). | | `started_before` | No | string / null | Only runs started before (ISO 8601). | <details> <summary>Complete input schema</summary> ```json { "properties": { "sync_id": { "description": "Sync id (from GET /data-pipelines/syncs).", "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$", "title": "Sync Id", "type": "string" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Only runs in this status (e.g. SUCCESS, FAILED, RUNNING).", "title": "Status" }, "started_after": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Only runs started at or after (ISO 8601).", "title": "Started After" }, "started_before": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Only runs started before (ISO 8601).", "title": "Started Before" } }, "required": [ "sync_id" ], "title": "g8_data_list_sync_runsArguments", "type": "object" } ``` </details> ## g8_data_list_syncs > List Syncs. Uses the current organization and existing API permissions. API: GET /data-pipelines/syncs ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_data_list_syncsArguments", "type": "object" } ``` </details> ## g8_delete_audience_sync > Delete an audience sync configuration (soft delete). IMPORTANT: This deactivates the sync. The audience data on the ad platform is NOT deleted - manage that directly in the platform. Args: config_id: Sync config ID ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `config_id` | Yes | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "config_id": { "title": "Config Id", "type": "integer" } }, "required": [ "config_id" ], "title": "g8_delete_audience_syncArguments", "type": "object" } ``` </details> ## g8_delete_deal > Delete a deal by ID. Irreversible. Backed by `DELETE /api/v1/deals/{deal_id}`. Args: deal_id: Deal UUID returned by g8_get_deals / g8_create_deal. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `deal_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "deal_id": { "title": "Deal Id", "type": "string" } }, "required": [ "deal_id" ], "title": "g8_delete_dealArguments", "type": "object" } ``` </details> ## g8_delete_field > Soft-delete a custom column from contacts or companies. Always org-scoped. Pass `list_id` to additionally require that the column belong to a specific list - that is the safer default for list-scoped agents because the org may have many custom columns across different lists with similar names. Args: column_id: Column ID to delete (from g8_list_fields or g8_create_field) entity: Target entity - "contacts" or "companies" (default: contacts) list_id: Optional list scope guard. If set, returns 404 unless the column belongs to this list - prevents cross-list deletion. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `column_id` | Yes | integer | See the schema below for constraints and defaults. | | `entity` | No | string | See the schema below for constraints and defaults. | | `list_id` | No | integer / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "column_id": { "title": "Column Id", "type": "integer" }, "entity": { "default": "contacts", "title": "Entity", "type": "string" }, "list_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "List Id" } }, "required": [ "column_id" ], "title": "g8_delete_fieldArguments", "type": "object" } ``` </details> ## g8_delete_note > Delete a note by ID. Irreversible. Works on a contact, COMPANY or DEAL note - note ids are global, so no deal-specific variant is needed. Notes marked `read_only` by g8_list_notes cannot be deleted. Backed by `DELETE /api/v1/notes/{note_id}`. Args: note_id: Note UUID returned by g8_list_notes / g8_create_note. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `note_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "note_id": { "title": "Note Id", "type": "string" } }, "required": [ "note_id" ], "title": "g8_delete_noteArguments", "type": "object" } ``` </details> ## g8_delete_quote > Delete a draft quote. Irreversible. 400s on any non-draft status. Sent/accepted/voided quotes are preserved as legal records and cannot be hard-deleted - use g8_edit_quote_as_draft or g8_duplicate_quote instead. Args: quote_id: Quote UUID (must be in ``draft`` status). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `quote_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "quote_id": { "title": "Quote Id", "type": "string" } }, "required": [ "quote_id" ], "title": "g8_delete_quoteArguments", "type": "object" } ``` </details> ## g8_delete_sequence > Soft-delete (archive) a sequence. Sets is_archived=True. IMPORTANT: This archives a real sequence. Confirm with the user first. Archived sequences are hidden from the UI and excluded from worker processing. Args: sequence_id: Sequence ID to archive ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `sequence_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "sequence_id": { "title": "Sequence Id", "type": "string" } }, "required": [ "sequence_id" ], "title": "g8_delete_sequenceArguments", "type": "object" } ``` </details> ## g8_delete_task > Delete a task by ID. Irreversible. Backed by `DELETE /api/v1/tasks/{task_id}`. Args: task_id: Task UUID returned by g8_get_tasks / g8_create_task. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `task_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "task_id": { "title": "Task Id", "type": "string" } }, "required": [ "task_id" ], "title": "g8_delete_taskArguments", "type": "object" } ``` </details> ## g8_delete_workbench > Delete a workbench (soft delete). ALWAYS confirm with the user first. Two steps. Call with the default ``dry_run=True`` to get a preview card (name, rows, columns) WITHOUT deleting anything; show it to the user. Only after they agree, call again with ``dry_run=False``. What delete does: the workbench disappears from every workbench list and its rows are no longer readable; its webhook, email ingest address, source connections, schedule, triggers, pipelines and table connections stop. The data is kept (soft delete), so support can restore it on request. Workflow steps that still write to the id fail with "not found" until re-pointed; lookups and row pulls find nothing. Args: workbench_id: The ``workbench_id`` from ``g8_workflow_list_workbenches`` (same id as the Workbench page URL). Never guess it. dry_run: ``True`` (default) returns the confirmation preview only. ``False`` deletes. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `workbench_id` | Yes | string | See the schema below for constraints and defaults. | | `dry_run` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "workbench_id": { "title": "Workbench Id", "type": "string" }, "dry_run": { "default": true, "title": "Dry Run", "type": "boolean" } }, "required": [ "workbench_id" ], "title": "delete_workbenchArguments", "type": "object" } ``` </details> ## g8_doctor > Report what graph8 knows about a repository's GTM readiness. Three read-only checks against the stored repo record: is the repo connected and does it carry a write key, are there stored scan results, and is there an install patch set (and was it applied). This does NOT reach into your running application. graph8 has no server-side probe, so it cannot tell you whether a script tag renders, an env var is set, or an event actually arrived. Those you verify in your own environment; this tells you whether the graph8 side of the install is in place. Args: repo_id: Repository ID ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `repo_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "repo_id": { "title": "Repo Id", "type": "string" } }, "required": [ "repo_id" ], "title": "g8_doctorArguments", "type": "object" } ``` </details> ## g8_duplicate_quote > Clone any quote (any status) into a new draft. The copy keeps the source title; its new quote number tells them apart. Useful for renewals or recreating an expired quote without retyping line items. Args: quote_id: Source quote UUID. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `quote_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "quote_id": { "title": "Quote Id", "type": "string" } }, "required": [ "quote_id" ], "title": "g8_duplicate_quoteArguments", "type": "object" } ``` </details> ## g8_edit_quote_as_draft > Convert a sent quote back into an editable draft via void+clone. Side effects: voids the existing signing envelope (recipient's link stops resolving), marks the source voided, and creates a fresh draft copy with line items preserved. Use when a recipient needs revised pricing/terms on an already-sent quote. Returns the new quote_id to operate on next. Args: quote_id: Source quote UUID (must be in ``sent`` status). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `quote_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "quote_id": { "title": "Quote Id", "type": "string" } }, "required": [ "quote_id" ], "title": "g8_edit_quote_as_draftArguments", "type": "object" } ``` </details> ## g8_enrich_contacts > Enrich existing CRM contacts with third-party data or AI web research. CHARGES CREDITS per contact - call with dry_run=True first for a confirmation preview, then dry_run=False after the user confirms. Two modes share this entry point: 1. waterfall - runs the list's saved waterfall pipeline. Returns a job_id; poll with g8_get_enrichment_job. Credits per contact = the cost of the DEAREST charged step, not the sum of every step that ran. A step is charged only when it finds a value (apollo, and icypeas when it debits, also charge on a miss). 2. ai-research - runs a saved AI enrichment config (requires `ai_group_id`). Runs synchronously and returns final counts inline (`status="completed"`, `total`, `completed`, `results`); the job_id is also valid for g8_get_enrichment_job. ~3 credits per contact. Mode: "auto" (default) prefers AI when `ai_group_id` is given else waterfall; "waterfall" forces waterfall; "ai-research" forces AI (`ai_group_id` required). Waterfall pipeline discovery: - No `providers` / `email_verification` / `target_field`: uses the first active saved pipeline on the list, or auto-creates a minimal graph8 lookup pipeline (2 credits per contact the graph8 lookup finds; no charge when it finds nothing). - `target_field="phone"`: enrich a SPECIFIC column with the correct provider action for that column. The server builds the pipeline from its per-column template, so a phone request runs leadmagic `mobile_finder` (NOT `email_finder`) and writes into the real phone field. Accepts canonical columns (CONTACT_DIRECT_PHONE, CONTACT_MOBILE_PHONE, CONTACT_WORK_EMAIL, CONTACT_LINKEDIN_URL, COMPANY_PHONE, ...) or synonyms ("phone", "mobile", "email", "linkedin"). ALWAYS pass this when the user names a field to enrich ("enrich the phone", "find mobiles"). - `providers=["hunter","prospeo"]`: provisions a pipeline running those providers in order, deduped by name so re-calls reuse it. Allowed: graph8, hunter, prospeo, dropcontact, icypeas, leadmagic, emaillistverify, apollo, lusha, rocketreach. Combine with `target_field` to keep only those providers from the column's template (each keeps its correct per-field action, e.g. `providers=["leadmagic"]` + `target_field="phone"` gives leadmagic `mobile_finder`). - `email_verification=True`: bolts ZeroBounce (`valid` only) on after enrichment. ZeroBounce verify costs 2 credits per row it checks (it runs only on rows where a step found an email). It is not added on top: each row is charged its dearest step, so verification raises a row's cost only when it is dearer than the finder that ran. Credentials (system vs your own key): graph8 funds most providers with its own keys, so a provisioned pipeline uses graph8 system credentials by default and charges credits - no setup needed. If your org has its own key for a provider, that key is used instead (no credits). lusha and rocketreach are NOT graph8-funded: they need your own key under Settings > Providers or their step is skipped. Call g8_list_enrichment_providers to see which is which before enriching. Single-contact enrichment is fine (one-element list); the contact must already be in a list. For an unattached prospect use g8_lookup_person. Repeated contact IDs are enriched and counted once. The dry_run preview checks the request with the server first: an unknown list, mode, target_field or provider, or a contact ID of 0 or less, returns an error and nothing runs. The dry_run preview carries the same credit quote as the app's cost pop-up (validate-credits, charges nothing): `quote` (records x max credits per record = max total), `balance` (available credits) and `sufficient`. Show these to the user. A pipeline that is only created on confirm is priced by the server preflight (`quote.source` names which). `quote.available=false` with a `reason` means the server could not price it. Args: contact_ids: List of CRM contact IDs to enrich. list_id: ID of the list the contacts belong to. dry_run: When True, return a confirmation preview instead of running. mode: "auto" \| "waterfall" \| "ai-research". Defaults to "auto". ai_group_id: group_id of a saved AI config (required for ai-research). Discover via the `/enrichment/ai/configs?list_id=...` endpoint or the campaign builder UI. providers: Optional ordered list of provider names. When supplied, auto-provisions a pipeline that runs these providers (ignored for ai-research). Mutually exclusive with `ai_group_id`'s mode. email_verification: When True, bolts ZeroBounce email verification onto the auto-provisioned pipeline (system credentials, `valid` only). Ignored when an existing pipeline is reused without `providers` overrides. target_field: Optional column to enrich (e.g. "phone", "mobile", "email", or a canonical CONTACT_*/COMPANY_* column). Builds the pipeline from the server's per-column template so each provider runs the CORRECT action for that column (phone → leadmagic `mobile_finder`, not `email_finder`) and writes into the real field. Pass this whenever the user names a field to enrich; omit to run the list's default/saved pipeline. Ignored for ai-research. Approval: the dry_run=True preview returns a one-time approval_token (valid 15 min, single use, bound to these exact arguments). The real run (dry_run=False) after a preview must carry it: the user's Confirm click on the card sends it, or pass it yourself only after the user explicitly approves in chat. Without it the preview comes back (approval_required) and nothing runs. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `contact_ids` | Yes | array | See the schema below for constraints and defaults. | | `list_id` | Yes | integer | See the schema below for constraints and defaults. | | `dry_run` | No | boolean | See the schema below for constraints and defaults. | | `mode` | No | string | See the schema below for constraints and defaults. | | `ai_group_id` | No | string / null | See the schema below for constraints and defaults. | | `providers` | No | array / null | See the schema below for constraints and defaults. | | `email_verification` | No | boolean | See the schema below for constraints and defaults. | | `target_field` | No | string / null | See the schema below for constraints and defaults. | | `approval_token` | No | string / null | One-time approval token from this tool's dry_run=True preview. The graph8 Confirm card sends it when the user clicks Confirm. Pass it yourself ONLY after the user has explicitly approved this exact preview in chat. Never pass it on your own initiative. Without it, a real run that follows a preview returns the preview again instead of executing. | <details> <summary>Complete input schema</summary> ```json { "properties": { "contact_ids": { "items": { "type": "integer" }, "title": "Contact Ids", "type": "array" }, "list_id": { "title": "List Id", "type": "integer" }, "dry_run": { "default": false, "title": "Dry Run", "type": "boolean" }, "mode": { "default": "auto", "title": "Mode", "type": "string" }, "ai_group_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Ai Group Id" }, "providers": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Providers" }, "email_verification": { "default": false, "title": "Email Verification", "type": "boolean" }, "target_field": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Target Field" }, "approval_token": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "One-time approval token from this tool's dry_run=True preview. The graph8 Confirm card sends it when the user clicks Confirm. Pass it yourself ONLY after the user has explicitly approved this exact preview in chat. Never pass it on your own initiative. Without it, a real run that follows a preview returns the preview again instead of executing.", "title": "Approval Token" } }, "required": [ "contact_ids", "list_id" ], "title": "g8_enrich_contactsArguments", "type": "object" } ``` </details> ## g8_enrichment_list_ai_configs > List Ai Configs. Uses the current organization and existing API permissions. API: GET /enrichment/ai/configs ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `list_id` | Yes | integer | List ID whose saved AI configs to return | <details> <summary>Complete input schema</summary> ```json { "properties": { "list_id": { "description": "List ID whose saved AI configs to return", "title": "List Id", "type": "integer" } }, "required": [ "list_id" ], "title": "g8_enrichment_list_ai_configsArguments", "type": "object" } ``` </details> ## g8_enrichment_start_enrichment > Start Enrichment. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /enrichment/enrich ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `body` | Yes | #/$defs/ApiEnrichRequest | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "$defs": { "ApiEnrichRequest": { "additionalProperties": false, "properties": { "contact_ids": { "description": "Contact IDs to enrich", "items": { "type": "integer" }, "title": "Contact Ids", "type": "array" }, "fields_config": { "anyOf": [ { "additionalProperties": { "items": { "type": "string" }, "type": "array" }, "type": "object" }, { "type": "null" } ], "default": null, "description": "Fields to enrich and provider order. Example: {\"work_email\": [\"prospeo\", \"dropcontact\"]}", "title": "Fields Config" }, "list_id": { "description": "List ID the contacts belong to", "title": "List Id", "type": "integer" } }, "required": [ "contact_ids", "list_id" ], "title": "ApiEnrichRequest", "type": "object" } }, "properties": { "body": { "$ref": "#/$defs/ApiEnrichRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "body" ], "title": "g8_enrichment_start_enrichmentArguments", "type": "object" } ``` </details> ## g8_enrichment_verify_email > Verify Email. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /enrichment/verify-email ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `body` | Yes | #/$defs/ApiVerifyEmailRequest | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "$defs": { "ApiVerifyEmailRequest": { "additionalProperties": false, "properties": { "email": { "description": "Email address to verify", "title": "Email", "type": "string" } }, "required": [ "email" ], "title": "ApiVerifyEmailRequest", "type": "object" } }, "properties": { "body": { "$ref": "#/$defs/ApiVerifyEmailRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "body" ], "title": "g8_enrichment_verify_emailArguments", "type": "object" } ``` </details> ## g8_execute > Run ANY graph8 tool by name, even when it is not in your tools list. Use this when g8_tool_search found the tool you need but it did NOT appear in your tool palette (some MCP clients only refresh tools on reconnect). Flow: g8_tool_search returns matches with `input_schema` for the top hits -> build `arguments` from that schema -> call g8_execute(tool_name=..., arguments={...}). Never tell the user a graph8 capability is unavailable without trying this first. Args: tool_name: Exact tool name from g8_tool_search (e.g. "g8_voice_create_dialer_session"). arguments: Arguments for that tool, matching its input_schema. Omit or pass {} for tools without required arguments. Returns a G8ExecuteResult envelope: ok=true with `result` on success; ok=false with `error`, `error_type` and a `hint` on failure, including when the wrapped tool ran but reported an error (its text is then in both `error` and `result`). On `invalid_arguments` the failure includes the tool's `input_schema` - fix the arguments and retry. Permissions and org-context rules apply to the wrapped tool exactly as if it were called directly. The `ctx` parameter is auto-injected by FastMCP and hidden from the public tool schema - clients do not see it. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `tool_name` | Yes | string | See the schema below for constraints and defaults. | | `arguments` | No | object / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "tool_name": { "title": "Tool Name", "type": "string" }, "arguments": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Arguments" } }, "required": [ "tool_name" ], "title": "g8_executeArguments", "type": "object" } ``` </details> ## g8_find_companies > Search graph8's company database for new prospects. Unlike g8_search_companies (which searches YOUR CRM), this searches the open data index for companies you don't track yet. Free - no credits are consumed. The organization's default-on deliverability protection policy is applied automatically. It may exclude high-risk account categories configured under Settings > Compliance > Deliverability. Each filter is a dict with: field, operator, value. Fields: name, domain, industry, industry_group, employee_count, revenue, founded_year, country, state, city, linkedin_followers Operators: any_of, contains, all_of, none_of, is_empty, is_not_empty, between, exists Totals: `total` counts only the results that can be paged (at most 10,000). When more records match, `total_is_lower_bound` is true and `total_available` holds the real count (use it for market size), and `notes` says so. Results past the 10,000th can't be paged (page * limit <= 10,000): narrow the filters instead. Args: filters: List of filter objects (field, operator, value). `value` is always a list, e.g. ["Software"]; `between` takes [low, high]. page: Page number (default 1) limit: Results per page (default 25, max 100) ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `filters` | Yes | array | See the schema below for constraints and defaults. | | `page` | No | integer | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "filters": { "items": { "additionalProperties": true, "type": "object" }, "title": "Filters", "type": "array" }, "page": { "default": 1, "title": "Page", "type": "integer" }, "limit": { "default": 25, "title": "Limit", "type": "integer" } }, "required": [ "filters" ], "title": "g8_find_companiesArguments", "type": "object" } ``` </details> ## g8_find_contacts > Search graph8's contact database to find new prospects. Unlike g8_search_contacts (which searches YOUR CRM), this searches the open data index for leads you don't have yet. Free - no credits are consumed. The organization's default-on deliverability protection policy is applied automatically. It may exclude high-risk account categories configured under Settings > Compliance > Deliverability. Each filter is a dict with: field, operator, value. Fields: first_name, last_name, work_email, job_title, seniority_level, job_department, role, linkedin_url, city, state, country, company_name, company_domain, company_industry, company_employee_count, company_revenue, company_founded_year Operators: any_of, contains, all_of, none_of, is_empty, is_not_empty, between, exists Example filters: [ {"field": "seniority_level", "operator": "any_of", "value": ["Vice President", "Director"]}, {"field": "company_industry", "operator": "contains", "value": ["SaaS", "Software"]}, {"field": "company_employee_count", "operator": "between", "value": [50, 500]} ] Note: the index stores seniority as full strings ("Vice President", not "VP"). Common abbreviations (VP, SVP, EVP, AVP, C-Level, C-Suite) are auto-expanded, but prefer the canonical form. The stored levels are: Entry Level, Senior, Experienced Manager, Director, Vice President, CXO (C-Suite), Owner / Partner, In Training. SVP / EVP / AVP are not stored levels: asked for on their own, they match job titles (e.g. "SVP Sales", "Senior Vice President"). Totals: `total` counts only the results that can be paged (at most 10,000). When more records match, `total_is_lower_bound` is true and `total_available` holds the real count (use it for market size), and `notes` says so. Results past the 10,000th can't be paged (page * limit <= 10,000): narrow the filters instead. Locked values: "***" means the value exists but is locked until the contact is added to a list (`locked_fields` lists them); null means there is no value. Never treat "***" as real data. Args: filters: List of filter objects (field, operator, value). `value` is always a list, e.g. ["Director"]; `between` takes [low, high]. page: Page number (default 1) limit: Results per page (default 25, max 100) ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `filters` | Yes | array | See the schema below for constraints and defaults. | | `page` | No | integer | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "filters": { "items": { "additionalProperties": true, "type": "object" }, "title": "Filters", "type": "array" }, "page": { "default": 1, "title": "Page", "type": "integer" }, "limit": { "default": 25, "title": "Limit", "type": "integer" } }, "required": [ "filters" ], "title": "g8_find_contactsArguments", "type": "object" } ``` </details> ## g8_forms_get_form_overview > Get operational detail for one lead-capture form. Returns capture/delivery/activation health, submission metrics, the destination list, owner, and display name. delivery_source says where leads go ('route', 'legacy_mapping' or null = nowhere), destination names the list, delivery_warning explains a delivery gap in plain words (a legacy mapping delivers but writes no receipts, so processed/failed stay 0), and setup_hints lists what is missing (e.g. owner_missing). Args: form_key: The form's id / group key (from g8_forms_list_forms). source_id: The form's write key / site (from g8_forms_list_forms) - a form id is only unique within its source, so this is required. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `form_key` | Yes | string | See the schema below for constraints and defaults. | | `source_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "form_key": { "title": "Form Key", "type": "string" }, "source_id": { "title": "Source Id", "type": "string" } }, "required": [ "form_key", "source_id" ], "title": "g8_forms_get_form_overviewArguments", "type": "object" } ``` </details> ## g8_forms_list_forms > List and search the org's website lead-capture forms. Use this to FIND a specific form by name - e.g. search='discovery' to locate a "discovery" form. Each result carries the form_key + source_id needed by g8_forms_get_form_overview. delivery_health='failing' and failed_submissions flag forms whose deliveries failed. Args: page: 1-indexed page (default 1). per_page: Rows per page (default 20, max 200). search: Case-insensitive substring match on the form name/id. source_id: Restrict to one write key / site. sort_field: One of 'total_submissions', 'latest_submitted_at', 'form_tag_id'. sort_order: 'asc' or 'desc'. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `page` | No | integer | See the schema below for constraints and defaults. | | `per_page` | No | integer | See the schema below for constraints and defaults. | | `search` | No | string / null | See the schema below for constraints and defaults. | | `source_id` | No | string / null | See the schema below for constraints and defaults. | | `sort_field` | No | string / null | See the schema below for constraints and defaults. | | `sort_order` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "page": { "default": 1, "title": "Page", "type": "integer" }, "per_page": { "default": 20, "title": "Per Page", "type": "integer" }, "search": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Search" }, "source_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Source Id" }, "sort_field": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Sort Field" }, "sort_order": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Sort Order" } }, "title": "g8_forms_list_formsArguments", "type": "object" } ``` </details> ## g8_generate_spintax > Rewrite a manual email into spintax for per-contact variation. CHARGES CREDITS per call (one AI generation, token-metered). Confirm with the user before calling. Takes a plain subject + body and returns them rewritten with nested {{option a\|option b}} spintax blocks. At send time the sequencer picks one option per block per contact, so every lead receives a different-looking variant of the same email. IMPORTANT - the step MUST be input_type='MANUAL_TEMPLATE'. An email step left at the default input_type='ON_DEMAND' IGNORES subject/body entirely and asks the AI to write each email from step_data.instructions instead, so spintax you paste there is silently discarded and never sent. Writing this result onto an ON_DEMAND step now returns a 422 that names the fix. After writing the result into a step, verify with g8_list_sequence_steps: rendered.variants shows the actual per-contact variations, and raw {{a\|b\|c}} braces in step_data are correct storage rather than a failure. Campaign sequence steps resolve spintax identically at send time, so the result works there too. Generation is synchronous and can take up to ~2 minutes. The output is validated server-side (balanced braces, every {{variable}} preserved, links preserved for HTML) - invalid model output returns an error, never silently broken content. A 402 error means insufficient credits OR an inactive subscription. A 429 error means the server is at its concurrent-generation cap - wait ~15s and retry, do not treat it as a hard failure. Args: subject: Email subject to rewrite into spintax body: Email body to rewrite into spintax email_type: Body format, 'plain' (default) or 'html'. HTML preserves <a href> links. tone_instructions: Optional tone/style guidance for the rewrite strength: Variation intensity - 'light', 'balanced' (default), or 'maximum'. Each level has an enforced ceiling on total distinct renderings (subject x body), applied after generation: light up to 200 with flat blocks only, balanced up to 5,000, maximum up to 100,000. Output above the ceiling is trimmed down rather than rejected. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `subject` | Yes | string | See the schema below for constraints and defaults. | | `body` | Yes | string | See the schema below for constraints and defaults. | | `email_type` | No | string | See the schema below for constraints and defaults. | | `tone_instructions` | No | string / null | See the schema below for constraints and defaults. | | `strength` | No | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "subject": { "title": "Subject", "type": "string" }, "body": { "title": "Body", "type": "string" }, "email_type": { "default": "plain", "title": "Email Type", "type": "string" }, "tone_instructions": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Tone Instructions" }, "strength": { "default": "balanced", "title": "Strength", "type": "string" } }, "required": [ "subject", "body" ], "title": "g8_generate_spintaxArguments", "type": "object" } ``` </details> ## g8_get_activities > List recent CRM activities for the current organization. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `page` | No | integer | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | | `activity_type` | No | string / null | See the schema below for constraints and defaults. | | `contact_id` | No | string / null | See the schema below for constraints and defaults. | | `company_id` | No | string / null | See the schema below for constraints and defaults. | | `mashup_contact_id` | No | integer / null | See the schema below for constraints and defaults. | | `mashup_company_id` | No | integer / null | See the schema below for constraints and defaults. | | `deal_id` | No | string / null | See the schema below for constraints and defaults. | | `occurred_after` | No | string / null | See the schema below for constraints and defaults. | | `occurred_before` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "page": { "default": 1, "title": "Page", "type": "integer" }, "limit": { "default": 50, "title": "Limit", "type": "integer" }, "activity_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Activity Type" }, "contact_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Contact Id" }, "company_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Company Id" }, "mashup_contact_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Mashup Contact Id" }, "mashup_company_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Mashup Company Id" }, "deal_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Deal Id" }, "occurred_after": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Occurred After" }, "occurred_before": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Occurred Before" } }, "title": "g8_get_activitiesArguments", "type": "object" } ``` </details> ## g8_get_activity_summary > Summarize recent CRM activity for the current organization. Aggregates the most recent activities into a rollup: total count, counts by activity_type and outcome, daily counts for the window, top owners by activity count, and total call/meeting duration. Good for "how was this week" / "which reps are most active" without paging the raw feed. Pulls up to 200 most recent activities then filters client-side by the day window. If more than 200 fall in the window the summary covers only the newest 200 (`truncated` flag is true) - call `g8_get_activities` with narrower filters for a full feed. Args: days: Size of the rollup window in days ending now (default 7). activity_type: Optional activity type filter (e.g. "call", "email", "meeting"). Passed to the server-side filter. contact_id: Optional contact filter. company_id: Optional company filter. deal_id: Optional deal filter. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `days` | No | integer | See the schema below for constraints and defaults. | | `activity_type` | No | string / null | See the schema below for constraints and defaults. | | `contact_id` | No | string / null | See the schema below for constraints and defaults. | | `company_id` | No | string / null | See the schema below for constraints and defaults. | | `deal_id` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "days": { "default": 7, "title": "Days", "type": "integer" }, "activity_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Activity Type" }, "contact_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Contact Id" }, "company_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Company Id" }, "deal_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Deal Id" } }, "title": "g8_get_activity_summaryArguments", "type": "object" } ``` </details> ## g8_get_audience_sync > Get details for a specific audience sync configuration. Args: config_id: Sync config ID ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `config_id` | Yes | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "config_id": { "title": "Config Id", "type": "integer" } }, "required": [ "config_id" ], "title": "g8_get_audience_syncArguments", "type": "object" } ``` </details> ## g8_get_audience_sync_errors > Get error logs for an audience sync configuration. Returns recent failed sync runs with error messages and details. Args: config_id: Sync config ID limit: Max errors to return (default 20, max 100) ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `config_id` | Yes | integer | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "config_id": { "title": "Config Id", "type": "integer" }, "limit": { "default": 20, "title": "Limit", "type": "integer" } }, "required": [ "config_id" ], "title": "g8_get_audience_sync_errorsArguments", "type": "object" } ``` </details> ## g8_get_audience_sync_runs > Get sync run history for an audience sync configuration. Returns recent sync runs with status, member counts, and timing. Args: config_id: Sync config ID limit: Max runs to return (default 20, max 100) ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `config_id` | Yes | integer | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "config_id": { "title": "Config Id", "type": "integer" }, "limit": { "default": 20, "title": "Limit", "type": "integer" } }, "required": [ "config_id" ], "title": "g8_get_audience_sync_runsArguments", "type": "object" } ``` </details> ## g8_get_booking > Get a single booking by its UID. Returns the full detail: attendees, status, times, location, meeting_url, the attendee's form responses, and internal notes. Args: booking_uid: Booking UID (uuid string) from g8_appointments_list_bookings. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `booking_uid` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "booking_uid": { "title": "Booking Uid", "type": "string" } }, "required": [ "booking_uid" ], "title": "g8_appointments_get_bookingArguments", "type": "object" } ``` </details> ## g8_get_campaign > Get full details for a specific campaign. Args: repo_id: Repository ID campaign_id: Campaign ID from g8_list_campaigns ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `repo_id` | Yes | string | See the schema below for constraints and defaults. | | `campaign_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "repo_id": { "title": "Repo Id", "type": "string" }, "campaign_id": { "title": "Campaign Id", "type": "string" } }, "required": [ "repo_id", "campaign_id" ], "title": "g8_get_campaignArguments", "type": "object" } ``` </details> ## g8_get_company_contacts > List the contacts attached to a company. Returns the people your org has recorded at a given company. Useful for multi-threading prep ("who else do we know at Acme?") or for building account-based outreach lists. Args: company_id: Integer company ID. limit: Items per page (default 100, max 200). offset: Pagination offset (default 0). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `company_id` | Yes | integer | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | | `offset` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "company_id": { "title": "Company Id", "type": "integer" }, "limit": { "default": 100, "title": "Limit", "type": "integer" }, "offset": { "default": 0, "title": "Offset", "type": "integer" } }, "required": [ "company_id" ], "title": "g8_get_company_contactsArguments", "type": "object" } ``` </details> ## g8_get_company_deals > List all deals associated with a company (account view). Returns every deal linked to this company, with stage, value, currency, pipeline, role, owner, and close date. Useful for "what's in the pipeline for this account" questions. Mirrors g8_get_contact_deals. Backed by `GET /api/v1/companies/{company_id}/deals`. Args: company_id: Integer company ID (mashup_companies.id). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `company_id` | Yes | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "company_id": { "title": "Company Id", "type": "integer" } }, "required": [ "company_id" ], "title": "g8_get_company_dealsArguments", "type": "object" } ``` </details> ## g8_get_company_quotes > List every quote attached to a given company (account view). Args: company_id: MashupCompany ID (integer). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `company_id` | Yes | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "company_id": { "title": "Company Id", "type": "integer" } }, "required": [ "company_id" ], "title": "g8_get_company_quotesArguments", "type": "object" } ``` </details> ## g8_get_contact_activity > List the activity timeline for a single contact, newest first. Returns the same events as the contact's Activity tab in the app: emails, calls, meetings, notes, tasks, SMS, chats, stage moves, quotes, deals and record changes. The query is made for this contact directly, so busy orgs do not hide it. Good for "what have we done with this person". Page with `offset`: when `has_more` is true, call again with `offset=next_offset`. Like the app, the timeline reaches back over the contact's newest 200 events. Args: contact_id: Integer contact ID. limit: Max activities to return (default 50, max 200). activity_type: Optional type filter (e.g. "call", "email", "meeting"). offset: Number of newest activities to skip (default 0). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `contact_id` | Yes | integer | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | | `activity_type` | No | string / null | See the schema below for constraints and defaults. | | `offset` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "contact_id": { "title": "Contact Id", "type": "integer" }, "limit": { "default": 50, "title": "Limit", "type": "integer" }, "activity_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Activity Type" }, "offset": { "default": 0, "title": "Offset", "type": "integer" } }, "required": [ "contact_id" ], "title": "g8_get_contact_activityArguments", "type": "object" } ``` </details> ## g8_get_contact_company > Get the company record attached to a contact. Single-call wrapper: `/contacts/{id}` already embeds the contact's company as a nested object (id, name, domain, industry, location, linkedin_url, logo_url), so we extract and return that directly. Returns an empty ResourceDetail if the contact has no company linkage. Args: contact_id: Integer contact ID. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `contact_id` | Yes | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "contact_id": { "title": "Contact Id", "type": "integer" } }, "required": [ "contact_id" ], "title": "g8_get_contact_companyArguments", "type": "object" } ``` </details> ## g8_get_contact_deals > List all deals associated with a contact. Returns every deal where this contact is the primary or associated contact, with stage, value, currency, pipeline, and role. Useful for "what's this person working on with us" questions. Args: contact_id: Integer contact ID. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `contact_id` | Yes | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "contact_id": { "title": "Contact Id", "type": "integer" } }, "required": [ "contact_id" ], "title": "g8_get_contact_dealsArguments", "type": "object" } ``` </details> ## g8_get_contact_detail > Get the full detail record for a single CRM contact. Returns every backend field the org has on this contact (emails, phones, job title, company linkage, social URLs, location, custom attributes). Use after g8_search_contacts has narrowed down a contact_id to read the full record. With `include_suppression=True` the record also carries a `suppression` block - `is_suppressed`, `active_channels` and the ledger rows behind them - read from the SAME ledger the sequencer and newsletter send gates check. Ask for it BEFORE contacting anyone, or before enrolling them in a sequence. That read is a second API call, so leave it off when you only need the fields. If the suppression read fails, the whole call returns that error rather than a record with the block missing: a returned record either has a true suppression answer or is not returned at all. Nothing is ever inferred from an absent block. Args: contact_id: Integer contact ID (from g8_search_contacts or CRM). include_suppression: Attach the do-not-contact status. Default False: no second call is made. include_custom_fields: Fill `custom_fields` with the contact's custom column values, keyed by column slug (the `name` from g8_list_fields). Values are text: numbers and yes/no read as '42', '12.5', 'true'. Default True; pass False to skip that read. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `contact_id` | Yes | integer | See the schema below for constraints and defaults. | | `include_suppression` | No | boolean | See the schema below for constraints and defaults. | | `include_custom_fields` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "contact_id": { "title": "Contact Id", "type": "integer" }, "include_suppression": { "default": false, "title": "Include Suppression", "type": "boolean" }, "include_custom_fields": { "default": true, "title": "Include Custom Fields", "type": "boolean" } }, "required": [ "contact_id" ], "title": "g8_get_contact_detailArguments", "type": "object" } ``` </details> ## g8_get_contact_quotes > List quotes where this contact is the signer. Args: contact_id: MashupContact ID (integer). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `contact_id` | Yes | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "contact_id": { "title": "Contact Id", "type": "integer" } }, "required": [ "contact_id" ], "title": "g8_get_contact_quotesArguments", "type": "object" } ``` </details> ## g8_get_crm_fields > Discover available fields for an entity type on a CRM provider (use to build field mappings before pushing data). Args: provider: CRM provider (hubspot, salesforce, pipedrive, zoho, sugarcrm) entity_type: Entity type (contacts, companies, leads, deals) ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `provider` | Yes | string | See the schema below for constraints and defaults. | | `entity_type` | No | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "provider": { "title": "Provider", "type": "string" }, "entity_type": { "default": "contacts", "title": "Entity Type", "type": "string" } }, "required": [ "provider" ], "title": "g8_get_crm_fieldsArguments", "type": "object" } ``` </details> ## g8_get_crm_status > Check connection health for a CRM provider. Args: provider: CRM provider (hubspot, salesforce, pipedrive, zoho, sugarcrm) ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `provider` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "provider": { "title": "Provider", "type": "string" } }, "required": [ "provider" ], "title": "g8_get_crm_statusArguments", "type": "object" } ``` </details> ## g8_get_deal > Get details for a single CRM deal. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `deal_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "deal_id": { "title": "Deal Id", "type": "string" } }, "required": [ "deal_id" ], "title": "g8_get_dealArguments", "type": "object" } ``` </details> ## g8_get_deals > List deals with owner, outcome, stage, pipeline, activity and contact filters. For "my stale deals" use stale_before (ISO timestamp): it returns deals last touched before that time OR never touched at all. Note that last_activity_before alone EXCLUDES never-touched deals (no activity = no last-activity timestamp), which is usually not what "stale" means. Use outcome="won" \| "lost" \| "open" to filter by outcome. A deal has exactly one, so this is unambiguous. For closed-lost re-engagement use outcome="lost". For "how many deals did we close this month/quarter" combine outcome="won" with close_after / close_before (ISO timestamps) to scope by close_date. The window is half-open [close_after, close_before) and is applied server-side, so the returned total is the exact count for the period rather than a client-side tally of a truncated page. A period count needs BOTH bounds AND the outcome filter: close_before alone (no lower bound) sums the entire deal history into the period, and omitting outcome counts open/lost deals whose close_date is only a forecast. Deals with no close_date are excluded from a windowed query. Do NOT use is_closed_won=false for that - it is a LEGACY boolean whose false branch means "not won", which also returns open and unclassified deals. Prefer outcome= in all new code; passing an outcome that contradicts is_closed_won is rejected (400). Pair outcome="lost" with last_activity_before rather than stale_before when the result will drive an automated action: stale_before also returns deals that have NEVER had a recorded interaction, which is the wrong cohort for outreach that is meant to reference prior conversations. Add has_activity=true to be explicit. Use has_contact=false for "deals without a contact" - the orphaned-deal cohort. This IS filterable server-side, so never page the pipeline checking linkage yourself. "No contact" means no linked contact AND no live primary contact, so a pointer at a merged-away contact still counts as orphaned. Results carry closed_lost_reason when the rep recorded one; it is frequently null, because nothing in the platform requires it. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `page` | No | integer | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | | `stage_id` | No | string / null | See the schema below for constraints and defaults. | | `pipeline_id` | No | string / null | See the schema below for constraints and defaults. | | `search` | No | string / null | See the schema below for constraints and defaults. | | `owner_id` | No | string / null | See the schema below for constraints and defaults. | | `owner_email` | No | string / null | See the schema below for constraints and defaults. | | `is_closed_won` | No | boolean / null | See the schema below for constraints and defaults. | | `outcome` | No | string / null | See the schema below for constraints and defaults. | | `last_activity_before` | No | string / null | See the schema below for constraints and defaults. | | `stale_before` | No | string / null | See the schema below for constraints and defaults. | | `has_activity` | No | boolean / null | See the schema below for constraints and defaults. | | `has_contact` | No | boolean / null | See the schema below for constraints and defaults. | | `close_after` | No | string / null | See the schema below for constraints and defaults. | | `close_before` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "page": { "default": 1, "title": "Page", "type": "integer" }, "limit": { "default": 25, "title": "Limit", "type": "integer" }, "stage_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Stage Id" }, "pipeline_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Pipeline Id" }, "search": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Search" }, "owner_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Owner Id" }, "owner_email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Owner Email" }, "is_closed_won": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Closed Won" }, "outcome": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Outcome" }, "last_activity_before": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Activity Before" }, "stale_before": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Stale Before" }, "has_activity": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Has Activity" }, "has_contact": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Has Contact" }, "close_after": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Close After" }, "close_before": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Close Before" } }, "title": "g8_get_dealsArguments", "type": "object" } ``` </details> ## g8_get_enrichment_job > Poll the status of an enrichment job by the `job_id` from g8_enrich_contacts. - Waterfall jobs are async: response carries `status` (queued/running/completed/failed) until the worker reports progress, then `total`/`completed` populate. - AI jobs run synchronously inside g8_enrich_contacts(mode="ai-research"), so the job is already completed/failed by the time you have a job_id; polling is supported but rarely needed. IMPORTANT: `status="completed"` means the pipeline RAN, not that it wrote data. Always check `successful_enrichments` - a completed job with `successful_enrichments=0` (and `total_credits_used=0`) enriched nothing, almost always a missing provider credential. Pass `include_provider_errors=True` to get the per-provider reasons (e.g. "<provider> API key is not configured"). Args: job_id: job_id from the g8_enrich_contacts response. include_provider_errors: When True, include per-provider step errors (record_id, provider, error) for a completed/failed job. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `job_id` | Yes | string | See the schema below for constraints and defaults. | | `include_provider_errors` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "job_id": { "title": "Job Id", "type": "string" }, "include_provider_errors": { "default": false, "title": "Include Provider Errors", "type": "boolean" } }, "required": [ "job_id" ], "title": "g8_get_enrichment_jobArguments", "type": "object" } ``` </details> ## g8_get_event_type > Get full detail for one bookable event type: title, slug, duration (length minutes), locations, hosts, schedule, buffers, minimum notice, and the booking_fields an attendee must answer. Call before g8_appointments_create_booking to learn required fields + slot length. Find event_type_id via g8_appointments_list_event_types. Args: event_type_id: Event-type integer ID. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `event_type_id` | Yes | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "event_type_id": { "title": "Event Type Id", "type": "integer" } }, "required": [ "event_type_id" ], "title": "g8_appointments_get_event_typeArguments", "type": "object" } ``` </details> ## g8_get_field_value > Read a single custom column value from one contact or company row. The read-side counterpart of `g8_set_field_value`. Use `g8_list_fields` first to find the `column_id`. `record_id` is the contact_id (when entity="contacts") or company_id (when entity="companies"). Returns `value: null` when the column exists but has not been set for that row. To read the custom values of MANY rows at once, pass `include_custom_fields=true` to `g8_search_contacts` / `g8_search_companies` (or the contact/company read endpoints) instead of calling this per-row. Args: column_id: Column ID from `g8_list_fields` or `g8_create_field`. record_id: Contact ID or company ID to read. entity: Target entity - "contacts" or "companies" (default: contacts). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `column_id` | Yes | integer | See the schema below for constraints and defaults. | | `record_id` | Yes | integer | See the schema below for constraints and defaults. | | `entity` | No | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "column_id": { "title": "Column Id", "type": "integer" }, "record_id": { "title": "Record Id", "type": "integer" }, "entity": { "default": "contacts", "title": "Entity", "type": "string" } }, "required": [ "column_id", "record_id" ], "title": "g8_get_field_valueArguments", "type": "object" } ``` </details> ## g8_get_form_template > Return a complete, working progressive form-fill template for a framework. Progressive forms collect visitor data across multiple visits (stage 1: email triggers CDP lookup; later stages add name/company/title then budget/timeline, skipping fields CDP already knows). Each submit calls g8.identify() + g8.track(). PREREQUISITE: install the graph8 tracking snippet first (run g8_get_tracking_snippet); without it the g8 calls silently no-op and no data reaches graph8 CDP. Adapt the returned template freely; it tracks stage via localStorage. Args: framework: html, react, nextjs, vue, wordpress, webflow, or shopify. variant: embedded (inline) or popup (overlay with time/scroll/exit triggers). field_stages: Optional JSON array of stage objects (stage number + fields) for custom stages. Omit for defaults (email/name -> company/title -> budget/timeline). repo_id: Optional repository ID for a repo-specific write key. stream_id: Optional tracking stream ID (from g8_snippet_list_streams) whose write key the form uses. If the result has action_required=true, the org has several streams: ask which website this form is for and pass its stream_id. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `framework` | Yes | string | See the schema below for constraints and defaults. | | `variant` | Yes | string | See the schema below for constraints and defaults. | | `field_stages` | No | string / null | See the schema below for constraints and defaults. | | `repo_id` | No | string / null | See the schema below for constraints and defaults. | | `stream_id` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "framework": { "title": "Framework", "type": "string" }, "variant": { "title": "Variant", "type": "string" }, "field_stages": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Field Stages" }, "repo_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Repo Id" }, "stream_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Stream Id" } }, "required": [ "framework", "variant" ], "title": "g8_get_form_templateArguments", "type": "object" } ``` </details> ## g8_get_landing_page_source > Read the exact HTML and SHA-256 for a Studio landing-page edit session. The edit session comes from Studio's Open in AI dialog. It selects one draft but is not a credential: the connected graph8 account must match the user and organization that created it. Args: edit_session: Opaque page-scoped session copied from graph8 Studio. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `edit_session` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "edit_session": { "title": "Edit Session", "type": "string" } }, "required": [ "edit_session" ], "title": "g8_get_landing_page_sourceArguments", "type": "object" } ``` </details> ## g8_get_lists > List contact and company lists in your CRM, newest first. Args: page: Page number (default 1) limit: Items per page (default 50) search: Optional case-insensitive match on the list name, e.g. "Contact Us". Omit to return every list. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `page` | No | integer | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | | `search` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "page": { "default": 1, "title": "Page", "type": "integer" }, "limit": { "default": 50, "title": "Limit", "type": "integer" }, "search": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Search" } }, "title": "g8_get_listsArguments", "type": "object" } ``` </details> ## g8_get_meeting > Get a single meeting with FULL transcript bundled inline. Returns the meeting record plus the complete transcript body, summary, key topics, action items, CRM tasks, and custom-field extractions. If the caller is not a participant and lacks meetings:see_transcripts, transcript content is redacted (transcript_redacted=true). Use after g8_list_meetings narrows down a meeting id. Args: meeting_id: Meeting ID (string or integer form; both accepted). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `meeting_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "meeting_id": { "title": "Meeting Id", "type": "string" } }, "required": [ "meeting_id" ], "title": "g8_get_meetingArguments", "type": "object" } ``` </details> ## g8_get_pipeline > List deal pipelines and stages, and optionally the revenue lifecycle stages. Two different axes of the same board. A deal pipeline moves one DEAL through selling stages. The revenue lifecycle moves a whole COMPANY (or contact) through Suspects -> Leads -> Trial -> Customers -> Churned, plus any custom stages the org added. Ask for `include_lifecycle` before using a lifecycle stage anywhere else: the `slug` returned here is the value `g8_search_companies(lifecycle_stage=)`, `g8_update_contact(lifecycle_stage=)` and `g8_crm_update_company(lifecycle_stage=)` all take, and an org can rename or add stages so the five seeded slugs are not a safe guess. Args: include_lifecycle: Also return the org's lifecycle stages. include_counts: Join each lifecycle stage's company count onto it as `record_count`. Implies include_lifecycle. Counts are COMPANY-side only: moving a contact to a stage never moves one of these numbers. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `include_lifecycle` | No | boolean | See the schema below for constraints and defaults. | | `include_counts` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "include_lifecycle": { "default": false, "title": "Include Lifecycle", "type": "boolean" }, "include_counts": { "default": false, "title": "Include Counts", "type": "boolean" } }, "title": "g8_get_pipelineArguments", "type": "object" } ``` </details> ## g8_get_quote > Get full quote detail: line items, activity log, envelope tracking, totals. Args: quote_id: Quote UUID returned by g8_list_quotes / g8_create_quote. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `quote_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "quote_id": { "title": "Quote Id", "type": "string" } }, "required": [ "quote_id" ], "title": "g8_get_quoteArguments", "type": "object" } ``` </details> ## g8_get_quote_settings > Get org-level quote settings (branding, default terms, sender identity). Auto-creates default settings if none exist. Use to confirm the org's sender name/email, default payment terms, and default valid_until window before sending. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_get_quote_settingsArguments", "type": "object" } ``` </details> ## g8_get_reply > Get a single inbox thread with its message history. Long message bodies are capped by default; a capped message is marked with content_truncated=true and its real length in content_chars. Real threads accumulate quoted reply chains and signatures and can otherwise run to hundreds of thousands of tokens, which can blow the context window. For browsing an inbox use g8_list_inbox - its `preview` field already says what each thread is about. Args: reply_id: Thread/reply ID. channel: email, sms, or linkedin (default email). full_messages: Return untrimmed message bodies. Use only when the capped text is genuinely insufficient - e.g. quoting an exact clause from deep in a long thread. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `reply_id` | Yes | string | See the schema below for constraints and defaults. | | `channel` | No | string | See the schema below for constraints and defaults. | | `full_messages` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "reply_id": { "title": "Reply Id", "type": "string" }, "channel": { "default": "email", "title": "Channel", "type": "string" }, "full_messages": { "default": false, "title": "Full Messages", "type": "boolean" } }, "required": [ "reply_id" ], "title": "g8_get_replyArguments", "type": "object" } ``` </details> ## g8_get_reply_draft > Generate an AI draft reply for an inbox thread. Charges credits (402 if insufficient). Draft only - does not send. Args: reply_id: Thread/reply ID. channel: email, sms, or linkedin (default email). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `reply_id` | Yes | string | See the schema below for constraints and defaults. | | `channel` | No | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "reply_id": { "title": "Reply Id", "type": "string" }, "channel": { "default": "email", "title": "Channel", "type": "string" } }, "required": [ "reply_id" ], "title": "g8_get_reply_draftArguments", "type": "object" } ``` </details> ## g8_get_scan_results > Get the latest scan results for a repository. Returns detected tech stack, API routes, README summary, and GTM readiness flags. Args: repo_id: Repository ID ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `repo_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "repo_id": { "title": "Repo Id", "type": "string" } }, "required": [ "repo_id" ], "title": "g8_get_scan_resultsArguments", "type": "object" } ``` </details> ## g8_get_sequence_analytics > Get comprehensive analytics for a sequence. Returns overview, performance, engagement, timeline, and contact distribution. Args: sequence_id: Sequence ID to get analytics for ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `sequence_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "sequence_id": { "title": "Sequence Id", "type": "string" } }, "required": [ "sequence_id" ], "title": "g8_get_sequence_analyticsArguments", "type": "object" } ``` </details> ## g8_get_sequence_preview > Preview a sequence with steps, channels and RESOLVED copy (read-only, no enrollment). Each step returns two views of its content, and the difference matters: - step_data: the stored template, spintax braces and all. - rendered: the same copy with spintax collapsed to one real variant, plus total_combinations and up to 3 sample variants. To answer "is my spintax working?", read rendered.variants. If they differ from each other, per-contact variation is live and each lead receives a different email. Raw {{a\|b\|c}} in step_data is CORRECT storage, not a bug. {{variables}} stay visible in rendered because a preview has no contact; they fill in at send time. rendered is null for ON_DEMAND steps, whose copy the AI writes per contact at send time. Check rendered.will_send_as_shown before launching. When it is false, rendered.content_issues explains what will be silently dropped from the delivered email, and those same errors will block the launch. Args: sequence_id: Sequence ID to preview ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `sequence_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "sequence_id": { "title": "Sequence Id", "type": "string" } }, "required": [ "sequence_id" ], "title": "g8_get_sequence_previewArguments", "type": "object" } ``` </details> ## g8_get_task > Read one task, including typed related records and exact source context. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `task_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "task_id": { "title": "Task Id", "type": "string" } }, "required": [ "task_id" ], "title": "g8_get_taskArguments", "type": "object" } ``` </details> ## g8_get_task_execution > Read permitted execution history and result links for a CRM task. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `task_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "task_id": { "title": "Task Id", "type": "string" } }, "required": [ "task_id" ], "title": "g8_get_task_executionArguments", "type": "object" } ``` </details> ## g8_get_tasks > List tasks for the current organization with optional filters. Returns up to `limit` rows starting at `offset` (default: first 50). Response includes `tasks` plus `total` (API total, or unknown on older servers), `limit`, `offset`, and `has_next`. Args: status: Filter by status (e.g. "open", "completed"). priority: Filter by priority (0-4). assignee_id: Filter to tasks assigned to this user ID. search: Substring match on title. limit: Max rows to return (default 50, hard cap 100). Larger values are clamped silently. Use `offset` to walk past the first page. offset: Rows to skip from the start of the filtered list. Default 0. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `status` | No | string / null | See the schema below for constraints and defaults. | | `priority` | No | integer / null | See the schema below for constraints and defaults. | | `assignee_id` | No | string / null | See the schema below for constraints and defaults. | | `search` | No | string / null | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | | `offset` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "priority": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Priority" }, "assignee_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Assignee Id" }, "search": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Search" }, "limit": { "default": 50, "title": "Limit", "type": "integer" }, "offset": { "default": 0, "title": "Offset", "type": "integer" } }, "title": "g8_get_tasksArguments", "type": "object" } ``` </details> ## g8_get_tracking_snippet > Return the graph8 tracking snippet (p.js) with framework-specific install code. First step before any other graph8 integration: p.js must load on every page before g8.identify(), g8.track(), or progressive forms work. For CSR frameworks (React, Vue) it loads after hydration, so use window.g8?.track(). After installing, add g8.identify() on login/signup and g8.track() for key actions, then use g8_get_form_template for progressive data collection. Args: framework: Target framework - one of html, react, nextjs, vue, wordpress, webflow, shopify. repo_id: Optional connected-repository ID (from g8_repo_list_repos) for repo-specific config. Not a stream id. stream_id: Optional tracking stream ID (from g8_snippet_list_streams) whose write key to use. Without it the org's main stream is used: its only stream, or the one matching its website. If the result has action_required=true, the org has several streams and none matched its website: the write key is the FIRST stream's and may be the wrong site. Ask the user which site this is for, then call again with stream_id from available_streams before installing anything. Returns: TrackingSnippetResult, or a plain-string error envelope on invalid framework / API failure. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `framework` | Yes | string | See the schema below for constraints and defaults. | | `repo_id` | No | string / null | See the schema below for constraints and defaults. | | `stream_id` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "framework": { "title": "Framework", "type": "string" }, "repo_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Repo Id" }, "stream_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Stream Id" } }, "required": [ "framework" ], "title": "g8_get_tracking_snippetArguments", "type": "object" } ``` </details> ## g8_install_spine > Generate a GTM install patch set for a scanned repository. Produces code patches adding tracking, forms, identity resolution, and attribution. Review the patches before applying. Args: repo_id: Repository ID (must have scan results first) ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `repo_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "repo_id": { "title": "Repo Id", "type": "string" } }, "required": [ "repo_id" ], "title": "g8_install_spineArguments", "type": "object" } ``` </details> ## g8_knowledge_context > Assemble the smallest authorized, cited context pack for a task. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `task` | Yes | string | See the schema below for constraints and defaults. | | `entity_concept_ids` | No | array / null | See the schema below for constraints and defaults. | | `token_budget` | No | integer / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "task": { "title": "Task", "type": "string" }, "entity_concept_ids": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Entity Concept Ids" }, "token_budget": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Token Budget" } }, "required": [ "task" ], "title": "g8_knowledge_contextArguments", "type": "object" } ``` </details> ## g8_knowledge_export > Download authorized organization knowledge as a ZIP MCP embedded resource. Requires knowledge:export (or knowledge:admin) plus the existing knowledge read and MCP-channel gates. Contains only records authorized by the API. Maximum compressed archive size is 1 MiB; larger exports fail without truncation and must use the authenticated API download. No shared files or public download links are created. Decode resource.blob to save the ZIP. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_knowledge_exportArguments", "type": "object" } ``` </details> ## g8_knowledge_get > Read one authorized concept with Markdown, trust, sources, and warnings. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `concept_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "concept_id": { "title": "Concept Id", "type": "string" } }, "required": [ "concept_id" ], "title": "g8_knowledge_getArguments", "type": "object" } ``` </details> ## g8_knowledge_list > Browse authorized organization knowledge concepts. Git paths and organization selectors are intentionally unavailable; tenant identity comes from the authenticated graph8 connection. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `concept_type` | No | string / null | See the schema below for constraints and defaults. | | `lifecycle` | No | string / null | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "concept_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Concept Type" }, "lifecycle": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Lifecycle" }, "limit": { "default": 50, "title": "Limit", "type": "integer" } }, "title": "g8_knowledge_listArguments", "type": "object" } ``` </details> ## g8_knowledge_related > Traverse authorized typed relationships from one concept. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `concept_id` | Yes | string | See the schema below for constraints and defaults. | | `depth` | No | integer | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "concept_id": { "title": "Concept Id", "type": "string" }, "depth": { "default": 1, "title": "Depth", "type": "integer" }, "limit": { "default": 25, "title": "Limit", "type": "integer" } }, "required": [ "concept_id" ], "title": "g8_knowledge_relatedArguments", "type": "object" } ``` </details> ## g8_knowledge_search > Search authorized tenant knowledge and return concise cited matches. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `query` | Yes | string | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "query": { "title": "Query", "type": "string" }, "limit": { "default": 10, "title": "Limit", "type": "integer" } }, "required": [ "query" ], "title": "g8_knowledge_searchArguments", "type": "object" } ``` </details> ## g8_knowledge_sources > Read allowlisted evidence metadata for one authorized concept. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `concept_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "concept_id": { "title": "Concept Id", "type": "string" } }, "required": [ "concept_id" ], "title": "g8_knowledge_sourcesArguments", "type": "object" } ``` </details> ## g8_landing_page_create_landing_page_agent_session > Create Landing Page Agent Session. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /landing-pages/{page_id}/agent-sessions ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `page_id` | Yes | string | See the schema below for constraints and defaults. | | `body` | No | #/$defs/ApiCreateAgentSessionRequest / null | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "$defs": { "ApiCreateAgentSessionRequest": { "additionalProperties": false, "properties": { "approval_mode": { "default": "review", "description": "'review' (default) stages each patch for a human to approve in Studio. 'auto_apply' writes straight to the draft. Bound to the session at creation, so a later patch cannot escalate itself.", "pattern": "^(review|auto_apply)$", "title": "Approval Mode", "type": "string" } }, "title": "ApiCreateAgentSessionRequest", "type": "object" } }, "properties": { "page_id": { "title": "Page Id", "type": "string" }, "body": { "anyOf": [ { "$ref": "#/$defs/ApiCreateAgentSessionRequest" }, { "type": "null" } ], "default": null }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "page_id" ], "title": "g8_landing_page_create_landing_page_agent_sessionArguments", "type": "object" } ``` </details> ## g8_list_audience_syncs > List audience sync configs for your org (ad platforms: Meta, LinkedIn, Google Ads, X) with status, cadence, last sync time. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_list_audience_syncsArguments", "type": "object" } ``` </details> ## g8_list_bookings > List the caller's appointment bookings (paginated). Shows bookings the user owns, hosts, or shared a link for - never org-wide. Args: status: Filter by status - accepted \| pending \| cancelled \| rejected. event_type_id: Only bookings for this event type. after_start: ISO-8601 lower bound on start_time (e.g. "2026-06-01T00:00:00Z"). before_end: ISO-8601 upper bound on end_time. attendee_email: Filter to bookings including this attendee. sort_by: start_time \| end_time \| created_at \| status (default start_time). sort_order: asc \| desc (default desc). skip: Pagination offset (default 0). take: Page size, 1-100 (default 20). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `status` | No | string / null | See the schema below for constraints and defaults. | | `event_type_id` | No | integer / null | See the schema below for constraints and defaults. | | `after_start` | No | string / null | See the schema below for constraints and defaults. | | `before_end` | No | string / null | See the schema below for constraints and defaults. | | `attendee_email` | No | string / null | See the schema below for constraints and defaults. | | `sort_by` | No | string | See the schema below for constraints and defaults. | | `sort_order` | No | string | See the schema below for constraints and defaults. | | `skip` | No | integer | See the schema below for constraints and defaults. | | `take` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "event_type_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Event Type Id" }, "after_start": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "After Start" }, "before_end": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Before End" }, "attendee_email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Attendee Email" }, "sort_by": { "default": "start_time", "title": "Sort By", "type": "string" }, "sort_order": { "default": "desc", "title": "Sort Order", "type": "string" }, "skip": { "default": 0, "title": "Skip", "type": "integer" }, "take": { "default": 20, "title": "Take", "type": "integer" } }, "title": "g8_appointments_list_bookingsArguments", "type": "object" } ``` </details> ## g8_list_campaigns > List generated campaigns for a repository. Args: repo_id: Repository ID page: Page number (default 1) limit: Items per page (default 50, max 200) ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `repo_id` | Yes | string | See the schema below for constraints and defaults. | | `page` | No | integer | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "repo_id": { "title": "Repo Id", "type": "string" }, "page": { "default": 1, "title": "Page", "type": "integer" }, "limit": { "default": 50, "title": "Limit", "type": "integer" } }, "required": [ "repo_id" ], "title": "g8_list_campaignsArguments", "type": "object" } ``` </details> ## g8_list_crm_syncs > List configured CRM integrations for your org (HubSpot, Salesforce, Pipedrive, Zoho, SugarCRM) with connection status. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_list_crm_syncsArguments", "type": "object" } ``` </details> ## g8_list_enrichment_providers > List enrichment providers with their credential mode and credit cost. Use this before enriching to see which providers graph8 funds with its own keys (usable with no setup, charges credits) versus which need your own API key (BYOK). For each provider it returns: - system_funded: graph8 holds a system key for it (works out of the box). - byok_configured: your org has its own key configured for it. - default_credential_source: what credential_mode="auto" uses right now ("system" = graph8 key + credits, "byok" = your key, no credits). - credits_per_row: per-action credit cost on graph8 system credentials. Providers that are NOT system_funded (e.g. lusha, rocketreach) require a BYOK key under Settings > Providers, otherwise their step is skipped. Args: None. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_list_enrichment_providersArguments", "type": "object" } ``` </details> ## g8_list_event_types > List bookable calendar event types (Cal.com appointments). An event type is the "where do they book?" picker: a title, a slug, and a duration. Pass the row's integer id back as appointment_id on g8_gtm_launch_campaign to attach the booking link. Different from g8_list_meetings (the inbox of already-booked meetings); this is the picker for which booking template the sequence references. Args: include_hidden: Include event types marked hidden (not bookable publicly). Default False matches the in-app picker. scheduling_type: Filter by Cal.com scheduling type: managed \| round_robin \| collective. Omit to return all. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `include_hidden` | No | boolean | See the schema below for constraints and defaults. | | `scheduling_type` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "include_hidden": { "default": false, "title": "Include Hidden", "type": "boolean" }, "scheduling_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Scheduling Type" } }, "title": "list_event_typesArguments", "type": "object" } ``` </details> ## g8_list_fields > List the field definitions (base + custom columns) for contacts or companies. Use this to see which custom columns already exist before calling `g8_create_field`, or to discover the column `name` to reference in downstream APIs. Each field also has a human-readable `label` (e.g. "First name" for CONTACT_FIRST_NAME): show the `label` to users, but keep using `name` as the identifier. Args: entity: Target entity - "contacts" or "companies" (default: contacts) list_id: Optional list ID. When provided, includes custom columns scoped to that list in addition to global columns. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `entity` | No | string | See the schema below for constraints and defaults. | | `list_id` | No | integer / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "entity": { "default": "contacts", "title": "Entity", "type": "string" }, "list_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "List Id" } }, "title": "g8_list_fieldsArguments", "type": "object" } ``` </details> ## g8_list_hot_contacts > Most-ENGAGED ('hot') contacts by recent INBOUND activity. Ranks contacts by inbound interaction events (replies / opens / meeting RSVPs) over the window - NOT a static score. Use for "who are my hottest contacts" / "most engaged prospects". Org-scoped to your API key. Args: days: Lookback window in days (default 30, max 365). limit: Max contacts to return (default 10, max 50). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `days` | No | integer | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "days": { "default": 30, "title": "Days", "type": "integer" }, "limit": { "default": 10, "title": "Limit", "type": "integer" } }, "title": "g8_list_hot_contactsArguments", "type": "object" } ``` </details> ## g8_list_inbox > List inbox threads (email, SMS, LinkedIn) with tags, assignees, and status. Each thread carries `preview` (one line of the latest inbound message) and `contact.name`. Answer "what is in my inbox", "who replied" and "what did they say" questions FROM THIS RESULT - do not call g8_get_reply per thread just to see what a thread is about. Open a thread only when the user asks about that specific one or you need the full message history to draft a reply. Args: channel: Filter by channel (email, sms, linkedin). Omit for all channels. sequence_id: Filter by sequence/campaign ID. status: Filter by status. assignee: Filter by assignee email. tag: Filter by tag ID. page: Page number (default 1). page_size: Items per page (default 25, max 200). Raise it only when the user actually wants more than one screenful. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `channel` | No | string / null | See the schema below for constraints and defaults. | | `sequence_id` | No | string / null | See the schema below for constraints and defaults. | | `status` | No | string / null | See the schema below for constraints and defaults. | | `assignee` | No | string / null | See the schema below for constraints and defaults. | | `tag` | No | string / null | See the schema below for constraints and defaults. | | `page` | No | integer | See the schema below for constraints and defaults. | | `page_size` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "channel": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Channel" }, "sequence_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Sequence Id" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "assignee": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Assignee" }, "tag": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Tag" }, "page": { "default": 1, "title": "Page", "type": "integer" }, "page_size": { "default": 25, "title": "Page Size", "type": "integer" } }, "title": "g8_list_inboxArguments", "type": "object" } ``` </details> ## g8_list_kb_documents > List all documents in the GTM knowledge base. Args: repo_id: Repository ID ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `repo_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "repo_id": { "title": "Repo Id", "type": "string" } }, "required": [ "repo_id" ], "title": "g8_list_kb_documentsArguments", "type": "object" } ``` </details> ## g8_list_meetings > List calendar meetings as compact summary cards. Use for "show me meetings" or browsing the meeting inbox. Each card has title, time, attendees, and transcript status; transcript bodies are omitted - call g8_get_meeting(meeting_id) for the full transcript. Filter to one person via email, contact_id, or participant_emails (matched across organizer + owner + attendees, case-insensitive, OR). Args: email: Single email to filter by (organizer, owner, or attendee). contact_id: CRM contact ID, resolved to its work email and used as a participant filter. Combine with email to broaden (OR). participant_emails: Explicit list of emails to match (OR). scope: 'my' (default) or 'all' (org-wide, needs meetings:see_all). timeframe: 'all', 'upcoming', or 'past' (default 'past'). has_transcript: True = only with transcript, False = only without. Omit for all. include_internal: True = include internal-only meetings (team syncs, standups, same-domain attendees). Omit/False follows org policy (most orgs exclude internal). Pass True when the user asks for team meetings, internal standups, or 1:1s. search: Match against meeting title or transcript text. page: Page number (default 1). page_size: Items per page (default 50). Clamped to 25; use page to walk through more rows. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `email` | No | string / null | See the schema below for constraints and defaults. | | `contact_id` | No | integer / null | See the schema below for constraints and defaults. | | `participant_emails` | No | array / null | See the schema below for constraints and defaults. | | `scope` | No | string / null | See the schema below for constraints and defaults. | | `timeframe` | No | string / null | See the schema below for constraints and defaults. | | `has_transcript` | No | boolean / null | See the schema below for constraints and defaults. | | `include_internal` | No | boolean / null | See the schema below for constraints and defaults. | | `search` | No | string / null | See the schema below for constraints and defaults. | | `page` | No | integer | See the schema below for constraints and defaults. | | `page_size` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "contact_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Contact Id" }, "participant_emails": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Participant Emails" }, "scope": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Scope" }, "timeframe": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Timeframe" }, "has_transcript": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Has Transcript" }, "include_internal": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Include Internal" }, "search": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Search" }, "page": { "default": 1, "title": "Page", "type": "integer" }, "page_size": { "default": 50, "title": "Page Size", "type": "integer" } }, "title": "g8_list_meetingsArguments", "type": "object" } ``` </details> ## g8_list_notes > List all notes on a CRM contact, COMPANY or DEAL. Pass exactly ONE of contact_id / company_id / deal_id. Use `deal_id` to read a deal's note history - there is no separate "deal note" tool. Each note carries `read_only`. A `read_only` note came from a CRM import or the activity log, and g8_update_note / g8_delete_note cannot change it - do not offer to edit those. Backed by `GET /api/v1/{contacts\|companies\|deals}/{id}/notes`. Args: contact_id: Integer contact ID from g8_find_contacts. company_id: Integer company ID from g8_find_companies. deal_id: Deal UUID from g8_get_deals / g8_create_deal. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `contact_id` | No | integer / null | See the schema below for constraints and defaults. | | `company_id` | No | integer / null | See the schema below for constraints and defaults. | | `deal_id` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "contact_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Contact Id" }, "company_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Company Id" }, "deal_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Deal Id" } }, "title": "g8_list_notesArguments", "type": "object" } ``` </details> ## g8_list_orgs > List the organizations the signed-in user belongs to, marking the one currently in effect (`is_current`). Use when the user asks to switch orgs, asks which orgs they have, or when `action_required` is true (multiple orgs, none chosen) - present the options, ASK which to use before any write, then call g8_switch_org with the chosen org. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_list_orgsArguments", "type": "object" } ``` </details> ## g8_list_quotable_products > List products available for quote line items (Stripe + org manual catalog). Call before g8_create_quote / g8_update_quote to discover valid line-item ids; non-quotable products are rejected server-side. Each row's ``source`` tells you which id family to use: ``stripe`` -> stripe_product_id + stripe_price_id; ``manual`` -> manual_product_id. Args: currency: Optional 3-letter currency filter (e.g. USD) - drops prices not in this currency. page: 1-indexed page (default 1). limit: Rows per page (default 100, max 500). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `currency` | No | string / null | See the schema below for constraints and defaults. | | `page` | No | integer | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "currency": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Currency" }, "page": { "default": 1, "title": "Page", "type": "integer" }, "limit": { "default": 100, "title": "Limit", "type": "integer" } }, "title": "g8_list_quotable_productsArguments", "type": "object" } ``` </details> ## g8_list_quotes > List quotes in your org with optional status / company / deal / owner filters. Args: page: 1-indexed page (default 1). limit: Rows per page (default 50, max 200). status: Filter by lifecycle stage. One of: ``draft``, ``sent``, ``viewed``, ``accepted``, ``declined``, ``voided``, ``expired``, ``archived``. Omit to list every non-archived status. include_archived: When no status is given, also return archived quotes (default False, matching the Quotes page). mashup_company_id: Restrict to quotes attached to one company (mashup_companies.id, integer). deal_id: Restrict to quotes linked to one deal (UUID string). owner_id: Restrict to quotes owned by one user (email). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `page` | No | integer | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | | `status` | No | string / null | See the schema below for constraints and defaults. | | `mashup_company_id` | No | integer / null | See the schema below for constraints and defaults. | | `deal_id` | No | string / null | See the schema below for constraints and defaults. | | `owner_id` | No | string / null | See the schema below for constraints and defaults. | | `include_archived` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "page": { "default": 1, "title": "Page", "type": "integer" }, "limit": { "default": 50, "title": "Limit", "type": "integer" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "mashup_company_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Mashup Company Id" }, "deal_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Deal Id" }, "owner_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Owner Id" }, "include_archived": { "default": false, "title": "Include Archived", "type": "boolean" } }, "title": "g8_list_quotesArguments", "type": "object" } ``` </details> ## g8_list_schedules > List sending-window schedules the user can attach at launch. A schedule is the "when do we send?" picker: a name, a timezone, and per-day windows. Pass the row's id back as schedule_id on g8_gtm_launch_campaign. Use BEFORE launching whenever send timing matters; without it the sequence falls back to the org default window. Args: include_archived: When True, also include archived schedules (rare; default False matches the in-app picker). search: Substring filter on schedule name + description. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `include_archived` | No | boolean | See the schema below for constraints and defaults. | | `search` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "include_archived": { "default": false, "title": "Include Archived", "type": "boolean" }, "search": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Search" } }, "title": "list_schedulesArguments", "type": "object" } ``` </details> ## g8_list_sequence_steps > List a sequence's steps with their resolved copy (read-only). The step list from g8_get_sequence_preview without the sequence and channel envelope. Use it to verify what a specific step will actually send after writing spintax into it. Each step carries the same rendered block, so rendered.variants is the direct proof that per-contact variation works. Args: sequence_id: Sequence ID whose steps to list ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `sequence_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "sequence_id": { "title": "Sequence Id", "type": "string" } }, "required": [ "sequence_id" ], "title": "g8_list_sequence_stepsArguments", "type": "object" } ``` </details> ## g8_list_sequences > List available outbound sequences in your organization. Returns sequence IDs, names, status, and step counts. Use this to find a sequence_id before calling g8_add_to_sequence. Nurture sequences are ordinary sequences with sequence_kind="nurture"; pass sequence_kind="nurture" to list only those (or "cold_outbound" for standard outreach). Omit to list every kind. Args: page: Page number (default 1) limit: Items per page (default 50) sequence_kind: Optional filter by kind ('cold_outbound' or 'nurture'). Omit to list all. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `page` | No | integer | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | | `sequence_kind` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "page": { "default": 1, "title": "Page", "type": "integer" }, "limit": { "default": 50, "title": "Limit", "type": "integer" }, "sequence_kind": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Sequence Kind" } }, "title": "g8_list_sequencesArguments", "type": "object" } ``` </details> ## g8_lookup_company > Instantly look up a single company in graph8's data index. Provide at least one of: domain or name. Returns enriched data including revenue, headcount, industry, tech stack, funding, and a confidence score. Costs 2 credits per lookup, charged only when a match is found (found=false costs nothing). This searches external data - the company does NOT need to be in your CRM. Args: domain: Company domain (e.g. acme.com) name: Company name ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `domain` | No | string / null | See the schema below for constraints and defaults. | | `name` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "domain": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Domain" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" } }, "title": "g8_lookup_companyArguments", "type": "object" } ``` </details> ## g8_lookup_person > Instantly look up a single person in graph8's data index. Provide at least one of: email, linkedin_url, or (first_name + last_name + company_domain). Returns enriched data including verified email, phone, job title, company info, and a confidence score. Costs 2 credits per lookup, charged only when a match is found (found=false costs nothing). This searches external data - the person does NOT need to be in your CRM. To save them to your CRM afterward, use g8_create_contact. Args: email: Person's email address linkedin_url: Person's LinkedIn profile URL first_name: Person's first name (combine with last_name + company_domain) last_name: Person's last name company_domain: Person's company domain (e.g. acme.com) ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `email` | No | string / null | See the schema below for constraints and defaults. | | `linkedin_url` | No | string / null | See the schema below for constraints and defaults. | | `first_name` | No | string / null | See the schema below for constraints and defaults. | | `last_name` | No | string / null | See the schema below for constraints and defaults. | | `company_domain` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "linkedin_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Linkedin Url" }, "first_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "First Name" }, "last_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Name" }, "company_domain": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Company Domain" } }, "title": "g8_lookup_personArguments", "type": "object" } ``` </details> ## g8_meetings_booked > Count + recent list of BOOKED meetings (not cancelled/deleted/private). Use for "how many meetings did we book", "meetings this week", or "upcoming meetings" (set upcoming_only=true). Org-scoped to your API key. Metadata only - no transcript content; call g8_get_meeting for a transcript. The result's count is the real total; page past it with page until has_next is false. Args: days: Lookback window in days (default 30, max 365). limit: Max meetings per page (default 20, max 50). page: 1-based page over the booked list (default 1). upcoming_only: Only meetings from now forward (ignores days). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `days` | No | integer | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | | `page` | No | integer | See the schema below for constraints and defaults. | | `upcoming_only` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "days": { "default": 30, "title": "Days", "type": "integer" }, "limit": { "default": 20, "title": "Limit", "type": "integer" }, "page": { "default": 1, "title": "Page", "type": "integer" }, "upcoming_only": { "default": false, "title": "Upcoming Only", "type": "boolean" } }, "title": "g8_meetings_bookedArguments", "type": "object" } ``` </details> ## g8_newsletter_add_subscriber > Add a single subscriber to a newsletter (status=subscribed). Also creates/links a first-class CRM contact so the subscriber joins the central suppression model - mirrors the public subscribe form. Args: newsletter_id: Newsletter UUID. email: Subscriber email address (max 255 chars). first_name: Optional first name. last_name: Optional last name. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `newsletter_id` | Yes | string | See the schema below for constraints and defaults. | | `email` | Yes | string | See the schema below for constraints and defaults. | | `first_name` | No | string / null | See the schema below for constraints and defaults. | | `last_name` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "newsletter_id": { "title": "Newsletter Id", "type": "string" }, "email": { "title": "Email", "type": "string" }, "first_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "First Name" }, "last_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Name" } }, "required": [ "newsletter_id", "email" ], "title": "g8_newsletter_add_subscriberArguments", "type": "object" } ``` </details> ## g8_newsletter_create_issue > Create a draft newsletter issue. Always creates, never sends. Args: newsletter_id: Newsletter UUID to attach the issue to. subject: Email subject line (max 2000 chars). html_body: Full HTML email body. preview_text: Optional inbox preview snippet (max 500 chars). plain_text: Optional plain-text fallback body. sender_name: Optional From display name (max 255 chars). Falls back to the newsletter's default sender settings if omitted. sender_email: Optional From email address. Falls back to the newsletter's default sender settings if omitted. reply_to: Optional Reply-To email address. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `newsletter_id` | Yes | string | See the schema below for constraints and defaults. | | `subject` | Yes | string | See the schema below for constraints and defaults. | | `html_body` | Yes | string | See the schema below for constraints and defaults. | | `preview_text` | No | string / null | See the schema below for constraints and defaults. | | `plain_text` | No | string / null | See the schema below for constraints and defaults. | | `sender_name` | No | string / null | See the schema below for constraints and defaults. | | `sender_email` | No | string / null | See the schema below for constraints and defaults. | | `reply_to` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "newsletter_id": { "title": "Newsletter Id", "type": "string" }, "subject": { "title": "Subject", "type": "string" }, "html_body": { "title": "Html Body", "type": "string" }, "preview_text": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Preview Text" }, "plain_text": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Plain Text" }, "sender_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Sender Name" }, "sender_email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Sender Email" }, "reply_to": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Reply To" } }, "required": [ "newsletter_id", "subject", "html_body" ], "title": "g8_newsletter_create_issueArguments", "type": "object" } ``` </details> ## g8_newsletter_create_newsletter > Create a new newsletter - the parent container issues live under. Always starts in `draft` status. Call this before creating any issue. Args: name: Newsletter name (max 255 chars). goal: Optional free-text description of what this newsletter is for. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `name` | Yes | string | See the schema below for constraints and defaults. | | `goal` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "name": { "title": "Name", "type": "string" }, "goal": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Goal" } }, "required": [ "name" ], "title": "g8_newsletter_create_newsletterArguments", "type": "object" } ``` </details> ## g8_newsletter_duplicate_issue > Duplicate any issue (any status) into a new draft. Useful for recurring sends - clone last week's issue instead of rebuilding it from scratch. Args: newsletter_id: Newsletter UUID the source issue belongs to. issue_id: Source issue id. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `newsletter_id` | Yes | string | See the schema below for constraints and defaults. | | `issue_id` | Yes | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "newsletter_id": { "title": "Newsletter Id", "type": "string" }, "issue_id": { "title": "Issue Id", "type": "integer" } }, "required": [ "newsletter_id", "issue_id" ], "title": "g8_newsletter_duplicate_issueArguments", "type": "object" } ``` </details> ## g8_newsletter_get_issue > Get full issue detail: subject, html body, status, send stats. Args: newsletter_id: Newsletter UUID the issue belongs to. issue_id: Issue id from g8_newsletter_list_issues / g8_newsletter_create_issue. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `newsletter_id` | Yes | string | See the schema below for constraints and defaults. | | `issue_id` | Yes | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "newsletter_id": { "title": "Newsletter Id", "type": "string" }, "issue_id": { "title": "Issue Id", "type": "integer" } }, "required": [ "newsletter_id", "issue_id" ], "title": "g8_newsletter_get_issueArguments", "type": "object" } ``` </details> ## g8_newsletter_get_issue_analytics > Bucketed KPI counters for a sent issue: opens, clicks, bounces, unsubscribes. Args: newsletter_id: Newsletter UUID the issue belongs to. issue_id: Issue id (should be a sent issue - a draft has no engagement yet). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `newsletter_id` | Yes | string | See the schema below for constraints and defaults. | | `issue_id` | Yes | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "newsletter_id": { "title": "Newsletter Id", "type": "string" }, "issue_id": { "title": "Issue Id", "type": "integer" } }, "required": [ "newsletter_id", "issue_id" ], "title": "g8_newsletter_get_issue_analyticsArguments", "type": "object" } ``` </details> ## g8_newsletter_get_newsletter > Get a single newsletter (name, status, goal, audience list). Args: newsletter_id: Newsletter UUID from g8_newsletter_list_newsletters / g8_newsletter_create_newsletter. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `newsletter_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "newsletter_id": { "title": "Newsletter Id", "type": "string" } }, "required": [ "newsletter_id" ], "title": "g8_newsletter_get_newsletterArguments", "type": "object" } ``` </details> ## g8_newsletter_list_issues > List every issue (draft, scheduled, or sent) on a newsletter. Args: newsletter_id: Newsletter UUID. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `newsletter_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "newsletter_id": { "title": "Newsletter Id", "type": "string" } }, "required": [ "newsletter_id" ], "title": "g8_newsletter_list_issuesArguments", "type": "object" } ``` </details> ## g8_newsletter_list_newsletters > List every newsletter in your org. Call this first to discover a newsletter_id - every issue/subscriber tool below requires one. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_newsletter_list_newslettersArguments", "type": "object" } ``` </details> ## g8_newsletter_list_subscribers > Paginated subscriber list for a newsletter. Args: newsletter_id: Newsletter UUID. page: 1-indexed page (default 1). page_size: Rows per page (default 25, max 100). search: Optional free-text search over email/first/last name. status: Optional comma-separated status filter, e.g. 'subscribed,bounced'. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `newsletter_id` | Yes | string | See the schema below for constraints and defaults. | | `page` | No | integer | See the schema below for constraints and defaults. | | `page_size` | No | integer | See the schema below for constraints and defaults. | | `search` | No | string / null | See the schema below for constraints and defaults. | | `status` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "newsletter_id": { "title": "Newsletter Id", "type": "string" }, "page": { "default": 1, "title": "Page", "type": "integer" }, "page_size": { "default": 25, "title": "Page Size", "type": "integer" }, "search": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Search" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" } }, "required": [ "newsletter_id" ], "title": "g8_newsletter_list_subscribersArguments", "type": "object" } ``` </details> ## g8_newsletter_list_templates > List org-level newsletter templates (reusable HTML scaffolds). Use with g8_newsletter_create_issue by pasting a template's html_body, or build an issue from scratch without one. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_newsletter_list_templatesArguments", "type": "object" } ``` </details> ## g8_newsletter_send_issue > Send a newsletter issue to every subscribed, deliverable subscriber. Sends real customer-facing email. Not idempotent - calling twice sends twice. Returns immediately once the send is enqueued (202 semantics) - actual delivery happens asynchronously. Poll g8_newsletter_get_issue to observe completion, or g8_newsletter_get_issue_analytics once sent. Args: newsletter_id: Newsletter UUID the issue belongs to. issue_id: Issue id (must be a draft or already-scheduled issue). scheduled_at: Optional ISO-8601 timestamp to schedule the send for later. Omit to send immediately. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `newsletter_id` | Yes | string | See the schema below for constraints and defaults. | | `issue_id` | Yes | integer | See the schema below for constraints and defaults. | | `scheduled_at` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "newsletter_id": { "title": "Newsletter Id", "type": "string" }, "issue_id": { "title": "Issue Id", "type": "integer" }, "scheduled_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Scheduled At" } }, "required": [ "newsletter_id", "issue_id" ], "title": "g8_newsletter_send_issueArguments", "type": "object" } ``` </details> ## g8_newsletter_send_test > Send a test copy of an issue to ONE email address. Sends a real email, but never touches the subscriber list or issue status - safe to call repeatedly while drafting. Args: newsletter_id: Newsletter UUID the issue belongs to. issue_id: Issue id to preview. test_email: Address to send the test to. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `newsletter_id` | Yes | string | See the schema below for constraints and defaults. | | `issue_id` | Yes | integer | See the schema below for constraints and defaults. | | `test_email` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "newsletter_id": { "title": "Newsletter Id", "type": "string" }, "issue_id": { "title": "Issue Id", "type": "integer" }, "test_email": { "title": "Test Email", "type": "string" } }, "required": [ "newsletter_id", "issue_id", "test_email" ], "title": "g8_newsletter_send_testArguments", "type": "object" } ``` </details> ## g8_newsletter_unsubscribe_subscriber > Soft-unsubscribe a subscriber. Sets status, preserves the row - does NOT hard-delete. Args: newsletter_id: Newsletter UUID. subscriber_id: Subscriber id from g8_newsletter_list_subscribers. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `newsletter_id` | Yes | string | See the schema below for constraints and defaults. | | `subscriber_id` | Yes | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "newsletter_id": { "title": "Newsletter Id", "type": "string" }, "subscriber_id": { "title": "Subscriber Id", "type": "integer" } }, "required": [ "newsletter_id", "subscriber_id" ], "title": "g8_newsletter_unsubscribe_subscriberArguments", "type": "object" } ``` </details> ## g8_newsletter_update_issue > Update a draft issue (partial). Pass only the fields you want to change. Rejects with an error for issues no longer in a draft-editable state - use g8_newsletter_duplicate_issue to get a fresh editable copy of a sent issue instead. Args: see g8_newsletter_create_issue - same field meanings. issue_id: Issue id to update. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `newsletter_id` | Yes | string | See the schema below for constraints and defaults. | | `issue_id` | Yes | integer | See the schema below for constraints and defaults. | | `subject` | No | string / null | See the schema below for constraints and defaults. | | `html_body` | No | string / null | See the schema below for constraints and defaults. | | `preview_text` | No | string / null | See the schema below for constraints and defaults. | | `plain_text` | No | string / null | See the schema below for constraints and defaults. | | `sender_name` | No | string / null | See the schema below for constraints and defaults. | | `sender_email` | No | string / null | See the schema below for constraints and defaults. | | `reply_to` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "newsletter_id": { "title": "Newsletter Id", "type": "string" }, "issue_id": { "title": "Issue Id", "type": "integer" }, "subject": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Subject" }, "html_body": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Html Body" }, "preview_text": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Preview Text" }, "plain_text": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Plain Text" }, "sender_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Sender Name" }, "sender_email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Sender Email" }, "reply_to": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Reply To" } }, "required": [ "newsletter_id", "issue_id" ], "title": "g8_newsletter_update_issueArguments", "type": "object" } ``` </details> ## g8_object_archive > Archive a native object without deleting data. Requires objects:manage and Admin access; update can restore it. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `object_slug` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "object_slug": { "title": "Object Slug", "type": "string" } }, "required": [ "object_slug" ], "title": "g8_object_archiveArguments", "type": "object" } ``` </details> ## g8_object_attribute_archive > Archive a custom field without deleting its values; update can restore it. Requires objects:manage and Admin access. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `object_slug` | Yes | string | See the schema below for constraints and defaults. | | `attribute_slug` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "object_slug": { "title": "Object Slug", "type": "string" }, "attribute_slug": { "title": "Attribute Slug", "type": "string" } }, "required": [ "object_slug", "attribute_slug" ], "title": "g8_object_attribute_archiveArguments", "type": "object" } ``` </details> ## g8_object_attribute_create > Create a CRM field with slug, title, attribute_type and optional constraints/config/defaults. Requires objects:manage and current Admin access. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `object_slug` | Yes | string | See the schema below for constraints and defaults. | | `definition` | Yes | object | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "object_slug": { "title": "Object Slug", "type": "string" }, "definition": { "additionalProperties": true, "title": "Definition", "type": "object" } }, "required": [ "object_slug", "definition" ], "title": "g8_object_attribute_createArguments", "type": "object" } ``` </details> ## g8_object_attribute_update > Edit or restore a field using is_archived=false. Omitted fields are preserved; default_value=null clears the default. Type and slug are immutable. Requires objects:manage and Admin access. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `object_slug` | Yes | string | See the schema below for constraints and defaults. | | `attribute_slug` | Yes | string | See the schema below for constraints and defaults. | | `changes` | Yes | object | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "object_slug": { "title": "Object Slug", "type": "string" }, "attribute_slug": { "title": "Attribute Slug", "type": "string" }, "changes": { "additionalProperties": true, "title": "Changes", "type": "object" } }, "required": [ "object_slug", "attribute_slug", "changes" ], "title": "g8_object_attribute_updateArguments", "type": "object" } ``` </details> ## g8_object_attributes > Discover typed fields, constraints, configured defaults and reference targets. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `object_slug` | Yes | string | See the schema below for constraints and defaults. | | `include_archived` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "object_slug": { "title": "Object Slug", "type": "string" }, "include_archived": { "default": false, "title": "Include Archived", "type": "boolean" } }, "required": [ "object_slug" ], "title": "g8_object_attributesArguments", "type": "object" } ``` </details> ## g8_object_backup_create > Start a backup of everything this credential can read in the custom-object workspace (a job, 202). A key's backup is always restricted: exactly the objects, fields, links, reports and history it can read, with hidden fields absent. A restricted backup can be downloaded but never restored. Poll g8_object_backup_get until status is completed, then use g8_object_backup_download. 409 while another backup of the workspace runs; 503 backup_storage_unavailable when backup storage is not configured (no job is created); 404 while workspace backups are switched off. Requires explicit objects:read and objects:export. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_object_backup_createArguments", "type": "object" } ``` </details> ## g8_object_backup_download > Get a link to this credential's own backup archive (NDJSON), valid for five minutes, with filename, archive_bytes, archive_sha256 and format_version. The archive bytes are not sent through MCP: give the url to whatever downloads the file before expires_at, then check archive_sha256. The key is revalidated on every call (no cached grant); a key needs no recent sign-in, its explicit scopes are the gate. 403 when the key lacks objects:read or objects:export, or no longer reads everything in the archive (access_changed: start a new backup); 410 once the archive is deleted, 14 days after the backup was created; 409 while the backup is not completed. Requires explicit objects:read and objects:export. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `backup_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "backup_id": { "title": "Backup Id", "type": "string" } }, "required": [ "backup_id" ], "title": "g8_object_backup_downloadArguments", "type": "object" } ``` </details> ## g8_object_backup_get > Read one of this credential's workspace backups: status, scope, size, sha256, per-section counts, expiry and whether it is restorable. Requires explicit objects:export. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `backup_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "backup_id": { "title": "Backup Id", "type": "string" } }, "required": [ "backup_id" ], "title": "g8_object_backup_getArguments", "type": "object" } ``` </details> ## g8_object_backup_list > List this credential's workspace backups, newest first: status, scope, size, sha256, per-section counts, expiry, restorable. storage_configured false means starting a backup would answer 503. Limit 1-200. Requires explicit objects:export. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `limit` | No | integer | See the schema below for constraints and defaults. | | `offset` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "limit": { "default": 50, "title": "Limit", "type": "integer" }, "offset": { "default": 0, "title": "Offset", "type": "integer" } }, "title": "g8_object_backup_listArguments", "type": "object" } ``` </details> ## g8_object_board_groups > Read complete filtered board status counts, including empty stages. State requires layout=board and a single-value status groupBy. Requires objects:read; no writes. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `object_slug` | Yes | string | See the schema below for constraints and defaults. | | `state` | Yes | object | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "object_slug": { "title": "Object Slug", "type": "string" }, "state": { "additionalProperties": true, "title": "State", "type": "object" } }, "required": [ "object_slug", "state" ], "title": "g8_object_board_groupsArguments", "type": "object" } ``` </details> ## g8_object_board_records > Page a status group's records with revisions. Null selects No stage; limit 1-50, offset 0-1000000. Reuse exactly the groups query state. Requires objects:read; no writes. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `object_slug` | Yes | string | See the schema below for constraints and defaults. | | `state` | Yes | object | See the schema below for constraints and defaults. | | `group_value` | No | string / null | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | | `offset` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "object_slug": { "title": "Object Slug", "type": "string" }, "state": { "additionalProperties": true, "title": "State", "type": "object" }, "group_value": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Group Value" }, "limit": { "default": 20, "title": "Limit", "type": "integer" }, "offset": { "default": 0, "title": "Offset", "type": "integer" } }, "required": [ "object_slug", "state" ], "title": "g8_object_board_recordsArguments", "type": "object" } ``` </details> ## g8_object_create > Create a native custom object. Requires objects:manage and current Admin access; standard names are reserved. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `slug` | Yes | string | See the schema below for constraints and defaults. | | `singular_noun` | Yes | string | See the schema below for constraints and defaults. | | `plural_noun` | Yes | string | See the schema below for constraints and defaults. | | `description` | No | string / null | See the schema below for constraints and defaults. | | `icon` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "slug": { "title": "Slug", "type": "string" }, "singular_noun": { "title": "Singular Noun", "type": "string" }, "plural_noun": { "title": "Plural Noun", "type": "string" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "icon": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Icon" } }, "required": [ "slug", "singular_noun", "plural_noun" ], "title": "g8_object_createArguments", "type": "object" } ``` </details> ## g8_object_export > Export every matching custom record of a view as a CSV or typed JSON embedded file. state accepts version 1 view filters, search, sortBy/sortOrder, columns and includeArchived. Requires explicit objects:read and objects:export and the key's read grant, rechecked before delivery. CSV is spreadsheet-safe. At most 1 MiB here: a larger file fails without truncation; narrow the view or use the Developer API download. No public URL is created. Not a backup. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `object_slug` | Yes | string | See the schema below for constraints and defaults. | | `format` | No | string | See the schema below for constraints and defaults. | | `state` | No | object / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "object_slug": { "title": "Object Slug", "type": "string" }, "format": { "default": "csv", "enum": [ "csv", "json" ], "title": "Format", "type": "string" }, "state": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "State" } }, "required": [ "object_slug" ], "title": "g8_object_exportArguments", "type": "object" } ``` </details> ## g8_object_formula_validate > Dry-run type and cycle check of a typed JSON formula expression (a rollup is a rollup node); nothing is stored. Pass attribute_slug when editing an existing computed field and preview_record_id to evaluate it on one record (preview_rollups counts linked records each rollup kept). Fields or objects the key cannot see are unknown_field (valid false). Requires objects:read; no writes. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `object_slug` | Yes | string | See the schema below for constraints and defaults. | | `expression` | Yes | object | See the schema below for constraints and defaults. | | `attribute_slug` | No | string / null | See the schema below for constraints and defaults. | | `preview_record_id` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "object_slug": { "title": "Object Slug", "type": "string" }, "expression": { "additionalProperties": true, "title": "Expression", "type": "object" }, "attribute_slug": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Attribute Slug" }, "preview_record_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Preview Record Id" } }, "required": [ "object_slug", "expression" ], "title": "g8_object_formula_validateArguments", "type": "object" } ``` </details> ## g8_object_get > Read a native custom object's definition by its discovered API slug. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `object_slug` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "object_slug": { "title": "Object Slug", "type": "string" } }, "required": [ "object_slug" ], "title": "g8_object_getArguments", "type": "object" } ``` </details> ## g8_object_import_commit > Commit one previewed page (at most 100 rows) in one transaction: valid rows are imported, rows that do not validate are skipped and listed in skipped_rows (fetch them with g8_object_import_errors). Send the same revision, offset and limit, the preview's schema_fingerprint, and row_number, record_id and revision for every row. created, updated and skipped count this call; resending a committed page replays its receipts as already_imported and counts nothing twice. A 409 is never fixed by resending the same body: preview the page again. Requires objects:read and objects:write. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `object_slug` | Yes | string | See the schema below for constraints and defaults. | | `draft_id` | Yes | string | See the schema below for constraints and defaults. | | `revision` | Yes | integer | See the schema below for constraints and defaults. | | `schema_fingerprint` | Yes | string | See the schema below for constraints and defaults. | | `expected` | Yes | array | See the schema below for constraints and defaults. | | `offset` | No | integer | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "object_slug": { "title": "Object Slug", "type": "string" }, "draft_id": { "title": "Draft Id", "type": "string" }, "revision": { "title": "Revision", "type": "integer" }, "schema_fingerprint": { "title": "Schema Fingerprint", "type": "string" }, "expected": { "items": { "additionalProperties": true, "type": "object" }, "title": "Expected", "type": "array" }, "offset": { "default": 0, "title": "Offset", "type": "integer" }, "limit": { "default": 100, "title": "Limit", "type": "integer" } }, "required": [ "object_slug", "draft_id", "revision", "schema_fingerprint", "expected" ], "title": "g8_object_import_commitArguments", "type": "object" } ``` </details> ## g8_object_import_create > Upload a CSV once and save its setup: mapping from CSV header to field slug, mode create or upsert, and for upsert the unique matching field. Writes no records. The file is kept for 30 days or until every row is imported. Requires objects:read and objects:write. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `object_slug` | Yes | string | See the schema below for constraints and defaults. | | `filename` | Yes | string | See the schema below for constraints and defaults. | | `csv` | Yes | string | See the schema below for constraints and defaults. | | `mapping` | No | object / null | See the schema below for constraints and defaults. | | `mode` | No | string | See the schema below for constraints and defaults. | | `matching_attribute` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "object_slug": { "title": "Object Slug", "type": "string" }, "filename": { "title": "Filename", "type": "string" }, "csv": { "title": "Csv", "type": "string" }, "mapping": { "anyOf": [ { "additionalProperties": { "type": "string" }, "type": "object" }, { "type": "null" } ], "default": null, "title": "Mapping" }, "mode": { "default": "create", "enum": [ "create", "upsert" ], "title": "Mode", "type": "string" }, "matching_attribute": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Matching Attribute" } }, "required": [ "object_slug", "filename", "csv" ], "title": "g8_object_import_createArguments", "type": "object" } ``` </details> ## g8_object_import_delete > Delete an import's stored file and setup. Records already imported, their history and import receipts are kept. Requires objects:read and objects:write. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `object_slug` | Yes | string | See the schema below for constraints and defaults. | | `draft_id` | Yes | string | See the schema below for constraints and defaults. | | `revision` | Yes | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "object_slug": { "title": "Object Slug", "type": "string" }, "draft_id": { "title": "Draft Id", "type": "string" }, "revision": { "title": "Revision", "type": "integer" } }, "required": [ "object_slug", "draft_id", "revision" ], "title": "g8_object_import_deleteArguments", "type": "object" } ``` </details> ## g8_object_import_errors > Download skipped and rejected rows as CSV text. While the file is stored: the original columns plus row_number, column and error, ready to fix and re-upload. Afterwards: row_number, column and code only, never values. Requires objects:read and objects:write. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `object_slug` | Yes | string | See the schema below for constraints and defaults. | | `draft_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "object_slug": { "title": "Object Slug", "type": "string" }, "draft_id": { "title": "Draft Id", "type": "string" } }, "required": [ "object_slug", "draft_id" ], "title": "g8_object_import_errorsArguments", "type": "object" } ``` </details> ## g8_object_import_get > Read one of this credential's imports: setup, headers, revision, progress, status and up to five sample rows while the file is stored. Requires objects:read and objects:write. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `object_slug` | Yes | string | See the schema below for constraints and defaults. | | `draft_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "object_slug": { "title": "Object Slug", "type": "string" }, "draft_id": { "title": "Draft Id", "type": "string" } }, "required": [ "object_slug", "draft_id" ], "title": "g8_object_import_getArguments", "type": "object" } ``` </details> ## g8_object_import_inspect > Read a CSV's headers, five sample rows and row count without storing it. UTF-8 text, at most 2 MB, 5000 rows, 200 columns. Requires objects:read and objects:write. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `object_slug` | Yes | string | See the schema below for constraints and defaults. | | `csv` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "object_slug": { "title": "Object Slug", "type": "string" }, "csv": { "title": "Csv", "type": "string" } }, "required": [ "object_slug", "csv" ], "title": "g8_object_import_inspectArguments", "type": "object" } ``` </details> ## g8_object_import_list > List this credential's CSV imports for an object, newest first: filename, status, progress, error rows, whether the file is still stored. No cell values. Requires objects:read and objects:write. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `object_slug` | Yes | string | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | | `offset` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "object_slug": { "title": "Object Slug", "type": "string" }, "limit": { "default": 25, "title": "Limit", "type": "integer" }, "offset": { "default": 0, "title": "Offset", "type": "integer" } }, "required": [ "object_slug" ], "title": "g8_object_import_listArguments", "type": "object" } ``` </details> ## g8_object_import_preview > Preview up to 100 rows of a stored import: normalized values, per-row errors, upsert matches (record_id, revision) and rows already imported. Writes no records. Read every page and fix errors before committing. Empty cells are omitted; \N clears a field. Requires objects:read and objects:write. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `object_slug` | Yes | string | See the schema below for constraints and defaults. | | `draft_id` | Yes | string | See the schema below for constraints and defaults. | | `revision` | Yes | integer | See the schema below for constraints and defaults. | | `offset` | No | integer | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "object_slug": { "title": "Object Slug", "type": "string" }, "draft_id": { "title": "Draft Id", "type": "string" }, "revision": { "title": "Revision", "type": "integer" }, "offset": { "default": 0, "title": "Offset", "type": "integer" }, "limit": { "default": 100, "title": "Limit", "type": "integer" } }, "required": [ "object_slug", "draft_id", "revision" ], "title": "g8_object_import_previewArguments", "type": "object" } ``` </details> ## g8_object_import_update > Change an import's filename or setup. revision must be its current revision; a started import keeps its setup. The stored file never changes. Requires objects:read and objects:write. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `object_slug` | Yes | string | See the schema below for constraints and defaults. | | `draft_id` | Yes | string | See the schema below for constraints and defaults. | | `revision` | Yes | integer | See the schema below for constraints and defaults. | | `filename` | Yes | string | See the schema below for constraints and defaults. | | `mapping` | No | object / null | See the schema below for constraints and defaults. | | `mode` | No | string | See the schema below for constraints and defaults. | | `matching_attribute` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "object_slug": { "title": "Object Slug", "type": "string" }, "draft_id": { "title": "Draft Id", "type": "string" }, "revision": { "title": "Revision", "type": "integer" }, "filename": { "title": "Filename", "type": "string" }, "mapping": { "anyOf": [ { "additionalProperties": { "type": "string" }, "type": "object" }, { "type": "null" } ], "default": null, "title": "Mapping" }, "mode": { "default": "create", "enum": [ "create", "upsert" ], "title": "Mode", "type": "string" }, "matching_attribute": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Matching Attribute" } }, "required": [ "object_slug", "draft_id", "revision", "filename" ], "title": "g8_object_import_updateArguments", "type": "object" } ``` </details> ## g8_object_list > Discover native custom CRM objects, optionally including archived definitions without restoring them. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `include_archived` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "include_archived": { "default": false, "title": "Include Archived", "type": "boolean" } }, "title": "g8_object_listArguments", "type": "object" } ``` </details> ## g8_object_process_list_archive > Archive a process list without deleting its entries; restore with g8_object_process_list_update is_archived=false. Requires objects:manage and current Admin access. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `slug` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "slug": { "title": "Slug", "type": "string" } }, "required": [ "slug" ], "title": "g8_object_process_list_archiveArguments", "type": "object" } ``` </details> ## g8_object_process_list_create > Create a process list: its object, its parent relationship and the list marker, atomically. parent_kind is fixed for life; a custom parent needs parent_object_id, a native kind takes none. parent_label is what an entry calls its parent, entries_label what the parent calls its entries. Add fields with g8_object_attribute_create on api_slug. Requires objects:manage and current Admin access. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `api_slug` | Yes | string | See the schema below for constraints and defaults. | | `singular_noun` | Yes | string | See the schema below for constraints and defaults. | | `plural_noun` | Yes | string | See the schema below for constraints and defaults. | | `parent_kind` | Yes | string | See the schema below for constraints and defaults. | | `parent_label` | Yes | string | See the schema below for constraints and defaults. | | `entries_label` | Yes | string | See the schema below for constraints and defaults. | | `parent_object_id` | No | string / null | See the schema below for constraints and defaults. | | `description` | No | string / null | See the schema below for constraints and defaults. | | `icon` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "api_slug": { "title": "Api Slug", "type": "string" }, "singular_noun": { "title": "Singular Noun", "type": "string" }, "plural_noun": { "title": "Plural Noun", "type": "string" }, "parent_kind": { "enum": [ "custom", "contacts", "companies", "deals" ], "title": "Parent Kind", "type": "string" }, "parent_label": { "title": "Parent Label", "type": "string" }, "entries_label": { "title": "Entries Label", "type": "string" }, "parent_object_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Parent Object Id" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "icon": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Icon" } }, "required": [ "api_slug", "singular_noun", "plural_noun", "parent_kind", "parent_label", "entries_label" ], "title": "g8_object_process_list_createArguments", "type": "object" } ``` </details> ## g8_object_process_list_entry_create > Create one entry and link it to its parent record, atomically; the entry id differs from the parent id and a parent may hold many entries. expected_parent_revision guards a concurrent parent change (409). A retry with the same idempotency_key within 24 hours returns the original entry (replayed true); reusing a key for a different request is a 409. Requires objects:read and objects:write, plus the native kind's read and write scopes for a contact, company or deal parent. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `slug` | Yes | string | See the schema below for constraints and defaults. | | `parent_record_id` | Yes | string | See the schema below for constraints and defaults. | | `values` | No | object / null | See the schema below for constraints and defaults. | | `expected_parent_revision` | No | integer / null | See the schema below for constraints and defaults. | | `idempotency_key` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "slug": { "title": "Slug", "type": "string" }, "parent_record_id": { "title": "Parent Record Id", "type": "string" }, "values": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Values" }, "expected_parent_revision": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Expected Parent Revision" }, "idempotency_key": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Idempotency Key" } }, "required": [ "slug", "parent_record_id" ], "title": "g8_object_process_list_entry_createArguments", "type": "object" } ``` </details> ## g8_object_process_list_get > Read one process list, including its parent binding (parent_kind, parent_object_slug, labels). Requires objects:read. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `slug` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "slug": { "title": "Slug", "type": "string" } }, "required": [ "slug" ], "title": "g8_object_process_list_getArguments", "type": "object" } ``` </details> ## g8_object_process_list_list > List the process lists this credential may read (a list is a custom object whose entries each point at ONE parent record: a custom record, contact, company or deal). Backing objects are not in g8_object_list. Requires objects:read. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `include_archived` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "include_archived": { "default": false, "title": "Include Archived", "type": "boolean" } }, "title": "g8_object_process_list_listArguments", "type": "object" } ``` </details> ## g8_object_process_list_update > Rename, describe or archive a list: singular_noun, plural_noun, description, icon, display_attribute_slug, is_archived (false restores). Omitted keys are preserved. The parent binding is never accepted. Requires objects:manage and current Admin access. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `slug` | Yes | string | See the schema below for constraints and defaults. | | `changes` | Yes | object | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "slug": { "title": "Slug", "type": "string" }, "changes": { "additionalProperties": true, "title": "Changes", "type": "object" } }, "required": [ "slug", "changes" ], "title": "g8_object_process_list_updateArguments", "type": "object" } ``` </details> ## g8_object_record_archive > Archive a custom record, hiding it from listings while retaining its identity and history. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `object_slug` | Yes | string | See the schema below for constraints and defaults. | | `record_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "object_slug": { "title": "Object Slug", "type": "string" }, "record_id": { "title": "Record Id", "type": "string" } }, "required": [ "object_slug", "record_id" ], "title": "g8_object_record_archiveArguments", "type": "object" } ``` </details> ## g8_object_record_changes > Read custom-record or canonical contact/company mutation facts. Limit 1-200; pass next_cursor to continue. Contact/company IDs stay canonical; current record access and field privacy apply to historical values. Archived records and older unrecorded mutations are not supplied by this reader. An unavailable store fails with 503 rather than reporting zero history. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `object_slug` | Yes | string | See the schema below for constraints and defaults. | | `record_id` | Yes | string | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | | `cursor` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "object_slug": { "title": "Object Slug", "type": "string" }, "record_id": { "title": "Record Id", "type": "string" }, "limit": { "default": 100, "title": "Limit", "type": "integer" }, "cursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Cursor" } }, "required": [ "object_slug", "record_id" ], "title": "g8_object_record_changesArguments", "type": "object" } ``` </details> ## g8_object_record_create > Create a native custom record. Discover attributes first; schema validation and scoped access apply. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `object_slug` | Yes | string | See the schema below for constraints and defaults. | | `values` | Yes | object | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "object_slug": { "title": "Object Slug", "type": "string" }, "values": { "additionalProperties": true, "title": "Values", "type": "object" } }, "required": [ "object_slug", "values" ], "title": "g8_object_record_createArguments", "type": "object" } ``` </details> ## g8_object_record_get > Read one native custom record, canonical contact/company, or deal using its stable graph8 ID. Contacts and companies use canonical IDs, not source CONTACT_ID/COMPANY_ID values. Deals retain UUIDs; company, primary-contact and contact_ids references require related-record access. Generic deal mutations are not yet supported. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `object_slug` | Yes | string | See the schema below for constraints and defaults. | | `record_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "object_slug": { "title": "Object Slug", "type": "string" }, "record_id": { "title": "Record Id", "type": "string" } }, "required": [ "object_slug", "record_id" ], "title": "g8_object_record_getArguments", "type": "object" } ``` </details> ## g8_object_record_history > Read historical value generations and actor attribution, newest first. Limit 1-500. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `object_slug` | Yes | string | See the schema below for constraints and defaults. | | `record_id` | Yes | string | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "object_slug": { "title": "Object Slug", "type": "string" }, "record_id": { "title": "Record Id", "type": "string" }, "limit": { "default": 100, "title": "Limit", "type": "integer" } }, "required": [ "object_slug", "record_id" ], "title": "g8_object_record_historyArguments", "type": "object" } ``` </details> ## g8_object_record_link > Link from a custom record or from a canonical contact, company or deal. expected_source_revision is this route's record revision and expected_target_revision the selected record's; read both records and the definition first - a stale revision is a 409 and nothing changes. The response is in the definition's stored direction. Needs objects write plus the native endpoint's read and write scopes. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `object_slug` | Yes | string | See the schema below for constraints and defaults. | | `record_id` | Yes | string | See the schema below for constraints and defaults. | | `definition_id` | Yes | string | See the schema below for constraints and defaults. | | `target_record_id` | Yes | string | See the schema below for constraints and defaults. | | `expected_source_revision` | Yes | integer | See the schema below for constraints and defaults. | | `expected_target_revision` | Yes | integer | See the schema below for constraints and defaults. | | `expected_definition_revision` | Yes | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "object_slug": { "title": "Object Slug", "type": "string" }, "record_id": { "title": "Record Id", "type": "string" }, "definition_id": { "title": "Definition Id", "type": "string" }, "target_record_id": { "title": "Target Record Id", "type": "string" }, "expected_source_revision": { "title": "Expected Source Revision", "type": "integer" }, "expected_target_revision": { "title": "Expected Target Revision", "type": "integer" }, "expected_definition_revision": { "title": "Expected Definition Revision", "type": "integer" } }, "required": [ "object_slug", "record_id", "definition_id", "target_record_id", "expected_source_revision", "expected_target_revision", "expected_definition_revision" ], "title": "g8_object_record_linkArguments", "type": "object" } ``` </details> ## g8_object_record_relationships > Read a record's visible related records in either direction. Needs objects:read plus each native endpoint's read scope; far ends the credential cannot see are omitted before paging. IDs are canonical. Limit 1-100; use next_offset to continue. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `object_slug` | Yes | string | See the schema below for constraints and defaults. | | `record_id` | Yes | string | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | | `offset` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "object_slug": { "title": "Object Slug", "type": "string" }, "record_id": { "title": "Record Id", "type": "string" }, "limit": { "default": 50, "title": "Limit", "type": "integer" }, "offset": { "default": 0, "title": "Offset", "type": "integer" } }, "required": [ "object_slug", "record_id" ], "title": "g8_object_record_relationshipsArguments", "type": "object" } ``` </details> ## g8_object_record_restore > Restore an archived custom record under current schema and unique constraints. Conflicts preserve the archive. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `object_slug` | Yes | string | See the schema below for constraints and defaults. | | `record_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "object_slug": { "title": "Object Slug", "type": "string" }, "record_id": { "title": "Record Id", "type": "string" } }, "required": [ "object_slug", "record_id" ], "title": "g8_object_record_restoreArguments", "type": "object" } ``` </details> ## g8_object_record_unlink > Remove one relationship edge and keep both records. Address it from either end (custom source or native record), name the edge, the definition and the other record, and pass the current revisions of both records and the definition; a stale request fails with 409 and removes nothing. Needs the same scopes as linking. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `object_slug` | Yes | string | See the schema below for constraints and defaults. | | `record_id` | Yes | string | See the schema below for constraints and defaults. | | `relationship_id` | Yes | string | See the schema below for constraints and defaults. | | `definition_id` | Yes | string | See the schema below for constraints and defaults. | | `target_record_id` | Yes | string | See the schema below for constraints and defaults. | | `expected_source_revision` | Yes | integer | See the schema below for constraints and defaults. | | `expected_target_revision` | Yes | integer | See the schema below for constraints and defaults. | | `expected_definition_revision` | Yes | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "object_slug": { "title": "Object Slug", "type": "string" }, "record_id": { "title": "Record Id", "type": "string" }, "relationship_id": { "title": "Relationship Id", "type": "string" }, "definition_id": { "title": "Definition Id", "type": "string" }, "target_record_id": { "title": "Target Record Id", "type": "string" }, "expected_source_revision": { "title": "Expected Source Revision", "type": "integer" }, "expected_target_revision": { "title": "Expected Target Revision", "type": "integer" }, "expected_definition_revision": { "title": "Expected Definition Revision", "type": "integer" } }, "required": [ "object_slug", "record_id", "relationship_id", "definition_id", "target_record_id", "expected_source_revision", "expected_target_revision", "expected_definition_revision" ], "title": "g8_object_record_unlinkArguments", "type": "object" } ``` </details> ## g8_object_record_update > PATCH a custom record, canonical contact or company. Omitted fields stay unchanged; null clears optional values; arrays replace values. Contact/company updates require expected_revision from a fresh read. Custom records also support append_values/remove_values; field groups cannot overlap. Stale revisions return 409. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `object_slug` | Yes | string | See the schema below for constraints and defaults. | | `record_id` | Yes | string | See the schema below for constraints and defaults. | | `values` | Yes | object | See the schema below for constraints and defaults. | | `expected_revision` | No | integer / null | See the schema below for constraints and defaults. | | `append_values` | No | object / null | See the schema below for constraints and defaults. | | `remove_values` | No | object / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "object_slug": { "title": "Object Slug", "type": "string" }, "record_id": { "title": "Record Id", "type": "string" }, "values": { "additionalProperties": true, "title": "Values", "type": "object" }, "expected_revision": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Expected Revision" }, "append_values": { "anyOf": [ { "additionalProperties": { "items": {}, "type": "array" }, "type": "object" }, { "type": "null" } ], "default": null, "title": "Append Values" }, "remove_values": { "anyOf": [ { "additionalProperties": { "items": {}, "type": "array" }, "type": "object" }, { "type": "null" } ], "default": null, "title": "Remove Values" } }, "required": [ "object_slug", "record_id", "values" ], "title": "g8_object_record_updateArguments", "type": "object" } ``` </details> ## g8_object_record_upsert > Create or update by an active single-value unique attribute. Supply its value in values. Omitted fields are preserved. A revision requires an existing, unchanged match. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `object_slug` | Yes | string | See the schema below for constraints and defaults. | | `matching_attribute` | Yes | string | See the schema below for constraints and defaults. | | `values` | Yes | object | See the schema below for constraints and defaults. | | `expected_revision` | No | integer / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "object_slug": { "title": "Object Slug", "type": "string" }, "matching_attribute": { "title": "Matching Attribute", "type": "string" }, "values": { "additionalProperties": true, "title": "Values", "type": "object" }, "expected_revision": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Expected Revision" } }, "required": [ "object_slug", "matching_attribute", "values" ], "title": "g8_object_record_upsertArguments", "type": "object" } ``` </details> ## g8_object_records > List custom records or canonical deals with current values and revisions. Deal totals and related references respect current access. Pass next_cursor for the next page. Limit 1-200. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `object_slug` | Yes | string | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | | `cursor` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "object_slug": { "title": "Object Slug", "type": "string" }, "limit": { "default": 50, "title": "Limit", "type": "integer" }, "cursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Cursor" } }, "required": [ "object_slug" ], "title": "g8_object_recordsArguments", "type": "object" } ``` </details> ## g8_object_records_bulk_update > Apply the same values to many custom records. Exactly one scope: up to 500 record_ids, or filters (this object's record-filter shape, resolved server-side, at most 500 per call - pass after_record_id to continue a scope reported as truncated). Per-record failures are reported in the response, not raised. Needs objects read and write; custom objects only (contacts, companies and deals answer 409). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `object_slug` | Yes | string | See the schema below for constraints and defaults. | | `values` | Yes | object | See the schema below for constraints and defaults. | | `record_ids` | No | array / null | See the schema below for constraints and defaults. | | `filters` | No | array / null | See the schema below for constraints and defaults. | | `after_record_id` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "object_slug": { "title": "Object Slug", "type": "string" }, "values": { "additionalProperties": true, "title": "Values", "type": "object" }, "record_ids": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Record Ids" }, "filters": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Filters" }, "after_record_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "After Record Id" } }, "required": [ "object_slug", "values" ], "title": "g8_object_records_bulk_updateArguments", "type": "object" } ``` </details> ## g8_object_records_merge > Merge the secondary custom record into the primary: the primary keeps its identity, the secondary is archived (never hard-deleted) and its relationships and associations are repointed with dedupe. field_overrides choose the secondary's value for named attributes ({attribute_slug, winner}); expected revisions make the merge conditional (409 when stale). Needs objects write and delete; custom objects only. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `object_slug` | Yes | string | See the schema below for constraints and defaults. | | `primary_record_id` | Yes | string | See the schema below for constraints and defaults. | | `secondary_record_id` | Yes | string | See the schema below for constraints and defaults. | | `field_overrides` | No | array / null | See the schema below for constraints and defaults. | | `expected_primary_revision` | No | integer / null | See the schema below for constraints and defaults. | | `expected_secondary_revision` | No | integer / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "object_slug": { "title": "Object Slug", "type": "string" }, "primary_record_id": { "title": "Primary Record Id", "type": "string" }, "secondary_record_id": { "title": "Secondary Record Id", "type": "string" }, "field_overrides": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Field Overrides" }, "expected_primary_revision": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Expected Primary Revision" }, "expected_secondary_revision": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Expected Secondary Revision" } }, "required": [ "object_slug", "primary_record_id", "secondary_record_id" ], "title": "g8_object_records_mergeArguments", "type": "object" } ``` </details> ## g8_object_relationship_definitions > Discover the relationship definitions involving a custom object or a native endpoint (contacts, companies, deals): reciprocal labels, target kind/object, cardinality and definition revisions. Link from the custom source or from the native record using these ids; limit 1-100. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `object_slug` | Yes | string | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | | `offset` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "object_slug": { "title": "Object Slug", "type": "string" }, "limit": { "default": 50, "title": "Limit", "type": "integer" }, "offset": { "default": 0, "title": "Offset", "type": "integer" } }, "required": [ "object_slug" ], "title": "g8_object_relationship_definitionsArguments", "type": "object" } ``` </details> ## g8_object_report_archive > Delete (archive) a report this credential owns. The source's records are untouched. Requires objects:read and objects:delete. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `report_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "report_id": { "title": "Report Id", "type": "string" } }, "required": [ "report_id" ], "title": "g8_object_report_archiveArguments", "type": "object" } ``` </details> ## g8_object_report_create > Save a report owned by this credential over a custom object or process list slug. config holds filters (up to 10), group_by (up to 3), measures (up to 10: count, sum, avg, min, max, related_count), and for funnel or history charts a stage (attribute_slug and ordered stages) and range, plus an optional IANA timezone. Fields the credential cannot see are unknown. A shared report (visibility org) needs objects:manage and current Admin access. Requires objects:read and objects:write. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `title` | Yes | string | See the schema below for constraints and defaults. | | `object_slug` | Yes | string | See the schema below for constraints and defaults. | | `chart_type` | Yes | string | See the schema below for constraints and defaults. | | `config` | Yes | object | See the schema below for constraints and defaults. | | `description` | No | string / null | See the schema below for constraints and defaults. | | `visibility` | No | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "title": { "title": "Title", "type": "string" }, "object_slug": { "title": "Object Slug", "type": "string" }, "chart_type": { "enum": [ "table", "bar", "line", "funnel", "stage_over_time", "time_in_stage" ], "title": "Chart Type", "type": "string" }, "config": { "additionalProperties": true, "title": "Config", "type": "object" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "visibility": { "default": "private", "enum": [ "private", "org" ], "title": "Visibility", "type": "string" } }, "required": [ "title", "object_slug", "chart_type", "config" ], "title": "g8_object_report_createArguments", "type": "object" } ``` </details> ## g8_object_report_execute > Run a saved report as this credential, now or as of a YYYY-MM-DD date. drilldown opens the records behind one number: {"target": <a result row's drilldown, unchanged>, "limit": 1-200, "after_record_id": <next_after_record_id>}. Archived source 409, config the schema no longer fits 422, over 10 s 504, 429 too_many_report_runs. Requires objects:read; no writes. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `report_id` | Yes | string | See the schema below for constraints and defaults. | | `as_of` | No | string / null | See the schema below for constraints and defaults. | | `drilldown` | No | object / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "report_id": { "title": "Report Id", "type": "string" }, "as_of": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "As Of" }, "drilldown": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Drilldown" } }, "required": [ "report_id" ], "title": "g8_object_report_executeArguments", "type": "object" } ``` </details> ## g8_object_report_get > Read one report definition. A part this credential cannot see comes back as {"id": ..., "restricted": true} with no field name. Requires objects:read. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `report_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "report_id": { "title": "Report Id", "type": "string" } }, "required": [ "report_id" ], "title": "g8_object_report_getArguments", "type": "object" } ``` </details> ## g8_object_report_list > List the reports this credential may open: its own and shared ones whose source it can read. Filter by source object_slug, owner me or others, visibility private or org. can_share says whether it may save a shared report. Limit 1-200. Requires objects:read. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `object_slug` | No | string / null | See the schema below for constraints and defaults. | | `owner` | No | string / null | See the schema below for constraints and defaults. | | `visibility` | No | string / null | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | | `offset` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "object_slug": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Object Slug" }, "owner": { "anyOf": [ { "enum": [ "me", "others" ], "type": "string" }, { "type": "null" } ], "default": null, "title": "Owner" }, "visibility": { "anyOf": [ { "enum": [ "private", "org" ], "type": "string" }, { "type": "null" } ], "default": null, "title": "Visibility" }, "limit": { "default": 50, "title": "Limit", "type": "integer" }, "offset": { "default": 0, "title": "Offset", "type": "integer" } }, "title": "g8_object_report_listArguments", "type": "object" } ``` </details> ## g8_object_report_preview > Run an unsaved report (the same config as g8_object_report_create) with this credential's own access and a 10 second budget; nothing is stored. as_of is a YYYY-MM-DD date in the report's time zone. A restricted group-by, filter or stage withholds the whole result (status restricted). 429 too_many_report_runs when the organization's run slots are taken. Requires objects:read; no writes. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `object_slug` | Yes | string | See the schema below for constraints and defaults. | | `chart_type` | Yes | string | See the schema below for constraints and defaults. | | `config` | Yes | object | See the schema below for constraints and defaults. | | `as_of` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "object_slug": { "title": "Object Slug", "type": "string" }, "chart_type": { "enum": [ "table", "bar", "line", "funnel", "stage_over_time", "time_in_stage" ], "title": "Chart Type", "type": "string" }, "config": { "additionalProperties": true, "title": "Config", "type": "object" }, "as_of": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "As Of" } }, "required": [ "object_slug", "chart_type", "config" ], "title": "g8_object_report_previewArguments", "type": "object" } ``` </details> ## g8_object_report_update > Edit a report this credential owns: title, description, chart_type, config, visibility. Omitted keys are unchanged (a shared report stays shared). Pass expected_revision to refuse a concurrent edit (409). Requires objects:read and objects:write; sharing also needs objects:manage and current Admin access. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `report_id` | Yes | string | See the schema below for constraints and defaults. | | `changes` | Yes | object | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "report_id": { "title": "Report Id", "type": "string" }, "changes": { "additionalProperties": true, "title": "Changes", "type": "object" } }, "required": [ "report_id", "changes" ], "title": "g8_object_report_updateArguments", "type": "object" } ``` </details> ## g8_object_restore_create > Restore one completed backup of this workspace, additively, as this credential (202, a restore job). DRY RUN BY DEFAULT: dry_run=true runs every check and rolls everything back, returning exact counts and every conflict with nothing written. A real restore needs dry_run=false AND confirm=true; without confirm nothing is sent and the call answers status confirmation_required. Run the dry run first, show the user its counts and conflicts, then confirm. A real restore matches rows by id: a missing record or report is added and an existing one is updated to the backup's values where they differ; objects, fields, relationships, process lists, grants and links are only added; nothing is deleted. It reruns every write check with this key's own write grants and recomputes computed values. Follow the job with g8_object_restore_get. Only a complete backup (made by a workspace administrator in the app's Workspace backup settings, which show its id) can be restored; a key's own backups are restricted (409 backup_not_restorable). 410 once the backup expired; one restore at a time per workspace (409 restore_in_progress). Requires an explicit objects:restore grant and current Admin access. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `backup_id` | Yes | string | See the schema below for constraints and defaults. | | `dry_run` | No | boolean | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "backup_id": { "title": "Backup Id", "type": "string" }, "dry_run": { "default": true, "title": "Dry Run", "type": "boolean" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "backup_id" ], "title": "g8_object_restore_createArguments", "type": "object" } ``` </details> ## g8_object_restore_get > Read one restore this credential started: status, per-section counts (created, updated, unchanged, conflicts, skipped) and one page of its receipt, every conflict as section, id, field slug, why and fix, never a value. conflict_limit 1-1000. Requires an explicit objects:restore grant and current Admin access. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `restore_id` | Yes | string | See the schema below for constraints and defaults. | | `conflict_offset` | No | integer | See the schema below for constraints and defaults. | | `conflict_limit` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "restore_id": { "title": "Restore Id", "type": "string" }, "conflict_offset": { "default": 0, "title": "Conflict Offset", "type": "integer" }, "conflict_limit": { "default": 200, "title": "Conflict Limit", "type": "integer" } }, "required": [ "restore_id" ], "title": "g8_object_restore_getArguments", "type": "object" } ``` </details> ## g8_object_restore_list > List this credential's workspace restores, newest first: status, dry_run and progress, without the receipt (read one restore for that). Limit 1-50. Requires an explicit objects:restore grant and current Admin access. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `limit` | No | integer | See the schema below for constraints and defaults. | | `offset` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "limit": { "default": 10, "title": "Limit", "type": "integer" }, "offset": { "default": 0, "title": "Offset", "type": "integer" } }, "title": "g8_object_restore_listArguments", "type": "object" } ``` </details> ## g8_object_update > Update a native object; slug/data are preserved. presentation accepts description, icon, display_attribute_slug; omit keys to preserve or use null to clear. Requires objects:manage and Admin access. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `object_slug` | Yes | string | See the schema below for constraints and defaults. | | `singular_noun` | No | string / null | See the schema below for constraints and defaults. | | `plural_noun` | No | string / null | See the schema below for constraints and defaults. | | `is_archived` | No | boolean / null | See the schema below for constraints and defaults. | | `presentation` | No | object / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "object_slug": { "title": "Object Slug", "type": "string" }, "singular_noun": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Singular Noun" }, "plural_noun": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Plural Noun" }, "is_archived": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Archived" }, "presentation": { "anyOf": [ { "additionalProperties": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "type": "object" }, { "type": "null" } ], "default": null, "title": "Presentation" } }, "required": [ "object_slug" ], "title": "g8_object_updateArguments", "type": "object" } ``` </details> ## g8_object_view_create > Save a credential-owned view. State version 1 supports layout table/board, filters, search, sortBy/sortOrder, columns, groupBy and includeArchived. Visibility is private or org; sharing a view never grants data access. Requires objects:read/write. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `object_slug` | Yes | string | See the schema below for constraints and defaults. | | `title` | Yes | string | See the schema below for constraints and defaults. | | `state` | Yes | object | See the schema below for constraints and defaults. | | `visibility` | No | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "object_slug": { "title": "Object Slug", "type": "string" }, "title": { "title": "Title", "type": "string" }, "state": { "additionalProperties": true, "title": "State", "type": "object" }, "visibility": { "default": "private", "title": "Visibility", "type": "string" } }, "required": [ "object_slug", "title", "state" ], "title": "g8_object_view_createArguments", "type": "object" } ``` </details> ## g8_object_view_default > Set this credential's object default or clear with null. Does not change a human default. Requires objects:read/write. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `object_slug` | Yes | string | See the schema below for constraints and defaults. | | `view_id` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "object_slug": { "title": "Object Slug", "type": "string" }, "view_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "View Id" } }, "required": [ "object_slug" ], "title": "g8_object_view_defaultArguments", "type": "object" } ``` </details> ## g8_object_view_delete > Delete this credential's saved view without deleting records. Requires objects:read/delete. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `object_slug` | Yes | string | See the schema below for constraints and defaults. | | `view_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "object_slug": { "title": "Object Slug", "type": "string" }, "view_id": { "title": "View Id", "type": "string" } }, "required": [ "object_slug", "view_id" ], "title": "g8_object_view_deleteArguments", "type": "object" } ``` </details> ## g8_object_view_list > List this credential's private object views and workspace-shared views. Human private views/defaults are separate. Requires objects:read and a stable key identity. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `object_slug` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "object_slug": { "title": "Object Slug", "type": "string" } }, "required": [ "object_slug" ], "title": "g8_object_view_listArguments", "type": "object" } ``` </details> ## g8_object_view_update > Change title or replace state on this credential's view. Omit unchanged fields. Requires objects:read/write; another owner's view cannot be changed. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `object_slug` | Yes | string | See the schema below for constraints and defaults. | | `view_id` | Yes | string | See the schema below for constraints and defaults. | | `changes` | Yes | object | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "object_slug": { "title": "Object Slug", "type": "string" }, "view_id": { "title": "View Id", "type": "string" }, "changes": { "additionalProperties": true, "title": "Changes", "type": "object" } }, "required": [ "object_slug", "view_id", "changes" ], "title": "g8_object_view_updateArguments", "type": "object" } ``` </details> ## g8_org_bulk_invite > Invite up to 500 people into this graph8 organisation in one call. THIS SENDS A REAL EMAIL TO EVERY ROW. Show the user the count and a sample, then call again with confirm=true. Rows are applied INDEPENDENTLY and the run does not stop at the first failure: one bad address or unknown role comes back as its own `ok: false` row while the rest still go out. The response carries `invited`, `added`, `failed` and a `results` array in the order the rows were given, each with `email`, `ok`, and either `status` or `error`. A row that fails can be retried on its own; a row that succeeded is safe to send again, since re-inviting updates rather than duplicates. Rows may each name their own `role`/`role_id`, or inherit the call-level default. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `rows` | Yes | array | Rows to invite, 1 to 500. Each row is an object with `email` and either `role` or `role_id`; an omitted role falls back to this call's `role`/`role_id`. | | `role` | No | string / null | Default role for rows that omit one. | | `role_id` | No | string / null | Default role id for rows that omit one. | | `confirm` | No | boolean | Set true to actually send. Left false, no request is made and the tool returns the invitation it WOULD send, so it can be reviewed with the user first. | <details> <summary>Complete input schema</summary> ```json { "properties": { "rows": { "description": "Rows to invite, 1 to 500. Each row is an object with `email` and either `role` or `role_id`; an omitted role falls back to this call's `role`/`role_id`.", "items": { "additionalProperties": true, "type": "object" }, "title": "Rows", "type": "array" }, "role": { "anyOf": [ { "maxLength": 100, "type": "string" }, { "type": "null" } ], "default": null, "description": "Default role for rows that omit one.", "title": "Role" }, "role_id": { "anyOf": [ { "maxLength": 64, "type": "string" }, { "type": "null" } ], "default": null, "description": "Default role id for rows that omit one.", "title": "Role Id" }, "confirm": { "default": false, "description": "Set true to actually send. Left false, no request is made and the tool returns the invitation it WOULD send, so it can be reviewed with the user first.", "title": "Confirm", "type": "boolean" } }, "required": [ "rows" ], "title": "g8_org_bulk_inviteArguments", "type": "object" } ``` </details> ## g8_org_invite_member > Invite one person into this graph8 organisation at a non-administrator role. THIS SENDS A REAL EMAIL TO A REAL PERSON and gives them access to the tenant. Review the address and the role with the user, then call again with confirm=true. The response `status` says what happened: `invited` when an invitation went out to someone with no graph8 account, `added` when the person already had one and now holds the role immediately. Repeating the same address is safe - the pending invitation is updated to the new role rather than duplicated - so this is callable once per row of a list without de-duplicating first. Administrator roles are refused with a 403: the API does not let a credential make someone an owner or an admin of an organisation. Invite at a normal role and promote in the app. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `email` | Yes | string | Email address to invite. Matched case-insensitively against existing members and invitations. | | `role` | No | string / null | Role slug or display name to grant, resolved within this organisation. Administrator roles (`owner`, `admin`) are refused by the API. Call g8_org_list_members or the roles API first if unsure what exists. | | `role_id` | No | string / null | Id of the role to grant, when known. Pass this OR `role`, not both. | | `list_ids` | No | array / null | List ids to assign. Honoured for the `service_provider` role only. | | `confirm` | No | boolean | Set true to actually send. Left false, no request is made and the tool returns the invitation it WOULD send, so it can be reviewed with the user first. | <details> <summary>Complete input schema</summary> ```json { "properties": { "email": { "description": "Email address to invite. Matched case-insensitively against existing members and invitations.", "maxLength": 255, "title": "Email", "type": "string" }, "role": { "anyOf": [ { "maxLength": 100, "type": "string" }, { "type": "null" } ], "default": null, "description": "Role slug or display name to grant, resolved within this organisation. Administrator roles (`owner`, `admin`) are refused by the API. Call g8_org_list_members or the roles API first if unsure what exists.", "title": "Role" }, "role_id": { "anyOf": [ { "maxLength": 64, "type": "string" }, { "type": "null" } ], "default": null, "description": "Id of the role to grant, when known. Pass this OR `role`, not both.", "title": "Role Id" }, "list_ids": { "anyOf": [ { "items": { "type": "integer" }, "type": "array" }, { "type": "null" } ], "default": null, "description": "List ids to assign. Honoured for the `service_provider` role only.", "title": "List Ids" }, "confirm": { "default": false, "description": "Set true to actually send. Left false, no request is made and the tool returns the invitation it WOULD send, so it can be reviewed with the user first.", "title": "Confirm", "type": "boolean" } }, "required": [ "email" ], "title": "g8_org_invite_memberArguments", "type": "object" } ``` </details> ## g8_org_list_invites > List invitations that have been sent but not yet accepted, and the role each carries. An invitation is a role assignment that has not landed: the role applies on the invitee's first sign-in. So this is the honest answer to "who is about to get access, and to what" - and the list to check before re-inviting someone, since a second invitation to the same address silently replaces the role on the first. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_org_list_invitesArguments", "type": "object" } ``` </details> ## g8_org_list_members > List the people who are IN this graph8 organisation, and the role each holds. This is the organisation's identity-provider membership, so it answers "who can sign in" - not the CRM contact list and not the Work directory. Each row carries the person's id, email, name, their role and its slug, when they last signed in, and whether their account is still enabled. Pair with g8_org_list_invites to reconcile a list of people against the tenant: this tool is who already arrived, that one is who was asked and has not yet. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_org_list_membersArguments", "type": "object" } ``` </details> ## g8_org_revoke_invite > Withdraw an invitation to this graph8 organisation before it is accepted. Only ever REMOVES a pending grant: it cannot affect anyone who already joined, and it does not remove an existing member. Returns 404 when there is no pending invitation for the address, so check g8_org_list_invites if unsure. Review with the user and call again with confirm=true to execute. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `email` | Yes | string | Email address the invitation was sent to. | | `confirm` | No | boolean | Set true to actually send. Left false, no request is made and the tool returns the invitation it WOULD send, so it can be reviewed with the user first. | <details> <summary>Complete input schema</summary> ```json { "properties": { "email": { "description": "Email address the invitation was sent to.", "maxLength": 255, "title": "Email", "type": "string" }, "confirm": { "default": false, "description": "Set true to actually send. Left false, no request is made and the tool returns the invitation it WOULD send, so it can be reviewed with the user first.", "title": "Confirm", "type": "boolean" } }, "required": [ "email" ], "title": "g8_org_revoke_inviteArguments", "type": "object" } ``` </details> ## g8_pause_sequence > Pause a live sequence. Contacts in progress will be wound down. IMPORTANT: This pauses real outreach. Confirm with the user first. Args: sequence_id: Sequence ID to pause ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `sequence_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "sequence_id": { "title": "Sequence Id", "type": "string" } }, "required": [ "sequence_id" ], "title": "g8_pause_sequenceArguments", "type": "object" } ``` </details> ## g8_push_to_crm_company > Push companies to an external CRM provider. IMPORTANT: This creates real records in the external CRM. Confirm with the user before pushing. Args: provider: CRM provider (hubspot, salesforce, pipedrive, zoho, sugarcrm) records: List of company records to push (each a dict with company fields) ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `provider` | Yes | string | See the schema below for constraints and defaults. | | `records` | Yes | array | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "provider": { "title": "Provider", "type": "string" }, "records": { "items": { "additionalProperties": true, "type": "object" }, "title": "Records", "type": "array" } }, "required": [ "provider", "records" ], "title": "g8_push_to_crm_companyArguments", "type": "object" } ``` </details> ## g8_push_to_crm_contact > Push contacts to an external CRM provider. IMPORTANT: This creates real records in the external CRM. Confirm with the user before pushing. Args: provider: CRM provider (hubspot, salesforce, pipedrive, zoho, sugarcrm) records: List of contact records to push (each a dict with contact fields) ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `provider` | Yes | string | See the schema below for constraints and defaults. | | `records` | Yes | array | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "provider": { "title": "Provider", "type": "string" }, "records": { "items": { "additionalProperties": true, "type": "object" }, "title": "Records", "type": "array" } }, "required": [ "provider", "records" ], "title": "g8_push_to_crm_contactArguments", "type": "object" } ``` </details> ## g8_push_to_crm_list > Modify CRM list memberships (add/remove contacts from lists). Each record should contain: list_id, add_contact_ids, remove_contact_ids. Args: provider: CRM provider (hubspot, salesforce, pipedrive, zoho, sugarcrm) records: List of membership operations ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `provider` | Yes | string | See the schema below for constraints and defaults. | | `records` | Yes | array | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "provider": { "title": "Provider", "type": "string" }, "records": { "items": { "additionalProperties": true, "type": "object" }, "title": "Records", "type": "array" } }, "required": [ "provider", "records" ], "title": "g8_push_to_crm_listArguments", "type": "object" } ``` </details> ## g8_radar_ad_detail > One ad's full detail for the creative drawer - a plain read on the stored ad_creative snapshot (copy / headline / image / dates) joined to the rival's traffic + threat context. NO LLM, NO live call. Args: ad_id: The ad id (matched via the snapshot data->>'ad_id' key). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `ad_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "ad_id": { "title": "Ad Id", "type": "string" } }, "required": [ "ad_id" ], "title": "g8_radar_ad_detailArguments", "type": "object" } ``` </details> ## g8_radar_add_competitor > Add one competitor to track. Returns the created competitor profile. 409 if already tracked, 422 if the 20-competitor limit is reached. Args: domain: Competitor domain (required; normalized to its apex). name: Optional display name. tier: tier1 or tier2 (default tier2). scan_config: Optional {mode: auto\|manual, include_paths: [...]} page selection config. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `domain` | Yes | string | See the schema below for constraints and defaults. | | `name` | No | string / null | See the schema below for constraints and defaults. | | `tier` | No | string / null | See the schema below for constraints and defaults. | | `scan_config` | No | object / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "domain": { "title": "Domain", "type": "string" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "tier": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Tier" }, "scan_config": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Scan Config" } }, "required": [ "domain" ], "title": "g8_radar_add_competitorArguments", "type": "object" } ``` </details> ## g8_radar_aeo_overview > Answer-engine share-of-voice: mention rate, citation rate, SoV vs rivals, leaderboard, and recent runs. Empty (not an error) before any run or before the AEO tables are provisioned. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_radar_aeo_overviewArguments", "type": "object" } ``` </details> ## g8_radar_aeo_probes > List the org's tracked AEO buyer-intent probes (the questions queried against the baseline answer engine). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_radar_aeo_probesArguments", "type": "object" } ``` </details> ## g8_radar_analyze_gaps > BILLABLE: enqueue gap analysis across the tracked competitor set. Runs an LLM pass async on Celery; returns {task_id}. Restrict to a subset of lenses to scope (and bound) the run. Args: lenses: Optional subset of allowed gap lenses to analyze. Omit to run all. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `lenses` | No | array / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "lenses": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Lenses" } }, "title": "g8_radar_analyze_gapsArguments", "type": "object" } ``` </details> ## g8_radar_approve_all_opportunities > DESTRUCTIVE / BILLABLE: approve all scored opportunities in one lane (top-50 by score per call; re-run to drain the rest, `remaining` signals more). Converts each into an initiative + seeds stubs (consumes credits when content is generated). Args: lane: Which lane to drain - now, next, or later (default now). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `lane` | No | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "lane": { "default": "now", "title": "Lane", "type": "string" } }, "title": "g8_radar_approve_all_opportunitiesArguments", "type": "object" } ``` </details> ## g8_radar_approve_opportunity > DESTRUCTIVE / BILLABLE: approve one scored opportunity (open gap) - converts it into an initiative + seeds the content stubs. May create content-generation work for the requested content types (consumes credits when content is generated). Args: gap_id: The gap UUID to approve (must be status='open'). content_types: Optional subset of allowed kinds to seed (else the lens default). seed_context: Optional {primary_keyword, volume, cpc} to seed an SEO page. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `gap_id` | Yes | string | See the schema below for constraints and defaults. | | `content_types` | No | array / null | See the schema below for constraints and defaults. | | `seed_context` | No | object / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "gap_id": { "title": "Gap Id", "type": "string" }, "content_types": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Content Types" }, "seed_context": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Seed Context" } }, "required": [ "gap_id" ], "title": "g8_radar_approve_opportunityArguments", "type": "object" } ``` </details> ## g8_radar_battle_card > Sales-enablement battle card composed from existing Radar intel (NO LLM / paid call): our evidence-gated advantages, the rival's edges + our exposure (open gaps citing them), their recent moves, and a talk-track. Only advantages verified against the proof catalog are listed by default (same as the app); `unverified_advantages_hidden` says how many were left out and `other_rival_claims_excluded` how many named a different rival. Args: competitor_id: Competitor profile UUID. include_unverified: Also list unverified (hypothesis) advantages. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `competitor_id` | Yes | string | See the schema below for constraints and defaults. | | `include_unverified` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "competitor_id": { "title": "Competitor Id", "type": "string" }, "include_unverified": { "default": false, "title": "Include Unverified", "type": "boolean" } }, "required": [ "competitor_id" ], "title": "g8_radar_battle_cardArguments", "type": "object" } ``` </details> ## g8_radar_bulk_add_competitors > Add many competitors at once (toggle-from-suggestions UX). Per item: normalize + dedup + respect the 20-competitor cap. Returns {created: <count>, skipped: [{domain, reason}]} where reason is limit / exists / invalid. Args: competitors: List of {domain, name?, tier?, scan_config?} dicts (max 20). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `competitors` | Yes | array | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "competitors": { "items": { "additionalProperties": true, "type": "object" }, "title": "Competitors", "type": "array" } }, "required": [ "competitors" ], "title": "g8_radar_bulk_add_competitorsArguments", "type": "object" } ``` </details> ## g8_radar_compare_snapshot > Before/after diff for one page snapshot. By default compares against the prior capture of the same URL; pass `against` to diff against a specific earlier capture (the version-timeline 'compare any two'). Args: snapshot_id: The snapshot UUID to diff (current side). against: Optional earlier snapshot UUID of the same page. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `snapshot_id` | Yes | string | See the schema below for constraints and defaults. | | `against` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "snapshot_id": { "title": "Snapshot Id", "type": "string" }, "against": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Against" } }, "required": [ "snapshot_id" ], "title": "g8_radar_compare_snapshotArguments", "type": "object" } ``` </details> ## g8_radar_competitor_ads > Captured ad creatives for one competitor (distinct by ad, latest capture, newest first). Reads the durable ad_creative snapshot corpus so it works for all tiers. Captured-corpus, not a guaranteed-live set. Args: competitor_id: Competitor profile UUID. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `competitor_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "competitor_id": { "title": "Competitor Id", "type": "string" } }, "required": [ "competitor_id" ], "title": "g8_radar_competitor_adsArguments", "type": "object" } ``` </details> ## g8_radar_competitor_engagers > Steal-Their-Audience: people who engage this competitor's LinkedIn content who are ALREADY known to us - counts (engaged / in CRM / at open-deal accounts) + a per-engager list. Listener-gated: with no per-org LinkedIn listener the response is honest-empty (configured=True, listener=False). Requires a deals-read permission. Args: competitor_id: Competitor profile UUID. limit: Max engagers (default 100, 1-200). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `competitor_id` | Yes | string | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "competitor_id": { "title": "Competitor Id", "type": "string" }, "limit": { "default": 100, "title": "Limit", "type": "integer" } }, "required": [ "competitor_id" ], "title": "g8_radar_competitor_engagersArguments", "type": "object" } ``` </details> ## g8_radar_competitor_pages > Distinct tracked pages for one competitor (latest snapshot per URL, kind='page'), newest-captured first. The "Pages tracked" click-through; each row's id opens the per-page version timeline. Args: competitor_id: Competitor profile UUID. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `competitor_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "competitor_id": { "title": "Competitor Id", "type": "string" } }, "required": [ "competitor_id" ], "title": "g8_radar_competitor_pagesArguments", "type": "object" } ``` </details> ## g8_radar_competitor_posts > LinkedIn posts for one competitor (the "Posts / wk" click-through). Reads the matched-post store posts_per_week is derived from. Degrades to a configured-aware empty list (never a 500) when no source is wired. Args: competitor_id: Competitor profile UUID. limit: Max posts (default 50, 1-200). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `competitor_id` | Yes | string | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "competitor_id": { "title": "Competitor Id", "type": "string" }, "limit": { "default": 50, "title": "Limit", "type": "integer" } }, "required": [ "competitor_id" ], "title": "g8_radar_competitor_postsArguments", "type": "object" } ``` </details> ## g8_radar_competitor_suggestions > Detected-but-untracked competitors surfaced from the org's existing intelligence (competitor_discovery) + the competitors global-context doc. Each suggestion flags already_tracked. Best-effort (degrades to an empty list on source failure). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_radar_competitor_suggestionsArguments", "type": "object" } ``` </details> ## g8_radar_competitor_traffic > Traffic detail for one competitor: current estimate + trend plus the ETV / ranked-keyword-count history from the kind='traffic' snapshot series. Args: competitor_id: Competitor profile UUID. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `competitor_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "competitor_id": { "title": "Competitor Id", "type": "string" } }, "required": [ "competitor_id" ], "title": "g8_radar_competitor_trafficArguments", "type": "object" } ``` </details> ## g8_radar_delete_competitor > DESTRUCTIVE: delete a tracked competitor and all its captured snapshots (pages, ads, traffic history). Irreversible. Returns {ok: True}. Args: competitor_id: Competitor profile UUID. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `competitor_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "competitor_id": { "title": "Competitor Id", "type": "string" } }, "required": [ "competitor_id" ], "title": "g8_radar_delete_competitorArguments", "type": "object" } ``` </details> ## g8_radar_discover_pages > Discover a competitor's pages (Firecrawl /map) for the tracked-pages picker - traffic-ranked, pricing/feature boosted, current selection flagged. Cached per competitor (6h). BILLABLE when refresh=True: bypasses the cache and re-maps the site (a paid Firecrawl call). Leave refresh=False to read the cached inventory. Args: competitor_id: Competitor profile UUID. refresh: When True, force a fresh paid site map (default False). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `competitor_id` | Yes | string | See the schema below for constraints and defaults. | | `refresh` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "competitor_id": { "title": "Competitor Id", "type": "string" }, "refresh": { "default": false, "title": "Refresh", "type": "boolean" } }, "required": [ "competitor_id" ], "title": "g8_radar_discover_pagesArguments", "type": "object" } ``` </details> ## g8_radar_dispatch_alerts > DESTRUCTIVE: detect + deliver new competitor-move alerts now (in-app bell + bridge mirror for opted-in users), respecting the org's alert toggles + channels. Sends real notifications to users. Per-snapshot deduped so repeated calls can't re-spam. Returns {delivered: <count>}. Requires settings:write. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_radar_dispatch_alertsArguments", "type": "object" } ``` </details> ## g8_radar_feature_matrix > Capability/feature comparison matrix (v1: one capability row per advantage claim) across the tracked competitor set. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_radar_feature_matrixArguments", "type": "object" } ``` </details> ## g8_radar_get_competitor > Single-competitor aggregation: profile + pages-tracked count + recent content changes + open gaps citing this rival. Args: competitor_id: Competitor profile UUID (from g8_radar_list_competitors). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `competitor_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "competitor_id": { "title": "Competitor Id", "type": "string" } }, "required": [ "competitor_id" ], "title": "g8_radar_get_competitorArguments", "type": "object" } ``` </details> ## g8_radar_get_settings > Read the org's Radar settings: tier cadence days, alert toggles, delivery channels (in_app / bridge), and ad_source. Seed-on-empty: degrades to defaults when the org schema isn't yet migrated. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_radar_get_settingsArguments", "type": "object" } ``` </details> ## g8_radar_keyword_gap > Keywords each rival ranks for that the org does NOT ("what you're missing"). Computed from persisted competitor keywords minus the org's own ranked set. available=False when the org's own keywords can't be resolved. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_radar_keyword_gapArguments", "type": "object" } ``` </details> ## g8_radar_list_competitors > List every tracked competitor profile (active + inactive), tier then domain order. Each carries threat_score, traffic_estimate, posts/wk, ads-live count, scan status, scan_config, and page_budget. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_radar_list_competitorsArguments", "type": "object" } ``` </details> ## g8_radar_list_gaps > List competitive gaps, optionally filtered. Args: lens: Filter by gap lens (must be an allowed lens, else 400). status: Filter by status - open, dismissed, or converted. competitor_id: Filter to gaps citing this competitor. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `lens` | No | string / null | See the schema below for constraints and defaults. | | `status` | No | string / null | See the schema below for constraints and defaults. | | `competitor_id` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "lens": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Lens" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "competitor_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Competitor Id" } }, "title": "g8_radar_list_gapsArguments", "type": "object" } ``` </details> ## g8_radar_list_initiatives > List radar initiatives (converted opportunities under execution), optionally filtered by lane (now / next / later / live / done). Args: lane: Filter by initiative lane. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `lane` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "lane": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Lane" } }, "title": "g8_radar_list_initiativesArguments", "type": "object" } ``` </details> ## g8_radar_list_opportunities > List opportunities - the scored OPEN gaps (no separate table), optionally filtered to one lane (now / next / later). Args: lane: Filter by opportunity lane (now, next, or later). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `lane` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "lane": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Lane" } }, "title": "g8_radar_list_opportunitiesArguments", "type": "object" } ``` </details> ## g8_radar_measure_initiatives > Run the performance loopback now: write content-progression + rival-traffic outcome signals onto the org's active initiatives. Free (DB-only; also runs on the weekly sweep). Returns {measured: <count>}. Requires settings:write. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_radar_measure_initiativesArguments", "type": "object" } ``` </details> ## g8_radar_monitoring_ads > Ads & keywords. Reads the durable ad_creative snapshot corpus (never empty-by-design); optionally augments with a live provider fetch per the org's ad_source setting. Keywords = persisted organic + paid. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_radar_monitoring_adsArguments", "type": "object" } ``` </details> ## g8_radar_monitoring_content > Competitor content-change feed. Filter by competitor, page kind, change category / type, minimum significance (0-100), date range, and free-text search. Args: competitor_id: Filter to one competitor. kind: Filter by page kind (e.g. pricing, page, features). change_category: Filter by change category. change_type: Filter by change type (new, changed, removed). min_significance: Minimum significance score (0-100). date_from: ISO start date. date_to: ISO end date. search: Free-text search (max 200 chars). limit: Max rows (default 50, 1-200). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `competitor_id` | No | string / null | See the schema below for constraints and defaults. | | `kind` | No | string / null | See the schema below for constraints and defaults. | | `change_category` | No | string / null | See the schema below for constraints and defaults. | | `change_type` | No | string / null | See the schema below for constraints and defaults. | | `min_significance` | No | integer / null | See the schema below for constraints and defaults. | | `date_from` | No | string / null | See the schema below for constraints and defaults. | | `date_to` | No | string / null | See the schema below for constraints and defaults. | | `search` | No | string / null | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "competitor_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Competitor Id" }, "kind": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Kind" }, "change_category": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Change Category" }, "change_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Change Type" }, "min_significance": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Min Significance" }, "date_from": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Date From" }, "date_to": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Date To" }, "search": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Search" }, "limit": { "default": 50, "title": "Limit", "type": "integer" } }, "title": "g8_radar_monitoring_contentArguments", "type": "object" } ``` </details> ## g8_radar_monitoring_linkedin > Competitor LinkedIn posts for the LinkedIn sub-tab (listener store first, then the MixRank company-post corpus fallback for tracked competitors). Args: limit: Max posts (default 50, 1-200). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `limit` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "limit": { "default": 50, "title": "Limit", "type": "integer" } }, "title": "g8_radar_monitoring_linkedinArguments", "type": "object" } ``` </details> ## g8_radar_optimization_runs > Radar-spawned content-refresh grids (ContentGrid 'Radar: ...') with lean score aggregates over their content jobs. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_radar_optimization_runsArguments", "type": "object" } ``` </details> ## g8_radar_overview > Radar dashboard summary: tracked counts, 7-day moves, open gaps, act-now / live-initiative funnel, the threat board (one row per active competitor), recent moves, and derived alerts. The single best entry point for "what's happening with my competitors". ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_radar_overviewArguments", "type": "object" } ``` </details> ## g8_radar_own_traffic_estimate > Estimated own-domain traffic (DataForSEO) - fallback for the Traffic & market view when the first-party tracking snippet isn't installed. 6h success cache + 1h miss-cooldown bound the paid Labs call; the result is an APPROXIMATION, not first-party analytics. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_radar_own_traffic_estimateArguments", "type": "object" } ``` </details> ## g8_radar_page_history > Full capture history for one page (competitor + url), newest-first - powers the per-page version timeline. Excludes unchanged re-captures. Args: url: The page URL (required, max 2048 chars). competitor_id: Competitor profile UUID (required). limit: Max history points (default 20, 1-50). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `url` | Yes | string | See the schema below for constraints and defaults. | | `competitor_id` | Yes | string | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "url": { "title": "Url", "type": "string" }, "competitor_id": { "title": "Competitor Id", "type": "string" }, "limit": { "default": 20, "title": "Limit", "type": "integer" } }, "required": [ "url", "competitor_id" ], "title": "g8_radar_page_historyArguments", "type": "object" } ``` </details> ## g8_radar_run_aeo > BILLABLE: trigger an AEO run (generate probes on first use, then query the baseline answer engine for each). Async via Celery - one paid LLM call per probe. Guarded by settings:write + a per-org Redis cooldown (returns 429 while a recent run is in flight). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_radar_run_aeoArguments", "type": "object" } ``` </details> ## g8_radar_scan_all > BILLABLE: enqueue a scan for every active competitor (up to 20). Runs async on Celery; returns the number of scans queued. Consumes paid crawl/provider credits per competitor. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_radar_scan_allArguments", "type": "object" } ``` </details> ## g8_radar_scan_competitor > BILLABLE: enqueue a fresh scan of one competitor (crawl pages, ads, traffic). Runs async on Celery; returns {task_id}. Consumes paid crawl/provider credits. Args: competitor_id: Competitor profile UUID. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `competitor_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "competitor_id": { "title": "Competitor Id", "type": "string" } }, "required": [ "competitor_id" ], "title": "g8_radar_scan_competitorArguments", "type": "object" } ``` </details> ## g8_radar_traffic > Traffic & market view: per-competitor visit estimates + trend, with the org's own domain marked is_self. The market-share comparison series. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_radar_trafficArguments", "type": "object" } ``` </details> ## g8_radar_update_competitor > Update a competitor profile (tier, active flag, name, or scan_config). Returns the updated profile. Use scan_config to pin the manual tracked-page selection from g8_radar_discover_pages. Args: competitor_id: Competitor profile UUID. tier: New tier - tier1 or tier2. is_active: Active / paused flag. name: New display name. scan_config: {mode: auto\|manual, include_paths: [...]} selection. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `competitor_id` | Yes | string | See the schema below for constraints and defaults. | | `tier` | No | string / null | See the schema below for constraints and defaults. | | `is_active` | No | boolean / null | See the schema below for constraints and defaults. | | `name` | No | string / null | See the schema below for constraints and defaults. | | `scan_config` | No | object / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "competitor_id": { "title": "Competitor Id", "type": "string" }, "tier": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Tier" }, "is_active": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Active" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "scan_config": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Scan Config" } }, "required": [ "competitor_id" ], "title": "g8_radar_update_competitorArguments", "type": "object" } ``` </details> ## g8_radar_update_gap > Update a gap's status (open / dismissed / converted). Returns the updated gap. Args: gap_id: Gap UUID. status: New status - open, dismissed, or converted. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `gap_id` | Yes | string | See the schema below for constraints and defaults. | | `status` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "gap_id": { "title": "Gap Id", "type": "string" }, "status": { "title": "Status", "type": "string" } }, "required": [ "gap_id", "status" ], "title": "g8_radar_update_gapArguments", "type": "object" } ``` </details> ## g8_radar_update_settings > Replace the org's Radar settings (PUT - full object). Reads current settings via g8_radar_get_settings first when you only want to change a field; any omitted field falls back to its default. Args: tier1_cadence_days: Scan cadence for tier1 competitors (1-365). tier2_cadence_days: Scan cadence for tier2 competitors (1-365). alerts: Toggles dict (pricing_change, new_ads, content_velocity, keyword_overtake, warm_account). channels: Delivery channels dict (in_app, bridge). ad_source: Ad source - stored, linkedin, meta, or all. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `tier1_cadence_days` | No | integer / null | See the schema below for constraints and defaults. | | `tier2_cadence_days` | No | integer / null | See the schema below for constraints and defaults. | | `alerts` | No | object / null | See the schema below for constraints and defaults. | | `channels` | No | object / null | See the schema below for constraints and defaults. | | `ad_source` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "tier1_cadence_days": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Tier1 Cadence Days" }, "tier2_cadence_days": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Tier2 Cadence Days" }, "alerts": { "anyOf": [ { "additionalProperties": { "type": "boolean" }, "type": "object" }, { "type": "null" } ], "default": null, "title": "Alerts" }, "channels": { "anyOf": [ { "additionalProperties": { "type": "boolean" }, "type": "object" }, { "type": "null" } ], "default": null, "title": "Channels" }, "ad_source": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Ad Source" } }, "title": "g8_radar_update_settingsArguments", "type": "object" } ``` </details> ## g8_repo_create_campaign > Create Campaign. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /repos/{repo_id}/campaigns ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `repo_id` | Yes | string | Repository ID | | `body` | Yes | #/$defs/Apideveloper_api__interfaces__v1__cli_campaigns_router__CampaignCreateRequest | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "$defs": { "Apideveloper_api__interfaces__v1__cli_campaigns_router__CampaignCreateRequest": { "additionalProperties": false, "properties": { "brief": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Campaign brief", "title": "Brief" }, "category": { "default": "Outbound", "description": "Campaign category", "title": "Category", "type": "string" }, "core_concept": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Core concept", "title": "Core Concept" }, "goal": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Campaign goal", "title": "Goal" }, "name": { "description": "Campaign name", "title": "Name", "type": "string" }, "primary_hook": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Primary hook text", "title": "Primary Hook" }, "target_persona": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Target persona", "title": "Target Persona" } }, "required": [ "name" ], "title": "Apideveloper_api__interfaces__v1__cli_campaigns_router__CampaignCreateRequest", "type": "object" } }, "properties": { "repo_id": { "description": "Repository ID", "title": "Repo Id", "type": "string" }, "body": { "$ref": "#/$defs/Apideveloper_api__interfaces__v1__cli_campaigns_router__CampaignCreateRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "repo_id", "body" ], "title": "g8_repo_create_campaignArguments", "type": "object" } ``` </details> ## g8_repo_delete_repo > Delete Repo. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: DELETE /repos/{repo_id} ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `repo_id` | Yes | string | Repository ID | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "repo_id": { "description": "Repository ID", "title": "Repo Id", "type": "string" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "repo_id" ], "title": "g8_repo_delete_repoArguments", "type": "object" } ``` </details> ## g8_repo_get_campaign_document > Get Campaign Document. Uses the current organization and existing API permissions. API: GET /repos/{repo_id}/campaigns/{campaign_id}/documents/{document_id} ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `repo_id` | Yes | string | Repository ID | | `campaign_id` | Yes | string | Campaign ID | | `document_id` | Yes | string | Document ID | <details> <summary>Complete input schema</summary> ```json { "properties": { "repo_id": { "description": "Repository ID", "title": "Repo Id", "type": "string" }, "campaign_id": { "description": "Campaign ID", "title": "Campaign Id", "type": "string" }, "document_id": { "description": "Document ID", "title": "Document Id", "type": "string" } }, "required": [ "repo_id", "campaign_id", "document_id" ], "title": "g8_repo_get_campaign_documentArguments", "type": "object" } ``` </details> ## g8_repo_launch_campaign > Launch Campaign. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /repos/{repo_id}/campaigns/{campaign_id}/launch ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `repo_id` | Yes | string | Repository ID | | `campaign_id` | Yes | string | Campaign ID | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "repo_id": { "description": "Repository ID", "title": "Repo Id", "type": "string" }, "campaign_id": { "description": "Campaign ID", "title": "Campaign Id", "type": "string" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "repo_id", "campaign_id" ], "title": "g8_repo_launch_campaignArguments", "type": "object" } ``` </details> ## g8_repo_list_repos > List Repos. Uses the current organization and existing API permissions. API: GET /repos ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `page` | No | integer / null | Page number (1-indexed) | | `limit` | No | integer / null | Items per page (max 200) | <details> <summary>Complete input schema</summary> ```json { "properties": { "page": { "anyOf": [ { "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "description": "Page number (1-indexed)", "title": "Page" }, "limit": { "anyOf": [ { "maximum": 200, "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "description": "Items per page (max 200)", "title": "Limit" } }, "title": "g8_repo_list_reposArguments", "type": "object" } ``` </details> ## g8_repo_list_streams > List Streams. Uses the current organization and existing API permissions. API: GET /repos/{repo_id}/streams ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `repo_id` | Yes | string | Repository ID | | `page` | No | integer / null | Page number (1-indexed). Omit page and limit to get every stream. | | `limit` | No | integer / null | Streams per page (max 200). Omit page and limit to get every stream. | <details> <summary>Complete input schema</summary> ```json { "properties": { "repo_id": { "description": "Repository ID", "title": "Repo Id", "type": "string" }, "page": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "description": "Page number (1-indexed). Omit page and limit to get every stream.", "title": "Page" }, "limit": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "description": "Streams per page (max 200). Omit page and limit to get every stream.", "title": "Limit" } }, "required": [ "repo_id" ], "title": "g8_repo_list_streamsArguments", "type": "object" } ``` </details> ## g8_repo_rollback_install > Rollback Install. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /repos/{repo_id}/rollback ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `repo_id` | Yes | string | Repository ID | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "repo_id": { "description": "Repository ID", "title": "Repo Id", "type": "string" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "repo_id" ], "title": "g8_repo_rollback_installArguments", "type": "object" } ``` </details> ## g8_repo_update_campaign > Update Campaign. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: PATCH /repos/{repo_id}/campaigns/{campaign_id} ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `repo_id` | Yes | string | Repository ID | | `campaign_id` | Yes | string | Campaign ID | | `body` | Yes | #/$defs/Apideveloper_api__interfaces__v1__cli_campaigns_router__CampaignUpdateRequest | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "$defs": { "Apideveloper_api__interfaces__v1__cli_campaigns_router__CampaignUpdateRequest": { "additionalProperties": false, "properties": { "brief": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Campaign brief", "title": "Brief" }, "category": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Campaign category", "title": "Category" }, "core_concept": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Core concept", "title": "Core Concept" }, "goal": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Campaign goal", "title": "Goal" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Campaign name", "title": "Name" }, "primary_hook": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Primary hook text", "title": "Primary Hook" }, "target_persona": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Target persona", "title": "Target Persona" } }, "title": "Apideveloper_api__interfaces__v1__cli_campaigns_router__CampaignUpdateRequest", "type": "object" } }, "properties": { "repo_id": { "description": "Repository ID", "title": "Repo Id", "type": "string" }, "campaign_id": { "description": "Campaign ID", "title": "Campaign Id", "type": "string" }, "body": { "$ref": "#/$defs/Apideveloper_api__interfaces__v1__cli_campaigns_router__CampaignUpdateRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "repo_id", "campaign_id", "body" ], "title": "g8_repo_update_campaignArguments", "type": "object" } ``` </details> ## g8_reschedule_booking > Reschedule a booking to a new start time. Free (no extra credit charge); re-sends the updated calendar invite and notifies the attendee. Returns the NEW booking detail. Args: booking_uid: Booking UID to move. start_time: New ISO-8601 start time (must be an available slot). rescheduling_reason: Optional reason shown in the notification. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `booking_uid` | Yes | string | See the schema below for constraints and defaults. | | `start_time` | Yes | string | See the schema below for constraints and defaults. | | `rescheduling_reason` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "booking_uid": { "title": "Booking Uid", "type": "string" }, "start_time": { "title": "Start Time", "type": "string" }, "rescheduling_reason": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Rescheduling Reason" } }, "required": [ "booking_uid", "start_time" ], "title": "g8_appointments_reschedule_bookingArguments", "type": "object" } ``` </details> ## g8_resume_sequence > Resume a paused sequence. Contacts will resume receiving messages. IMPORTANT: This resumes real outreach. Confirm with the user first. Args: sequence_id: Sequence ID to resume ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `sequence_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "sequence_id": { "title": "Sequence Id", "type": "string" } }, "required": [ "sequence_id" ], "title": "g8_resume_sequenceArguments", "type": "object" } ``` </details> ## g8_scan_repo > Record repository scan results that YOU produced by reading the code. graph8 does not clone or scan the repository server-side. Walk the checked-out tree yourself (or run the `g8` CLI) and send what you found here. The stored results are what `g8_install_spine` reads to decide which patches to generate, so a scan recorded from a stale or guessed tree produces a patch set for a codebase that does not exist. `tech_stack` is required: a call without it is rejected by the API. Args: repo_id: Repository ID from g8_connect_repo or g8_status tech_stack: What you detected, e.g. {"framework": "nextjs", "language": "typescript", "package_manager": "pnpm"} api_routes: Routes you found, e.g. [{"method": "POST", "path": "/api/signup"}] readme_summary: A short summary of the README, if there is one product_type: b2b_saas, api, marketplace, ecommerce, ... gtm_readiness: Flags you observed, e.g. {"has_analytics": false, "has_forms": true} scan_duration_ms: How long your scan took, in milliseconds ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `repo_id` | Yes | string | See the schema below for constraints and defaults. | | `tech_stack` | Yes | object | See the schema below for constraints and defaults. | | `api_routes` | No | array / null | See the schema below for constraints and defaults. | | `readme_summary` | No | string / null | See the schema below for constraints and defaults. | | `product_type` | No | string / null | See the schema below for constraints and defaults. | | `gtm_readiness` | No | object / null | See the schema below for constraints and defaults. | | `scan_duration_ms` | No | integer / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "repo_id": { "title": "Repo Id", "type": "string" }, "tech_stack": { "additionalProperties": true, "title": "Tech Stack", "type": "object" }, "api_routes": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Api Routes" }, "readme_summary": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Readme Summary" }, "product_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Product Type" }, "gtm_readiness": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Gtm Readiness" }, "scan_duration_ms": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Scan Duration Ms" } }, "required": [ "repo_id", "tech_stack" ], "title": "g8_scan_repoArguments", "type": "object" } ``` </details> ## g8_search_companies > Search companies already in your CRM database. Returns companies that your organization has imported or created. Use this to find companies you already track - NOT for prospecting. For finding new companies, use g8_find_companies instead. `lifecycle_stage` answers "who is in Trial / Customers / Churned right now?" using the exact membership rule the in-app lifecycle grid uses, so the two can never disagree. It is a different question from the other filters and cannot be combined with them. Companies only - "who is in Trial" has no contact-side answer on this surface. Each row then also carries status, custom_status, subscription_status, owner_id and mrr_cents, which a plain search leaves null. Args: domain: Filter by company domain (e.g. acme.com) industry: Filter by industry name: Filter by company name (partial match) page: Page number (default 1) limit: Items per page (default 25, max 200). Raise it only when the user actually wants more than a screenful. lifecycle_stage: A stage `slug` (or entry id) from `g8_get_pipeline(include_lifecycle=True)`. Returns the companies in that stage. A 403 on this branch means the key is missing the deals:read scope this stage read needs, not that the stage is empty. include_custom_fields: When True, each company carries a `custom_fields` object of its custom column values keyed by column slug (values are text). Default False. Not available with lifecycle_stage. query: Free text for when you don't know which field to use: a value that looks like a domain ("hubspot.com") searches `domain`, anything else searches `name` (partial match). Ignored when `domain` or `name` is also given. Arguments this tool does not have are not silently dropped: a call whose only "filters" are unknown arguments is rejected with the list of valid ones, instead of returning an unfiltered page. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `domain` | No | string / null | See the schema below for constraints and defaults. | | `industry` | No | string / null | See the schema below for constraints and defaults. | | `name` | No | string / null | See the schema below for constraints and defaults. | | `page` | No | integer | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | | `lifecycle_stage` | No | string / null | See the schema below for constraints and defaults. | | `include_custom_fields` | No | boolean | See the schema below for constraints and defaults. | | `query` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "domain": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Domain" }, "industry": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Industry" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "page": { "default": 1, "title": "Page", "type": "integer" }, "limit": { "default": 25, "title": "Limit", "type": "integer" }, "lifecycle_stage": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Lifecycle Stage" }, "include_custom_fields": { "default": false, "title": "Include Custom Fields", "type": "boolean" }, "query": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Query" } }, "title": "g8_search_companiesArguments", "type": "object" } ``` </details> ## g8_search_contacts > Search contacts already in your CRM database. Returns contacts that your organization has imported or created. Use this to find people you already know about - NOT for prospecting. For finding new leads, use g8_find_contacts instead. Args: email: Filter by email address (exact match, any capitals) list_id: Filter by contact list ID name: Filter by first or last name (partial match) job_title: Filter by job title (partial match) seniority_level: Filter by seniority (exact, any capitals: C-Suite, VP, Director, Manager, etc.). Spellings of one level match each other (VP = Vice President; C-Suite = CXO). "VP" means VP and above, so it also returns SVP/EVP; "SVP/EVP" alone returns only that band. Comma-separate levels to match any of them. company_name: Filter by company name (partial match) country: Filter by country (exact, any capitals; US = USA = United States, UK = United Kingdom) page: Page number (default 1) limit: Items per page (default 25, max 200). Raise it only when the user actually wants more than a screenful. include_custom_fields: When True, each contact carries a `custom_fields` object of its custom column values keyed by column slug (values are text). Default False. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `email` | No | string / null | See the schema below for constraints and defaults. | | `list_id` | No | string / null | See the schema below for constraints and defaults. | | `name` | No | string / null | See the schema below for constraints and defaults. | | `job_title` | No | string / null | See the schema below for constraints and defaults. | | `seniority_level` | No | string / null | See the schema below for constraints and defaults. | | `company_name` | No | string / null | See the schema below for constraints and defaults. | | `country` | No | string / null | See the schema below for constraints and defaults. | | `page` | No | integer | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | | `include_custom_fields` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "list_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "List Id" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "job_title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Job Title" }, "seniority_level": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Seniority Level" }, "company_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Company Name" }, "country": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Country" }, "page": { "default": 1, "title": "Page", "type": "integer" }, "limit": { "default": 25, "title": "Limit", "type": "integer" }, "include_custom_fields": { "default": false, "title": "Include Custom Fields", "type": "boolean" } }, "title": "g8_search_contactsArguments", "type": "object" } ``` </details> ## g8_search_kb > Search the GTM knowledge base for a repository. Args: repo_id: Repository ID query: Search query (e.g. "ICP for enterprise buyers") ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `repo_id` | Yes | string | See the schema below for constraints and defaults. | | `query` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "repo_id": { "title": "Repo Id", "type": "string" }, "query": { "title": "Query", "type": "string" } }, "required": [ "repo_id", "query" ], "title": "g8_search_kbArguments", "type": "object" } ``` </details> ## g8_search_save_company_search > Save Company Search. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /search/companies/save ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `body` | Yes | #/$defs/ApiSearchSaveRequest | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "$defs": { "ApiSearchFilter": { "additionalProperties": false, "properties": { "field": { "description": "Field name to filter on (e.g., 'job_title', 'country', 'industry')", "title": "Field", "type": "string" }, "operator": { "description": "Filter operator", "enum": [ "any_of", "contains", "all_of", "none_of", "is_empty", "is_not_empty", "between", "exists" ], "title": "Operator", "type": "string" }, "value": { "anyOf": [ { "items": {}, "type": "array" }, { "type": "null" } ], "default": null, "description": "Filter values", "title": "Value" } }, "required": [ "field", "operator" ], "title": "ApiSearchFilter", "type": "object" }, "ApiSearchSaveRequest": { "additionalProperties": false, "properties": { "filters": { "anyOf": [ { "items": { "$ref": "#/$defs/ApiSearchFilter" }, "type": "array" }, { "type": "null" } ], "default": null, "description": "Filter conditions (AND logic)", "title": "Filters" }, "limit": { "default": 25, "description": "Results per page (max 100)", "maximum": 100.0, "minimum": 1.0, "title": "Limit", "type": "integer" }, "list_title": { "description": "Title for the new list", "title": "List Title", "type": "string" }, "max_results": { "default": 1000, "description": "Maximum results to save (max 10,000)", "maximum": 10000.0, "minimum": 1.0, "title": "Max Results", "type": "integer" }, "page": { "default": 1, "description": "Page number (1-indexed)", "maximum": 100.0, "minimum": 1.0, "title": "Page", "type": "integer" } }, "required": [ "list_title" ], "title": "ApiSearchSaveRequest", "type": "object" } }, "properties": { "body": { "$ref": "#/$defs/ApiSearchSaveRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "body" ], "title": "g8_search_save_company_searchArguments", "type": "object" } ``` </details> ## g8_send_quote > Send (or resend) a quote for e-signature. Sends a real customer-facing email. Creates/reuses a signing envelope and rolls the quote status forward to sent. Recipient resolves from the quote's signer_contact_id or signer_email (one must be set). Idempotent for resend: if an envelope is already sent or viewed, it is re-emailed with the original signing link - safe to call twice. Refused with QUOTE_ZERO_TOTAL for a $0 quote and QUOTE_TAX_EXCEEDS_TAXABLE when tax is larger than the amount it taxes: fix the quote, then send. Args: quote_id: Quote UUID (must be in draft or sent status). subject: Optional email subject override (falls back to quote title). message: Optional cover-line override (falls back to default template). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `quote_id` | Yes | string | See the schema below for constraints and defaults. | | `subject` | No | string / null | See the schema below for constraints and defaults. | | `message` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "quote_id": { "title": "Quote Id", "type": "string" }, "subject": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Subject" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" } }, "required": [ "quote_id" ], "title": "g8_send_quoteArguments", "type": "object" } ``` </details> ## g8_send_reply > Send a reply through the specified channel. Sends a real message - confirm with user first. Args: reply_id: Thread/reply ID to reply to. body: Message body to send. channel: Channel to send through - email, sms, or linkedin. subject: Email subject (email channel only). to: Recipient (email address, phone number, or LinkedIn ID). from_address: Sender (mailbox email, phone number, or LinkedIn account ID). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `reply_id` | Yes | string | See the schema below for constraints and defaults. | | `body` | Yes | string | See the schema below for constraints and defaults. | | `channel` | Yes | string | See the schema below for constraints and defaults. | | `subject` | No | string / null | See the schema below for constraints and defaults. | | `to` | No | string / null | See the schema below for constraints and defaults. | | `from_address` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "reply_id": { "title": "Reply Id", "type": "string" }, "body": { "title": "Body", "type": "string" }, "channel": { "title": "Channel", "type": "string" }, "subject": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Subject" }, "to": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "To" }, "from_address": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "From Address" } }, "required": [ "reply_id", "body", "channel" ], "title": "g8_send_replyArguments", "type": "object" } ``` </details> ## g8_seo_claim_fix > Claim ONE queued SEO fix before working on it, so no other agent takes it. Records the claim in graph8 and returns the fix's instructions. Claiming again with the same `agent` label is a no-op; another agent gets a 409 until the claim expires (a few hours). Nothing is published and no credits are spent: you change the repository yourself, then call g8_seo_complete_fix. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `fix_id` | Yes | string | A fix id from g8_seo_list_fixes. | | `agent` | No | string / null | A short label for this agent (e.g. claude-code). Use the same label for claim and complete. | <details> <summary>Complete input schema</summary> ```json { "properties": { "fix_id": { "description": "A fix id from g8_seo_list_fixes.", "pattern": "^[0-9a-fA-F-]{32,36}$", "title": "Fix Id", "type": "string" }, "agent": { "anyOf": [ { "maxLength": 80, "type": "string" }, { "type": "null" } ], "default": null, "description": "A short label for this agent (e.g. claude-code). Use the same label for claim and complete.", "title": "Agent" } }, "required": [ "fix_id" ], "title": "g8_seo_claim_fixArguments", "type": "object" } ``` </details> ## g8_seo_complete_fix > Report a claimed SEO fix as done (with the commit or pull request) or as failed (with why). `done` moves the fix to applied and graph8 then re-fetches the LIVE page to verify it, so the change must be deployed for the check to pass. `failed` frees the work for a person. Claim it first with g8_seo_claim_fix, using the same `agent` label. Records state only; spends nothing. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `fix_id` | Yes | string | A fix id from g8_seo_list_fixes. | | `outcome` | No | string | done after you changed the repository; failed if you could not. | | `ref` | No | string / null | outcome=done: the commit SHA or the commit or pull request URL. | | `note` | No | string | What you changed; with outcome=failed, why not. | | `agent` | No | string / null | A short label for this agent (e.g. claude-code). Use the same label for claim and complete. | <details> <summary>Complete input schema</summary> ```json { "properties": { "fix_id": { "description": "A fix id from g8_seo_list_fixes.", "pattern": "^[0-9a-fA-F-]{32,36}$", "title": "Fix Id", "type": "string" }, "outcome": { "default": "done", "description": "done after you changed the repository; failed if you could not.", "enum": [ "done", "failed" ], "title": "Outcome", "type": "string" }, "ref": { "anyOf": [ { "maxLength": 500, "type": "string" }, { "type": "null" } ], "default": null, "description": "outcome=done: the commit SHA or the commit or pull request URL.", "title": "Ref" }, "note": { "default": "", "description": "What you changed; with outcome=failed, why not.", "maxLength": 2000, "title": "Note", "type": "string" }, "agent": { "anyOf": [ { "maxLength": 80, "type": "string" }, { "type": "null" } ], "default": null, "description": "A short label for this agent (e.g. claude-code). Use the same label for claim and complete.", "title": "Agent" } }, "required": [ "fix_id" ], "title": "g8_seo_complete_fixArguments", "type": "object" } ``` </details> ## g8_seo_get_fix_pack > Get the pack for ONE fix, or the AGENTS.md / CLAUDE.md for all queued fixes plus MCP setup lines. With `fix_id`: that fix's instructions, generated `files`, and `agents_md` / `claude_md` scoped to it. Without: the files for every queued fix (optionally one `domain`) and `setup` - the one-line commands that connect Claude Code or Cursor to graph8 over MCP. Free. Read-only: nothing is claimed. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `fix_id` | No | string / null | A fix id from g8_seo_list_fixes. | | `domain` | No | string / null | A domain the organization tracks in Signals > SEO (e.g. acme.com). Omit to list them all. | <details> <summary>Complete input schema</summary> ```json { "properties": { "fix_id": { "anyOf": [ { "pattern": "^[0-9a-fA-F-]{32,36}$", "type": "string" }, { "type": "null" } ], "default": null, "description": "A fix id from g8_seo_list_fixes.", "title": "Fix Id" }, "domain": { "anyOf": [ { "maxLength": 255, "pattern": "^[^/\\\\\\s?#]+$", "type": "string" }, { "type": "null" } ], "default": null, "description": "A domain the organization tracks in Signals > SEO (e.g. acme.com). Omit to list them all.", "title": "Domain" } }, "title": "g8_seo_get_fix_packArguments", "type": "object" } ``` </details> ## g8_seo_get_overview > Read the SEO overview from Signals > SEO: tracked domains with organic keywords, traffic, authority and 30-day change. Without `domain`: every saved domain (own first, primary first, then the Radar competitors shown beside them) with its last snapshot metrics, plus `credit_prices` for the paid refreshes. With `domain`: that domain's saved snapshot - overview, top keywords, top pages and backlinks. `snapshot_at` says how old it is. Free: reads what the app saved and never calls the data provider. To price a refresh or a live report first, use g8_seo_preview_cost. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `domain` | No | string / null | A domain the organization tracks in Signals > SEO (e.g. acme.com). Omit to list them all. | <details> <summary>Complete input schema</summary> ```json { "properties": { "domain": { "anyOf": [ { "maxLength": 255, "pattern": "^[^/\\\\\\s?#]+$", "type": "string" }, { "type": "null" } ], "default": null, "description": "A domain the organization tracks in Signals > SEO (e.g. acme.com). Omit to list them all.", "title": "Domain" } }, "title": "g8_seo_get_overviewArguments", "type": "object" } ``` </details> ## g8_seo_list_audits > List site audits from Signals > SEO > Audits, or read one audit with its issues. Without `audit_id`: the organization's audits newest first, each with status, pages crawled, on-page score, credits charged and a plain `state` (what happened and what to do next). With `audit_id`: that audit plus `issues` grouped by type and severity, with the affected URLs. Free. Starting a new audit spends credits and is not available here; price it with g8_seo_preview_cost(action='site_audit'). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `audit_id` | No | string / null | One audit id: return it with the issues it found. | | `limit` | No | integer | Audits to list, newest first. | <details> <summary>Complete input schema</summary> ```json { "properties": { "audit_id": { "anyOf": [ { "pattern": "^[0-9a-fA-F-]{32,36}$", "type": "string" }, { "type": "null" } ], "default": null, "description": "One audit id: return it with the issues it found.", "title": "Audit Id" }, "limit": { "default": 20, "description": "Audits to list, newest first.", "maximum": 100, "minimum": 1, "title": "Limit", "type": "integer" } }, "title": "g8_seo_list_auditsArguments", "type": "object" } ``` </details> ## g8_seo_list_fixes > List the SEO fixes the customer approved for their coding agent, each with exact instructions and code. Each fix has the page (`target_url`), ordered `steps`, the `snippets` to add (with a suggested file), any generated `files` (llms.txt, robots.txt, ...) and `acceptance` checks. `agents_md` is the same list as an AGENTS.md document. A fix whose claim expired is offered again. Free. Next: g8_seo_claim_fix on ONE fix, make the change in the repository, then g8_seo_complete_fix with the commit or pull request URL. Approving a fix for the agent happens in the app. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `domain` | No | string / null | A domain the organization tracks in Signals > SEO (e.g. acme.com). Omit to list them all. | | `limit` | No | integer | Fixes to return, most severe first. | <details> <summary>Complete input schema</summary> ```json { "properties": { "domain": { "anyOf": [ { "maxLength": 255, "pattern": "^[^/\\\\\\s?#]+$", "type": "string" }, { "type": "null" } ], "default": null, "description": "A domain the organization tracks in Signals > SEO (e.g. acme.com). Omit to list them all.", "title": "Domain" }, "limit": { "default": 20, "description": "Fixes to return, most severe first.", "maximum": 100, "minimum": 1, "title": "Limit", "type": "integer" } }, "title": "g8_seo_list_fixesArguments", "type": "object" } ``` </details> ## g8_seo_list_pages > List a tracked domain's ranking pages (Signals > SEO > Pages): traffic, keyword count, best position, audit issues. Free: reads the saved pages. `refreshed_at` is null when the pages were never fetched; fetching them is a paid action in the app (price it with g8_seo_preview_cost(action='pages_refresh')). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `domain` | Yes | string | A tracked domain (from g8_seo_get_overview). | | `limit` | No | integer | Rows per page, 1-200. | | `offset` | No | integer | Rows to skip. | | `sort` | No | string | Sort column. | | `order` | No | string | Sort direction. | | `search` | No | string / null | Only pages whose URL contains this. | <details> <summary>Complete input schema</summary> ```json { "properties": { "domain": { "description": "A tracked domain (from g8_seo_get_overview).", "maxLength": 255, "pattern": "^[^/\\\\\\s?#]+$", "title": "Domain", "type": "string" }, "limit": { "default": 50, "description": "Rows per page, 1-200.", "maximum": 200, "minimum": 1, "title": "Limit", "type": "integer" }, "offset": { "default": 0, "description": "Rows to skip.", "maximum": 1000, "minimum": 0, "title": "Offset", "type": "integer" }, "sort": { "default": "estimated_traffic", "description": "Sort column.", "enum": [ "estimated_traffic", "ranking_keywords_count", "best_position", "url", "traffic_change_30d", "audit_issues" ], "title": "Sort", "type": "string" }, "order": { "default": "desc", "description": "Sort direction.", "enum": [ "asc", "desc" ], "title": "Order", "type": "string" }, "search": { "anyOf": [ { "maxLength": 200, "type": "string" }, { "type": "null" } ], "default": null, "description": "Only pages whose URL contains this.", "title": "Search" } }, "required": [ "domain" ], "title": "g8_seo_list_pagesArguments", "type": "object" } ``` </details> ## g8_seo_list_rank_tracking > List the keywords tracked in Signals > SEO > Rank tracking with their latest Google rank, or one keyword's rank history. Without `keyword_id`: every tracked keyword (optionally for one domain) with latest_rank, latest_serp_url and latest_checked_at. With `keyword_id`: its rank snapshot by snapshot over `days`. Free: rank checks run in the background on each keyword's cadence. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `domain` | No | string / null | Only keywords tracked for this domain. | | `keyword_id` | No | string / null | A tracked keyword id: return its rank history instead of the list. | | `days` | No | integer | History window in days (with keyword_id). | <details> <summary>Complete input schema</summary> ```json { "properties": { "domain": { "anyOf": [ { "maxLength": 255, "type": "string" }, { "type": "null" } ], "default": null, "description": "Only keywords tracked for this domain.", "title": "Domain" }, "keyword_id": { "anyOf": [ { "pattern": "^[0-9a-fA-F-]{32,36}$", "type": "string" }, { "type": "null" } ], "default": null, "description": "A tracked keyword id: return its rank history instead of the list.", "title": "Keyword Id" }, "days": { "default": 90, "description": "History window in days (with keyword_id).", "maximum": 365, "minimum": 1, "title": "Days", "type": "integer" } }, "title": "g8_seo_list_rank_trackingArguments", "type": "object" } ``` </details> ## g8_seo_page_360 > Page 360: everything graph8 knows about a page, or the grid of all pages (which pages lost traffic, which touch deals). With `url`: Search Console clicks and position, indexing, Core Web Vitals, visitors, the companies that read it, deals it touched, top queries and the open fixes for it. Without: one row per page. Companies and pipeline are inferred from who visited, so they are labelled estimated. Free: reads what graph8 already stored. 404 means the page has not been analysed yet. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `domain` | Yes | string | A tracked domain (from g8_seo_get_overview). | | `url` | No | string / null | One page (URL or path, e.g. /pricing). Omit to list pages. | | `search` | No | string / null | Grid only: paths containing this text. | | `sort` | No | string | Grid only: sort column. | | `order` | No | string | Grid only: sort direction. | | `filter` | No | string | Grid only: money = pages on a deal path, decay = losing clicks, fixes = has open fixes. | | `limit` | No | integer | Grid only: rows per page. | | `offset` | No | integer | Grid only: rows to skip. | <details> <summary>Complete input schema</summary> ```json { "properties": { "domain": { "description": "A tracked domain (from g8_seo_get_overview).", "maxLength": 255, "pattern": "^[^/\\\\\\s?#]+$", "title": "Domain", "type": "string" }, "url": { "anyOf": [ { "maxLength": 2000, "type": "string" }, { "type": "null" } ], "default": null, "description": "One page (URL or path, e.g. /pricing). Omit to list pages.", "title": "Url" }, "search": { "anyOf": [ { "maxLength": 200, "type": "string" }, { "type": "null" } ], "default": null, "description": "Grid only: paths containing this text.", "title": "Search" }, "sort": { "default": "pageviews", "description": "Grid only: sort column.", "enum": [ "pageviews", "clicks", "position", "companies", "pipeline", "issues" ], "title": "Sort", "type": "string" }, "order": { "default": "desc", "description": "Grid only: sort direction.", "enum": [ "asc", "desc" ], "title": "Order", "type": "string" }, "filter": { "default": "all", "description": "Grid only: money = pages on a deal path, decay = losing clicks, fixes = has open fixes.", "enum": [ "all", "money", "decay", "fixes", "companies", "slow" ], "title": "Filter", "type": "string" }, "limit": { "default": 50, "description": "Grid only: rows per page.", "maximum": 200, "minimum": 1, "title": "Limit", "type": "integer" }, "offset": { "default": 0, "description": "Grid only: rows to skip.", "minimum": 0, "title": "Offset", "type": "integer" } }, "required": [ "domain" ], "title": "g8_seo_page_360Arguments", "type": "object" } ``` </details> ## g8_seo_preview_cost > Preview the credit cost of a paid SEO action before anyone runs it. Spends nothing and runs nothing. Use this when the user asks for keyword research, a live domain report, backlinks, a new site audit or a refresh: quote the price, then point them to Signals > SEO in the app to run it. These paid actions are not MCP tools. The refresh prices are read live from the app. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `action` | Yes | string | The paid SEO action to price: keyword_research, domain_overview, backlinks, site_audit, domain_refresh (re-snapshot a tracked domain), pages_refresh (fetch every ranking page), page_keywords (load all keywords for one page). | | `max_pages` | No | integer | site_audit only: the crawl ceiling the reservation is sized from. | <details> <summary>Complete input schema</summary> ```json { "properties": { "action": { "description": "The paid SEO action to price: keyword_research, domain_overview, backlinks, site_audit, domain_refresh (re-snapshot a tracked domain), pages_refresh (fetch every ranking page), page_keywords (load all keywords for one page).", "enum": [ "keyword_research", "domain_overview", "backlinks", "site_audit", "domain_refresh", "pages_refresh", "page_keywords" ], "title": "Action", "type": "string" }, "max_pages": { "default": 500, "description": "site_audit only: the crawl ceiling the reservation is sized from.", "maximum": 500, "minimum": 10, "title": "Max Pages", "type": "integer" } }, "required": [ "action" ], "title": "g8_seo_preview_costArguments", "type": "object" } ``` </details> ## g8_seo_today > Today in Signals > SEO: health score, KPIs, what changed, the approval queue, movers and open alerts. Every modelled number carries `confidence` (measured, estimated or partial). Sources that are not connected (Search Console, say) show up in `sources` as false and as a setup card, not as zeros. Free: it reads what graph8 already stored and never calls a paid provider. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `domain` | No | string / null | A domain the organization tracks in Signals > SEO (e.g. acme.com). Omit to list them all. | <details> <summary>Complete input schema</summary> ```json { "properties": { "domain": { "anyOf": [ { "maxLength": 255, "pattern": "^[^/\\\\\\s?#]+$", "type": "string" }, { "type": "null" } ], "default": null, "description": "A domain the organization tracks in Signals > SEO (e.g. acme.com). Omit to list them all.", "title": "Domain" } }, "title": "g8_seo_todayArguments", "type": "object" } ``` </details> ## g8_sequence_get_sequence > Get Sequence. Uses the current organization and existing API permissions. API: GET /sequences/{sequence_id} ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `sequence_id` | Yes | string | Sequence ID | <details> <summary>Complete input schema</summary> ```json { "properties": { "sequence_id": { "description": "Sequence ID", "title": "Sequence Id", "type": "string" } }, "required": [ "sequence_id" ], "title": "g8_sequence_get_sequenceArguments", "type": "object" } ``` </details> ## g8_sequence_list_sequence_contacts > List Sequence Contacts. Uses the current organization and existing API permissions. API: GET /sequences/{sequence_id}/contacts ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `sequence_id` | Yes | string | Sequence ID | | `page` | No | integer / null | Page number | | `limit` | No | integer / null | Items per page | | `state` | No | string / null | Filter by contact state | <details> <summary>Complete input schema</summary> ```json { "properties": { "sequence_id": { "description": "Sequence ID", "title": "Sequence Id", "type": "string" }, "page": { "anyOf": [ { "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "description": "Page number", "title": "Page" }, "limit": { "anyOf": [ { "maximum": 200, "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "description": "Items per page", "title": "Limit" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Filter by contact state", "title": "State" } }, "required": [ "sequence_id" ], "title": "g8_sequence_list_sequence_contactsArguments", "type": "object" } ``` </details> ## g8_sequence_run_sequence > Run Sequence. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /sequences/{sequence_id}/run ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `sequence_id` | Yes | string | Sequence ID | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "sequence_id": { "description": "Sequence ID", "title": "Sequence Id", "type": "string" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "sequence_id" ], "title": "g8_sequence_run_sequenceArguments", "type": "object" } ``` </details> ## g8_set_field_value > Set a custom column value on a single contact or company row. Use `g8_list_fields` first to find the `column_id` for the column you want to write into. The `record_id` is the contact_id (when entity="contacts") or company_id (when entity="companies") of the row to update. Pass `value=None` (or omit it) to clear the column for that row. A list-only column can only hold values for records in that list; a value for a record outside the list is rejected (clearing is allowed). A value of the wrong type is rejected with what the column accepts (e.g. "Value must be a number for column 'Score'"). This is a write, not a destructive op: setting the same value twice is idempotent and reversible by simply writing the previous value back. Args: column_id: Column ID from `g8_list_fields` or `g8_create_field` record_id: Contact ID or company ID to update value: Value to write into the custom column. Omit/None to clear. entity: Target entity - "contacts" or "companies" (default: contacts) ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `column_id` | Yes | integer | See the schema below for constraints and defaults. | | `record_id` | Yes | integer | See the schema below for constraints and defaults. | | `value` | No | string / null | See the schema below for constraints and defaults. | | `entity` | No | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "column_id": { "title": "Column Id", "type": "integer" }, "record_id": { "title": "Record Id", "type": "integer" }, "value": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Value" }, "entity": { "default": "contacts", "title": "Entity", "type": "string" } }, "required": [ "column_id", "record_id" ], "title": "g8_set_field_valueArguments", "type": "object" } ``` </details> ## g8_set_field_values > Set custom column values on ONE or MANY contact/company rows in a single call. The batch version of `g8_set_field_value` - prefer this whenever you write more than one value (multiple columns on a row, multiple rows, or both). `rows` is a list; a single-row update is just a list of length 1. `entity` is SHARED across every row. Each row is a dict: { "record_id": 123, # required: contact_id or company_id "fields": [ # columns to set on this row {"column_id": 5, "value": "hot"}, {"column_id": 6, "value": "9"}, {"column_id": 7} # value omitted -> clears that column ] } Use `g8_list_fields` to find `column_id`s. A field with no `value` (or value=null) clears that column for the row. Partial success: rows are applied independently, so one bad row (missing record, unknown column, a list-only column on a record outside that list, or a value of the wrong type, with the reason) comes back `ok: false` in `results[]` while the rest still commit. The response carries `total_rows_updated`, `total_values_written`, and a per-row `results[]` array. Limits: 1-500 rows per call; a record_id may appear only once per call (fold that record's writes into a single row). Args: rows: List of row specs (1-500). Each is a dict with `record_id` and a `fields` list of `{column_id, value}` pairs as shown above. entity: Target entity for ALL rows - "contacts" or "companies". ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `rows` | Yes | array | See the schema below for constraints and defaults. | | `entity` | No | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "rows": { "items": { "additionalProperties": true, "type": "object" }, "title": "Rows", "type": "array" }, "entity": { "default": "contacts", "title": "Entity", "type": "string" } }, "required": [ "rows" ], "title": "g8_set_field_valuesArguments", "type": "object" } ``` </details> ## g8_snippet_get_form_template > Get Form Template. Uses the current organization and existing API permissions. API: GET /snippet/form ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `stream_id` | No | string / null | Tracking stream whose write key the form uses (from GET /streams). Defaults as in GET /snippet. | <details> <summary>Complete input schema</summary> ```json { "properties": { "stream_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Tracking stream whose write key the form uses (from GET /streams). Defaults as in GET /snippet.", "title": "Stream Id" } }, "title": "g8_snippet_get_form_templateArguments", "type": "object" } ``` </details> ## g8_snippet_list_streams > List Org Streams. Uses the current organization and existing API permissions. API: GET /streams ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `page` | No | integer / null | Page number (1-indexed). Omit page and limit to get every stream. | | `limit` | No | integer / null | Streams per page (max 200). Omit page and limit to get every stream. | <details> <summary>Complete input schema</summary> ```json { "properties": { "page": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "description": "Page number (1-indexed). Omit page and limit to get every stream.", "title": "Page" }, "limit": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "description": "Streams per page (max 200). Omit page and limit to get every stream.", "title": "Limit" } }, "title": "g8_snippet_list_streamsArguments", "type": "object" } ``` </details> ## g8_social_listener_get_stats > Social listener summary: posts matched / reviewed / dismissed, contacts resolved, active and paused listeners, monthly credit burn. Free. Use it to report on social listening at a glance. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_social_listener_get_statsArguments", "type": "object" } ``` </details> ## g8_social_listener_list > List the social listeners in Signals > Social listener (brand mentions, competitors, keywords, engagement), or read one. Without `listener_id`: every listener with its platform, type, query, active / paused state (and why), last poll time and monthly credit cap. With `listener_id`: that listener plus `match_count` and `credit_burn`. Free. Creating or changing listeners is done in the app. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `listener_id` | No | string / null | One listener id: return it with its match count and credit burn. | <details> <summary>Complete input schema</summary> ```json { "properties": { "listener_id": { "anyOf": [ { "pattern": "^[0-9a-fA-F-]{32,36}$", "type": "string" }, { "type": "null" } ], "default": null, "description": "One listener id: return it with its match count and credit burn.", "title": "Listener Id" } }, "title": "g8_social_listener_listArguments", "type": "object" } ``` </details> ## g8_social_listener_list_engagers > List the people who engaged (commented or reacted) with a matched social post, or the most-engaged accounts overall. With `post_id`: that post's stored commenters / reactors. An empty list can mean nobody engaged OR engagement fetching is off for that network. Without `post_id`: the warm audience, accounts ranked by how many of the tracked posts they engaged with in the last `days`. Free: reads stored engagements only. Adding engagers to a list is done in the app. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `post_id` | No | string / null | A matched post's id (post_external_id from g8_social_listener_list_posts). Omit to list the accounts that engage with you most across all posts. | | `platform` | No | string / null | The post's network (default x when post_id is given). | | `engagement_type` | No | string / null | post_id only: commenters or reactors. | | `days` | No | integer | Top accounts only: look-back window in days. | | `min_posts` | No | integer | Top accounts only: engaged at least this often. | | `page` | No | integer | post_id only: page number. | | `limit` | No | integer | Rows to return, 1-200. | <details> <summary>Complete input schema</summary> ```json { "properties": { "post_id": { "anyOf": [ { "maxLength": 200, "pattern": "^[^/\\\\\\s?#]+$", "type": "string" }, { "type": "null" } ], "default": null, "description": "A matched post's id (post_external_id from g8_social_listener_list_posts). Omit to list the accounts that engage with you most across all posts.", "title": "Post Id" }, "platform": { "anyOf": [ { "enum": [ "x", "linkedin", "reddit", "instagram", "youtube" ], "type": "string" }, { "type": "null" } ], "default": null, "description": "The post's network (default x when post_id is given).", "title": "Platform" }, "engagement_type": { "anyOf": [ { "enum": [ "comment", "reaction" ], "type": "string" }, { "type": "null" } ], "default": null, "description": "post_id only: commenters or reactors.", "title": "Engagement Type" }, "days": { "default": 30, "description": "Top accounts only: look-back window in days.", "maximum": 365, "minimum": 1, "title": "Days", "type": "integer" }, "min_posts": { "default": 2, "description": "Top accounts only: engaged at least this often.", "minimum": 1, "title": "Min Posts", "type": "integer" }, "page": { "default": 1, "description": "post_id only: page number.", "minimum": 1, "title": "Page", "type": "integer" }, "limit": { "default": 50, "description": "Rows to return, 1-200.", "maximum": 200, "minimum": 1, "title": "Limit", "type": "integer" } }, "title": "g8_social_listener_list_engagersArguments", "type": "object" } ``` </details> ## g8_social_listener_list_posts > List the social posts and brand mentions the listeners matched: author, text, link, engagement counts, intent score. Reads posts the polling worker already stored; it fetches nothing live and spends nothing. Use listener_type='brand' for mentions of the company, listener_id for one listener, or contact_id for one contact's matched posts. Read who engaged with a post with g8_social_listener_list_engagers(post_id=...). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `listener_id` | No | string / null | Only posts this listener matched. | | `listener_type` | No | string / null | Only posts matched by listeners of this type. brand = mentions of your company; competitor = posts about competitors; keyword = topic keywords; engagement = buying-intent posts; user = posts by one tracked account. Ignored when listener_id is given. | | `contact_id` | No | integer / null | Everything this graph8 contact posted that a listener matched. | | `platform` | No | string / null | Only posts from this network. | | `status` | No | string / null | Triage state: new, reviewed or dismissed. | | `since` | No | string / null | Only posts created at or after this time (ISO 8601). | | `until` | No | string / null | Only posts created before this time (ISO 8601). | | `has_contact` | No | boolean / null | Only posts whose author resolved to a graph8 contact. | | `hide_low_confidence` | No | boolean / null | Hide posts the classifier scored confidently low (< 0.5). | | `page` | No | integer | Page number, 1-based. | | `limit` | No | integer | Posts per page, 1-200. | <details> <summary>Complete input schema</summary> ```json { "properties": { "listener_id": { "anyOf": [ { "pattern": "^[0-9a-fA-F-]{32,36}$", "type": "string" }, { "type": "null" } ], "default": null, "description": "Only posts this listener matched.", "title": "Listener Id" }, "listener_type": { "anyOf": [ { "enum": [ "brand", "competitor", "keyword", "engagement", "user" ], "type": "string" }, { "type": "null" } ], "default": null, "description": "Only posts matched by listeners of this type. brand = mentions of your company; competitor = posts about competitors; keyword = topic keywords; engagement = buying-intent posts; user = posts by one tracked account. Ignored when listener_id is given.", "title": "Listener Type" }, "contact_id": { "anyOf": [ { "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "description": "Everything this graph8 contact posted that a listener matched.", "title": "Contact Id" }, "platform": { "anyOf": [ { "enum": [ "x", "linkedin", "reddit", "instagram", "youtube" ], "type": "string" }, { "type": "null" } ], "default": null, "description": "Only posts from this network.", "title": "Platform" }, "status": { "anyOf": [ { "enum": [ "new", "reviewed", "dismissed" ], "type": "string" }, { "type": "null" } ], "default": null, "description": "Triage state: new, reviewed or dismissed.", "title": "Status" }, "since": { "anyOf": [ { "format": "date-time", "type": "string" }, { "type": "null" } ], "default": null, "description": "Only posts created at or after this time (ISO 8601).", "title": "Since" }, "until": { "anyOf": [ { "format": "date-time", "type": "string" }, { "type": "null" } ], "default": null, "description": "Only posts created before this time (ISO 8601).", "title": "Until" }, "has_contact": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "description": "Only posts whose author resolved to a graph8 contact.", "title": "Has Contact" }, "hide_low_confidence": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "description": "Hide posts the classifier scored confidently low (< 0.5).", "title": "Hide Low Confidence" }, "page": { "default": 1, "description": "Page number, 1-based.", "minimum": 1, "title": "Page", "type": "integer" }, "limit": { "default": 50, "description": "Posts per page, 1-200.", "maximum": 200, "minimum": 1, "title": "Limit", "type": "integer" } }, "title": "g8_social_listener_list_postsArguments", "type": "object" } ``` </details> ## g8_social_listener_preview_cost > Preview what a social listener WOULD cost per month before anyone creates it. Charges nothing and creates nothing. Returns the projected `credits_per_month_estimate` from a small sample. Creating the listener is done in Signals > Social listener in the app. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `listener_type` | Yes | string | The kind of listener to price. | | `platform` | No | string | The network it would watch. | | `query` | No | string / null | The search query it would run. | | `target_handle` | No | string / null | listener_type=user: the account to follow. | | `poll_interval_sec` | No | integer | How often it would poll, in seconds. Shorter costs more. | <details> <summary>Complete input schema</summary> ```json { "properties": { "listener_type": { "description": "The kind of listener to price.", "enum": [ "brand", "competitor", "keyword", "engagement", "user" ], "title": "Listener Type", "type": "string" }, "platform": { "default": "x", "description": "The network it would watch.", "enum": [ "x", "linkedin", "reddit", "instagram", "youtube" ], "title": "Platform", "type": "string" }, "query": { "anyOf": [ { "maxLength": 1000, "type": "string" }, { "type": "null" } ], "default": null, "description": "The search query it would run.", "title": "Query" }, "target_handle": { "anyOf": [ { "maxLength": 200, "type": "string" }, { "type": "null" } ], "default": null, "description": "listener_type=user: the account to follow.", "title": "Target Handle" }, "poll_interval_sec": { "default": 600, "description": "How often it would poll, in seconds. Shorter costs more.", "minimum": 60, "title": "Poll Interval Sec", "type": "integer" } }, "required": [ "listener_type" ], "title": "g8_social_listener_preview_costArguments", "type": "object" } ``` </details> ## g8_start_task_execution > Explicitly start an agent-assigned task using the caller's permissions. Only call when execution is requested. request_id must be a UUID and must be reused for retries of an uncertain response. Assignment alone does not authorize Start; existing consequential-action approvals apply. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `task_id` | Yes | string | See the schema below for constraints and defaults. | | `request_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "task_id": { "title": "Task Id", "type": "string" }, "request_id": { "title": "Request Id", "type": "string" } }, "required": [ "task_id", "request_id" ], "title": "g8_start_task_executionArguments", "type": "object" } ``` </details> ## g8_status > Get the current status of a connected repository. Args: repo_id: Repository ID ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `repo_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "repo_id": { "title": "Repo Id", "type": "string" } }, "required": [ "repo_id" ], "title": "g8_statusArguments", "type": "object" } ``` </details> ## g8_switch_org > Switch which organization all your MCP actions run against. Rejected unless the user is a member of the target org. Takes effect on the user's NEXT message and applies to every connected client (per-user choice). After switching, briefly confirm which org is now active. Args: org: graph8 id, uuid, or exact name (any value from g8_list_orgs). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `org` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "org": { "title": "Org", "type": "string" } }, "required": [ "org" ], "title": "g8_switch_orgArguments", "type": "object" } ``` </details> ## g8_tag_reply > Attach tags to an inbox thread. Args: reply_id: Thread/reply ID. tag_ids: List of tag IDs to attach. channel: email, sms, or linkedin (default email). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `reply_id` | Yes | string | See the schema below for constraints and defaults. | | `tag_ids` | Yes | array | See the schema below for constraints and defaults. | | `channel` | No | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "reply_id": { "title": "Reply Id", "type": "string" }, "tag_ids": { "items": { "type": "string" }, "title": "Tag Ids", "type": "array" }, "channel": { "default": "email", "title": "Channel", "type": "string" } }, "required": [ "reply_id", "tag_ids" ], "title": "g8_tag_replyArguments", "type": "object" } ``` </details> ## g8_tool_search > REQUIRED FIRST STEP when the user asks for anything outside your currently visible tools. graph8 ships 661 tools but only 126 are visible at connect time. Workflow, voice/dialer, intent, skill, custom fields, forms, snippet, and playbook tools are HIDDEN until you search for them. Call this BEFORE saying "I don't have a tool for that" - it returns ranked matches AND auto-activates them so they appear on your next tools/list call (the server emits notifications/tools/list_changed). Common category queries: "workflow", "dialer", "voice", "intent", "skill", "field", "form", "snippet", "playbook". Search is family-aware: it understands synonyms ("automation" -> workflow, "dialer"/"phone" -> voice, "competitor"/"signals" -> intent) and singular/plural drift, and it ranks tools that match in their NAME above tools that only mention a word in their description - so a category query returns that family cleanly instead of bleeding into adjacent groups. The result's `detected_groups` tells you which family/families the query resolved to; a multi-family result means the query was ambiguous. Args: query: Free-text search (e.g. "enrich contacts", "sequence pause", "push to CRM"). Ranks all registered tools by name/description match. tool_names: Explicit list of tool names to activate. Use when you already know the tool from a prior turn or skill and just need it visible in tools/list. limit: Max ranked matches to return (default 8). activate: When true (default) the top `limit` matches (or the `tool_names` entries) are written to this session's activated set. Set false to preview without widening tools/list. Returns a ToolSearchResult with: - matches: ranked tool hits (name, description, score, activated flag; the TOP matches also carry `input_schema` - the tool's full argument schema) - activated_session_tools: the full activated set AFTER this call - always_on_tools: the default visible set (for context) - total_registered_tools: how many tools exist in total After a successful activate=true call, the server emits `notifications/tools/list_changed` to prompt the client to re-fetch tools/list. Clients that don't support the notification still see the newly activated tools on their next session connect (Redis-cached 4h). IMPORTANT: if an activated tool does NOT show up in your tools list (some clients only refresh on reconnect), do not stall and do not tell the user the capability is missing - run it directly with g8_execute(tool_name=<match.name>, arguments={...}) using the match's `input_schema` to shape the arguments. To fetch the schema of a match beyond the top few, call again with tool_names=[<that name>]. The `ctx` parameter is auto-injected by FastMCP and hidden from the public tool schema - clients do not see it. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `query` | No | string / null | See the schema below for constraints and defaults. | | `tool_names` | No | array / null | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | | `activate` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "query": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Query" }, "tool_names": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Tool Names" }, "limit": { "default": 8, "title": "Limit", "type": "integer" }, "activate": { "default": true, "title": "Activate", "type": "boolean" } }, "title": "g8_tool_searchArguments", "type": "object" } ``` </details> ## g8_trigger_audience_sync > Manually trigger an audience sync to push latest members to the ad platform. IMPORTANT: This pushes real data to the ad platform immediately. Confirm with the user before triggering. Args: config_id: Sync config ID ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `config_id` | Yes | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "config_id": { "title": "Config Id", "type": "integer" } }, "required": [ "config_id" ], "title": "g8_trigger_audience_syncArguments", "type": "object" } ``` </details> ## g8_update_audience_sync > Update an audience sync configuration. Args: config_id: Sync config ID mode: Sync mode - "mirror" or "append_only" refresh_cadence_hours: Hours between auto-syncs is_active: Enable/disable the sync ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `config_id` | Yes | integer | See the schema below for constraints and defaults. | | `mode` | No | string / null | See the schema below for constraints and defaults. | | `refresh_cadence_hours` | No | integer / null | See the schema below for constraints and defaults. | | `is_active` | No | boolean / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "config_id": { "title": "Config Id", "type": "integer" }, "mode": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Mode" }, "refresh_cadence_hours": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Refresh Cadence Hours" }, "is_active": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Active" } }, "required": [ "config_id" ], "title": "g8_update_audience_syncArguments", "type": "object" } ``` </details> ## g8_update_contact > Update fields on an EXISTING contact (partial update). Only the arguments you pass are changed; the rest are left untouched. Attach a company (both optional; a contact may have none): - `company_id`: attach an EXISTING company (mashup_companies.id, as returned by g8_find_companies / g8_create_company). Wins if both given. - `company_domain` (e.g. "acme.com"): resolve-or-create a company from its domain, created if it does not exist. Attaching a company is how you give a company-less contact a company, which is required before g8_create_deal can derive the deal's company from that contact. `contact_id` is the mashup_contact_id (same id space as g8_search_contacts / g8_find_contacts / g8_get_contact_detail). Args: contact_id: The contact to update (required). first_name, last_name, work_email, job_title, job_department, seniority_level, direct_phone, mobile_phone, linkedin_url, city, state, country: Fields to overwrite (omit to leave as-is). company_domain: Company domain to resolve-or-create and link. company_id: Existing company id (mashup_companies.id) to link. lifecycle_stage: Move this contact to a revenue lifecycle stage (a `slug` from `g8_get_pipeline(include_lifecycle=True)`). A typo comes back as a 400 naming the valid slugs. suppressed: Do-not-contact switch. True stops outreach, False lifts the org's own block. Read the suppression section above before using False. suppression_reason: Why, recorded on the ledger row (max 1000 chars). Optional but worth writing - it is what a later audit reads. contact_ids: Suppress or reinstate MANY contacts in one call (max 5000). These must be mashup_contacts.id primary keys, not CONTACT_ID business keys. Ids that match no contact are listed in `not_found` (nothing is written for them). Use instead of contact_id, never with it. suppression_channel: all (default) \| email \| phone \| linkedin. Bulk suppress only. Careful: a row written for one channel is NOT returned by a query for another, and a per-channel block does not stop sends on other channels. SUPPRESSION, and why False is not the inverse of True: - `suppressed=True` writes an org-initiated do-not-contact block across all channels for one contact, or on `suppression_channel` for a bulk call. - `suppressed=False` clears ONLY the org's own umbrella block. A block the CONTACT asked for (an unsubscribe) or one the SYSTEM set (a hard bounce, a DNC registry hit) SURVIVES on purpose. So a contact can still come back `is_suppressed: true` after a successful unsuppress, and `channels_cleared` can be empty with `is_suppressed` still true - that means "nothing here was yours to reverse", not "half done". - The bulk reinstate refuses per contact rather than in bulk. Read `skipped[]`: `not_operator_reversible` means an opt-out the org may not lift is present (do NOT retry), `no_active_suppression` means there was nothing to clear (benign), `reinstate_failed` means that one write errored (retryable). `reinstated` plus `skipped` should account for `total_requested`, which counts DE-DUPLICATED ids - so a short `suppressed` count can mean duplicates, non-existent ids, or already-suppressed contacts. - `not_found` (bulk suppress and bulk reinstate) lists ids that match no contact at all - usually a typo. On reinstate such an id also appears in `skipped` as `no_active_suppression`; trust `not_found`. Combining field edits with `lifecycle_stage` or `suppressed` in one call issues SEPARATE writes in that order and returns the last ack: the PATCH can succeed and the suppression still fail, so on an error assume a partial change and re-read the contact. Backed by `PATCH /api/v1/contacts/{contact_id}`, plus `POST /contacts/{contact_id}/lifecycle-stage`, `POST /contacts/{contact_id}/suppress` / `/unsuppress`, and `POST /contacts/suppressions/bulk-add` / `/reinstate`. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `contact_id` | No | integer / null | See the schema below for constraints and defaults. | | `first_name` | No | string / null | See the schema below for constraints and defaults. | | `last_name` | No | string / null | See the schema below for constraints and defaults. | | `work_email` | No | string / null | See the schema below for constraints and defaults. | | `job_title` | No | string / null | See the schema below for constraints and defaults. | | `job_department` | No | string / null | See the schema below for constraints and defaults. | | `seniority_level` | No | string / null | See the schema below for constraints and defaults. | | `direct_phone` | No | string / null | See the schema below for constraints and defaults. | | `mobile_phone` | No | string / null | See the schema below for constraints and defaults. | | `linkedin_url` | No | string / null | See the schema below for constraints and defaults. | | `city` | No | string / null | See the schema below for constraints and defaults. | | `state` | No | string / null | See the schema below for constraints and defaults. | | `country` | No | string / null | See the schema below for constraints and defaults. | | `company_domain` | No | string / null | See the schema below for constraints and defaults. | | `company_id` | No | integer / null | See the schema below for constraints and defaults. | | `lifecycle_stage` | No | string / null | See the schema below for constraints and defaults. | | `suppressed` | No | boolean / null | See the schema below for constraints and defaults. | | `suppression_reason` | No | string / null | See the schema below for constraints and defaults. | | `contact_ids` | No | array / null | See the schema below for constraints and defaults. | | `suppression_channel` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "contact_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Contact Id" }, "first_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "First Name" }, "last_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Name" }, "work_email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Work Email" }, "job_title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Job Title" }, "job_department": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Job Department" }, "seniority_level": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Seniority Level" }, "direct_phone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Direct Phone" }, "mobile_phone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Mobile Phone" }, "linkedin_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Linkedin Url" }, "city": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "City" }, "state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "State" }, "country": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Country" }, "company_domain": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Company Domain" }, "company_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Company Id" }, "lifecycle_stage": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Lifecycle Stage" }, "suppressed": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Suppressed" }, "suppression_reason": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Suppression Reason" }, "contact_ids": { "anyOf": [ { "items": { "type": "integer" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Contact Ids" }, "suppression_channel": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Suppression Channel" } }, "title": "g8_update_contactArguments", "type": "object" } ``` </details> ## g8_update_deal > Update a deal (partial update). Common uses: change the deal stage, reassign the owner, add/remove contact associations, record the buying-committee role of contacts on the deal. Pass only the fields you want to change. At least one field (or one non-empty contact list) is required. Backed by `PATCH /api/v1/deals/{deal_id}`. Closed-stage rules (400 on violation, detail prefixed with a marker): moving a deal into a Closed Won stage requires an effective close_date AND amount; Closed Lost requires an effective close_date; either requires an owner. "Effective" means the value in this call when provided, else the deal's existing value - so a deal that already carries them moves freely. Re-submitting the deal's current stage is not a move and is never gated. Markers: close_date_required_for_won_stage, close_date_required_for_lost_stage, amount_required_for_won_stage, owner_required_for_closed_stage. Supply expected_revision from the canonical CRM record to reject stale edits with HTTP 409. A successful conditional edit returns its new revision. Args: expected_revision: Positive canonical CRM revision to compare before writing. deal_id: Deal UUID returned by g8_get_deals / g8_create_deal. name: New deal name. description: New description. amount: New monetary value. currency: New currency code. stage_id: New pipeline stage UUID (see g8_get_pipeline). Must be an existing stage in the deal's pipeline (422 otherwise). close_date: New expected close date (ISO 8601). owner_id: New deal owner (member id, email, or provider uid). Pass to reassign a deal whose owner is missing or wrong; it is resolved to a real team member (422 if it matches none). Reassign-only: a deal's owner cannot be cleared (#18469). Omitting owner_id (or passing None) leaves the owner unchanged. add_contact_ids: mashup_contact_ids to link to the deal. Must belong to the deal's company (if the deal has none yet, the added contacts' company is adopted); a cross-company add is rejected. Idempotent - already-linked ids skipped silently. remove_contact_ids: mashup_contact_ids to unlink from the deal. Idempotent - unlinked ids skipped silently. contact_roles: Buying-committee role per contact, keyed by mashup_contact_id as a STRING, e.g. {"5500429": "champion", "5500914": null}. Valid roles: champion, decision_maker, influencer, blocker, coach, end_user. null clears a contact's role. Use this to record roles on contacts ALREADY on the deal - read them back with g8_get_deal, which returns each contact's current role. Ids you link via add_contact_ids in the SAME call can be roled here too (roles are applied after the link). An id that is neither already linked nor being added is rejected and the whole update is rolled back. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `deal_id` | Yes | string | See the schema below for constraints and defaults. | | `name` | No | string / null | See the schema below for constraints and defaults. | | `description` | No | string / null | See the schema below for constraints and defaults. | | `amount` | No | number / null | See the schema below for constraints and defaults. | | `currency` | No | string / null | See the schema below for constraints and defaults. | | `stage_id` | No | string / null | See the schema below for constraints and defaults. | | `close_date` | No | string / null | See the schema below for constraints and defaults. | | `owner_id` | No | string / null | See the schema below for constraints and defaults. | | `add_contact_ids` | No | array / null | See the schema below for constraints and defaults. | | `remove_contact_ids` | No | array / null | See the schema below for constraints and defaults. | | `contact_roles` | No | object / null | See the schema below for constraints and defaults. | | `expected_revision` | No | integer / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "deal_id": { "title": "Deal Id", "type": "string" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "amount": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Amount" }, "currency": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Currency" }, "stage_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Stage Id" }, "close_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Close Date" }, "owner_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Owner Id" }, "add_contact_ids": { "anyOf": [ { "items": { "type": "integer" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Add Contact Ids" }, "remove_contact_ids": { "anyOf": [ { "items": { "type": "integer" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Remove Contact Ids" }, "contact_roles": { "anyOf": [ { "additionalProperties": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }, "type": "object" }, { "type": "null" } ], "default": null, "title": "Contact Roles" }, "expected_revision": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Expected Revision" } }, "required": [ "deal_id" ], "title": "g8_update_dealArguments", "type": "object" } ``` </details> ## g8_update_note > Update a note's content, on a contact, COMPANY or DEAL note. Note ids are global, so this works on any note regardless of which record it is filed against - no deal-specific variant is needed. Notes marked `read_only` by g8_list_notes cannot be updated. Backed by `PATCH /api/v1/notes/{note_id}`. Args: note_id: Note UUID returned by g8_list_notes / g8_create_note. content: Replacement content (plain text or markdown). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `note_id` | Yes | string | See the schema below for constraints and defaults. | | `content` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "note_id": { "title": "Note Id", "type": "string" }, "content": { "title": "Content", "type": "string" } }, "required": [ "note_id", "content" ], "title": "g8_update_noteArguments", "type": "object" } ``` </details> ## g8_update_quote > Update a quote (partial). Pass only the fields you want to change. Draft quotes edit in place. A sent/viewed quote is recalled to draft first (its live signing link is voided), keeping the same row + quote number; terminal quotes (accepted/declined/voided/expired) reject the update. owner_id reassigns the owner/AE (a blank value is ignored - never nulls an existing owner). Passing line_items replaces the entire set and re-runs quotable-product authorization. Args: see g8_create_quote - same field meanings. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `quote_id` | Yes | string | See the schema below for constraints and defaults. | | `title` | No | string / null | See the schema below for constraints and defaults. | | `line_items` | No | array / null | See the schema below for constraints and defaults. | | `signer_contact_id` | No | integer / null | See the schema below for constraints and defaults. | | `signer_email` | No | string / null | See the schema below for constraints and defaults. | | `signer_name` | No | string / null | See the schema below for constraints and defaults. | | `billing_contact_id` | No | integer / null | See the schema below for constraints and defaults. | | `billing_legal_name` | No | string / null | See the schema below for constraints and defaults. | | `billing_primary_email` | No | string / null | See the schema below for constraints and defaults. | | `billing_email` | No | string / null | See the schema below for constraints and defaults. | | `billing_address` | No | string / null | See the schema below for constraints and defaults. | | `mashup_company_id` | No | integer / null | See the schema below for constraints and defaults. | | `deal_id` | No | string / null | See the schema below for constraints and defaults. | | `owner_id` | No | string / null | See the schema below for constraints and defaults. | | `currency` | No | string / null | See the schema below for constraints and defaults. | | `payment_terms` | No | string / null | See the schema below for constraints and defaults. | | `valid_until` | No | string / null | See the schema below for constraints and defaults. | | `terms_content` | No | string / null | See the schema below for constraints and defaults. | | `privacy_content` | No | string / null | See the schema below for constraints and defaults. | | `notes` | No | string / null | See the schema below for constraints and defaults. | | `public_notes` | No | string / null | See the schema below for constraints and defaults. | | `tax_amount` | No | integer / null | See the schema below for constraints and defaults. | | `contract_months` | No | number / null | See the schema below for constraints and defaults. | | `contract_duration_value` | No | number / null | See the schema below for constraints and defaults. | | `contract_duration_unit` | No | string / null | See the schema below for constraints and defaults. | | `contract_start_date` | No | string / null | See the schema below for constraints and defaults. | | `contract_end_date` | No | string / null | See the schema below for constraints and defaults. | | `accepted_payment_methods` | No | array / null | See the schema below for constraints and defaults. | | `marketing_rights` | No | array / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "quote_id": { "title": "Quote Id", "type": "string" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "line_items": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Line Items" }, "signer_contact_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Signer Contact Id" }, "signer_email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Signer Email" }, "signer_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Signer Name" }, "billing_contact_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Billing Contact Id" }, "billing_legal_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Billing Legal Name" }, "billing_primary_email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Billing Primary Email" }, "billing_email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Billing Email" }, "billing_address": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Billing Address" }, "mashup_company_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Mashup Company Id" }, "deal_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Deal Id" }, "owner_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Owner Id" }, "currency": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Currency" }, "payment_terms": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Payment Terms" }, "valid_until": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Valid Until" }, "terms_content": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Terms Content" }, "privacy_content": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Privacy Content" }, "notes": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Notes" }, "public_notes": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Public Notes" }, "tax_amount": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Tax Amount" }, "contract_months": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Contract Months" }, "contract_duration_value": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Contract Duration Value" }, "contract_duration_unit": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Contract Duration Unit" }, "contract_start_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Contract Start Date" }, "contract_end_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Contract End Date" }, "accepted_payment_methods": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Accepted Payment Methods" }, "marketing_rights": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Marketing Rights" } }, "required": [ "quote_id" ], "title": "g8_update_quoteArguments", "type": "object" } ``` </details> ## g8_update_sequence > Update sequence metadata, or remove LinkedIn from the sequence entirely. Args: sequence_id: Sequence ID to update name: New name description: New description is_shared: Whether sequence is shared finish_on_reply: Finish on reply send_in_same_thread: Send in same thread wait_for_new_contacts: Wait for new contacts remove_linkedin: Remove LinkedIn from this sequence. Deletes its LinkedIn steps AND unbinds its LinkedIn sender account together, in one atomic operation. Use this rather than trying to unbind the account on its own: a sequence left holding LinkedIn steps with no sender cannot run or resume at all. Works for either provider (Netrion or legacy HeyReach) and also repairs a sequence already stuck in that state. A sequence with no LinkedIn returns success with zero counts, so it is safe to call across a batch. Cannot be combined with the metadata fields above. remove_linkedin_force: Required to remove LinkedIn from a LIVE or WAITING sequence. The sequence is paused afterwards so the result can be reviewed before it sends again. dry_run: With remove_linkedin, preview exactly which steps and channels would be removed without changing anything. IMPORTANT: this deletes real steps. Always run dry_run=True first and show the user what it reports before running it for real. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `sequence_id` | Yes | string | See the schema below for constraints and defaults. | | `name` | No | string / null | See the schema below for constraints and defaults. | | `description` | No | string / null | See the schema below for constraints and defaults. | | `is_shared` | No | boolean / null | See the schema below for constraints and defaults. | | `finish_on_reply` | No | boolean / null | See the schema below for constraints and defaults. | | `send_in_same_thread` | No | boolean / null | See the schema below for constraints and defaults. | | `wait_for_new_contacts` | No | boolean / null | See the schema below for constraints and defaults. | | `remove_linkedin` | No | boolean | See the schema below for constraints and defaults. | | `remove_linkedin_force` | No | boolean | See the schema below for constraints and defaults. | | `dry_run` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "sequence_id": { "title": "Sequence Id", "type": "string" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "is_shared": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Shared" }, "finish_on_reply": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Finish On Reply" }, "send_in_same_thread": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Send In Same Thread" }, "wait_for_new_contacts": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Wait For New Contacts" }, "remove_linkedin": { "default": false, "title": "Remove Linkedin", "type": "boolean" }, "remove_linkedin_force": { "default": false, "title": "Remove Linkedin Force", "type": "boolean" }, "dry_run": { "default": false, "title": "Dry Run", "type": "boolean" } }, "required": [ "sequence_id" ], "title": "g8_update_sequenceArguments", "type": "object" } ``` </details> ## g8_update_sequence_step > Update a single step within a sequence. Args: sequence_id: Sequence ID containing the step step_id: Step ID to update step_data: Step template/content data time_interval: Delay in seconds after previous step step_type: Step type. One of: EMAIL, PHONE, SMS, WHATSAPP, HEYREACH, MANUAL_DIALER. Case-insensitive. input_type: Input type. One of: ON_DEMAND, MANUAL_TEMPLATE, AI_GENERATED_TEMPLATE. Case-insensitive. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `sequence_id` | Yes | string | See the schema below for constraints and defaults. | | `step_id` | Yes | string | See the schema below for constraints and defaults. | | `step_data` | No | object / null | See the schema below for constraints and defaults. | | `time_interval` | No | integer / null | See the schema below for constraints and defaults. | | `step_type` | No | string / null | See the schema below for constraints and defaults. | | `input_type` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "sequence_id": { "title": "Sequence Id", "type": "string" }, "step_id": { "title": "Step Id", "type": "string" }, "step_data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Step Data" }, "time_interval": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Time Interval" }, "step_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Step Type" }, "input_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Input Type" } }, "required": [ "sequence_id", "step_id" ], "title": "g8_update_sequence_stepArguments", "type": "object" } ``` </details> ## g8_update_task > Update an existing task (partial update). Pass only the fields you want to change. At least one field is required. Backed by `PATCH /api/v1/tasks/{task_id}`. Read the task first and pass its updated_at as expected_updated_at to avoid overwriting another person's edit. On conflict, read and reconcile the task before trying again; do not retry using a newer revision blindly. Args: tags: Task labels (up to 20, each 1-50 characters); [] clears labels on update. task_id: Task UUID returned by g8_get_tasks / g8_create_task. title: New title. description: New description. due_date: New due date (ISO 8601). assignee_id: New assignee user ID. clear_due_date: Remove the due date; cannot be combined with due_date. clear_assignee: Unassign the task; cannot be combined with assignee_id. task_type: New activity type. clear_task_type: Remove the activity type; cannot be combined with task_type. expected_updated_at: Revision from the task read before editing. Omit only for legacy callers. status: New status (open, completed). priority: New priority (0=none, 1=high, 2=medium, 3=low; historical 4=low is preserved). parent_task_id: New parent task UUID. Re-parents this task into a subtask of the given parent. Parent must exist and must not itself be a subtask. A task that already has subtasks of its own cannot be demoted into a subtask. subtask_sort_order: New sort position within the parent's subtasks (reorder). Can be passed alone to reorder without re-parenting. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `task_id` | Yes | string | See the schema below for constraints and defaults. | | `title` | No | string / null | See the schema below for constraints and defaults. | | `description` | No | string / null | See the schema below for constraints and defaults. | | `tags` | No | array / null | See the schema below for constraints and defaults. | | `due_date` | No | string / null | See the schema below for constraints and defaults. | | `assignee_id` | No | string / null | See the schema below for constraints and defaults. | | `status` | No | string / null | See the schema below for constraints and defaults. | | `priority` | No | integer / null | See the schema below for constraints and defaults. | | `parent_task_id` | No | string / null | See the schema below for constraints and defaults. | | `subtask_sort_order` | No | integer / null | See the schema below for constraints and defaults. | | `records` | No | array / null | See the schema below for constraints and defaults. | | `clear_due_date` | No | boolean | See the schema below for constraints and defaults. | | `clear_assignee` | No | boolean | See the schema below for constraints and defaults. | | `task_type` | No | string / null | See the schema below for constraints and defaults. | | `clear_task_type` | No | boolean | See the schema below for constraints and defaults. | | `expected_updated_at` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "task_id": { "title": "Task Id", "type": "string" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "tags": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Tags" }, "due_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Due Date" }, "assignee_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Assignee Id" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "priority": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Priority" }, "parent_task_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Parent Task Id" }, "subtask_sort_order": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Subtask Sort Order" }, "records": { "anyOf": [ { "items": { "additionalProperties": { "type": "string" }, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Records" }, "clear_due_date": { "default": false, "title": "Clear Due Date", "type": "boolean" }, "clear_assignee": { "default": false, "title": "Clear Assignee", "type": "boolean" }, "task_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Task Type" }, "clear_task_type": { "default": false, "title": "Clear Task Type", "type": "boolean" }, "expected_updated_at": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Expected Updated At" } }, "required": [ "task_id" ], "title": "g8_update_taskArguments", "type": "object" } ``` </details> ## g8_usage_get_usage > Get Usage. Uses the current organization and existing API permissions. API: GET /usage ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_usage_get_usageArguments", "type": "object" } ``` </details> ## g8_usage_list_usage_transactions > List Usage Transactions. Uses the current organization and existing API permissions. API: GET /usage/transactions ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `page` | No | integer / null | Page number (ignored when `cursor` is supplied) | | `limit` | No | integer / null | Items per page | | `cursor` | No | string / null | Opaque pagination cursor from a prior response's `next_cursor`. When supplied it takes precedence over `page`. | <details> <summary>Complete input schema</summary> ```json { "properties": { "page": { "anyOf": [ { "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "description": "Page number (ignored when `cursor` is supplied)", "title": "Page" }, "limit": { "anyOf": [ { "maximum": 200, "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "description": "Items per page", "title": "Limit" }, "cursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Opaque pagination cursor from a prior response's `next_cursor`. When supplied it takes precedence over `page`.", "title": "Cursor" } }, "title": "g8_usage_list_usage_transactionsArguments", "type": "object" } ``` </details> ## g8_visitors_list_identified > List the identified visitors of your own website: which companies and which known people visited, newest first. Use it for "who visited our website", website visitors, visiting companies, de-anonymized / reverse-IP identified traffic, and "follow up with the people who hit our pricing page". This is Signals > Visitors in the app. `kind="companies"` lists the companies resolved from site traffic; `kind="people"` lists the CRM contacts who visited. Each row has the company or contact id and name, the last visit time, the page, the referrer / UTM / sequence that brought them, a page-view count and their earlier pages. Device data (IP address, browser, network) is never returned. Emails and phone numbers are not in this feed: read them with g8_get_contact_detail, which keeps credit-locked values masked. For traffic COUNTS (users, sessions, sources, live visitors) use g8_analytics_get_campaign_visitors with a `report` instead. On an unfiltered first page the result also carries `newest_visit_at`, and a `freshness_warning` when that is more than 48 hours old (the feed may have stopped; say so before acting on the rows). API: GET /visitors/companies/events (kind=companies), GET /visitors/contacts/events (kind=people). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `kind` | No | string | companies: the companies that visited the website (Signals > Visitors > Companies). people: the known contacts who visited (Signals > Visitors > Contacts; also needs Contacts read access). | | `page` | No | integer | Page number, 1-based. Rows are newest visit first. | | `limit` | No | integer | Rows per page, 1-200. Defaults to 25. | | `write_key` | No | string / null | Only visits recorded by this tracking stream (its public write key). Omit for every stream in the organization. | | `company_id` | No | integer / null | kind=companies only: just this company's visits. | | `contact_id` | No | integer / null | kind=people only: just this contact's visits. | | `utm_source` | No | string / null | Only visits whose landing URL carried this utm_source. | <details> <summary>Complete input schema</summary> ```json { "properties": { "kind": { "default": "companies", "description": "companies: the companies that visited the website (Signals > Visitors > Companies). people: the known contacts who visited (Signals > Visitors > Contacts; also needs Contacts read access).", "enum": [ "companies", "people" ], "title": "Kind", "type": "string" }, "page": { "default": 1, "description": "Page number, 1-based. Rows are newest visit first.", "minimum": 1, "title": "Page", "type": "integer" }, "limit": { "default": 25, "description": "Rows per page, 1-200. Defaults to 25.", "maximum": 200, "minimum": 1, "title": "Limit", "type": "integer" }, "write_key": { "anyOf": [ { "maxLength": 200, "type": "string" }, { "type": "null" } ], "default": null, "description": "Only visits recorded by this tracking stream (its public write key). Omit for every stream in the organization.", "title": "Write Key" }, "company_id": { "anyOf": [ { "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "description": "kind=companies only: just this company's visits.", "title": "Company Id" }, "contact_id": { "anyOf": [ { "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "description": "kind=people only: just this contact's visits.", "title": "Contact Id" }, "utm_source": { "anyOf": [ { "maxLength": 200, "type": "string" }, { "type": "null" } ], "default": null, "description": "Only visits whose landing URL carried this utm_source.", "title": "Utm Source" } }, "title": "g8_visitors_list_identifiedArguments", "type": "object" } ``` </details> ## g8_webhook_create_webhook > Create Webhook. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /webhooks ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `body` | Yes | #/$defs/ApiWebhookCreateRequest | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "$defs": { "ApiWebhookCreateRequest": { "additionalProperties": false, "properties": { "events": { "description": "Event types to subscribe to", "items": { "type": "string" }, "title": "Events", "type": "array" }, "field_filters": { "anyOf": [ { "additionalProperties": { "items": { "type": "string" }, "type": "array" }, "type": "object" }, { "type": "null" } ], "default": null, "description": "Per-object field filter for crm.record.updated: {object_slug: [field_slug]}. When set for an object, an update delivery fires only if a watched field's value changed. Omit or leave empty to fire on any change. Ignored for events other than crm.record.updated.", "title": "Field Filters" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Human-readable name", "title": "Name" }, "url": { "description": "Target URL for webhook delivery", "title": "Url", "type": "string" } }, "required": [ "events", "url" ], "title": "ApiWebhookCreateRequest", "type": "object" } }, "properties": { "body": { "$ref": "#/$defs/ApiWebhookCreateRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "body" ], "title": "g8_webhook_create_webhookArguments", "type": "object" } ``` </details> ## g8_webhook_delete_webhook > Delete Webhook. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: DELETE /webhooks/{webhook_id} ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `webhook_id` | Yes | string | Webhook subscription ID | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "webhook_id": { "description": "Webhook subscription ID", "title": "Webhook Id", "type": "string" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "webhook_id" ], "title": "g8_webhook_delete_webhookArguments", "type": "object" } ``` </details> ## g8_webhook_get_webhook > Get Webhook. Uses the current organization and existing API permissions. API: GET /webhooks/{webhook_id} ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `webhook_id` | Yes | string | Webhook subscription ID | <details> <summary>Complete input schema</summary> ```json { "properties": { "webhook_id": { "description": "Webhook subscription ID", "title": "Webhook Id", "type": "string" } }, "required": [ "webhook_id" ], "title": "g8_webhook_get_webhookArguments", "type": "object" } ``` </details> ## g8_webhook_list_webhook_deliveries > List Webhook Deliveries. Uses the current organization and existing API permissions. API: GET /webhooks/{webhook_id}/deliveries ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `webhook_id` | Yes | string | Webhook subscription ID | | `page` | No | integer / null | Page number | | `limit` | No | integer / null | Items per page | | `status` | No | string / null | Filter by delivery status | <details> <summary>Complete input schema</summary> ```json { "properties": { "webhook_id": { "description": "Webhook subscription ID", "title": "Webhook Id", "type": "string" }, "page": { "anyOf": [ { "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "description": "Page number", "title": "Page" }, "limit": { "anyOf": [ { "maximum": 200, "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "description": "Items per page", "title": "Limit" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Filter by delivery status", "title": "Status" } }, "required": [ "webhook_id" ], "title": "g8_webhook_list_webhook_deliveriesArguments", "type": "object" } ``` </details> ## g8_webhook_list_webhook_events > List Webhook Events. Uses the current organization and existing API permissions. API: GET /webhooks/events ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_webhook_list_webhook_eventsArguments", "type": "object" } ``` </details> ## g8_webhook_list_webhooks > List Webhooks. Uses the current organization and existing API permissions. API: GET /webhooks ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `is_active` | No | boolean / null | Filter by active status | <details> <summary>Complete input schema</summary> ```json { "properties": { "is_active": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "description": "Filter by active status", "title": "Is Active" } }, "title": "g8_webhook_list_webhooksArguments", "type": "object" } ``` </details> ## g8_webhook_rotate_webhook_secret > Rotate Webhook Secret. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /webhooks/{webhook_id}/rotate-secret ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `webhook_id` | Yes | string | Webhook subscription ID | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "webhook_id": { "description": "Webhook subscription ID", "title": "Webhook Id", "type": "string" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "webhook_id" ], "title": "g8_webhook_rotate_webhook_secretArguments", "type": "object" } ``` </details> ## g8_webhook_update_webhook > Update Webhook. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: PATCH /webhooks/{webhook_id} ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `webhook_id` | Yes | string | Webhook subscription ID | | `body` | Yes | #/$defs/ApiWebhookUpdateRequest | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "$defs": { "ApiWebhookUpdateRequest": { "additionalProperties": false, "properties": { "events": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "description": "Event types to subscribe to", "title": "Events" }, "field_filters": { "anyOf": [ { "additionalProperties": { "items": { "type": "string" }, "type": "array" }, "type": "object" }, { "type": "null" } ], "default": null, "description": "Per-object field filter for crm.record.updated: {object_slug: [field_slug]}. When set for an object, an update delivery fires only if a watched field's value changed. Omit or leave empty to fire on any change. Ignored for events other than crm.record.updated.", "title": "Field Filters" }, "is_active": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "description": "Whether the webhook is active", "title": "Is Active" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Human-readable name", "title": "Name" }, "url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Target URL for webhook delivery", "title": "Url" } }, "title": "ApiWebhookUpdateRequest", "type": "object" } }, "properties": { "webhook_id": { "description": "Webhook subscription ID", "title": "Webhook Id", "type": "string" }, "body": { "$ref": "#/$defs/ApiWebhookUpdateRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "webhook_id", "body" ], "title": "g8_webhook_update_webhookArguments", "type": "object" } ``` </details> ## g8_work_delete > Delete one graph8 Work message, channel or routine. The three are NOT equally severe and this tool does not pretend otherwise. Deleting a MESSAGE is soft - the author retracts their own words and it is safe to repeat. Deleting a ROUTINE stops a schedule. Deleting a CHANNEL is irreversible: it removes the room for EVERYONE, leaves a non-restorable tombstone, and requires `confirmation_name` to equal the channel's own slug as a second check. Review the intended change with the user and set confirm=true to execute; otherwise no API request is sent. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `resource` | Yes | string | What to delete. | | `resource_id` | Yes | string | Id of the message, channel or routine to delete. | | `confirmation_name` | No | string / null | The channel's own slug, repeated back. REQUIRED for resource=channel. | | `confirm` | No | boolean | Set true to execute. No API request is sent while this is false. | <details> <summary>Complete input schema</summary> ```json { "properties": { "resource": { "description": "What to delete.", "enum": [ "message", "channel", "routine" ], "title": "Resource", "type": "string" }, "resource_id": { "description": "Id of the message, channel or routine to delete.", "maxLength": 255, "title": "Resource Id", "type": "string" }, "confirmation_name": { "anyOf": [ { "maxLength": 255, "type": "string" }, { "type": "null" } ], "default": null, "description": "The channel's own slug, repeated back. REQUIRED for resource=channel.", "title": "Confirmation Name" }, "confirm": { "default": false, "description": "Set true to execute. No API request is sent while this is false.", "title": "Confirm", "type": "boolean" } }, "required": [ "resource", "resource_id" ], "title": "g8_work_deleteArguments", "type": "object" } ``` </details> ## g8_work_manage_channel > Create a graph8 Work channel, change its settings, or assign its purpose. Renaming changes `display_name` only: a channel's slug is its room address, so a rename never moves the room. `set-purpose` TAKES the purpose from whichever channel held it - exactly one channel holds each - and reports the move in `reassignedChannelIds`. 409 when a new channel's name is taken, and for a built-in channel's identity or visibility. 403 when the caller neither owns the channel nor holds admin rights. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `action` | Yes | string | create opens a channel, update changes its settings, set-purpose assigns the organisation-wide purpose. | | `channel_id` | No | string / null | The channel to change. Required for update and set-purpose. | | `name` | No | string / null | Slug for a new channel; normalized to lowercase and hyphens. action=create. | | `display_name` | No | string / null | Human-readable name. | | `description` | No | string / null | What the channel is for; 280 characters or fewer. | | `visibility` | No | string / null | open is self-serve joinable, private is invite only. | | `kind` | No | string / null | Channel layout. action=create. | | `member_user_ids` | No | array / null | People seated in the channel; they must be in this organisation. | | `participant_agent_ids` | No | array / null | Agents seated in the channel. | | `template_id` | No | string / null | Channel template; seats its specialists and seeds its Canvas. action=create. | | `agent_response_policy` | No | string / null | When seated agents may answer. action=update. | | `archived` | No | boolean / null | Archive or unarchive the channel. action=update. | | `purpose` | No | string / null | One of the organisation's purposes (work, campaigns, hot, pipeline, customers, wins), or null to clear. action=set-purpose. | <details> <summary>Complete input schema</summary> ```json { "properties": { "action": { "description": "create opens a channel, update changes its settings, set-purpose assigns the organisation-wide purpose.", "enum": [ "create", "update", "set-purpose" ], "title": "Action", "type": "string" }, "channel_id": { "anyOf": [ { "maxLength": 255, "type": "string" }, { "type": "null" } ], "default": null, "description": "The channel to change. Required for update and set-purpose.", "title": "Channel Id" }, "name": { "anyOf": [ { "maxLength": 255, "type": "string" }, { "type": "null" } ], "default": null, "description": "Slug for a new channel; normalized to lowercase and hyphens. action=create.", "title": "Name" }, "display_name": { "anyOf": [ { "maxLength": 255, "type": "string" }, { "type": "null" } ], "default": null, "description": "Human-readable name.", "title": "Display Name" }, "description": { "anyOf": [ { "maxLength": 1000, "type": "string" }, { "type": "null" } ], "default": null, "description": "What the channel is for; 280 characters or fewer.", "title": "Description" }, "visibility": { "anyOf": [ { "enum": [ "open", "private" ], "type": "string" }, { "type": "null" } ], "default": null, "description": "open is self-serve joinable, private is invite only.", "title": "Visibility" }, "kind": { "anyOf": [ { "enum": [ "stream", "forum" ], "type": "string" }, { "type": "null" } ], "default": null, "description": "Channel layout. action=create.", "title": "Kind" }, "member_user_ids": { "anyOf": [ { "items": { "type": "string" }, "maxItems": 200, "type": "array" }, { "type": "null" } ], "default": null, "description": "People seated in the channel; they must be in this organisation.", "title": "Member User Ids" }, "participant_agent_ids": { "anyOf": [ { "items": { "type": "string" }, "maxItems": 16, "type": "array" }, { "type": "null" } ], "default": null, "description": "Agents seated in the channel.", "title": "Participant Agent Ids" }, "template_id": { "anyOf": [ { "maxLength": 128, "type": "string" }, { "type": "null" } ], "default": null, "description": "Channel template; seats its specialists and seeds its Canvas. action=create.", "title": "Template Id" }, "agent_response_policy": { "anyOf": [ { "enum": [ "mention_required", "proactive" ], "type": "string" }, { "type": "null" } ], "default": null, "description": "When seated agents may answer. action=update.", "title": "Agent Response Policy" }, "archived": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "description": "Archive or unarchive the channel. action=update.", "title": "Archived" }, "purpose": { "anyOf": [ { "maxLength": 64, "type": "string" }, { "type": "null" } ], "default": null, "description": "One of the organisation's purposes (work, campaigns, hot, pipeline, customers, wins), or null to clear. action=set-purpose.", "title": "Purpose" } }, "required": [ "action" ], "title": "g8_work_manage_channelArguments", "type": "object" } ``` </details> ## g8_work_manage_message > Edit, forward or react to an existing graph8 Work message. `edit` only works on a message this credential authored, and is deliberately not a re-send: no agent turn runs again and nobody is notified. `forward` checks both ends - you must be able to read the original and write into the destination. `react` toggles a single emoji and is safe to repeat. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `action` | Yes | string | edit rewrites the message, forward copies it into another conversation, react toggles one emoji. | | `message_id` | Yes | string | The message to act on. | | `text` | No | string / null | New plain text. action=edit. | | `document` | No | object / null | New rich-text document. action=edit. | | `expected_revision` | No | integer / null | Make the edit conditional on the revision you read. action=edit. | | `channel` | No | string / null | Destination conversation name or id. action=forward. | | `emoji` | No | string / null | Reaction shortcode, e.g. `tada`. action=react. | | `active` | No | boolean / null | True adds the reaction, False removes it. action=react. | <details> <summary>Complete input schema</summary> ```json { "properties": { "action": { "description": "edit rewrites the message, forward copies it into another conversation, react toggles one emoji.", "enum": [ "edit", "forward", "react" ], "title": "Action", "type": "string" }, "message_id": { "description": "The message to act on.", "maxLength": 128, "title": "Message Id", "type": "string" }, "text": { "anyOf": [ { "maxLength": 20000, "type": "string" }, { "type": "null" } ], "default": null, "description": "New plain text. action=edit.", "title": "Text" }, "document": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "description": "New rich-text document. action=edit.", "title": "Document" }, "expected_revision": { "anyOf": [ { "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "description": "Make the edit conditional on the revision you read. action=edit.", "title": "Expected Revision" }, "channel": { "anyOf": [ { "maxLength": 255, "type": "string" }, { "type": "null" } ], "default": null, "description": "Destination conversation name or id. action=forward.", "title": "Channel" }, "emoji": { "anyOf": [ { "maxLength": 64, "type": "string" }, { "type": "null" } ], "default": null, "description": "Reaction shortcode, e.g. `tada`. action=react.", "title": "Emoji" }, "active": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "description": "True adds the reaction, False removes it. action=react.", "title": "Active" } }, "required": [ "action", "message_id" ], "title": "g8_work_manage_messageArguments", "type": "object" } ``` </details> ## g8_work_manage_routine > Create, change or immediately run a graph8 Work routine. A routine is a standing instruction to an agent, delivered into one conversation, on a schedule or on an event. `delivery_conversation_id` must be a conversation the caller can read - otherwise a routine would be a way to post into a room its author cannot see - and re-pointing it later is checked again. `run-now` posts the instruction as the caller's own message and lets the normal turn machinery answer, so it exercises exactly what a live ask would. It returns a receipt with a `runId`; read the outcome with g8_work_read(resource="routine-runs"). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `action` | Yes | string | create adds a routine, update changes one, run-now runs it immediately. | | `routine_id` | No | string / null | The routine to act on. Required for update and run-now. | | `agent_id` | No | string / null | Agent that runs the routine. | | `title` | No | string / null | What the routine is called. | | `instruction` | No | string / null | What the agent is asked to do on each run. | | `delivery_conversation_id` | No | string / null | Conversation the routine posts into; the caller must be able to read it. | | `weekdays` | No | array / null | Days to run on, 0-6. Schedule routines. | | `local_time` | No | string / null | Local run time `HH:MM`. Schedule routines. | | `timezone` | No | string / null | IANA timezone the local time is read in, e.g. Europe/London. | | `enabled` | No | boolean / null | Turn the routine on or off. | | `trigger_kind` | No | string / null | schedule runs on a clock, event runs when something happens. | | `trigger` | No | object / null | Trigger configuration. Event routines. | | `event_policy` | No | object / null | Optional event-handling policy. Event routines. | | `sample_event` | No | object / null | Example event to run an event routine against. action=run-now. | <details> <summary>Complete input schema</summary> ```json { "properties": { "action": { "description": "create adds a routine, update changes one, run-now runs it immediately.", "enum": [ "create", "update", "run-now" ], "title": "Action", "type": "string" }, "routine_id": { "anyOf": [ { "maxLength": 64, "type": "string" }, { "type": "null" } ], "default": null, "description": "The routine to act on. Required for update and run-now.", "title": "Routine Id" }, "agent_id": { "anyOf": [ { "maxLength": 128, "type": "string" }, { "type": "null" } ], "default": null, "description": "Agent that runs the routine.", "title": "Agent Id" }, "title": { "anyOf": [ { "maxLength": 255, "type": "string" }, { "type": "null" } ], "default": null, "description": "What the routine is called.", "title": "Title" }, "instruction": { "anyOf": [ { "maxLength": 20000, "type": "string" }, { "type": "null" } ], "default": null, "description": "What the agent is asked to do on each run.", "title": "Instruction" }, "delivery_conversation_id": { "anyOf": [ { "maxLength": 255, "type": "string" }, { "type": "null" } ], "default": null, "description": "Conversation the routine posts into; the caller must be able to read it.", "title": "Delivery Conversation Id" }, "weekdays": { "anyOf": [ { "items": { "type": "integer" }, "maxItems": 7, "type": "array" }, { "type": "null" } ], "default": null, "description": "Days to run on, 0-6. Schedule routines.", "title": "Weekdays" }, "local_time": { "anyOf": [ { "maxLength": 8, "type": "string" }, { "type": "null" } ], "default": null, "description": "Local run time `HH:MM`. Schedule routines.", "title": "Local Time" }, "timezone": { "anyOf": [ { "maxLength": 64, "type": "string" }, { "type": "null" } ], "default": null, "description": "IANA timezone the local time is read in, e.g. Europe/London.", "title": "Timezone" }, "enabled": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "description": "Turn the routine on or off.", "title": "Enabled" }, "trigger_kind": { "anyOf": [ { "enum": [ "schedule", "event" ], "type": "string" }, { "type": "null" } ], "default": null, "description": "schedule runs on a clock, event runs when something happens.", "title": "Trigger Kind" }, "trigger": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "description": "Trigger configuration. Event routines.", "title": "Trigger" }, "event_policy": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "description": "Optional event-handling policy. Event routines.", "title": "Event Policy" }, "sample_event": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "description": "Example event to run an event routine against. action=run-now.", "title": "Sample Event" } }, "required": [ "action" ], "title": "g8_work_manage_routineArguments", "type": "object" } ``` </details> ## g8_work_read > Read the graph8 Work room: channels, conversations, messages, files, search, the activity feed, the All Work board and scheduled routines. Work is the team's operating room - where people and agents talk, where work is tracked, and where routines post on a schedule. Pick what to read with `resource`; it defaults to `room`, the whole readable room in one call. An API key is an organisation credential with no channel seat, so it reads the organisation's built-in channels (#work, #signals) and whatever channels it created. Private channels and channels it has not been seated in are refused by the same rule that governs a person. Direct messages are not reachable through the API at all. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `resource` | No | string | What to read. `room` is the whole Work room in one call; `channels` lists the channels you can read and `channels-available` the ones you are not in; `messages` / `events` / `files` page one conversation (send `conversation`); `search` finds text across everything you can read (send `q`); `activity` is the feed of what happened; `work-items`, `work-items-history` and `oversight` are the All Work board; `routines` and `routine-runs` are the scheduled agent instructions. | | `conversation` | No | string / null | Conversation to read: a channel name (`work`) or a channel id. Required for messages/events/files. | | `channel` | No | string / null | Channel name or id. Required for channel-canvas. | | `message_id` | No | string / null | Message id. Required for resource=message. | | `attachment_id` | No | string / null | Attachment id. Required for resource=attachment. | | `routine_id` | No | string / null | Routine id. Required for resource=routine-runs. | | `q` | No | string / null | Search text, 2-200 characters. Required for resource=search. | | `limit` | No | integer / null | Row cap. The per-resource maximum differs (50 to 200); the API rejects an over-large value. | | `cursor` | No | string / null | Opaque cursor from a previous page's `next_cursor`. | | `actor` | No | string / null | Activity filter: human, agent, runner or system. | | `category` | No | string / null | Activity filter: message, conversation, approval, execution, workflow, canvas or configuration. | | `consequence` | No | string / null | Activity filter: requested, changed, approved, completed, failed or recorded. | | `agent_id` | No | string / null | Activity filter: only this agent's activity. | | `date_from` | No | string / null | Activity filter: ISO timestamp, inclusive lower bound. | | `date_to` | No | string / null | Activity filter: ISO timestamp, inclusive upper bound. | <details> <summary>Complete input schema</summary> ```json { "properties": { "resource": { "default": "room", "description": "What to read. `room` is the whole Work room in one call; `channels` lists the channels you can read and `channels-available` the ones you are not in; `messages` / `events` / `files` page one conversation (send `conversation`); `search` finds text across everything you can read (send `q`); `activity` is the feed of what happened; `work-items`, `work-items-history` and `oversight` are the All Work board; `routines` and `routine-runs` are the scheduled agent instructions.", "enum": [ "room", "channels", "channels-available", "channel-canvas", "messages", "events", "files", "message", "search", "attachment", "activity", "work-items", "work-items-history", "oversight", "routines", "routine-runs" ], "title": "Resource", "type": "string" }, "conversation": { "anyOf": [ { "maxLength": 255, "type": "string" }, { "type": "null" } ], "default": null, "description": "Conversation to read: a channel name (`work`) or a channel id. Required for messages/events/files.", "title": "Conversation" }, "channel": { "anyOf": [ { "maxLength": 255, "type": "string" }, { "type": "null" } ], "default": null, "description": "Channel name or id. Required for channel-canvas.", "title": "Channel" }, "message_id": { "anyOf": [ { "maxLength": 128, "type": "string" }, { "type": "null" } ], "default": null, "description": "Message id. Required for resource=message.", "title": "Message Id" }, "attachment_id": { "anyOf": [ { "maxLength": 128, "type": "string" }, { "type": "null" } ], "default": null, "description": "Attachment id. Required for resource=attachment.", "title": "Attachment Id" }, "routine_id": { "anyOf": [ { "maxLength": 64, "type": "string" }, { "type": "null" } ], "default": null, "description": "Routine id. Required for resource=routine-runs.", "title": "Routine Id" }, "q": { "anyOf": [ { "maxLength": 200, "minLength": 2, "type": "string" }, { "type": "null" } ], "default": null, "description": "Search text, 2-200 characters. Required for resource=search.", "title": "Q" }, "limit": { "anyOf": [ { "maximum": 200, "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "description": "Row cap. The per-resource maximum differs (50 to 200); the API rejects an over-large value.", "title": "Limit" }, "cursor": { "anyOf": [ { "maxLength": 512, "type": "string" }, { "type": "null" } ], "default": null, "description": "Opaque cursor from a previous page's `next_cursor`.", "title": "Cursor" }, "actor": { "anyOf": [ { "maxLength": 32, "type": "string" }, { "type": "null" } ], "default": null, "description": "Activity filter: human, agent, runner or system.", "title": "Actor" }, "category": { "anyOf": [ { "maxLength": 32, "type": "string" }, { "type": "null" } ], "default": null, "description": "Activity filter: message, conversation, approval, execution, workflow, canvas or configuration.", "title": "Category" }, "consequence": { "anyOf": [ { "maxLength": 32, "type": "string" }, { "type": "null" } ], "default": null, "description": "Activity filter: requested, changed, approved, completed, failed or recorded.", "title": "Consequence" }, "agent_id": { "anyOf": [ { "maxLength": 128, "type": "string" }, { "type": "null" } ], "default": null, "description": "Activity filter: only this agent's activity.", "title": "Agent Id" }, "date_from": { "anyOf": [ { "maxLength": 64, "type": "string" }, { "type": "null" } ], "default": null, "description": "Activity filter: ISO timestamp, inclusive lower bound.", "title": "Date From" }, "date_to": { "anyOf": [ { "maxLength": 64, "type": "string" }, { "type": "null" } ], "default": null, "description": "Activity filter: ISO timestamp, inclusive upper bound.", "title": "Date To" } }, "title": "g8_work_readArguments", "type": "object" } ``` </details> ## g8_work_send_message > Post a message into a graph8 Work conversation. This is a real message other people and agents will see, so say what the user asked you to say and nothing else. Mentioning an agent via `mention_agent_ids` starts an agent turn: that agent reads the room and answers in it. 403 when the credential is not a member of the channel - an organisation API key holds a seat in the built-in channels (#work, #signals) and in channels it created. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `channel` | Yes | string | Conversation to post into: a channel name (`work`) or id. | | `message` | No | string / null | Plain text to send. Send this OR `document`. | | `document` | No | object / null | Rich-text document; normalized server-side. | | `parent_message_id` | No | string / null | Reply inside this message's thread. | | `root_message_id` | No | string / null | Thread root, when replying deeper than one level. | | `mention_agent_ids` | No | array / null | Agents to @mention. Mentioning one starts an agent turn that answers in the room. | | `mention_user_ids` | No | array / null | People to @mention. | | `attachment_ids` | No | array / null | Already-uploaded attachment ids to carry. | <details> <summary>Complete input schema</summary> ```json { "properties": { "channel": { "description": "Conversation to post into: a channel name (`work`) or id.", "maxLength": 255, "title": "Channel", "type": "string" }, "message": { "anyOf": [ { "maxLength": 20000, "type": "string" }, { "type": "null" } ], "default": null, "description": "Plain text to send. Send this OR `document`.", "title": "Message" }, "document": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "description": "Rich-text document; normalized server-side.", "title": "Document" }, "parent_message_id": { "anyOf": [ { "maxLength": 128, "type": "string" }, { "type": "null" } ], "default": null, "description": "Reply inside this message's thread.", "title": "Parent Message Id" }, "root_message_id": { "anyOf": [ { "maxLength": 128, "type": "string" }, { "type": "null" } ], "default": null, "description": "Thread root, when replying deeper than one level.", "title": "Root Message Id" }, "mention_agent_ids": { "anyOf": [ { "items": { "type": "string" }, "maxItems": 16, "type": "array" }, { "type": "null" } ], "default": null, "description": "Agents to @mention. Mentioning one starts an agent turn that answers in the room.", "title": "Mention Agent Ids" }, "mention_user_ids": { "anyOf": [ { "items": { "type": "string" }, "maxItems": 64, "type": "array" }, { "type": "null" } ], "default": null, "description": "People to @mention.", "title": "Mention User Ids" }, "attachment_ids": { "anyOf": [ { "items": { "type": "string" }, "maxItems": 20, "type": "array" }, { "type": "null" } ], "default": null, "description": "Already-uploaded attachment ids to carry.", "title": "Attachment Ids" } }, "required": [ "channel" ], "title": "g8_work_send_messageArguments", "type": "object" } ``` </details> --- # Intent signals MCP tools > Tool descriptions and complete input schemas for intent operations. Source: https://docs.graph8.com/developers/mcp-reference/intent/ Use this reference to find a tool and its inputs. [Connect an MCP client](https://docs.graph8.com/developers/mcp/) before calling tools. Tool visibility depends on discovery and permissions. For actions that send messages, spend credits, delete data, or start execution, review the intended records and confirm the action before proceeding. Obtain IDs from a prior result; do not invent them. A registered tool does not guarantee that its supporting service is available in every organization. ## g8_intent_add_keywords > Bulk-add plain compete-tracking keyword phrases (web-UI parity). The MCP twin of the Buyer Intent > Keywords bulk-add screen: saves each phrase as a plain keyword row (stored lowercased). Keywords that already exist for this org + signal_type are reported as skipped, not duplicated. For signal_type "intent" each NEW keyword seeds its URL set in the background - URLs and resolved contacts appear later, not in this response. Use g8_intent_update_keyword_filters afterwards to set per-keyword search filters (e.g. a hybrid semantic blend). Args: keywords: 1-50 keyword phrases (each max 500 chars). signal_type: "intent" (default), "jobs", or "job_changes". ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `keywords` | Yes | array | See the schema below for constraints and defaults. | | `signal_type` | No | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "keywords": { "items": { "type": "string" }, "title": "Keywords", "type": "array" }, "signal_type": { "default": "intent", "title": "Signal Type", "type": "string" } }, "required": [ "keywords" ], "title": "g8_intent_add_keywordsArguments", "type": "object" } ``` </details> ## g8_intent_create_from_domain > Compete-tracking keyword create from a competitor domain. Pulls on-domain pages, resolves contacts, and saves a keyword row with both URLs and pre-resolved contacts. Strict off-domain guard: only <domain> and *.<domain> are saved. Returns the new keyword_id plus counts (pages_seeded, contacts_seeded, companies_seeded, off_domain_rejected). CONFIRM STEP: a call without confirm=true saves NOTHING. It returns status "confirmation_required" with the preview (counts + sample_pages + sample_contacts, keyword_id null). Show the user that preview, then call again with confirm=true to create the keyword, which the daily resolver keeps processing. Args: domain: Competitor domain, bare (no scheme), e.g. "cognism.com". page_limit: Max pages to seed (default 200, 1-500). contact_limit: Max contacts to pre-resolve (default 200, 1-500). dry_run: True = preview only, even with confirm=true. False (explicit) = create in one call without the confirm step (kept for existing scripted callers). confirm: True = create the keyword (after showing the preview). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `domain` | Yes | string | See the schema below for constraints and defaults. | | `page_limit` | No | integer | See the schema below for constraints and defaults. | | `contact_limit` | No | integer | See the schema below for constraints and defaults. | | `dry_run` | No | boolean / null | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "domain": { "title": "Domain", "type": "string" }, "page_limit": { "default": 200, "title": "Page Limit", "type": "integer" }, "contact_limit": { "default": 200, "title": "Contact Limit", "type": "integer" }, "dry_run": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Dry Run" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "domain" ], "title": "g8_intent_create_from_domainArguments", "type": "object" } ``` </details> ## g8_intent_delete_keyword > Soft-delete a compete-tracking keyword row. Unknown or already deleted ids return "not found". Args: keyword_id: UUID of the keyword row (from g8_intent_list_keywords). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `keyword_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "keyword_id": { "title": "Keyword Id", "type": "string" } }, "required": [ "keyword_id" ], "title": "g8_intent_delete_keywordArguments", "type": "object" } ``` </details> ## g8_intent_domain_search > Find every indexed page on a competitor's domain (and subdomains). Returns the apex domain and every subdomain (www, blog, help, etc.) - not news articles that merely mention the competitor. Args: domain: Competitor root domain, bare (no scheme/path), e.g. "cognism.com". limit: Max pages (default 200, max 500). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `domain` | Yes | string | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "domain": { "title": "Domain", "type": "string" }, "limit": { "default": 200, "title": "Limit", "type": "integer" } }, "required": [ "domain" ], "title": "g8_intent_domain_searchArguments", "type": "object" } ``` </details> ## g8_intent_keyword_companies > Companies resolved against a keyword's URL set. Aggregates contacts by company_domain so you see which orgs are poking around the competitor, not just individuals. One row per company (`total` counts the same companies), strongest first: `signal_count` is its number of visits, `signal_strength` the same strong / light rule as the People rows, and `topics_of_interest` up to 3 other keywords it also lit up for. Placeholder employers ("self-employed", "freelance") are not listed. `company_id` is this org's CRM record id (null when not in the CRM); `global_company_id` is the exact global COMPANY_ID. An unknown keyword_id returns "not found". ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `keyword_id` | Yes | string | See the schema below for constraints and defaults. | | `page` | No | integer | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "keyword_id": { "title": "Keyword Id", "type": "string" }, "page": { "default": 1, "title": "Page", "type": "integer" }, "limit": { "default": 100, "title": "Limit", "type": "integer" } }, "required": [ "keyword_id" ], "title": "g8_intent_keyword_companiesArguments", "type": "object" } ``` </details> ## g8_intent_keyword_contacts > People resolved against a keyword's URL set. Each row includes full contact identity (name, job title, LinkedIn, work email) plus the company they're tied to (null when unknown). IDs: CONTACT_ID / COMPANY_ID are graph8's global keys (too large for JSON numbers, so they can appear rounded); use `company_id` / `contact_id` (this org's CRM record ids, null when the company or person is not in the CRM) with the company and contact tools, and `global_company_id` / `global_contact_id` for the exact global key. `mode_counts.signals_only` is the signal-contact total; the ICP modes stay 0 unless the keyword has a buying committee. An unknown keyword_id returns "not found". ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `keyword_id` | Yes | string | See the schema below for constraints and defaults. | | `page` | No | integer | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "keyword_id": { "title": "Keyword Id", "type": "string" }, "page": { "default": 1, "title": "Page", "type": "integer" }, "limit": { "default": 100, "title": "Limit", "type": "integer" } }, "required": [ "keyword_id" ], "title": "g8_intent_keyword_contactsArguments", "type": "object" } ``` </details> ## g8_intent_keyword_urls > URLs linked to a compete-tracking keyword. Args: keyword_id: UUID from g8_intent_list_keywords. page: Page (default 1). limit: Per page (default 100, max 500). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `keyword_id` | Yes | string | See the schema below for constraints and defaults. | | `page` | No | integer | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "keyword_id": { "title": "Keyword Id", "type": "string" }, "page": { "default": 1, "title": "Page", "type": "integer" }, "limit": { "default": 100, "title": "Limit", "type": "integer" } }, "required": [ "keyword_id" ], "title": "g8_intent_keyword_urlsArguments", "type": "object" } ``` </details> ## g8_intent_keywords_create_from_search > Keywords Create From Search. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise nothing is changed and the preview's `target` says what would be created (nothing is resolved or saved). API: POST /intent/keywords/create-from-search ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `body` | Yes | #/$defs/ApiKeywordsCreateFromSearchRequest | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "$defs": { "ApiKeywordsCreateFromSearchRequest": { "additionalProperties": false, "properties": { "contact_limit": { "default": 50, "description": "Max contacts to pre-resolve per keyword.", "maximum": 100.0, "minimum": 1.0, "title": "Contact Limit", "type": "integer" }, "keywords": { "description": "1-10 free-text keyword queries. Each is searched against the intent page index, contacts/companies are resolved, and a compete-tracking keyword row is saved. Duplicates within the request (case-insensitive, trimmed) are skipped, not re-saved.", "items": { "type": "string" }, "maxItems": 10, "minItems": 1, "title": "Keywords", "type": "array" }, "page_limit": { "default": 20, "description": "Max pages to seed per keyword. Deliberately smaller than create-from-domain's 200 default since this runs once per keyword.", "maximum": 50.0, "minimum": 1.0, "title": "Page Limit", "type": "integer" }, "search_filters": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "description": "Optional intent-search filters applied to EVERY keyword in the batch (audience, page_type, commercial_score_min, etc.). Stored verbatim on each saved keyword row so the daily resolver replays them.", "title": "Search Filters" }, "semantic_ratio": { "default": 0.0, "description": "0 = text search (default, cheapest), 1 = semantic, between = hybrid.", "maximum": 1.0, "minimum": 0.0, "title": "Semantic Ratio", "type": "number" } }, "required": [ "keywords" ], "title": "ApiKeywordsCreateFromSearchRequest", "type": "object" } }, "properties": { "body": { "$ref": "#/$defs/ApiKeywordsCreateFromSearchRequest" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "body" ], "title": "g8_intent_keywords_create_from_searchArguments", "type": "object" } ``` </details> ## g8_intent_list_keywords > All compete-tracking keyword rows for this org. Each row carries total_resolved_contacts and total_resolved_companies so you can rank by delivered compete intelligence. Companies can exceed contacts: a company is also counted when a visit resolved only to the company (by IP), with no named person. Args: keyword_contains: Optional case-insensitive substring filter. page: Page number (default 1). limit: Rows per page (default 100, max 500). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `keyword_contains` | No | string / null | See the schema below for constraints and defaults. | | `page` | No | integer | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "keyword_contains": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Keyword Contains" }, "page": { "default": 1, "title": "Page", "type": "integer" }, "limit": { "default": 100, "title": "Limit", "type": "integer" } }, "title": "g8_intent_list_keywordsArguments", "type": "object" } ``` </details> ## g8_intent_page_visitors > Resolve the people who visited a specific page URL. `visitors` holds this page of resolved people and `total` is how many are in it; `total_visitors` is every distinct visitor on the URL in the lookback window. `breakdown` tallies every SCANNED visitor by tier (verified / b2b / b2c / name_only / company / anonymous) and sums to `scanned`. On a busy page the server stops early and sets `partial=true` with a `warnings` entry; page on with nextOffset. `newest_visit` is the latest visit date seen on the URL and `feed` says whether the intent feed itself is delayed (feed.degraded). Args: url: Canonical PAGE_URL (no scheme, no www), e.g. "cognism.com/pricing". limit: Max visitors (default 50, 1-500; larger values are capped). mode: "quick" (CID + UP_ID only) or "full" (all identity paths, slower). Busy pages can take 10-20 s either way. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `url` | Yes | string | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | | `mode` | No | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "url": { "title": "Url", "type": "string" }, "limit": { "default": 50, "title": "Limit", "type": "integer" }, "mode": { "default": "quick", "title": "Mode", "type": "string" } }, "required": [ "url" ], "title": "g8_intent_page_visitorsArguments", "type": "object" } ``` </details> ## g8_intent_pages_contacts > Aggregated, deduplicated resolved contacts across multiple page URLs. Useful for compete-tracking setup: pre-populate a keyword's contacts so it shows real counts immediately. Counts differ from g8_intent_pages_visitor_counts BY DEFINITION: `total` = visitors that resolve to a profiled person among the `scanned_visitors` most active of `total_visitors` distinct visitors (anonymous visitors are not contacts). See `definition`. Args: urls: Up to 200 canonical PAGE_URLs. limit: Max contacts returned (default 100, 1-500; larger values are capped). mode: "quick" or "full". ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `urls` | Yes | array | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | | `mode` | No | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "urls": { "items": { "type": "string" }, "title": "Urls", "type": "array" }, "limit": { "default": 100, "title": "Limit", "type": "integer" }, "mode": { "default": "quick", "title": "Mode", "type": "string" } }, "required": [ "urls" ], "title": "g8_intent_pages_contactsArguments", "type": "object" } ``` </details> ## g8_intent_pages_visitor_counts > Batched unique-visitor count per page URL. Useful for ranking a URL list by traffic before drilling into contacts. Every requested URL is in `counts` (0 = no visitors). These are ALL distinct visitors on the web, so they are larger than the resolved contact counts of g8_intent_pages_contacts (see `definition`). Args: urls: Up to 15,000 canonical PAGE_URLs. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `urls` | Yes | array | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "urls": { "items": { "type": "string" }, "title": "Urls", "type": "array" } }, "required": [ "urls" ], "title": "g8_intent_pages_visitor_countsArguments", "type": "object" } ``` </details> ## g8_intent_search_pages > Text / semantic / hybrid search across intent-enriched pages. Supports operator syntax: bare domain (cognism.com) -> domain-mode; site:cognism.com, intitle:pricing, inurl:blog, type:blog, audience:b2b, -excluded_word. A bare-domain search matches the apex + www only; use g8_intent_domain_search for every subdomain (it can list more pages). With include_visitors=true (default) only pages with visitors are returned and each carries `visitors`. If the visitor count is slow or fails, the pages still come back (unfiltered, visitors=null) and `warnings` / `visitors_error` say why. `filtered_no_visitors` counts hits hidden for having no visitors (total_hits still counts them). Args: query: Search string (free-form text + operators). Must not be blank. limit: Max results (default 50, 1-500; larger values are capped at 500). semantic_ratio: 0.0 text-only, 1.0 semantic-only, between = hybrid (must be 0.0-1.0). filters: Optional filter dict (audience, page_type, commercial_intent_level, etc.). sort: Optional sort fields like ["commercial_score:desc"]. include_visitors: When true, enriches each hit with a visitor count. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `query` | Yes | string | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | | `semantic_ratio` | No | number | See the schema below for constraints and defaults. | | `filters` | No | object / null | See the schema below for constraints and defaults. | | `sort` | No | array / null | See the schema below for constraints and defaults. | | `include_visitors` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "query": { "title": "Query", "type": "string" }, "limit": { "default": 50, "title": "Limit", "type": "integer" }, "semantic_ratio": { "default": 0.0, "title": "Semantic Ratio", "type": "number" }, "filters": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Filters" }, "sort": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Sort" }, "include_visitors": { "default": true, "title": "Include Visitors", "type": "boolean" } }, "required": [ "query" ], "title": "g8_intent_search_pagesArguments", "type": "object" } ``` </details> ## g8_intent_search_url_companies > Companies that visited a single page URL, aggregated by company. Returns each company's audience bucket (b2b/b2c), visit_count, contacts_seen, last_seen, plus light enrichment (industry, employee_count range, logo_url). Scope: companies of contacts YOUR keywords have already resolved on this URL, so a URL none of your keywords tracks returns 0 even when its visitor count is high (see `definition`). For keyword-keyed rollups across many URLs, use g8_intent_keyword_companies instead. Args: url: Canonical page_url, no scheme/www (e.g. "cognism.com/pricing"). days: Lookback window in days (default 90, must be 1-365). limit: Max companies returned (default 200, 1-500; larger values are capped). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `url` | Yes | string | See the schema below for constraints and defaults. | | `days` | No | integer | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "url": { "title": "Url", "type": "string" }, "days": { "default": 90, "title": "Days", "type": "integer" }, "limit": { "default": 200, "title": "Limit", "type": "integer" } }, "required": [ "url" ], "title": "g8_intent_search_url_companiesArguments", "type": "object" } ``` </details> ## g8_intent_stats > Intent index coverage stats (doc count + size) and feed freshness. Call before starting a compete-tracking program to confirm the index has meaningful coverage for the orgs / domains of interest. `feed` reports the visitor-signal feed: status ok / delayed / unknown, newest_day, lag_days and gap_days; when feed.degraded is true, tell the user intent results are out of date. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_intent_statsArguments", "type": "object" } ``` </details> ## g8_intent_update_keyword_filters > Replace a keyword's search_filters JSON. Full replacement, not merge. Optional typed fields: audience ("b2b"\|"b2c"\|"both"); exclude_page_types (list of "news"\|"finance"\|"forum"\|"vendor_marketing"); min_commercial_intent_level (int 0-100); max_urls_per_domain (int 1-20); llm_triage (bool); exclude_domains (list of domain strings). Args: keyword_id: UUID of the keyword. search_filters: Full replacement filter dict. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `keyword_id` | Yes | string | See the schema below for constraints and defaults. | | `search_filters` | Yes | object | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "keyword_id": { "title": "Keyword Id", "type": "string" }, "search_filters": { "additionalProperties": true, "title": "Search Filters", "type": "object" } }, "required": [ "keyword_id", "search_filters" ], "title": "g8_intent_update_keyword_filtersArguments", "type": "object" } ``` </details> --- # Skill library MCP tools > Tool descriptions and complete input schemas for library operations. Source: https://docs.graph8.com/developers/mcp-reference/library/ Use this reference to find a tool and its inputs. [Connect an MCP client](https://docs.graph8.com/developers/mcp/) before calling tools. Tool visibility depends on discovery and permissions. For actions that send messages, spend credits, delete data, or start execution, review the intended records and confirm the action before proceeding. Obtain IDs from a prior result; do not invent them. A registered tool does not guarantee that its supporting service is available in every organization. ## g8_library_get > Load one graph8 Skill Library skill - full SKILL.md content by slug. Use after g8_library_search to read a skill before installing it, or to follow its recipe directly in this session. To give the user install steps for their client, prefer g8_library_install_instructions. Args: slug: Skill slug from g8_library_search (e.g. "find-and-list-leads"). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `slug` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "slug": { "title": "Slug", "type": "string" } }, "required": [ "slug" ], "title": "g8_library_getArguments", "type": "object" } ``` </details> ## g8_library_install_instructions > Install a graph8 Skill Library skill into the user's AI client. Returns the full SKILL.md content plus exact save/install steps for the target client. MCP servers cannot write to the host filesystem, so surface these steps (and the content) to the user - or, if you are an agent with file access (e.g. Claude Code), perform the save step yourself after confirming with the user. Args: slug: Skill slug from g8_library_search. client: Target client - claude_code, claude_desktop, claude_ai, cursor, or other. Default claude_code. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `slug` | Yes | string | See the schema below for constraints and defaults. | | `client` | No | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "slug": { "title": "Slug", "type": "string" }, "client": { "default": "claude_code", "title": "Client", "type": "string" } }, "required": [ "slug" ], "title": "g8_library_install_instructionsArguments", "type": "object" } ``` </details> ## g8_library_search > Search the graph8 Skill Library - installable agent skills. The Skill Library is the public catalog of SKILL.md workflow recipes (prospecting, enrichment, outbound, inbox, signals, workflows, voice, calendar) that a USER installs into their own AI client. Use this when the user asks what graph8 skills exist, wants a ready-made workflow recipe, or asks how to automate a graph8 job in their agent. Distinct from playbooks (guides this agent reads in-session via g8_load_playbook) and from g8_skill_* (workflow Action-node records). Args: query: Free-text filter over name, description, category, outcome, and required tool names. Omit to list everything. category: Exact category filter - one of crm, enrichment, outbound, inbox, signals, workflows, voice, calendar. limit: Max results (default 20). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `query` | No | string / null | See the schema below for constraints and defaults. | | `category` | No | string / null | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "query": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Query" }, "category": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Category" }, "limit": { "default": 20, "title": "Limit", "type": "integer" } }, "title": "g8_library_searchArguments", "type": "object" } ``` </details> --- # Marketplace MCP tools > Tool descriptions and complete input schemas for marketplace operations. Source: https://docs.graph8.com/developers/mcp-reference/marketplace/ Use this reference to find a tool and its inputs. [Connect an MCP client](https://docs.graph8.com/developers/mcp/) before calling tools. Tool visibility depends on discovery and permissions. For actions that send messages, spend credits, delete data, or start execution, review the intended records and confirm the action before proceeding. Obtain IDs from a prior result; do not invent them. A registered tool does not guarantee that its supporting service is available in every organization. ## g8_marketplace_accept_offer > Accept a pending marketplace hire offer. Moves the hiring to ``active``, creates the hiring-fee invoice, grants you membership in the client org, and notifies the client. Requires a personal API key. Args: hiring_id: Hiring offer to accept (from g8_marketplace_list_my_offers). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `hiring_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "hiring_id": { "title": "Hiring Id", "type": "string" } }, "required": [ "hiring_id" ], "title": "accept_marketplace_offerArguments", "type": "object" } ``` </details> ## g8_marketplace_admin_funnel > Marketplace SDR supply funnel - conversion stages + snapshot counts. graph8 platform admin only (403 otherwise). Args: period_from: YYYY-MM-DD inclusive start (max 365-day range). period_to: YYYY-MM-DD inclusive end. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `period_from` | Yes | string | See the schema below for constraints and defaults. | | `period_to` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "period_from": { "title": "Period From", "type": "string" }, "period_to": { "title": "Period To", "type": "string" } }, "required": [ "period_from", "period_to" ], "title": "admin_marketplace_funnelArguments", "type": "object" } ``` </details> ## g8_marketplace_admin_hires > Hire conversion + termination + rehire metrics. graph8 admin only. Args: period_from: YYYY-MM-DD inclusive start. period_to: YYYY-MM-DD inclusive end. group_by: day / week / month. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `period_from` | Yes | string | See the schema below for constraints and defaults. | | `period_to` | Yes | string | See the schema below for constraints and defaults. | | `group_by` | No | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "period_from": { "title": "Period From", "type": "string" }, "period_to": { "title": "Period To", "type": "string" }, "group_by": { "default": "month", "title": "Group By", "type": "string" } }, "required": [ "period_from", "period_to" ], "title": "admin_marketplace_hiresArguments", "type": "object" } ``` </details> ## g8_marketplace_admin_operational > Operational health - SLA, disputes, payout success. graph8 admin only. Args: period_from: YYYY-MM-DD inclusive start. period_to: YYYY-MM-DD inclusive end. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `period_from` | Yes | string | See the schema below for constraints and defaults. | | `period_to` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "period_from": { "title": "Period From", "type": "string" }, "period_to": { "title": "Period To", "type": "string" } }, "required": [ "period_from", "period_to" ], "title": "admin_marketplace_operationalArguments", "type": "object" } ``` </details> ## g8_marketplace_admin_revenue > GMV, net commission, and money-loop state distribution. graph8 admin only. Args: period_from: YYYY-MM-DD inclusive start. period_to: YYYY-MM-DD inclusive end. group_by: day / week / month. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `period_from` | Yes | string | See the schema below for constraints and defaults. | | `period_to` | Yes | string | See the schema below for constraints and defaults. | | `group_by` | No | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "period_from": { "title": "Period From", "type": "string" }, "period_to": { "title": "Period To", "type": "string" }, "group_by": { "default": "month", "title": "Group By", "type": "string" } }, "required": [ "period_from", "period_to" ], "title": "admin_marketplace_revenueArguments", "type": "object" } ``` </details> ## g8_marketplace_approve_my_invoice > Approve your own draft invoice (sdr_review -> pending), submitting it for client review. SDR-side only; the org uses approve_org_invoice to release payment. Args: invoice_id: Draft invoice to approve (from g8_marketplace_list_my_invoices). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `invoice_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "invoice_id": { "title": "Invoice Id", "type": "string" } }, "required": [ "invoice_id" ], "title": "approve_my_marketplace_invoiceArguments", "type": "object" } ``` </details> ## g8_marketplace_approve_org_invoice > Approve an SDR invoice - triggers an IMMEDIATE Stripe charge to your org's default payment method, transferring funds to the SDR. Irreversible; confirm with the user before invoking. Args: invoice_id: Invoice to approve and charge (from g8_marketplace_list_org_invoices). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `invoice_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "invoice_id": { "title": "Invoice Id", "type": "string" } }, "required": [ "invoice_id" ], "title": "approve_org_marketplace_invoiceArguments", "type": "object" } ``` </details> ## g8_marketplace_browse_talent > Browse available marketplace talent. Already-hired SDRs are filtered out; anonymous profiles stay masked unless your org previously hired them. Args: country_code: ISO-2 country filter. payout_track: Payout track filter. role: sdr / ae / gtm_engineer. availability: open / limited / fully_booked. tier: Certification tier (TitleCase, case-sensitive). AE: "Top Performer" \| "Leadership-track" \| "Senior" \| "Solid" \| "Transition". SDR: "Strategic" \| "Data-Driven" \| "Independent" \| "Disciplined". Other values return an empty set. limit: Page size, clamped to 1-5000. offset: Pagination offset. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `country_code` | No | string / null | See the schema below for constraints and defaults. | | `payout_track` | No | string / null | See the schema below for constraints and defaults. | | `role` | No | string / null | See the schema below for constraints and defaults. | | `availability` | No | string / null | See the schema below for constraints and defaults. | | `tier` | No | string / null | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | | `offset` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "country_code": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Country Code" }, "payout_track": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Payout Track" }, "role": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Role" }, "availability": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Availability" }, "tier": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Tier" }, "limit": { "default": 50, "title": "Limit", "type": "integer" }, "offset": { "default": 0, "title": "Offset", "type": "integer" } }, "title": "browse_marketplace_talentArguments", "type": "object" } ``` </details> ## g8_marketplace_create_job_post > Publish a marketplace job post (Open Role) under your org. Applicants apply to your org, so a graph8 key owns every applicant. Put the client's JD + requirements in ``description``. Set ``status="published"`` to go live to talent now, or leave it ``"draft"`` to stage and publish later. Args: title: Role title (e.g. "Outbound SDR - Acme Corp"). role_type: sdr \| ae \| gtm \| gtm_engineer \| campaign_manager. description: The client's JD + requirements (free text). engagement_type: monthly \| per_meeting \| hybrid \| commission_only. industries: Optional target industries. tools: Optional required tools (e.g. Outreach, Salesforce). countries: Optional ISO-2 country filters. languages: Optional required languages. seniority: e.g. junior \| mid \| senior. monthly_rate_usd_min: Monthly base range floor. monthly_rate_usd_max: Monthly base range ceiling. per_meeting_rate_usd: Per-meeting rate. openings_count: Number of openings (>= 1). status: draft (default) \| published. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `title` | Yes | string | See the schema below for constraints and defaults. | | `role_type` | Yes | string | See the schema below for constraints and defaults. | | `description` | No | string / null | See the schema below for constraints and defaults. | | `engagement_type` | No | string | See the schema below for constraints and defaults. | | `industries` | No | array / null | See the schema below for constraints and defaults. | | `tools` | No | array / null | See the schema below for constraints and defaults. | | `countries` | No | array / null | See the schema below for constraints and defaults. | | `languages` | No | array / null | See the schema below for constraints and defaults. | | `seniority` | No | string / null | See the schema below for constraints and defaults. | | `monthly_rate_usd_min` | No | number / null | See the schema below for constraints and defaults. | | `monthly_rate_usd_max` | No | number / null | See the schema below for constraints and defaults. | | `per_meeting_rate_usd` | No | number / null | See the schema below for constraints and defaults. | | `commission_plan_version_id` | No | string / null | See the schema below for constraints and defaults. | | `openings_count` | No | integer | See the schema below for constraints and defaults. | | `status` | No | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "title": { "title": "Title", "type": "string" }, "role_type": { "title": "Role Type", "type": "string" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "engagement_type": { "default": "monthly", "title": "Engagement Type", "type": "string" }, "industries": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Industries" }, "tools": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Tools" }, "countries": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Countries" }, "languages": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Languages" }, "seniority": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Seniority" }, "monthly_rate_usd_min": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Monthly Rate Usd Min" }, "monthly_rate_usd_max": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Monthly Rate Usd Max" }, "per_meeting_rate_usd": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Per Meeting Rate Usd" }, "commission_plan_version_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Commission Plan Version Id" }, "openings_count": { "default": 1, "title": "Openings Count", "type": "integer" }, "status": { "default": "draft", "title": "Status", "type": "string" } }, "required": [ "title", "role_type" ], "title": "create_marketplace_job_postArguments", "type": "object" } ``` </details> ## g8_marketplace_delete_job_post > Delete a job post your org owns, plus its applications. Use for draft/test or obsolete roles. If the role already has applicants, this fails unless you pass ``force=true`` - a guard so an applicant inbox isn't wiped by mistake. Args: post_id: Job post to delete. force: Set true to confirm deleting a role that has applications (removes their application history). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `post_id` | Yes | string | See the schema below for constraints and defaults. | | `force` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "post_id": { "title": "Post Id", "type": "string" }, "force": { "default": false, "title": "Force", "type": "boolean" } }, "required": [ "post_id" ], "title": "delete_marketplace_job_postArguments", "type": "object" } ``` </details> ## g8_marketplace_dispute_org_invoice > Dispute an SDR invoice within the grace period. Must be in ``pending`` state and within the 5-calendar-day review window. Irreversible - the invoice goes to ops for resolution and there is no programmatic un-dispute. Args: invoice_id: Invoice to dispute (from g8_marketplace_list_org_invoices). reason: Required free-form dispute reason. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `invoice_id` | Yes | string | See the schema below for constraints and defaults. | | `reason` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "invoice_id": { "title": "Invoice Id", "type": "string" }, "reason": { "title": "Reason", "type": "string" } }, "required": [ "invoice_id", "reason" ], "title": "dispute_org_marketplace_invoiceArguments", "type": "object" } ``` </details> ## g8_marketplace_get_job_post > Get one job post your org owns. Args: post_id: Job post id (from g8_marketplace_list_job_posts). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `post_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "post_id": { "title": "Post Id", "type": "string" } }, "required": [ "post_id" ], "title": "get_marketplace_job_postArguments", "type": "object" } ``` </details> ## g8_marketplace_get_my_connect_status > Get your Stripe Connect onboarding status (live Stripe-enriched). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "get_my_marketplace_connect_statusArguments", "type": "object" } ``` </details> ## g8_marketplace_get_my_meeting > Get one of your meetings by ID - includes transcript, recording, attendees, notes. Args: meeting_id: Meeting to fetch (from g8_marketplace_list_my_meetings). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `meeting_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "meeting_id": { "title": "Meeting Id", "type": "string" } }, "required": [ "meeting_id" ], "title": "get_my_marketplace_meetingArguments", "type": "object" } ``` </details> ## g8_marketplace_get_my_payout_account > Get your payout account status (Stripe Connect / Wise / fallback). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "get_my_marketplace_payout_accountArguments", "type": "object" } ``` </details> ## g8_marketplace_get_my_profile > Get your own marketplace SDR profile. Returns rate, availability, role, country, bio, and an ``is_complete`` flag (gates marketplace visibility). Requires a personal API key; org-scoped keys return 404 (use g8_marketplace_list_org_hirings instead). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "get_my_marketplace_profileArguments", "type": "object" } ``` </details> ## g8_marketplace_get_org_hiring > Get one hiring contract for your org, with the SDR's profile. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `hiring_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "hiring_id": { "title": "Hiring Id", "type": "string" } }, "required": [ "hiring_id" ], "title": "get_org_marketplace_hiringArguments", "type": "object" } ``` </details> ## g8_marketplace_get_org_invoice > Get one invoice in full detail (SDR profile + hiring contract context). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `invoice_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "invoice_id": { "title": "Invoice Id", "type": "string" } }, "required": [ "invoice_id" ], "title": "get_org_marketplace_invoiceArguments", "type": "object" } ``` </details> ## g8_marketplace_get_talent_match_score > Get the cached match score for your org and this talent. If none exists, dispatches an async compute and returns ``{"status": "generating"}``; poll again after a few seconds. Args: sdr_id: Talent to score against your org (from g8_marketplace_browse_talent). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `sdr_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "sdr_id": { "title": "Sdr Id", "type": "string" } }, "required": [ "sdr_id" ], "title": "get_marketplace_talent_matchArguments", "type": "object" } ``` </details> ## g8_marketplace_get_talent_profile > Get one talent profile by ID. Anonymous profiles stay masked unless your org has previously hired this SDR. Args: sdr_id: Talent profile to fetch (from g8_marketplace_browse_talent). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `sdr_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "sdr_id": { "title": "Sdr Id", "type": "string" } }, "required": [ "sdr_id" ], "title": "get_marketplace_talent_profileArguments", "type": "object" } ``` </details> ## g8_marketplace_list_job_applications > List applicants for a job post - each with their role-scoped match scorecard and status. This is how you track everyone who applied. Args: post_id: Job post whose applicants to list. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `post_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "post_id": { "title": "Post Id", "type": "string" } }, "required": [ "post_id" ], "title": "list_marketplace_job_applicationsArguments", "type": "object" } ``` </details> ## g8_marketplace_list_job_posts > List your org's marketplace job posts (any status), newest first. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "list_marketplace_job_postsArguments", "type": "object" } ``` </details> ## g8_marketplace_list_my_hirings > List active hiring contracts where you are the SDR side. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "list_my_marketplace_hiringsArguments", "type": "object" } ``` </details> ## g8_marketplace_list_my_invoices > List invoices you issued, optionally filtered by status. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `invoice_status` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "invoice_status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Invoice Status" } }, "title": "list_my_marketplace_invoicesArguments", "type": "object" } ``` </details> ## g8_marketplace_list_my_meetings > List your meetings across all hirings. Args: hiring_id: Filter to one hiring contract. meeting_status: scheduled / taken / rescheduled / missed / cancelled. start_date: YYYY-MM-DD lower bound. end_date: YYYY-MM-DD upper bound. search: Matches contact name / email / notes. limit: Page size (1-500). offset: Pagination offset. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `hiring_id` | No | string / null | See the schema below for constraints and defaults. | | `meeting_status` | No | string / null | See the schema below for constraints and defaults. | | `start_date` | No | string / null | See the schema below for constraints and defaults. | | `end_date` | No | string / null | See the schema below for constraints and defaults. | | `search` | No | string / null | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | | `offset` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "hiring_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Hiring Id" }, "meeting_status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Meeting Status" }, "start_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Start Date" }, "end_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "End Date" }, "search": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Search" }, "limit": { "default": 100, "title": "Limit", "type": "integer" }, "offset": { "default": 0, "title": "Offset", "type": "integer" } }, "title": "list_my_marketplace_meetingsArguments", "type": "object" } ``` </details> ## g8_marketplace_list_my_offers > List pending marketplace hire offers sent to your profile. Enriched with the hiring org's display name. Requires a personal API key. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "list_my_marketplace_offersArguments", "type": "object" } ``` </details> ## g8_marketplace_list_org_hirings > List active marketplace hiring contracts for your organization, enriched with the hired SDR's display name, role, and email. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "list_org_marketplace_hiringsArguments", "type": "object" } ``` </details> ## g8_marketplace_list_org_invoices > List marketplace invoices owed by your organization, enriched with the issuing SDR's display name. Args: invoice_status: Filter by draft / pending_client_approval / approved / charged / paid / disputed. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `invoice_status` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "invoice_status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Invoice Status" } }, "title": "list_org_marketplace_invoicesArguments", "type": "object" } ``` </details> ## g8_marketplace_reject_offer > Reject a pending marketplace hire offer. Irreversible - the client must send a new offer to re-engage. Args: hiring_id: Hiring offer to reject (from g8_marketplace_list_my_offers). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `hiring_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "hiring_id": { "title": "Hiring Id", "type": "string" } }, "required": [ "hiring_id" ], "title": "reject_marketplace_offerArguments", "type": "object" } ``` </details> ## g8_marketplace_send_hire_offer > Send a hire offer to a marketplace talent profile. Creates a pending offer and notifies the SDR. Returns ``status: active_contract_exists`` (409) if your org already has an active contract with this SDR. Supply explicit terms acceptance; a selected commission plan may have zero base and meeting rates. Args: sdr_id: Talent to hire (from g8_marketplace_browse_talent). start_date: Contract start, YYYY-MM-DD. monthly_rate_usd: Monthly retainer. per_meeting_rate_usd: Per-meeting rate. scope_text: Scope shown in the signed contract. contract_length_months: Optional fixed duration. commission_plan_version_id: Active commission plan version; snapshotted at signing. accepted_terms: Explicitly accepted document IDs; never infer acceptance. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `sdr_id` | Yes | string | See the schema below for constraints and defaults. | | `start_date` | Yes | string | See the schema below for constraints and defaults. | | `monthly_rate_usd` | No | number | See the schema below for constraints and defaults. | | `per_meeting_rate_usd` | No | number | See the schema below for constraints and defaults. | | `scope_text` | No | string / null | See the schema below for constraints and defaults. | | `contract_length_months` | No | integer / null | See the schema below for constraints and defaults. | | `commission_plan_version_id` | No | string / null | See the schema below for constraints and defaults. | | `accepted_terms` | No | array / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "sdr_id": { "title": "Sdr Id", "type": "string" }, "start_date": { "title": "Start Date", "type": "string" }, "monthly_rate_usd": { "default": 0, "title": "Monthly Rate Usd", "type": "number" }, "per_meeting_rate_usd": { "default": 0, "title": "Per Meeting Rate Usd", "type": "number" }, "scope_text": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Scope Text" }, "contract_length_months": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Contract Length Months" }, "commission_plan_version_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Commission Plan Version Id" }, "accepted_terms": { "anyOf": [ { "items": { "additionalProperties": { "type": "string" }, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Accepted Terms" } }, "required": [ "sdr_id", "start_date" ], "title": "send_marketplace_hire_offerArguments", "type": "object" } ``` </details> ## g8_marketplace_submit_my_invoice > Submit a monthly invoice for an active hiring contract. Computes meeting count + amounts automatically, transitions to ``pending``, starts the client's 5-calendar-day review window. Args: hiring_id: Active hiring contract (from g8_marketplace_list_my_hirings). period_start: Billing period start, YYYY-MM-DD. period_end: Billing period end, YYYY-MM-DD. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `hiring_id` | Yes | string | See the schema below for constraints and defaults. | | `period_start` | Yes | string | See the schema below for constraints and defaults. | | `period_end` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "hiring_id": { "title": "Hiring Id", "type": "string" }, "period_start": { "title": "Period Start", "type": "string" }, "period_end": { "title": "Period End", "type": "string" } }, "required": [ "hiring_id", "period_start", "period_end" ], "title": "submit_my_marketplace_invoiceArguments", "type": "object" } ``` </details> ## g8_marketplace_terminate_org_hiring > Terminate an active hiring contract (org side). The contract ends immediately, the SDR loses workspace access, and a final prorated invoice is issued. Irreversible - re-hiring requires a fresh hire offer. Args: hiring_id: Active hiring to terminate (from g8_marketplace_list_org_hirings). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `hiring_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "hiring_id": { "title": "Hiring Id", "type": "string" } }, "required": [ "hiring_id" ], "title": "terminate_org_marketplace_hiringArguments", "type": "object" } ``` </details> ## g8_marketplace_update_application > Move an applicant through review, or link a created hiring to it. Args: application_id: Application to update (from g8_marketplace_list_job_applications). status: viewed \| shortlisted \| rejected \| offer_sent \| hired. hiring_id: Optional hiring id to link to this application. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `application_id` | Yes | string | See the schema below for constraints and defaults. | | `status` | No | string / null | See the schema below for constraints and defaults. | | `hiring_id` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "application_id": { "title": "Application Id", "type": "string" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "hiring_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Hiring Id" } }, "required": [ "application_id" ], "title": "update_marketplace_applicationArguments", "type": "object" } ``` </details> ## g8_marketplace_update_job_post > Edit a job post or change its status. Only the fields you pass change. Args: post_id: Job post to update. status: draft \| published \| paused \| closed \| filled - set this to publish, pause, close, or mark a role filled. (all other args mirror create_job_post; omit to leave unchanged.) ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `post_id` | Yes | string | See the schema below for constraints and defaults. | | `title` | No | string / null | See the schema below for constraints and defaults. | | `description` | No | string / null | See the schema below for constraints and defaults. | | `role_type` | No | string / null | See the schema below for constraints and defaults. | | `engagement_type` | No | string / null | See the schema below for constraints and defaults. | | `industries` | No | array / null | See the schema below for constraints and defaults. | | `tools` | No | array / null | See the schema below for constraints and defaults. | | `countries` | No | array / null | See the schema below for constraints and defaults. | | `languages` | No | array / null | See the schema below for constraints and defaults. | | `seniority` | No | string / null | See the schema below for constraints and defaults. | | `monthly_rate_usd_min` | No | number / null | See the schema below for constraints and defaults. | | `monthly_rate_usd_max` | No | number / null | See the schema below for constraints and defaults. | | `per_meeting_rate_usd` | No | number / null | See the schema below for constraints and defaults. | | `commission_plan_version_id` | No | string / null | See the schema below for constraints and defaults. | | `openings_count` | No | integer / null | See the schema below for constraints and defaults. | | `status` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "post_id": { "title": "Post Id", "type": "string" }, "title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Title" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "role_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Role Type" }, "engagement_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Engagement Type" }, "industries": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Industries" }, "tools": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Tools" }, "countries": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Countries" }, "languages": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Languages" }, "seniority": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Seniority" }, "monthly_rate_usd_min": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Monthly Rate Usd Min" }, "monthly_rate_usd_max": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Monthly Rate Usd Max" }, "per_meeting_rate_usd": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Per Meeting Rate Usd" }, "commission_plan_version_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Commission Plan Version Id" }, "openings_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Openings Count" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" } }, "required": [ "post_id" ], "title": "update_marketplace_job_postArguments", "type": "object" } ``` </details> ## g8_marketplace_update_my_meeting > Update meeting notes + contact info (SDR side). Lifecycle fields (status, meeting_date, meeting_type) are not editable here. Args: meeting_id: Meeting to update (from g8_marketplace_list_my_meetings). notes: Meeting notes. contact_name: Contact name. contact_email: Contact email. contact_phone: Contact phone. contact_title: Contact job title. contact_company: Contact company. contact_linkedin_url: Contact LinkedIn URL. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `meeting_id` | Yes | string | See the schema below for constraints and defaults. | | `notes` | No | string / null | See the schema below for constraints and defaults. | | `contact_name` | No | string / null | See the schema below for constraints and defaults. | | `contact_email` | No | string / null | See the schema below for constraints and defaults. | | `contact_phone` | No | string / null | See the schema below for constraints and defaults. | | `contact_title` | No | string / null | See the schema below for constraints and defaults. | | `contact_company` | No | string / null | See the schema below for constraints and defaults. | | `contact_linkedin_url` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "meeting_id": { "title": "Meeting Id", "type": "string" }, "notes": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Notes" }, "contact_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Contact Name" }, "contact_email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Contact Email" }, "contact_phone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Contact Phone" }, "contact_title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Contact Title" }, "contact_company": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Contact Company" }, "contact_linkedin_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Contact Linkedin Url" } }, "required": [ "meeting_id" ], "title": "update_my_marketplace_meetingArguments", "type": "object" } ``` </details> ## g8_marketplace_withdraw_offer > Withdraw a hiring offer the talent has NOT yet accepted (org side). Use this to pull back an offer sent to the wrong talent or with the wrong terms: it cancels the pending offer and voids its contract so it can no longer be signed. No invoice is generated (an unsigned offer was never billable). For an already-accepted (active) contract use ``terminate_org_hiring`` instead - this returns an error if the offer is no longer pending. Args: hiring_id: Pending offer to withdraw (status ``pending_acceptance`` from g8_marketplace_list_org_hirings). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `hiring_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "hiring_id": { "title": "Hiring Id", "type": "string" } }, "required": [ "hiring_id" ], "title": "withdraw_org_marketplace_offerArguments", "type": "object" } ``` </details> --- # Opensearch MCP tools > Tool descriptions and complete input schemas for opensearch operations. Source: https://docs.graph8.com/developers/mcp-reference/opensearch/ Use this reference to find a tool and its inputs. [Connect an MCP client](https://docs.graph8.com/developers/mcp/) before calling tools. Tool visibility depends on discovery and permissions. For actions that send messages, spend credits, delete data, or start execution, review the intended records and confirm the action before proceeding. Obtain IDs from a prior result; do not invent them. A registered tool does not guarantee that its supporting service is available in every organization. ## g8_opensearch_aggregate > Generic GROUP BY over a whitelisted mashup index -- the TAM matrix-walk primitive. Counts company/contact distributions across 1-3 dimensions in one call (e.g. NAICS x state x employee band). Aggregations on contact/company mashup indices automatically apply the organization's deliverability protection policy. Args: index: One of "mashup_contacts", "mashup_companies", "hem2contact", "ip2company". group_by: 1-3 RAW UPPERCASE keyword fields to group on, e.g. ["COMPANY_NAICS", "COMPANY_STATE"]. Capped at 3 dimensions. filters: Pre-aggregation equality filters (same shape as search). agg: Metrics per group. Each item is {"function": ..., "field": ..., "alias": ...}. function in count/uniqExact/sum/avg/min/max. count needs no field; the rest require one. Defaults to a single count. For unique companies: {"function": "uniqExact", "field": "COMPANY_ID", "alias": "companies"}. having: Post-aggregation group filter keyed by alias, e.g. {"count": {"gte": 50}}. Operators: eq, gte, lte, gt, lt. sort: Sort groups by an alias, e.g. [{"count": "desc"}]. limit: Max groups returned (default 100, server-capped at 1000). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `index` | Yes | string | See the schema below for constraints and defaults. | | `group_by` | Yes | array | See the schema below for constraints and defaults. | | `filters` | No | object / null | See the schema below for constraints and defaults. | | `agg` | No | array / null | See the schema below for constraints and defaults. | | `having` | No | object / null | See the schema below for constraints and defaults. | | `sort` | No | array / null | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "index": { "title": "Index", "type": "string" }, "group_by": { "items": { "type": "string" }, "title": "Group By", "type": "array" }, "filters": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Filters" }, "agg": { "anyOf": [ { "items": { "additionalProperties": true, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Agg" }, "having": { "anyOf": [ { "additionalProperties": { "additionalProperties": { "type": "number" }, "type": "object" }, "type": "object" }, { "type": "null" } ], "default": null, "title": "Having" }, "sort": { "anyOf": [ { "items": { "additionalProperties": { "type": "string" }, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Sort" }, "limit": { "default": 100, "title": "Limit", "type": "integer" } }, "required": [ "index", "group_by" ], "title": "g8_opensearch_aggregateArguments", "type": "object" } ``` </details> ## g8_opensearch_count > Exact match count for a whitelisted mashup index (TAM cell sizing). Same index + filters shape as g8_opensearch_search, but returns just {index, count} via the native OpenSearch _count API -- NOT capped at the 10000 ceiling that search's `total` saturates at. Counts on contact/company mashup indices automatically apply the organization's deliverability protection policy. Note: mashup_companies and mashup_contacts are the SAME contact-grained index, so a count on mashup_companies counts contact rows, not unique companies. For a unique-company total use g8_opensearch_aggregate with uniqExact on COMPANY_ID. Args: index: One of "mashup_contacts", "mashup_companies", "hem2contact", "ip2company". filters: Field equality filters, AND-joined. RAW UPPERCASE field names, e.g. {"COMPANY_NAICS": ["3311", "3311.00000"], "COMPANY_COUNTRY": ["US", "United States"]}. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `index` | Yes | string | See the schema below for constraints and defaults. | | `filters` | No | object / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "index": { "title": "Index", "type": "string" }, "filters": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Filters" } }, "required": [ "index" ], "title": "g8_opensearch_countArguments", "type": "object" } ``` </details> ## g8_opensearch_resolve_ids > Bulk HEM -> CONTACT_ID or IP -> COMPANY_ID resolution. Take a batch of opaque IDs and get back the canonical graph8 CONTACT_ID / COMPANY_ID. Args: kind: "hem_to_contact" or "ip_to_company". ids: Up to 500 input IDs. The response includes EVERY input as a key (null for misses) so you can iterate without re-querying. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `kind` | Yes | string | See the schema below for constraints and defaults. | | `ids` | Yes | array | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "kind": { "title": "Kind", "type": "string" }, "ids": { "items": { "type": "string" }, "title": "Ids", "type": "array" } }, "required": [ "kind", "ids" ], "title": "g8_opensearch_resolve_idsArguments", "type": "object" } ``` </details> ## g8_opensearch_search > Structured search against the graph8 OpenSearch mashup indices. Use when the higher-level CRM tools (g8_find_contacts, g8_lookup_person) don't surface the filter you need. For ID-only resolution prefer g8_opensearch_resolve_ids (single round trip). For a count with no rows prefer g8_opensearch_count; for GROUP BY use g8_opensearch_aggregate. Contact/company mashup results automatically apply the organization's deliverability protection policy. Args: index: Logical index name. One of: "mashup_contacts" (person-level enrichment), "mashup_companies" (company-level enrichment), "hem2contact" (HEM -> CONTACT_ID), "ip2company" (IP -> COMPANY_ID). filters: Field equality filters, AND-joined. List values become a terms clause. E.g. {"COMPANY_DOMAIN": "acme.com"}, {"COMPANY_INDUSTRY": ["SaaS", "Fintech"]}. Use RAW UPPERCASE field names (e.g. COMPANY_INDUSTRY, not "industry"). fields: Optional _source columns to return. Omit for full document. limit: Max hits (default 50, server-capped at 200). sort: Optional sort spec, e.g. [{"COMPANY_REVENUE": "desc"}]. Required if paginating via cursor. cursor: search_after token from a prior call (the sort field of the last hit). Pass back to fetch the next page. track_total_hits: Optional exact-count ceiling for `total` (e.g. 100000). Omit for the default 10000-saturating behaviour. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `index` | Yes | string | See the schema below for constraints and defaults. | | `filters` | No | object / null | See the schema below for constraints and defaults. | | `fields` | No | array / null | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | | `sort` | No | array / null | See the schema below for constraints and defaults. | | `cursor` | No | array / null | See the schema below for constraints and defaults. | | `track_total_hits` | No | integer / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "index": { "title": "Index", "type": "string" }, "filters": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Filters" }, "fields": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Fields" }, "limit": { "default": 50, "title": "Limit", "type": "integer" }, "sort": { "anyOf": [ { "items": { "additionalProperties": { "type": "string" }, "type": "object" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Sort" }, "cursor": { "anyOf": [ { "items": {}, "type": "array" }, { "type": "null" } ], "default": null, "title": "Cursor" }, "track_total_hits": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Track Total Hits" } }, "required": [ "index" ], "title": "g8_opensearch_searchArguments", "type": "object" } ``` </details> --- # Playbook MCP tools > Tool descriptions and complete input schemas for playbook operations. Source: https://docs.graph8.com/developers/mcp-reference/playbook/ Use this reference to find a tool and its inputs. [Connect an MCP client](https://docs.graph8.com/developers/mcp/) before calling tools. Tool visibility depends on discovery and permissions. For actions that send messages, spend credits, delete data, or start execution, review the intended records and confirm the action before proceeding. Obtain IDs from a prior result; do not invent them. A registered tool does not guarantee that its supporting service is available in every organization. ## g8_list_playbooks > List available graph8 playbooks - prescriptive workflow guides. Each playbook is a markdown doc covering a specific user journey (prospecting, running a campaign, installing tracking, handling inbox replies). Returns a catalog with slug, title, summary, and when_to_use; use when_to_use to decide which playbook to load, then call g8_load_playbook(slug) for the full content. Distinct from the `g8_skill_*` / `g8_workflow_skill_*` tool family (those author skill records that workflows execute; playbooks are docs for the agent to read). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "g8_list_playbooksArguments", "type": "object" } ``` </details> ## g8_load_playbook > Load the full markdown content of a graph8 playbook by slug. Use after g8_list_playbooks when the user's task matches a playbook's when_to_use. The content is prescriptive - follow the tool sequencing and confirmations it describes. Args: slug: Playbook slug from g8_list_playbooks (e.g. "prospecting-and-lists"). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `slug` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "slug": { "title": "Slug", "type": "string" } }, "required": [ "slug" ], "title": "g8_load_playbookArguments", "type": "object" } ``` </details> --- # Skill MCP tools > Tool descriptions and complete input schemas for skill operations. Source: https://docs.graph8.com/developers/mcp-reference/skill/ Use this reference to find a tool and its inputs. [Connect an MCP client](https://docs.graph8.com/developers/mcp/) before calling tools. Tool visibility depends on discovery and permissions. For actions that send messages, spend credits, delete data, or start execution, review the intended records and confirm the action before proceeding. Obtain IDs from a prior result; do not invent them. A registered tool does not guarantee that its supporting service is available in every organization. ## g8_skill_create_api > Create an API-runtime skill. Persists a new skill in the org; call with ``dry_run=True`` first to confirm, then ``dry_run=False``. Args: name: Display name. endpoint: Full URL. May contain ``{variable}`` path placeholders (e.g. ``https://api.x.com/v1/{id}``). method: HTTP method (default ``"POST"``). One of ``GET\|POST\|PUT\|PATCH\|DELETE``. description: Optional human-readable summary. category: Optional grouping. object_type: Optional entity target - ``"Company"\|"Contact"\|"Deal"\|"Campaign"``. headers: Static request headers. Values may use ``{var}``. body_template: Request body template; ``{var}`` substituted at runtime. query_params: Static query params dict. Values may use ``{var}``. body_type: ``json``, ``form-urlencoded``, ``form-data``, ``raw``, or ``graphql``. Defaults to ``json``. content_type: Custom Content-Type header. Auto-detected from ``body_type`` if omitted. requires_approval: Force manual approval before execution. dry_run: When True, return a confirmation preview. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `name` | Yes | string | See the schema below for constraints and defaults. | | `endpoint` | Yes | string | See the schema below for constraints and defaults. | | `method` | No | string | See the schema below for constraints and defaults. | | `description` | No | string / null | See the schema below for constraints and defaults. | | `category` | No | string / null | See the schema below for constraints and defaults. | | `object_type` | No | string / null | See the schema below for constraints and defaults. | | `headers` | No | object / null | See the schema below for constraints and defaults. | | `body_template` | No | string / null | See the schema below for constraints and defaults. | | `query_params` | No | object / null | See the schema below for constraints and defaults. | | `body_type` | No | string / null | See the schema below for constraints and defaults. | | `content_type` | No | string / null | See the schema below for constraints and defaults. | | `requires_approval` | No | boolean | See the schema below for constraints and defaults. | | `dry_run` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "name": { "title": "Name", "type": "string" }, "endpoint": { "title": "Endpoint", "type": "string" }, "method": { "default": "POST", "title": "Method", "type": "string" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "category": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Category" }, "object_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Object Type" }, "headers": { "anyOf": [ { "additionalProperties": { "type": "string" }, "type": "object" }, { "type": "null" } ], "default": null, "title": "Headers" }, "body_template": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Body Template" }, "query_params": { "anyOf": [ { "additionalProperties": { "type": "string" }, "type": "object" }, { "type": "null" } ], "default": null, "title": "Query Params" }, "body_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Body Type" }, "content_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Content Type" }, "requires_approval": { "default": false, "title": "Requires Approval", "type": "boolean" }, "dry_run": { "default": false, "title": "Dry Run", "type": "boolean" } }, "required": [ "name", "endpoint" ], "title": "create_api_skillArguments", "type": "object" } ``` </details> ## g8_skill_create_from_node > Lift a workflow action node into a standalone skill (a copy; the workflow is NOT modified). Persists a new skill; confirm with ``dry_run=True`` before calling with ``dry_run=False``. To make the node reference the new skill, update it afterward with ``g8_workflow_update_node`` to set ``config.action_id``. Args: workflow_id: Workflow UUID. node_id: Node id inside the workflow's graph. name: Optional new name; defaults to ``"<source name> (Copy)"``. dry_run: When True, return a confirmation preview. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `workflow_id` | Yes | string | See the schema below for constraints and defaults. | | `node_id` | Yes | string | See the schema below for constraints and defaults. | | `name` | No | string / null | See the schema below for constraints and defaults. | | `dry_run` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "workflow_id": { "title": "Workflow Id", "type": "string" }, "node_id": { "title": "Node Id", "type": "string" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "dry_run": { "default": false, "title": "Dry Run", "type": "boolean" } }, "required": [ "workflow_id", "node_id" ], "title": "create_from_nodeArguments", "type": "object" } ``` </details> ## g8_skill_create_from_template > Clone a skill template into the org. Persists a new skill; confirm with ``dry_run=True`` before calling with ``dry_run=False``. Only llm/api templates can be cloned (workflow/script runtimes are refused). Args: template_id: Template UUID from ``g8_skill_list_templates``. name: Display name for the new skill. overrides: Optional dict of fields to override on the clone. dry_run: When True, return a confirmation preview. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `template_id` | Yes | string | See the schema below for constraints and defaults. | | `name` | Yes | string | See the schema below for constraints and defaults. | | `overrides` | No | object / null | See the schema below for constraints and defaults. | | `dry_run` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "template_id": { "title": "Template Id", "type": "string" }, "name": { "title": "Name", "type": "string" }, "overrides": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Overrides" }, "dry_run": { "default": false, "title": "Dry Run", "type": "boolean" } }, "required": [ "template_id", "name" ], "title": "create_from_templateArguments", "type": "object" } ``` </details> ## g8_skill_create_llm > Create an LLM-runtime skill. Persists a new skill in the org; call with ``dry_run=True`` first to render a confirmation preview, then again with ``dry_run=False`` after the user confirms. Args: name: Display name. model: Model id from ``g8_skill_list_models`` (e.g. ``"gpt-4o"``). prompt_template: Prompt template. Use ``{variable}`` for inputs; they become the skill's input surface (see ``g8_skill_extract_variables``). description: Optional human-readable summary. category: Optional grouping (e.g. ``"Research"``, ``"Sales"``). object_type: Optional entity target - ``"Company"\|"Contact"\|"Deal"\|"Campaign"``. None (default) is global. temperature: LLM temperature (default 0.7). max_tokens: Max output tokens (default 1000). requires_approval: Force manual approval before execution (default False). dry_run: When True, return a confirmation preview instead of persisting. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `name` | Yes | string | See the schema below for constraints and defaults. | | `model` | Yes | string | See the schema below for constraints and defaults. | | `prompt_template` | Yes | string | See the schema below for constraints and defaults. | | `description` | No | string / null | See the schema below for constraints and defaults. | | `category` | No | string / null | See the schema below for constraints and defaults. | | `object_type` | No | string / null | See the schema below for constraints and defaults. | | `temperature` | No | number | See the schema below for constraints and defaults. | | `max_tokens` | No | integer | See the schema below for constraints and defaults. | | `requires_approval` | No | boolean | See the schema below for constraints and defaults. | | `dry_run` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "name": { "title": "Name", "type": "string" }, "model": { "title": "Model", "type": "string" }, "prompt_template": { "title": "Prompt Template", "type": "string" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "category": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Category" }, "object_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Object Type" }, "temperature": { "default": 0.7, "title": "Temperature", "type": "number" }, "max_tokens": { "default": 1000, "title": "Max Tokens", "type": "integer" }, "requires_approval": { "default": false, "title": "Requires Approval", "type": "boolean" }, "dry_run": { "default": false, "title": "Dry Run", "type": "boolean" } }, "required": [ "name", "model", "prompt_template" ], "title": "create_llm_skillArguments", "type": "object" } ``` </details> ## g8_skill_delete > Delete a skill (with reference check). Scans every workflow in the org; if ANY node references the skill the delete is refused and the offending workflows are listed, otherwise it hard-deletes. IMPORTANT: hard-delete is final - there is no restore. Call with ``dry_run=True`` to see the reference report + confirmation, then again with ``dry_run=False`` once the user confirms. Args: action_id: Skill UUID. dry_run: When True, return a confirmation preview (including any reference list) instead of deleting. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `action_id` | Yes | string | See the schema below for constraints and defaults. | | `dry_run` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "action_id": { "title": "Action Id", "type": "string" }, "dry_run": { "default": false, "title": "Dry Run", "type": "boolean" } }, "required": [ "action_id" ], "title": "delete_skillArguments", "type": "object" } ``` </details> ## g8_skill_execute > Run a skill once and return the execution result. Triggers a real execution (LLM call or HTTP request) and may consume credits; confirm with ``dry_run=True`` before calling with ``dry_run=False``. ``input_data`` keys should match the ``{variable}`` placeholders from ``g8_skill_extract_variables``; other keys pass through but won't substitute. Args: action_id: Skill UUID. input_data: Dict of input field values for placeholder substitution (e.g. ``{"company_name": "Acme"}``). dry_run: When True, return a confirmation preview. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `action_id` | Yes | string | See the schema below for constraints and defaults. | | `input_data` | No | object / null | See the schema below for constraints and defaults. | | `dry_run` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "action_id": { "title": "Action Id", "type": "string" }, "input_data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Input Data" }, "dry_run": { "default": false, "title": "Dry Run", "type": "boolean" } }, "required": [ "action_id" ], "title": "execute_skillArguments", "type": "object" } ``` </details> ## g8_skill_extract_variables > Extract single-brace ``{variable}`` placeholders from a skill's templates (deduplicated, first-seen order). Double-brace ``{{var}}`` is ignored. LLM skills scan ``prompt_template``; API skills scan ``endpoint``, ``body_template``, header values, and query param values. Use BEFORE wiring the skill into a workflow Action node so you know which input mappings to declare upstream. Args: action_id: Skill UUID. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `action_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "action_id": { "title": "Action Id", "type": "string" } }, "required": [ "action_id" ], "title": "extract_variablesArguments", "type": "object" } ``` </details> ## g8_skill_get > Get a skill's full configuration (metadata + llm_config or api_config). Returns 404 if the action_id is a workflow or script; use ``g8_workflow_get`` for workflow rows. Args: action_id: Skill UUID. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `action_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "action_id": { "title": "Action Id", "type": "string" } }, "required": [ "action_id" ], "title": "get_skillArguments", "type": "object" } ``` </details> ## g8_skill_list > List skills (LLM + API runtimes) owned by the caller's org. Workflows (use ``g8_workflow_list``) and script-runtime actions are not shown. Args: runtime_type: Filter to ``"llm"`` or ``"api"``. Omit to see both. enabled: Filter by enabled flag. category: Filter by free-text category (e.g. ``"Sales"``). object_type: Filter by entity target - ``"Company"\|"Contact"\|"Deal"\|"Campaign"``. Omit for global. include_system: When True (default), merge in shared system-default skills alongside org-owned ones. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `runtime_type` | No | string / null | See the schema below for constraints and defaults. | | `enabled` | No | boolean / null | See the schema below for constraints and defaults. | | `category` | No | string / null | See the schema below for constraints and defaults. | | `object_type` | No | string / null | See the schema below for constraints and defaults. | | `include_system` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "runtime_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Runtime Type" }, "enabled": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Enabled" }, "category": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Category" }, "object_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Object Type" }, "include_system": { "default": true, "title": "Include System", "type": "boolean" } }, "title": "list_skillsArguments", "type": "object" } ``` </details> ## g8_skill_list_models > List supported LLM model identifiers for the ``model`` field of ``g8_skill_create_llm`` / ``g8_skill_update_llm``. Returns ``{models: [{id, provider, label}], total: N}``. Curated list; the create/update endpoints accept any string. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "list_modelsArguments", "type": "object" } ``` </details> ## g8_skill_list_templates > List skill templates available in the caller's org (plus shared system templates). Workflow templates are excluded. Pass the returned ``action_id`` to ``g8_skill_create_from_template`` to clone one. Args: category: Optional category filter. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `category` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "category": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Category" } }, "title": "list_templatesArguments", "type": "object" } ``` </details> ## g8_skill_update_api > Update an API-runtime skill. Pass only the fields you want to change. Returns 404 if the action is not an API skill (runtime cannot switch). Args: action_id: Skill UUID. name, description, category, object_type, enabled: Top-level metadata. endpoint, method, headers, body_template, query_params, body_type, content_type: API config. If ANY one is passed, all are sent (update replaces the full api_config block), so fetch current values via ``g8_skill_get`` first for a partial change. requires_approval: Approval gate toggle. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `action_id` | Yes | string | See the schema below for constraints and defaults. | | `name` | No | string / null | See the schema below for constraints and defaults. | | `description` | No | string / null | See the schema below for constraints and defaults. | | `category` | No | string / null | See the schema below for constraints and defaults. | | `object_type` | No | string / null | See the schema below for constraints and defaults. | | `enabled` | No | boolean / null | See the schema below for constraints and defaults. | | `endpoint` | No | string / null | See the schema below for constraints and defaults. | | `method` | No | string / null | See the schema below for constraints and defaults. | | `headers` | No | object / null | See the schema below for constraints and defaults. | | `body_template` | No | string / null | See the schema below for constraints and defaults. | | `query_params` | No | object / null | See the schema below for constraints and defaults. | | `body_type` | No | string / null | See the schema below for constraints and defaults. | | `content_type` | No | string / null | See the schema below for constraints and defaults. | | `requires_approval` | No | boolean / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "action_id": { "title": "Action Id", "type": "string" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "category": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Category" }, "object_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Object Type" }, "enabled": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Enabled" }, "endpoint": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Endpoint" }, "method": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Method" }, "headers": { "anyOf": [ { "additionalProperties": { "type": "string" }, "type": "object" }, { "type": "null" } ], "default": null, "title": "Headers" }, "body_template": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Body Template" }, "query_params": { "anyOf": [ { "additionalProperties": { "type": "string" }, "type": "object" }, { "type": "null" } ], "default": null, "title": "Query Params" }, "body_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Body Type" }, "content_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Content Type" }, "requires_approval": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Requires Approval" } }, "required": [ "action_id" ], "title": "update_api_skillArguments", "type": "object" } ``` </details> ## g8_skill_update_llm > Update an LLM-runtime skill. Pass only the fields you want to change. Returns 404 if the action is not an LLM skill (runtime cannot switch). Args: action_id: Skill UUID. name, description, category, object_type, enabled: Top-level metadata. model, prompt_template, temperature, max_tokens: LLM config. If ANY one is passed, all four are sent (update replaces the full llm_config block), so fetch current values via ``g8_skill_get`` first for a partial change. requires_approval: Approval gate toggle. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `action_id` | Yes | string | See the schema below for constraints and defaults. | | `name` | No | string / null | See the schema below for constraints and defaults. | | `description` | No | string / null | See the schema below for constraints and defaults. | | `category` | No | string / null | See the schema below for constraints and defaults. | | `object_type` | No | string / null | See the schema below for constraints and defaults. | | `enabled` | No | boolean / null | See the schema below for constraints and defaults. | | `model` | No | string / null | See the schema below for constraints and defaults. | | `prompt_template` | No | string / null | See the schema below for constraints and defaults. | | `temperature` | No | number / null | See the schema below for constraints and defaults. | | `max_tokens` | No | integer / null | See the schema below for constraints and defaults. | | `requires_approval` | No | boolean / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "action_id": { "title": "Action Id", "type": "string" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "category": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Category" }, "object_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Object Type" }, "enabled": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Enabled" }, "model": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Model" }, "prompt_template": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Prompt Template" }, "temperature": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Temperature" }, "max_tokens": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Max Tokens" }, "requires_approval": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Requires Approval" } }, "required": [ "action_id" ], "title": "update_llm_skillArguments", "type": "object" } ``` </details> ## g8_skill_validate_template > Dry-run a skill template through the variable extractor and warn-only validator. Two call shapes: pass a single ``template`` string, OR pass ``runtime_type`` + a matching ``llm_config``/``api_config`` to scan every template field the runtime would use. Warn-only: ``valid`` is always True for parseable templates; warnings list soft issues (double-brace usage, unmatched braces) to repair before saving. Args: template: Single template string. runtime_type: ``"llm"`` or ``"api"`` (when passing a full skill shape). llm_config: Full LLM config dict. api_config: Full API config dict. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `template` | No | string / null | See the schema below for constraints and defaults. | | `runtime_type` | No | string / null | See the schema below for constraints and defaults. | | `llm_config` | No | object / null | See the schema below for constraints and defaults. | | `api_config` | No | object / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "template": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Template" }, "runtime_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Runtime Type" }, "llm_config": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Llm Config" }, "api_config": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Api Config" } }, "title": "validate_templateArguments", "type": "object" } ``` </details> ## g8_workflow_skill_create_api > Create an API-runtime skill. Persists a new skill in the org; call with ``dry_run=True`` first to confirm, then ``dry_run=False``. Args: name: Display name. endpoint: Full URL. May contain ``{variable}`` path placeholders (e.g. ``https://api.x.com/v1/{id}``). method: HTTP method (default ``"POST"``). One of ``GET\|POST\|PUT\|PATCH\|DELETE``. description: Optional human-readable summary. category: Optional grouping. object_type: Optional entity target - ``"Company"\|"Contact"\|"Deal"\|"Campaign"``. headers: Static request headers. Values may use ``{var}``. body_template: Request body template; ``{var}`` substituted at runtime. query_params: Static query params dict. Values may use ``{var}``. body_type: ``json``, ``form-urlencoded``, ``form-data``, ``raw``, or ``graphql``. Defaults to ``json``. content_type: Custom Content-Type header. Auto-detected from ``body_type`` if omitted. requires_approval: Force manual approval before execution. dry_run: When True, return a confirmation preview. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `name` | Yes | string | See the schema below for constraints and defaults. | | `endpoint` | Yes | string | See the schema below for constraints and defaults. | | `method` | No | string | See the schema below for constraints and defaults. | | `description` | No | string / null | See the schema below for constraints and defaults. | | `category` | No | string / null | See the schema below for constraints and defaults. | | `object_type` | No | string / null | See the schema below for constraints and defaults. | | `headers` | No | object / null | See the schema below for constraints and defaults. | | `body_template` | No | string / null | See the schema below for constraints and defaults. | | `query_params` | No | object / null | See the schema below for constraints and defaults. | | `body_type` | No | string / null | See the schema below for constraints and defaults. | | `content_type` | No | string / null | See the schema below for constraints and defaults. | | `requires_approval` | No | boolean | See the schema below for constraints and defaults. | | `dry_run` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "name": { "title": "Name", "type": "string" }, "endpoint": { "title": "Endpoint", "type": "string" }, "method": { "default": "POST", "title": "Method", "type": "string" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "category": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Category" }, "object_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Object Type" }, "headers": { "anyOf": [ { "additionalProperties": { "type": "string" }, "type": "object" }, { "type": "null" } ], "default": null, "title": "Headers" }, "body_template": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Body Template" }, "query_params": { "anyOf": [ { "additionalProperties": { "type": "string" }, "type": "object" }, { "type": "null" } ], "default": null, "title": "Query Params" }, "body_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Body Type" }, "content_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Content Type" }, "requires_approval": { "default": false, "title": "Requires Approval", "type": "boolean" }, "dry_run": { "default": false, "title": "Dry Run", "type": "boolean" } }, "required": [ "name", "endpoint" ], "title": "create_api_skillArguments", "type": "object" } ``` </details> ## g8_workflow_skill_create_from_node > Lift a workflow action node into a standalone skill (a copy; the workflow is NOT modified). Persists a new skill; confirm with ``dry_run=True`` before calling with ``dry_run=False``. To make the node reference the new skill, update it afterward with ``g8_workflow_update_node`` to set ``config.action_id``. Args: workflow_id: Workflow UUID. node_id: Node id inside the workflow's graph. name: Optional new name; defaults to ``"<source name> (Copy)"``. dry_run: When True, return a confirmation preview. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `workflow_id` | Yes | string | See the schema below for constraints and defaults. | | `node_id` | Yes | string | See the schema below for constraints and defaults. | | `name` | No | string / null | See the schema below for constraints and defaults. | | `dry_run` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "workflow_id": { "title": "Workflow Id", "type": "string" }, "node_id": { "title": "Node Id", "type": "string" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "dry_run": { "default": false, "title": "Dry Run", "type": "boolean" } }, "required": [ "workflow_id", "node_id" ], "title": "create_from_nodeArguments", "type": "object" } ``` </details> ## g8_workflow_skill_create_from_template > Clone a skill template into the org. Persists a new skill; confirm with ``dry_run=True`` before calling with ``dry_run=False``. Only llm/api templates can be cloned (workflow/script runtimes are refused). Args: template_id: Template UUID from ``g8_skill_list_templates``. name: Display name for the new skill. overrides: Optional dict of fields to override on the clone. dry_run: When True, return a confirmation preview. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `template_id` | Yes | string | See the schema below for constraints and defaults. | | `name` | Yes | string | See the schema below for constraints and defaults. | | `overrides` | No | object / null | See the schema below for constraints and defaults. | | `dry_run` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "template_id": { "title": "Template Id", "type": "string" }, "name": { "title": "Name", "type": "string" }, "overrides": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Overrides" }, "dry_run": { "default": false, "title": "Dry Run", "type": "boolean" } }, "required": [ "template_id", "name" ], "title": "create_from_templateArguments", "type": "object" } ``` </details> ## g8_workflow_skill_create_llm > Create an LLM-runtime skill. Persists a new skill in the org; call with ``dry_run=True`` first to render a confirmation preview, then again with ``dry_run=False`` after the user confirms. Args: name: Display name. model: Model id from ``g8_skill_list_models`` (e.g. ``"gpt-4o"``). prompt_template: Prompt template. Use ``{variable}`` for inputs; they become the skill's input surface (see ``g8_skill_extract_variables``). description: Optional human-readable summary. category: Optional grouping (e.g. ``"Research"``, ``"Sales"``). object_type: Optional entity target - ``"Company"\|"Contact"\|"Deal"\|"Campaign"``. None (default) is global. temperature: LLM temperature (default 0.7). max_tokens: Max output tokens (default 1000). requires_approval: Force manual approval before execution (default False). dry_run: When True, return a confirmation preview instead of persisting. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `name` | Yes | string | See the schema below for constraints and defaults. | | `model` | Yes | string | See the schema below for constraints and defaults. | | `prompt_template` | Yes | string | See the schema below for constraints and defaults. | | `description` | No | string / null | See the schema below for constraints and defaults. | | `category` | No | string / null | See the schema below for constraints and defaults. | | `object_type` | No | string / null | See the schema below for constraints and defaults. | | `temperature` | No | number | See the schema below for constraints and defaults. | | `max_tokens` | No | integer | See the schema below for constraints and defaults. | | `requires_approval` | No | boolean | See the schema below for constraints and defaults. | | `dry_run` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "name": { "title": "Name", "type": "string" }, "model": { "title": "Model", "type": "string" }, "prompt_template": { "title": "Prompt Template", "type": "string" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "category": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Category" }, "object_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Object Type" }, "temperature": { "default": 0.7, "title": "Temperature", "type": "number" }, "max_tokens": { "default": 1000, "title": "Max Tokens", "type": "integer" }, "requires_approval": { "default": false, "title": "Requires Approval", "type": "boolean" }, "dry_run": { "default": false, "title": "Dry Run", "type": "boolean" } }, "required": [ "name", "model", "prompt_template" ], "title": "create_llm_skillArguments", "type": "object" } ``` </details> ## g8_workflow_skill_delete > Delete a skill (with reference check). Scans every workflow in the org; if ANY node references the skill the delete is refused and the offending workflows are listed, otherwise it hard-deletes. IMPORTANT: hard-delete is final - there is no restore. Call with ``dry_run=True`` to see the reference report + confirmation, then again with ``dry_run=False`` once the user confirms. Args: action_id: Skill UUID. dry_run: When True, return a confirmation preview (including any reference list) instead of deleting. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `action_id` | Yes | string | See the schema below for constraints and defaults. | | `dry_run` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "action_id": { "title": "Action Id", "type": "string" }, "dry_run": { "default": false, "title": "Dry Run", "type": "boolean" } }, "required": [ "action_id" ], "title": "delete_skillArguments", "type": "object" } ``` </details> ## g8_workflow_skill_execute > Run a skill once and return the execution result. Triggers a real execution (LLM call or HTTP request) and may consume credits; confirm with ``dry_run=True`` before calling with ``dry_run=False``. ``input_data`` keys should match the ``{variable}`` placeholders from ``g8_skill_extract_variables``; other keys pass through but won't substitute. Args: action_id: Skill UUID. input_data: Dict of input field values for placeholder substitution (e.g. ``{"company_name": "Acme"}``). dry_run: When True, return a confirmation preview. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `action_id` | Yes | string | See the schema below for constraints and defaults. | | `input_data` | No | object / null | See the schema below for constraints and defaults. | | `dry_run` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "action_id": { "title": "Action Id", "type": "string" }, "input_data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Input Data" }, "dry_run": { "default": false, "title": "Dry Run", "type": "boolean" } }, "required": [ "action_id" ], "title": "execute_skillArguments", "type": "object" } ``` </details> ## g8_workflow_skill_extract_variables > Extract single-brace ``{variable}`` placeholders from a skill's templates (deduplicated, first-seen order). Double-brace ``{{var}}`` is ignored. LLM skills scan ``prompt_template``; API skills scan ``endpoint``, ``body_template``, header values, and query param values. Use BEFORE wiring the skill into a workflow Action node so you know which input mappings to declare upstream. Args: action_id: Skill UUID. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `action_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "action_id": { "title": "Action Id", "type": "string" } }, "required": [ "action_id" ], "title": "extract_variablesArguments", "type": "object" } ``` </details> ## g8_workflow_skill_get > Get a skill's full configuration (metadata + llm_config or api_config). Returns 404 if the action_id is a workflow or script; use ``g8_workflow_get`` for workflow rows. Args: action_id: Skill UUID. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `action_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "action_id": { "title": "Action Id", "type": "string" } }, "required": [ "action_id" ], "title": "get_skillArguments", "type": "object" } ``` </details> ## g8_workflow_skill_list > List skills (LLM + API runtimes) owned by the caller's org. Workflows (use ``g8_workflow_list``) and script-runtime actions are not shown. Args: runtime_type: Filter to ``"llm"`` or ``"api"``. Omit to see both. enabled: Filter by enabled flag. category: Filter by free-text category (e.g. ``"Sales"``). object_type: Filter by entity target - ``"Company"\|"Contact"\|"Deal"\|"Campaign"``. Omit for global. include_system: When True (default), merge in shared system-default skills alongside org-owned ones. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `runtime_type` | No | string / null | See the schema below for constraints and defaults. | | `enabled` | No | boolean / null | See the schema below for constraints and defaults. | | `category` | No | string / null | See the schema below for constraints and defaults. | | `object_type` | No | string / null | See the schema below for constraints and defaults. | | `include_system` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "runtime_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Runtime Type" }, "enabled": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Enabled" }, "category": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Category" }, "object_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Object Type" }, "include_system": { "default": true, "title": "Include System", "type": "boolean" } }, "title": "list_skillsArguments", "type": "object" } ``` </details> ## g8_workflow_skill_list_models > List supported LLM model identifiers for the ``model`` field of ``g8_skill_create_llm`` / ``g8_skill_update_llm``. Returns ``{models: [{id, provider, label}], total: N}``. Curated list; the create/update endpoints accept any string. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "list_modelsArguments", "type": "object" } ``` </details> ## g8_workflow_skill_list_templates > List skill templates available in the caller's org (plus shared system templates). Workflow templates are excluded. Pass the returned ``action_id`` to ``g8_skill_create_from_template`` to clone one. Args: category: Optional category filter. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `category` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "category": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Category" } }, "title": "list_templatesArguments", "type": "object" } ``` </details> ## g8_workflow_skill_update_api > Update an API-runtime skill. Pass only the fields you want to change. Returns 404 if the action is not an API skill (runtime cannot switch). Args: action_id: Skill UUID. name, description, category, object_type, enabled: Top-level metadata. endpoint, method, headers, body_template, query_params, body_type, content_type: API config. If ANY one is passed, all are sent (update replaces the full api_config block), so fetch current values via ``g8_skill_get`` first for a partial change. requires_approval: Approval gate toggle. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `action_id` | Yes | string | See the schema below for constraints and defaults. | | `name` | No | string / null | See the schema below for constraints and defaults. | | `description` | No | string / null | See the schema below for constraints and defaults. | | `category` | No | string / null | See the schema below for constraints and defaults. | | `object_type` | No | string / null | See the schema below for constraints and defaults. | | `enabled` | No | boolean / null | See the schema below for constraints and defaults. | | `endpoint` | No | string / null | See the schema below for constraints and defaults. | | `method` | No | string / null | See the schema below for constraints and defaults. | | `headers` | No | object / null | See the schema below for constraints and defaults. | | `body_template` | No | string / null | See the schema below for constraints and defaults. | | `query_params` | No | object / null | See the schema below for constraints and defaults. | | `body_type` | No | string / null | See the schema below for constraints and defaults. | | `content_type` | No | string / null | See the schema below for constraints and defaults. | | `requires_approval` | No | boolean / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "action_id": { "title": "Action Id", "type": "string" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "category": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Category" }, "object_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Object Type" }, "enabled": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Enabled" }, "endpoint": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Endpoint" }, "method": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Method" }, "headers": { "anyOf": [ { "additionalProperties": { "type": "string" }, "type": "object" }, { "type": "null" } ], "default": null, "title": "Headers" }, "body_template": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Body Template" }, "query_params": { "anyOf": [ { "additionalProperties": { "type": "string" }, "type": "object" }, { "type": "null" } ], "default": null, "title": "Query Params" }, "body_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Body Type" }, "content_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Content Type" }, "requires_approval": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Requires Approval" } }, "required": [ "action_id" ], "title": "update_api_skillArguments", "type": "object" } ``` </details> ## g8_workflow_skill_update_llm > Update an LLM-runtime skill. Pass only the fields you want to change. Returns 404 if the action is not an LLM skill (runtime cannot switch). Args: action_id: Skill UUID. name, description, category, object_type, enabled: Top-level metadata. model, prompt_template, temperature, max_tokens: LLM config. If ANY one is passed, all four are sent (update replaces the full llm_config block), so fetch current values via ``g8_skill_get`` first for a partial change. requires_approval: Approval gate toggle. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `action_id` | Yes | string | See the schema below for constraints and defaults. | | `name` | No | string / null | See the schema below for constraints and defaults. | | `description` | No | string / null | See the schema below for constraints and defaults. | | `category` | No | string / null | See the schema below for constraints and defaults. | | `object_type` | No | string / null | See the schema below for constraints and defaults. | | `enabled` | No | boolean / null | See the schema below for constraints and defaults. | | `model` | No | string / null | See the schema below for constraints and defaults. | | `prompt_template` | No | string / null | See the schema below for constraints and defaults. | | `temperature` | No | number / null | See the schema below for constraints and defaults. | | `max_tokens` | No | integer / null | See the schema below for constraints and defaults. | | `requires_approval` | No | boolean / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "action_id": { "title": "Action Id", "type": "string" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "category": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Category" }, "object_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Object Type" }, "enabled": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Enabled" }, "model": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Model" }, "prompt_template": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Prompt Template" }, "temperature": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Temperature" }, "max_tokens": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Max Tokens" }, "requires_approval": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Requires Approval" } }, "required": [ "action_id" ], "title": "update_llm_skillArguments", "type": "object" } ``` </details> ## g8_workflow_skill_validate_template > Dry-run a skill template through the variable extractor and warn-only validator. Two call shapes: pass a single ``template`` string, OR pass ``runtime_type`` + a matching ``llm_config``/``api_config`` to scan every template field the runtime would use. Warn-only: ``valid`` is always True for parseable templates; warnings list soft issues (double-brace usage, unmatched braces) to repair before saving. Args: template: Single template string. runtime_type: ``"llm"`` or ``"api"`` (when passing a full skill shape). llm_config: Full LLM config dict. api_config: Full API config dict. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `template` | No | string / null | See the schema below for constraints and defaults. | | `runtime_type` | No | string / null | See the schema below for constraints and defaults. | | `llm_config` | No | object / null | See the schema below for constraints and defaults. | | `api_config` | No | object / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "template": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Template" }, "runtime_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Runtime Type" }, "llm_config": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Llm Config" }, "api_config": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Api Config" } }, "title": "validate_templateArguments", "type": "object" } ``` </details> --- # Voice and dialer MCP tools > Tool descriptions and complete input schemas for voice operations. Source: https://docs.graph8.com/developers/mcp-reference/voice/ Use this reference to find a tool and its inputs. [Connect an MCP client](https://docs.graph8.com/developers/mcp/) before calling tools. Tool visibility depends on discovery and permissions. For actions that send messages, spend credits, delete data, or start execution, review the intended records and confirm the action before proceeding. Obtain IDs from a prior result; do not invent them. A registered tool does not guarantee that its supporting service is available in every organization. ## g8_voice_create_dialer_session > Create a parallel-dialer session, or place ONE ad-hoc call. Two modes, chosen by which target you pass: - ``list_id`` (or neither): creates a dialer SESSION in PAUSED state for an SDR to work through. Nothing dials until the SDR opens the dialer UI. - ``to_phone``: places ONE call to that number RIGHT NOW with an AI agent - no session, no list, no SDR. The call starts as soon as the request succeeds. Run it with ``dry_run=True`` first: the flag is forwarded to the API, which validates the org's subscription, its concurrent-call cap and ownership of ``from_phone`` WITHOUT dialing, and the preview you get back quotes that real pre-flight. Creates a real session bound to the caller's org and SDR. ALWAYS confirm with the user first. The session starts PAUSED; the SDR must open the dialer UI to begin actual calls. Recommended flow: call with dry_run=True for a confirmation preview, then dry_run=False after the user confirms. Args: name: Session display name (e.g. "Tuesday cold dial - Q4 ICP"). from_phone: Caller-ID number; must be one of the org's dialer numbers from ``g8_voice_list_numbers``. list_id: Contact list to dial (from ``g8_get_lists``). Optional but recommended - without it the SDR can only manual-dial. list_title: Display title for the list (e.g. "Q4 ICP"). agent_id: Voice agent UUID from ``g8_voice_list_agents``. agent_name: Legacy V1 agent name fallback. Skip if you have ``agent_id``. entity_type: "agent" or "twin". Skip unless you know. studio_campaign_id: Optional campaign UUID from ``g8_gtm_list_campaigns``. user_timezone: IANA timezone for analytics grouping (e.g. "America/New_York"). Defaults to the SDR's setting. skip_voicemails: Per-session voicemail-skip override. None = use the SDR's default. dry_run: When True, return a confirmation preview instead of creating the session. On the one-off branch the flag is forwarded to the API, so the preview reflects a real pre-flight rather than a local echo. to_phone: ONE-OFF CALL. Setting this switches the tool to "dial this single number now" and no session is created (see the one-off section above). Mutually exclusive with list_id. first_name, last_name, email, contact_id: one-off only - context handed to the agent for that call. timezone: one-off only. IANA timezone of the person being called (defaults to America/New_York server-side). This is the callee's timezone; user_timezone is the SDR's and applies to sessions. campaign_id: one-off only, for attribution. event_id: one-off only. Appointments event-type id the agent may offer to book. Omit when the call has no booking flow. callback_url: one-off only. Webhook notified with the result. Approval: the dry_run=True preview returns a one-time approval_token (valid 15 min, single use, bound to these exact arguments). The real run (dry_run=False) after a preview must carry it: the user's Confirm click on the card sends it, or pass it yourself only after the user explicitly approves in chat. Without it the preview comes back (approval_required) and nothing runs. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `name` | Yes | string | See the schema below for constraints and defaults. | | `from_phone` | Yes | string | See the schema below for constraints and defaults. | | `list_id` | No | string / null | See the schema below for constraints and defaults. | | `list_title` | No | string / null | See the schema below for constraints and defaults. | | `agent_id` | No | string / null | See the schema below for constraints and defaults. | | `agent_name` | No | string / null | See the schema below for constraints and defaults. | | `entity_type` | No | string / null | See the schema below for constraints and defaults. | | `studio_campaign_id` | No | string / null | See the schema below for constraints and defaults. | | `user_timezone` | No | string / null | See the schema below for constraints and defaults. | | `skip_voicemails` | No | boolean / null | See the schema below for constraints and defaults. | | `dry_run` | No | boolean | See the schema below for constraints and defaults. | | `to_phone` | No | string / null | See the schema below for constraints and defaults. | | `first_name` | No | string / null | See the schema below for constraints and defaults. | | `last_name` | No | string / null | See the schema below for constraints and defaults. | | `email` | No | string / null | See the schema below for constraints and defaults. | | `contact_id` | No | string / null | See the schema below for constraints and defaults. | | `timezone` | No | string / null | See the schema below for constraints and defaults. | | `campaign_id` | No | string / null | See the schema below for constraints and defaults. | | `event_id` | No | integer / null | See the schema below for constraints and defaults. | | `callback_url` | No | string / null | See the schema below for constraints and defaults. | | `approval_token` | No | string / null | One-time approval token from this tool's dry_run=True preview. The graph8 Confirm card sends it when the user clicks Confirm. Pass it yourself ONLY after the user has explicitly approved this exact preview in chat. Never pass it on your own initiative. Without it, a real run that follows a preview returns the preview again instead of executing. | <details> <summary>Complete input schema</summary> ```json { "properties": { "name": { "title": "Name", "type": "string" }, "from_phone": { "title": "From Phone", "type": "string" }, "list_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "List Id" }, "list_title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "List Title" }, "agent_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Agent Id" }, "agent_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Agent Name" }, "entity_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Entity Type" }, "studio_campaign_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Studio Campaign Id" }, "user_timezone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "User Timezone" }, "skip_voicemails": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Skip Voicemails" }, "dry_run": { "default": false, "title": "Dry Run", "type": "boolean" }, "to_phone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "To Phone" }, "first_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "First Name" }, "last_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Last Name" }, "email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Email" }, "contact_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Contact Id" }, "timezone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Timezone" }, "campaign_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Campaign Id" }, "event_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Event Id" }, "callback_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Callback Url" }, "approval_token": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "One-time approval token from this tool's dry_run=True preview. The graph8 Confirm card sends it when the user clicks Confirm. Pass it yourself ONLY after the user has explicitly approved this exact preview in chat. Never pass it on your own initiative. Without it, a real run that follows a preview returns the preview again instead of executing.", "title": "Approval Token" } }, "required": [ "name", "from_phone" ], "title": "create_dialer_sessionArguments", "type": "object" } ``` </details> ## g8_voice_get_call_grading > Fetch the AI-generated grading for a single dialer call. Returns ``{status: "pending"}`` while grading is still computing, else the grading payload plus reviewed / reviewed_at. Polling at ~10s intervals is safe. Args: room_name: The ``livekit_room_name`` id from ``g8_voice_list_dialer_sessions`` or any call row. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `room_name` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "room_name": { "title": "Room Name", "type": "string" } }, "required": [ "room_name" ], "title": "get_call_gradingArguments", "type": "object" } ``` </details> ## g8_voice_get_call_transcript > Fetch the full transcript for a single dialer call. Returns ``status: "ready"`` with a ``transcript`` array of ``{speaker, content, timestamp}`` segments plus call metadata (summary, disposition, recording_url, contact_id, duration). Returns ``status: "empty"`` with ``transcript: []`` when the call ended without a flushed transcript (rare; summary is still set). A 404 means the call does not exist or belongs to another org. Args: room_name: The ``livekit_room_name`` id from ``g8_voice_list_calls_for_contact`` or any call row. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `room_name` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "room_name": { "title": "Room Name", "type": "string" } }, "required": [ "room_name" ], "title": "get_call_transcriptArguments", "type": "object" } ``` </details> ## g8_voice_get_dialer_stats > Get aggregated dialer analytics for the caller's org. Returns filters, metrics (per-day rows when aggregation=DAILY, one row when TOTAL), and an optional overall summary. Each row includes total_dials, connection_rate, talk time, disposition breakdown, success_rate, redial stats, peak hour, and inbound callback counts. Args: session_id: Restrict to a single dialer session. user_email: Restrict to a single SDR's calls. date_from: Start date "YYYY-MM-DD". date_to: End date "YYYY-MM-DD". aggregation: "DAILY" (default) or "TOTAL". ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `session_id` | No | string / null | See the schema below for constraints and defaults. | | `user_email` | No | string / null | See the schema below for constraints and defaults. | | `date_from` | No | string / null | See the schema below for constraints and defaults. | | `date_to` | No | string / null | See the schema below for constraints and defaults. | | `aggregation` | No | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "session_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Session Id" }, "user_email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "User Email" }, "date_from": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Date From" }, "date_to": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Date To" }, "aggregation": { "default": "DAILY", "title": "Aggregation", "type": "string" } }, "title": "get_dialer_statsArguments", "type": "object" } ``` </details> ## g8_voice_list_agents > List voice agents (personas), or read one agent's full config. Call BEFORE ``g8_voice_create_dialer_session`` to pick a real agent. Returns agent_id (UUID) plus name, role, status. Three shapes, one result contract (always ``{"agents": [...], ...}``, so a caller can treat every branch the same): - default: the dialer picker list, capped at 100 agents server-side and unpaged. Best for "which agent should I dial with?". - ``full=True`` (or any of is_template / page / page_size / sort_by / sort_order): the management list, which pages properly and carries total_count, is_template, avatar_url, voice, calendar_event_id and template_id. Use it when the picker's 100 cap could hide agents, or when you need templates. - ``agent_id``: ONE agent's complete configuration - persona text and its three 0-1 dials, inbound/outbound instructions, voicemail prompt, pronunciation, identity and knowledge collections. None of that is on either list shape. Returned as a one-entry ``agents`` list with agent_name/description hoisted to the top level, so the card grid renders it like any other row; the full nested ``persona``/``identity``/``knowledge`` objects ride along on that entry. Args: role: Filter by agent role (SDR, AE, Receptionist, CSM, etc.). agent_status: Filter by status (e.g. "active"). entity_type: "agent" or "twin". Defaults to all. search: Substring match on agent name. agent_id: Read this one agent in full instead of listing. full: Use the paged management list rather than the picker. is_template: true = templates only, false = non-templates only. page: 1-indexed page for the management list. page_size: Rows per page for the management list (max 100). sort_by: created_at \| agent_name \| role \| status. sort_order: asc \| desc. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `role` | No | string / null | See the schema below for constraints and defaults. | | `agent_status` | No | string / null | See the schema below for constraints and defaults. | | `entity_type` | No | string / null | See the schema below for constraints and defaults. | | `search` | No | string / null | See the schema below for constraints and defaults. | | `agent_id` | No | string / null | See the schema below for constraints and defaults. | | `full` | No | boolean | See the schema below for constraints and defaults. | | `is_template` | No | boolean / null | See the schema below for constraints and defaults. | | `page` | No | integer | See the schema below for constraints and defaults. | | `page_size` | No | integer | See the schema below for constraints and defaults. | | `sort_by` | No | string / null | See the schema below for constraints and defaults. | | `sort_order` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "role": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Role" }, "agent_status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Agent Status" }, "entity_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Entity Type" }, "search": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Search" }, "agent_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Agent Id" }, "full": { "default": false, "title": "Full", "type": "boolean" }, "is_template": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Template" }, "page": { "default": 1, "title": "Page", "type": "integer" }, "page_size": { "default": 20, "title": "Page Size", "type": "integer" }, "sort_by": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Sort By" }, "sort_order": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Sort Order" } }, "title": "list_agentsArguments", "type": "object" } ``` </details> ## g8_voice_list_calls_for_contact > List dialer calls for a specific contact, newest first. Returns summary rows ONLY (no transcript blob). Each row carries room_name (canonical call id), has_transcript, disposition, summary, call_duration, recording_url, parallel_session_id, from_, to, contact_id, created_at. Pass a row's ``room_name`` to ``g8_voice_get_call_transcript`` for the full transcript. Args: contact_id: The graph8 contact id (string; pass numeric ids as their string form). limit: Max rows to return (1-100, default 20). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `contact_id` | Yes | string | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "contact_id": { "title": "Contact Id", "type": "string" }, "limit": { "default": 20, "title": "Limit", "type": "integer" } }, "required": [ "contact_id" ], "title": "list_calls_for_contactArguments", "type": "object" } ``` </details> ## g8_voice_list_calls_for_sdr > List dialer calls placed by a specific SDR, newest first. Returns summary rows ONLY (no transcript blob). Each row carries room_name (canonical call id), has_transcript, disposition, summary, call_duration, recording_url, parallel_session_id, contact_id, from_, to (number dialed), created_at. Use room_name with ``g8_voice_get_call_transcript`` for the full transcript. For incremental polling, pass the previous page's max ``created_at`` as the next ``created_after``. Args: user_email: SDR email. Returns only dialer rows (those with a parallel_session_id). created_after: Optional ISO-8601 timestamp; returns rows with created_at >= created_after. Use for incremental polling. parallel_session_id: Optional - narrow to one session id (ANDs with user_email). limit: Max rows to return (1-100, default 20). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `user_email` | Yes | string | See the schema below for constraints and defaults. | | `created_after` | No | string / null | See the schema below for constraints and defaults. | | `parallel_session_id` | No | string / null | See the schema below for constraints and defaults. | | `limit` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "user_email": { "title": "User Email", "type": "string" }, "created_after": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Created After" }, "parallel_session_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Parallel Session Id" }, "limit": { "default": 20, "title": "Limit", "type": "integer" } }, "required": [ "user_email" ], "title": "list_calls_for_sdrArguments", "type": "object" } ``` </details> ## g8_voice_list_dialer_sessions > List parallel dialer sessions for the caller's organization. Each row carries session_id, status, user_email, campaign + list metadata, total_calls, and contact_ids. Pair with ``g8_voice_get_dialer_stats`` for "how did dialing go?" answers. Args: page: 1-indexed page number. page_size: Rows per page (1-200). status: Comma-separated session statuses ("ACTIVE,PAUSED"). user_email: Filter by SDR email (substring match). name: Filter by session name (substring match). campaign_id: Filter by campaign id (substring match). list_id: Exact list id. list_name: Filter by list title (substring match). date_from: ISO timestamp - sessions created on/after. date_to: ISO timestamp - sessions created on/before. sort_by: Column name (created_at, updated_at, total_calls...). sort_order: "asc" or "desc". Defaults to "desc". ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `page` | No | integer | See the schema below for constraints and defaults. | | `page_size` | No | integer | See the schema below for constraints and defaults. | | `status` | No | string / null | See the schema below for constraints and defaults. | | `user_email` | No | string / null | See the schema below for constraints and defaults. | | `name` | No | string / null | See the schema below for constraints and defaults. | | `campaign_id` | No | string / null | See the schema below for constraints and defaults. | | `list_id` | No | string / null | See the schema below for constraints and defaults. | | `list_name` | No | string / null | See the schema below for constraints and defaults. | | `date_from` | No | string / null | See the schema below for constraints and defaults. | | `date_to` | No | string / null | See the schema below for constraints and defaults. | | `sort_by` | No | string / null | See the schema below for constraints and defaults. | | `sort_order` | No | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "page": { "default": 1, "title": "Page", "type": "integer" }, "page_size": { "default": 50, "title": "Page Size", "type": "integer" }, "status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Status" }, "user_email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "User Email" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "campaign_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Campaign Id" }, "list_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "List Id" }, "list_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "List Name" }, "date_from": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Date From" }, "date_to": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Date To" }, "sort_by": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Sort By" }, "sort_order": { "default": "desc", "title": "Sort Order", "type": "string" } }, "title": "list_dialer_sessionsArguments", "type": "object" } ``` </details> ## g8_voice_list_dispositions > List the call outcomes this org can record against a call. Returns `system` (the fixed vocabulary, each with the `is_connected` flag that decides whether an outcome counts as a live connection in dialer analytics), `custom` (this org's own entries, with id, name, description and color), `custom_used` and `custom_limit`. `g8_workflow_list_dispositions` answers a different question: it gives value/label/category for building workflow-trigger filters, and carries `is_connected` only on custom entries. Read this one when you need the connection semantics of the SYSTEM outcomes, or the remaining custom slots. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "list_dispositionsArguments", "type": "object" } ``` </details> ## g8_voice_list_missed_callbacks > Inbound calls the org missed (no SDR pickup), with caller info. Each row links back to the original outbound session via ``parallel_session_id`` (did this lead call us back?). Args: limit: Max rows to return (1-200, default 50). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `limit` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "limit": { "default": 50, "title": "Limit", "type": "integer" } }, "title": "list_missed_callbacksArguments", "type": "object" } ``` </details> ## g8_voice_list_numbers > List the org's dialer-eligible phone numbers with usage stats. Use before starting a session to pick a healthy number (low connect_rate_7d rows usually need provider-side attention). Args: user_email: Optional - restrict to numbers assigned to one SDR. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `user_email` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "user_email": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "User Email" } }, "title": "list_dialer_numbersArguments", "type": "object" } ``` </details> ## g8_voice_pause_session > Pause an active parallel-dialer session. Reversible (call ``g8_voice_resume_session`` to flip back to ACTIVE) and idempotent (pausing a PAUSED session is a no-op). Args: session_id: Session UUID from ``g8_voice_list_dialer_sessions`` or ``g8_voice_create_dialer_session``. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `session_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "session_id": { "title": "Session Id", "type": "string" } }, "required": [ "session_id" ], "title": "pause_sessionArguments", "type": "object" } ``` </details> ## g8_voice_resume_session > Resume a paused parallel-dialer session by dialing the next batch. Flips the session to ACTIVE and places real outbound calls: skips contacts already called this session and dials the next batch (up to 4) from the source list. IMPORTANT: This actually dials phone numbers and consumes telephony capacity. ALWAYS confirm with the user before calling. Not idempotent - each call advances the list cursor. Returns the voice service's response plus ``dialed_count`` (calls actually placed). It can be lower than ``max_contacts`` when the list is near exhausted, rows have no usable phone, or candidates were already called. Args: session_id: Session UUID from ``g8_voice_list_dialer_sessions`` or ``g8_voice_create_dialer_session``. max_contacts: Contacts to dial in this batch (1-4, capped at 4). Defaults to 4. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `session_id` | Yes | string | See the schema below for constraints and defaults. | | `max_contacts` | No | integer | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "session_id": { "title": "Session Id", "type": "string" }, "max_contacts": { "default": 4, "title": "Max Contacts", "type": "integer" } }, "required": [ "session_id" ], "title": "resume_sessionArguments", "type": "object" } ``` </details> ## g8_voice_stop_session > Stop a parallel-dialer session permanently. IMPORTANT: Marks the session COMPLETED. One-way - re-opening is only possible from the dialer UI via manual-dial. ALWAYS confirm with the user first. Recommended flow: call with dry_run=True for a confirmation preview, then dry_run=False after they confirm. Args: session_id: Session UUID. dry_run: When True, return a confirmation preview instead of stopping. Approval: the dry_run=True preview returns a one-time approval_token (valid 15 min, single use, bound to these exact arguments). The real run (dry_run=False) after a preview must carry it: the user's Confirm click on the card sends it, or pass it yourself only after the user explicitly approves in chat. Without it the preview comes back (approval_required) and nothing runs. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `session_id` | Yes | string | See the schema below for constraints and defaults. | | `dry_run` | No | boolean | See the schema below for constraints and defaults. | | `approval_token` | No | string / null | One-time approval token from this tool's dry_run=True preview. The graph8 Confirm card sends it when the user clicks Confirm. Pass it yourself ONLY after the user has explicitly approved this exact preview in chat. Never pass it on your own initiative. Without it, a real run that follows a preview returns the preview again instead of executing. | <details> <summary>Complete input schema</summary> ```json { "properties": { "session_id": { "title": "Session Id", "type": "string" }, "dry_run": { "default": false, "title": "Dry Run", "type": "boolean" }, "approval_token": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "One-time approval token from this tool's dry_run=True preview. The graph8 Confirm card sends it when the user clicks Confirm. Pass it yourself ONLY after the user has explicitly approved this exact preview in chat. Never pass it on your own initiative. Without it, a real run that follows a preview returns the preview again instead of executing.", "title": "Approval Token" } }, "required": [ "session_id" ], "title": "stop_sessionArguments", "type": "object" } ``` </details> ## g8_voice_upsert_agent > Create a voice agent, or edit an existing one. Omit ``agent_id`` to CREATE (role, agent_name, persona and description are then required). Pass ``agent_id`` from ``g8_voice_list_agents`` to EDIT that agent; only the fields you pass change. Editing is safe with partial input even though the API replaces persona / identity / knowledge as whole objects: this tool reads the agent first and merges your fields onto its current configuration, so omitting `persona` does not blank the persona. That read is why an edit needs the agent to exist - a wrong agent_id returns not-found rather than silently creating one. ``phone`` must be E.164 (+15551234567); anything else is rejected before the write, because a drifted format makes the number read as unassigned later. Args: role: "SDR", "AE", "Receptionist", "CSM", ... Required on create. agent_name: Display name. Required on create. persona: Personality and behaviour description. Required on create. description: One-line summary of what this agent is for. Required on create. agent_id: Edit this agent instead of creating one. entity_type: "agent" (default) or "twin". agent_status: active \| inactive \| archived. formality_level, conciseness_level, assertiveness_level: 0.0-1.0 dials. Default to 0.5 on create; left alone on edit. inbound_instructions, outbound_instructions: extra prompt text per call direction. outbound_voicemail_prompt: what the agent leaves on voicemail. company_name_pronunciation: TTS override for the company name. phone: E.164 caller identity for this agent. voice: Voice id (Cartesia voice_id or OpenAI voice name). calendar_event_id: Appointments event-type id for booking. avatar_url: Avatar image URL. knowledge_collections: Knowledge collection ids (max 4). use_company_knowledge: Also draw on the org knowledge base. is_template: Save as a reusable template rather than a live agent. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `role` | No | string / null | See the schema below for constraints and defaults. | | `agent_name` | No | string / null | See the schema below for constraints and defaults. | | `persona` | No | string / null | See the schema below for constraints and defaults. | | `description` | No | string / null | See the schema below for constraints and defaults. | | `agent_id` | No | string / null | See the schema below for constraints and defaults. | | `entity_type` | No | string / null | See the schema below for constraints and defaults. | | `agent_status` | No | string / null | See the schema below for constraints and defaults. | | `formality_level` | No | number / null | See the schema below for constraints and defaults. | | `conciseness_level` | No | number / null | See the schema below for constraints and defaults. | | `assertiveness_level` | No | number / null | See the schema below for constraints and defaults. | | `inbound_instructions` | No | string / null | See the schema below for constraints and defaults. | | `outbound_instructions` | No | string / null | See the schema below for constraints and defaults. | | `outbound_voicemail_prompt` | No | string / null | See the schema below for constraints and defaults. | | `company_name_pronunciation` | No | string / null | See the schema below for constraints and defaults. | | `phone` | No | string / null | See the schema below for constraints and defaults. | | `voice` | No | string / null | See the schema below for constraints and defaults. | | `calendar_event_id` | No | integer / null | See the schema below for constraints and defaults. | | `avatar_url` | No | string / null | See the schema below for constraints and defaults. | | `knowledge_collections` | No | array / null | See the schema below for constraints and defaults. | | `use_company_knowledge` | No | boolean / null | See the schema below for constraints and defaults. | | `is_template` | No | boolean / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "role": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Role" }, "agent_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Agent Name" }, "persona": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Persona" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "agent_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Agent Id" }, "entity_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Entity Type" }, "agent_status": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Agent Status" }, "formality_level": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Formality Level" }, "conciseness_level": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Conciseness Level" }, "assertiveness_level": { "anyOf": [ { "type": "number" }, { "type": "null" } ], "default": null, "title": "Assertiveness Level" }, "inbound_instructions": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Inbound Instructions" }, "outbound_instructions": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Outbound Instructions" }, "outbound_voicemail_prompt": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Outbound Voicemail Prompt" }, "company_name_pronunciation": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Company Name Pronunciation" }, "phone": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Phone" }, "voice": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Voice" }, "calendar_event_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Calendar Event Id" }, "avatar_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Avatar Url" }, "knowledge_collections": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Knowledge Collections" }, "use_company_knowledge": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Use Company Knowledge" }, "is_template": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Template" } }, "title": "upsert_agentArguments", "type": "object" } ``` </details> ## g8_voice_upsert_disposition > Create a custom call outcome, or edit one. Omit ``disposition_id`` to CREATE (name, color and is_connected are then required). Pass one from ``g8_voice_list_dispositions`` to EDIT; only the fields you pass change. This changes what every rep in the org can pick after a call, so confirm with the user before calling it. Three rules are enforced upstream and surface as errors: at most 10 custom outcomes per org, colors must come from the preset palette, and names must be unique case-insensitively against system AND custom entries. ``is_connected`` becomes locked once any call has been recorded against the outcome - editing it then returns a conflict, on purpose, because flipping it would retroactively re-classify calls that already happened. Args: name: Display name, 1-40 chars. Required on create. color: Hex value from the preset palette (e.g. "#10b981"). Required on create. is_connected: Does this outcome count as a live connection in analytics? Required on create. description: Optional helper text, up to 200 chars. disposition_id: Edit this custom outcome instead of creating one. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `name` | No | string / null | See the schema below for constraints and defaults. | | `color` | No | string / null | See the schema below for constraints and defaults. | | `is_connected` | No | boolean / null | See the schema below for constraints and defaults. | | `description` | No | string / null | See the schema below for constraints and defaults. | | `disposition_id` | No | integer / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "color": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Color" }, "is_connected": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Connected" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "disposition_id": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "default": null, "title": "Disposition Id" } }, "title": "upsert_dispositionArguments", "type": "object" } ``` </details> --- # Workflows MCP tools > Tool descriptions and complete input schemas for workflow operations. Source: https://docs.graph8.com/developers/mcp-reference/workflow/ Use this reference to find a tool and its inputs. [Connect an MCP client](https://docs.graph8.com/developers/mcp/) before calling tools. Tool visibility depends on discovery and permissions. For actions that send messages, spend credits, delete data, or start execution, review the intended records and confirm the action before proceeding. Obtain IDs from a prior result; do not invent them. A registered tool does not guarantee that its supporting service is available in every organization. ## g8_workflow_add_node > Add one node to an existing workflow's graph. Fetches the current graph, appends the node (and optionally one edge), and PUTs the result. Errors if ``node_id`` already exists - use ``g8_workflow_update_node`` for that. Writes the node in canonical snake_case. When ``connect_from`` is set, appends the edge AND appends ``node_id`` to the source node's ``connections`` list (the executor traverses ``connections``, not ``edges``, so an edge-only node never runs). Prefer ``add_node(..., connect_from=upstream)`` over a separate ``connect_nodes`` call - same result, one fewer PUT. Args: action_id: Workflow id. node_id: New node's id, unique within the graph. Convention: ``{node_type}-{8-hex}`` (e.g. ``send_email-2e3bb0d7``). node_type: A value from ``g8_workflow_list_node_types``. config: Per-node config dict; shape varies by node_type (see ``g8_workflow_describe_node_type``). label: Optional display label. Defaults to ``node_type``. position: Optional ``{"x": float, "y": float}``. Defaults to ``{"x": 0, "y": 0}``. connect_from: Optional source node id. Adds edge ``connect_from -> node_id`` and appends ``node_id`` to that node's ``connections``. Skip to wire up later via ``g8_workflow_connect_nodes``. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `action_id` | Yes | string | See the schema below for constraints and defaults. | | `node_id` | Yes | string | See the schema below for constraints and defaults. | | `node_type` | Yes | string | See the schema below for constraints and defaults. | | `config` | No | object / null | See the schema below for constraints and defaults. | | `label` | No | string / null | See the schema below for constraints and defaults. | | `position` | No | object / null | See the schema below for constraints and defaults. | | `connect_from` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "action_id": { "title": "Action Id", "type": "string" }, "node_id": { "title": "Node Id", "type": "string" }, "node_type": { "title": "Node Type", "type": "string" }, "config": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Config" }, "label": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Label" }, "position": { "anyOf": [ { "additionalProperties": { "type": "number" }, "type": "object" }, { "type": "null" } ], "default": null, "title": "Position" }, "connect_from": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Connect From" } }, "required": [ "action_id", "node_id", "node_type" ], "title": "add_nodeArguments", "type": "object" } ``` </details> ## g8_workflow_connect_nodes > Add an edge connecting one node to another. DUAL write: appends to top-level ``edges`` AND appends ``target`` to the source node's ``connections`` list (the field the executor actually traverses; an edge without it renders but never executes). Idempotent on (source, target) when both are already present. For branch / decision nodes, pass ``condition`` to set the edge label, and use ``g8_workflow_update_node`` to set the source node's ``conditional_connections`` ({path_id: target_node_id}). Args: action_id: Workflow id. source: Upstream node id. target: Downstream node id. edge_id: Optional explicit edge id. Defaults to ``"edge-{source}-{target}"``. condition: Optional condition string for branch / decision nodes (e.g. ``"true"``, ``"false"``, ``"matches_icp"``). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `action_id` | Yes | string | See the schema below for constraints and defaults. | | `source` | Yes | string | See the schema below for constraints and defaults. | | `target` | Yes | string | See the schema below for constraints and defaults. | | `edge_id` | No | string / null | See the schema below for constraints and defaults. | | `condition` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "action_id": { "title": "Action Id", "type": "string" }, "source": { "title": "Source", "type": "string" }, "target": { "title": "Target", "type": "string" }, "edge_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Edge Id" }, "condition": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Condition" } }, "required": [ "action_id", "source", "target" ], "title": "connect_nodesArguments", "type": "object" } ``` </details> ## g8_workflow_create > Create a new workflow. Persists a real workflow row - ALWAYS confirm with the user first. Recommended pattern: run ``g8_workflow_validate``, then call with ``dry_run=True`` to render a confirmation widget, then ``dry_run=False`` after the user confirms. ``config`` follows the canonical snake_case shape. Each node MUST carry a ``connections`` list (the executor traverses via ``node.connections[0]``, NOT top-level ``edges``); a node with only an edge halts the executor. Branch / condition nodes also use ``conditional_connections`` ({path_id: target_node_id}) to gate which downstream branch fires. Shape (per node): ``{node_id, node_type, name, config, position, connections: [...]}`` plus a top-level ``{metadata, settings, start_node_id, nodes: [...], edges: [...]}``. Get the per-node-type ``config`` fields from ``g8_workflow_describe_node_type``. Conventions: ``node_id`` = ``{node_type}-{8-hex}``, ``edge.id`` = ``edge-{source}-{target}``, cross-node refs = ``${node_id.field}``, and ``input_mappings`` = ``[{target_field, source_expression}]``. ``runtime_type='workflow'`` and ``org_id`` are pinned server-side. Args: name: Workflow display name. config: The graph dict (nodes / edges / start_node_id / trigger). description: Optional human-readable summary. category: Optional grouping (e.g. ``"sales"``). enabled: Default ``False`` so a fresh workflow stays paused until the user enables it. Set ``True`` only if already approved. dry_run: When ``True``, return a confirmation preview without creating the workflow. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `name` | Yes | string | See the schema below for constraints and defaults. | | `config` | Yes | object | See the schema below for constraints and defaults. | | `description` | No | string / null | See the schema below for constraints and defaults. | | `category` | No | string / null | See the schema below for constraints and defaults. | | `enabled` | No | boolean | See the schema below for constraints and defaults. | | `dry_run` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "name": { "title": "Name", "type": "string" }, "config": { "additionalProperties": true, "title": "Config", "type": "object" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "category": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Category" }, "enabled": { "default": false, "title": "Enabled", "type": "boolean" }, "dry_run": { "default": false, "title": "Dry Run", "type": "boolean" } }, "required": [ "name", "config" ], "title": "create_workflowArguments", "type": "object" } ``` </details> ## g8_workflow_create_workbench > Create an empty workbench and return its ``workbench_id``. Use when a workflow needs somewhere to write and the org has none, or the user asks for a NEW one. Check ``g8_workflow_list_workbenches`` first so you reuse an existing workbench instead of making a near-duplicate. Args: name: Display name. Required. entity_type: ``contact`` or ``company``. Optional; a blank workbench may defer it. Use ``company`` when each row is a company or domain (e.g. per-domain verdicts), ``contact`` when each row is a person. description: Optional free text. on_duplicate: What to do when an active workbench already has this name (case-insensitive). ``allow`` (default): create anyway and add ``warnings``. ``reuse``: create nothing and return the existing workbench with ``reused: true`` (get-or-create). ``error``: refuse with a 409 naming the existing id(s). Prefer ``reuse`` when a workflow just needs "the" workbench of a name. Returns ``{workbench_id, name, entity_type, description}``. Feed ``workbench_id`` straight into the workbench node config. Duplicate names are allowed by default, but the response then also carries ``warnings`` naming the existing workbench(es) with that name. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `name` | Yes | string | See the schema below for constraints and defaults. | | `entity_type` | No | string / null | See the schema below for constraints and defaults. | | `description` | No | string / null | See the schema below for constraints and defaults. | | `on_duplicate` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "name": { "title": "Name", "type": "string" }, "entity_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Entity Type" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "on_duplicate": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "On Duplicate" } }, "required": [ "name" ], "title": "create_workbenchArguments", "type": "object" } ``` </details> ## g8_workflow_delete > Delete a workflow permanently. One-way - ``g8_workflow_get`` 404s after. ALWAYS confirm with the user; call ``dry_run=True`` first to render the confirmation widget. Args: action_id: Workflow id. dry_run: When ``True``, return a confirmation preview without deleting. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `action_id` | Yes | string | See the schema below for constraints and defaults. | | `dry_run` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "action_id": { "title": "Action Id", "type": "string" }, "dry_run": { "default": false, "title": "Dry Run", "type": "boolean" } }, "required": [ "action_id" ], "title": "delete_workflowArguments", "type": "object" } ``` </details> ## g8_workflow_describe_form_fields > Discover the field names submitted to a form by sampling recent submissions. REQUIRED before authoring a ``new_form_submitted`` trigger node, otherwise the field picker is empty. Call right after the user names a form_id, then pass the returned ``fields`` list as ``form_fields`` on the trigger node config: g8_workflow_add_node(node_type="trigger", config={ "trigger_type": "new_form_submitted", "form_id": "contact-us-form", "form_fields": ["email", "name", "title", "message"], }) Downstream nodes reference values via ``${<trigger_node_id>.<field>}``. Returns ``{form_id, fields: [str], sample_size: int}``. An empty ``fields`` list means no submissions exist yet - ask the user instead. Args: form_id: HTML form id (form_tag_id) - same value used as ``form_id`` on the trigger node config. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `form_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "form_id": { "title": "Form Id", "type": "string" } }, "required": [ "form_id" ], "title": "describe_form_fieldsArguments", "type": "object" } ``` </details> ## g8_workflow_describe_node_type > Get the config schema for a single node type. Cheaper than ``g8_workflow_list_node_types(detail="full")`` when you already know the type - returns one entry, same payload shape. Args: node_type: A value from ``g8_workflow_list_node_types`` (e.g. ``"sequence_reply_trigger"``, ``"llm_completion"``). detail: ``"slim"`` or ``"full"`` (default ``"full"``). ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `node_type` | Yes | string | See the schema below for constraints and defaults. | | `detail` | No | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "node_type": { "title": "Node Type", "type": "string" }, "detail": { "default": "full", "title": "Detail", "type": "string" } }, "required": [ "node_type" ], "title": "describe_node_typeArguments", "type": "object" } ``` </details> ## g8_workflow_execute > Trigger a workflow run. Runs for real - can spend credits, send messages, or call external APIs depending on the nodes. ALWAYS confirm with the user; call ``dry_run=True`` first for a confirmation widget, then ``dry_run=False`` after the user confirms. Returns ``{execution_id, status: "queued" \| "running"}``. Poll with ``g8_workflow_get_execution`` until ``completed`` or ``failed``. If the response has ``status: "unknown"``, the run MAY have started: check ``possible_executions`` / the executions list before doing anything else. NEVER re-run after an error without passing the SAME ``idempotency_key`` (the preview's confirm_args carry one): with it a retry returns the first run instead of starting a second one. Args: action_id: Workflow id. input_data: Optional ``{key: value}`` dict - becomes ``{{trigger.key}}`` references inside the graph. Defaults to ``{}``. Inputs the workflow's ``input_schema`` marks ``required`` must be present: a run missing any is refused with a 400 that lists them. triggered_by: Optional caller user_id (defaults to the API key's user server-side). dry_run: When ``True``, return a confirmation preview without firing the workflow. idempotency_key: Optional key for this ONE logical run. Reuse it when retrying the same run; a new run needs a new key. The dry-run preview mints one into its confirm_args. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `action_id` | Yes | string | See the schema below for constraints and defaults. | | `input_data` | No | object / null | See the schema below for constraints and defaults. | | `triggered_by` | No | string / null | See the schema below for constraints and defaults. | | `dry_run` | No | boolean | See the schema below for constraints and defaults. | | `idempotency_key` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "action_id": { "title": "Action Id", "type": "string" }, "input_data": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Input Data" }, "triggered_by": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Triggered By" }, "dry_run": { "default": false, "title": "Dry Run", "type": "boolean" }, "idempotency_key": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Idempotency Key" } }, "required": [ "action_id" ], "title": "execute_workflowArguments", "type": "object" } ``` </details> ## g8_workflow_get > Get the full graph for a workflow (nodes + edges + trigger). Shape: ``{action_id, name, description, runtime_type, enabled, config: {metadata, settings, nodes: [...], edges: [...], start_node_id}, managed_by}``. Call before any granular edit to get current node/edge ids. Each node uses canonical snake_case (flat ``node_id`` / ``node_type`` / ``name`` / ``config`` / ``position``); the granular helpers also read legacy ReactFlow-shape rows. Args: action_id: Workflow id from ``g8_workflow_list`` or the URL ``app.graph8.com/agents/workflows/{action_id}``. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `action_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "action_id": { "title": "Action Id", "type": "string" } }, "required": [ "action_id" ], "title": "get_workflowArguments", "type": "object" } ``` </details> ## g8_workflow_get_execution > Get a workflow execution's status / output / token cost. Polling pattern: call every few seconds after ``g8_workflow_execute`` until ``status`` is ``completed`` or ``failed``. 404s for executions outside the caller's org. Args: execution_id: Execution UUID returned by ``g8_workflow_execute``. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `execution_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "execution_id": { "title": "Execution Id", "type": "string" } }, "required": [ "execution_id" ], "title": "get_executionArguments", "type": "object" } ``` </details> ## g8_workflow_get_trigger_status > Get the trigger status (cursor + config) for a workflow. Useful for debugging "why isn't my workflow firing?" - surfaces whether a trigger is configured, its type, and the last cursor timestamp processed. Args: action_id: Workflow id. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `action_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "action_id": { "title": "Action Id", "type": "string" } }, "required": [ "action_id" ], "title": "get_trigger_statusArguments", "type": "object" } ``` </details> ## g8_workflow_list > List workflows owned by the caller's organization. Returns ``runtime_type='workflow'`` actions only. A row with a non-null ``managed_by`` (``{kind: "work_routine", routine_id}``) is the backing workflow of an event-triggered Work routine: read it freely, but change it from Agents > Routines, not here. Args: enabled: ``True`` for enabled only, ``False`` for disabled. Omit to return both. category: Optional free-form category filter (e.g. ``"sales"``). is_template: ``True`` returns templates other orgs can fork. Omit for non-templates. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `enabled` | No | boolean / null | See the schema below for constraints and defaults. | | `category` | No | string / null | See the schema below for constraints and defaults. | | `is_template` | No | boolean / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "enabled": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Enabled" }, "category": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Category" }, "is_template": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Is Template" } }, "title": "list_workflowsArguments", "type": "object" } ``` </details> ## g8_workflow_list_dispositions > List every valid call disposition - system values AND this organization's own custom dispositions. The ``value`` of each entry is EXACTLY what goes into ``disposition_filters`` on the **new_call_disposition** trigger. That is the ONLY trigger with a server-side disposition filter. ``new_call_ended`` carries a ``disposition`` output field for downstream nodes but does NOT filter on it, and ``new_voicemail_detected`` has no disposition field at all; setting ``disposition_filters`` on either saves cleanly and is then silently ignored, so the workflow fires on every call. Returns ``{dispositions: [{value, label, category, origin, ...}], custom_count, source}``: - ``origin`` is ``system`` (built-in vocabulary) or ``custom`` (defined by this org). Custom entries also carry ``description``, ``color`` and ``is_connected``. - A custom disposition's ``value`` is its stored ``custom_{id}`` key - NEVER its display name. Filtering on the name silently never matches. - ``category`` is ``sdr_selectable`` (a rep can pick it) or ``system_set`` (only the dialer backend writes it). - ``source`` is ``voice`` normally, or ``static_fallback`` if the custom-disposition lookup was unavailable. On ``static_fallback``, say you could not confirm this org's custom dispositions rather than claiming there are none. Matching is an EXACT, case-sensitive comparison against the stored key, and an empty filter fires for every disposition. Always resolve the user's wording through this tool instead of guessing a key. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "list_dispositionsArguments", "type": "object" } ``` </details> ## g8_workflow_list_inbox_tags > List the org's AI Inbox tags, so a human description becomes a tag ID. REQUIRED before authoring a **new_inbox_tag_set** trigger: its ``tag_filters`` takes tag IDs, not tag names, and there is no other way to discover them. Resolve phrasing like "out of office", "interested", "referred me to a colleague", "unsubscribe request" to the matching tag's ``id`` here, then pass those ids: g8_workflow_add_node(node_type="trigger", config={ "trigger_type": "new_inbox_tag_set", "tag_filters": ["<id from this tool>", "..."], "tag_source_filters": ["ai"], }) ``tag_source_filters`` accepts ``"ai"`` (the inbox AI agent applied the tag, normally within seconds of the email arriving) and ``"manual"`` (a human applied it). Omit it to fire on both. Prefer this over a ``new_email_reply`` trigger plus a condition node that greps the body text. The inbox AI agent has already classified the reply; re-deriving intent from raw text is fragile and fires late. Args: ai_applicable_only: When ``True``, return only tags the AI agent is allowed to apply automatically (``ai_can_apply``). Those are the tags a ``new_inbox_tag_set`` trigger fires on with no human in the loop. Returns ``{tags: [{id, name, description, color, ai_can_apply}], total}``. An empty list means the org has no inbox tags configured yet - tell the user to create them in Inbox -> Tags rather than inventing ids. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `ai_applicable_only` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "ai_applicable_only": { "default": false, "title": "Ai Applicable Only", "type": "boolean" } }, "title": "list_inbox_tagsArguments", "type": "object" } ``` </details> ## g8_workflow_list_linkedin_senders > List the org's LinkedIn sender accounts (seats). Resolves the ``sender_account_id`` on ``send_netrion_connection_request`` and ``send_netrion_message``, and the ``linkedin_account_id`` carried by the ``linkedin_connection_accepted`` / ``linkedin_message_sent`` / ``linkedin_reply_received`` triggers. USE ``sender_account_id`` VERBATIM: it is the numeric PK as a string. Do NOT pass ``account_id`` (the ``lia_...`` form): the resolver cannot parse it, and these nodes run in strict mode, so naming it FAILS the send rather than falling back to another seat. Returns ``{senders: [{sender_account_id, account_id, display_name, public_identifier, status, account_tier}], total_count}``. Seats that are not ``status='OK'`` are included WITH their status, so a disconnected seat can be reported as such instead of looking like no seats at all. An empty list means LinkedIn is not connected for this org. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "list_linkedin_sendersArguments", "type": "object" } ``` </details> ## g8_workflow_list_mcp_servers > List MCP servers connected to the org with their available tools. Use to fill the ``tool`` node config: ``mcp_server_id`` from ``servers[].mcp_server_id`` and ``mcp_tool_name`` from the per-server tools list. Returns ``{servers: [...], total}``. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "list_mcp_serversArguments", "type": "object" } ``` </details> ## g8_workflow_list_node_types > List every workflow node type with its config schema. Call this BEFORE building a workflow to learn the valid ``type`` strings and what each one's ``config`` must contain. Covers triggers, actions, and integrations. Action-vs-Agent node choice: for ANY one-shot natural-language step (summarize, generate, extract, classify, draft, parse) pick an Action node (``llm_completion``) bound to an LLM skill authored via ``g8_workflow_skill_create_llm``; wire its ``{variable}`` placeholders to upstream nodes via ``input_mappings`` and read its output as ``{{<node_id>.result}}``. Pick an ``agent`` node ONLY for autonomous multi-turn tool use across turns. The catalog also includes ``trigger_output_schemas`` (the input_data each trigger run provides, for ``{{trigger.field}}`` refs), ``resolver_tool_hints`` (MCP tools that resolve opaque id fields), and ``example_workflows`` (worked runnable graphs). Args: detail: ``"slim"`` (default) returns a compact ``[(field_name, type_str)]`` per node. ``"full"`` returns the Pydantic JSON schema for every config field - use when slim is not enough. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `detail` | No | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "detail": { "default": "slim", "title": "Detail", "type": "string" } }, "title": "list_node_typesArguments", "type": "object" } ``` </details> ## g8_workflow_list_roam_groups > List Roam groups connected to the org. Resolves a group description into the ``address_id`` needed for ``send_roam_notification`` (mode=group, recipient_id=...). Returns ``{groups: [{address_id, name, group_type, access_mode}]}``. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "list_roam_groupsArguments", "type": "object" } ``` </details> ## g8_workflow_list_roam_users > List Roam users connected to the org. Resolves a user description into the user_ids needed for ``send_roam_notification`` (mode=dm, user_ids=[...]). Returns ``{users: [{id, name, email}]}``. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "list_roam_usersArguments", "type": "object" } ``` </details> ## g8_workflow_list_slack_channels > List Slack channels in the connected workspace. Resolves a channel description into the Slack channel_id needed for ``send_slack_notification`` (mode=channel, channel_id=...). Returns ``{channels: [{id, name, is_private, num_members}]}``. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "list_slack_channelsArguments", "type": "object" } ``` </details> ## g8_workflow_list_slack_users > List Slack users in the connected workspace. Resolves a user description into the Slack user_id needed for ``send_slack_notification`` (mode=dm, user_id=...). Returns ``{users: [{id, name, real_name, is_bot, deleted}]}``. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | None | No | — | No user-supplied inputs. | <details> <summary>Complete input schema</summary> ```json { "properties": {}, "title": "list_slack_usersArguments", "type": "object" } ``` </details> ## g8_workflow_list_workbenches > List the org's workbenches (staging tables). Resolves the ``workbench_id`` required by ``add_to_workbench``, ``workbench_lookup`` and ``workbench_pull_rows``. It is the same id the Workbench page URL shows, so you and the user are naming the same thing. Returns ``{workbenches: [{workbench_id, name, entity_type, description, row_count, created_at}], total, page, limit}``. An empty list means the org has none yet; create one with ``g8_workflow_create_workbench``. ``limit`` must be 1-100. ``filter_name`` is a case-insensitive literal substring: ``%`` and ``_`` match only themselves. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `limit` | No | integer | See the schema below for constraints and defaults. | | `page` | No | integer | See the schema below for constraints and defaults. | | `filter_name` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "limit": { "default": 50, "title": "Limit", "type": "integer" }, "page": { "default": 1, "title": "Page", "type": "integer" }, "filter_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Filter Name" } }, "title": "list_workbenchesArguments", "type": "object" } ``` </details> ## g8_workflow_list_workflow_executions > List workflow executions for the caller's org (recover a lost execution_id). Uses the current organization and existing API permissions. API: GET /workflows/executions ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `action_id` | No | string / null | Filter to a single workflow's executions. | | `status_filter` | No | string / null | Filter by execution status (equality match - one status per call). The backend writes seven values: pending\|running\|completed\|failed\|paused\|pending_approval\|stopped. The four terminal-or-early states are pending, running, completed, failed; paused and pending_approval are resumable, stopped is terminal. | | `triggered_by` | No | string / null | Filter by the identity that started the run (user email, agent id, 'api', ...). | | `limit` | No | integer / null | Page size (1..200). | | `offset` | No | integer / null | Rows to skip for pagination. | <details> <summary>Complete input schema</summary> ```json { "properties": { "action_id": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Filter to a single workflow's executions.", "title": "Action Id" }, "status_filter": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Filter by execution status (equality match - one status per call). The backend writes seven values: pending|running|completed|failed|paused|pending_approval|stopped. The four terminal-or-early states are pending, running, completed, failed; paused and pending_approval are resumable, stopped is terminal.", "title": "Status Filter" }, "triggered_by": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "Filter by the identity that started the run (user email, agent id, 'api', ...).", "title": "Triggered By" }, "limit": { "anyOf": [ { "maximum": 200, "minimum": 1, "type": "integer" }, { "type": "null" } ], "default": null, "description": "Page size (1..200).", "title": "Limit" }, "offset": { "anyOf": [ { "minimum": 0, "type": "integer" }, { "type": "null" } ], "default": null, "description": "Rows to skip for pagination.", "title": "Offset" } }, "title": "g8_workflow_list_workflow_executionsArguments", "type": "object" } ``` </details> ## g8_workflow_pause_execution > Pause a running workflow execution. Reversible - call ``g8_workflow_resume_execution`` to flip back to ``running``. Idempotent: pausing an already-paused execution is a no-op. Args: execution_id: Execution UUID. reason: Optional short reason saved with the execution. message: Optional human-readable note. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `execution_id` | Yes | string | See the schema below for constraints and defaults. | | `reason` | No | string / null | See the schema below for constraints and defaults. | | `message` | No | string / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "execution_id": { "title": "Execution Id", "type": "string" }, "reason": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Reason" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" } }, "required": [ "execution_id" ], "title": "pause_executionArguments", "type": "object" } ``` </details> ## g8_workflow_plan_inline_workflow > Preview a normalized execution plan for an inline workflow graph. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /workflows/plan ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `body` | Yes | object | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "body": { "additionalProperties": true, "title": "Body", "type": "object" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "body" ], "title": "g8_workflow_plan_inline_workflowArguments", "type": "object" } ``` </details> ## g8_workflow_plan_workflow > Preview a normalized execution plan for a saved workflow. Uses the current organization and existing API permissions. Review the intended change and set confirm=true to execute; otherwise no API request is sent. API: POST /workflows/{action_id}/plan ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `action_id` | Yes | string | Workflow action_id. | | `body` | No | object / null | See the schema below for constraints and defaults. | | `confirm` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "action_id": { "description": "Workflow action_id.", "minLength": 1, "title": "Action Id", "type": "string" }, "body": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Body" }, "confirm": { "default": false, "title": "Confirm", "type": "boolean" } }, "required": [ "action_id" ], "title": "g8_workflow_plan_workflowArguments", "type": "object" } ``` </details> ## g8_workflow_remove_node > Remove a node from a workflow's graph. Also drops any edge referencing the node and scrubs the id from other nodes' ``connections`` / ``conditional_connections`` and from the node ids they carry in config (a condition's ``true_node_id`` / ``false_node_id``, a loop's ``body_node_id``, an error ``fallback_node_id``) so no dangling reference remains - voice refuses to save a graph that still points at the removed node. If ``node_id`` is the workflow's ``start_node_id`` the graph becomes invalid and the PUT is rejected - pick a new start node via ``g8_workflow_update`` first. Args: action_id: Workflow id. node_id: Node to remove. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `action_id` | Yes | string | See the schema below for constraints and defaults. | | `node_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "action_id": { "title": "Action Id", "type": "string" }, "node_id": { "title": "Node Id", "type": "string" } }, "required": [ "action_id", "node_id" ], "title": "remove_nodeArguments", "type": "object" } ``` </details> ## g8_workflow_reset_trigger_cursor > Reset the workflow's trigger cursor to ``now()``. Use before re-enabling a workflow that was disabled for a while, so it does not replay the backlog - the cursor jumps forward and only events from now on fire it. Args: action_id: Workflow id. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `action_id` | Yes | string | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "action_id": { "title": "Action Id", "type": "string" } }, "required": [ "action_id" ], "title": "reset_trigger_cursorArguments", "type": "object" } ``` </details> ## g8_workflow_resume_execution > Resume a paused workflow execution. Idempotent. Args: execution_id: Execution UUID. from_step: Optional step id to resume from (defaults to the step where the pause happened). skip_failed: When ``True``, skip any previously-failed step instead of retrying. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `execution_id` | Yes | string | See the schema below for constraints and defaults. | | `from_step` | No | string / null | See the schema below for constraints and defaults. | | `skip_failed` | No | boolean / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "execution_id": { "title": "Execution Id", "type": "string" }, "from_step": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "From Step" }, "skip_failed": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Skip Failed" } }, "required": [ "execution_id" ], "title": "resume_executionArguments", "type": "object" } ``` </details> ## g8_workflow_stop_execution > Stop a workflow execution permanently. One-way - cannot be resumed after stop; use ``g8_workflow_pause_execution`` instead if the user might want to flip it back. ALWAYS confirm with the user; call ``dry_run=True`` first. Args: execution_id: Execution UUID. reason: Optional short reason saved with the execution. save_partial_results: When ``True``, persist per-step output produced before the stop. Default ``False``. message: Optional human-readable note. dry_run: When ``True``, return a confirmation preview. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `execution_id` | Yes | string | See the schema below for constraints and defaults. | | `reason` | No | string / null | See the schema below for constraints and defaults. | | `save_partial_results` | No | boolean / null | See the schema below for constraints and defaults. | | `message` | No | string / null | See the schema below for constraints and defaults. | | `dry_run` | No | boolean | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "execution_id": { "title": "Execution Id", "type": "string" }, "reason": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Reason" }, "save_partial_results": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Save Partial Results" }, "message": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Message" }, "dry_run": { "default": false, "title": "Dry Run", "type": "boolean" } }, "required": [ "execution_id" ], "title": "stop_executionArguments", "type": "object" } ``` </details> ## g8_workflow_update > Update a workflow's metadata or graph. Partial update - pass only the fields you want to change. For node/edge edits prefer the granular ``g8_workflow_add_node`` / ``update_node`` / ``remove_node`` / ``connect_nodes`` helpers, which fetch + mutate + put for you. A workflow whose ``managed_by.kind`` is ``work_routine`` accepts only ``enabled``; any other edit is refused with "This workflow is managed by a Work routine. Edit it in Agents › Routines." Relay that to the user instead of retrying. Args: action_id: Workflow id. name: New display name (omit to keep current). description: New description. category: New category. enabled: ``True`` to enable, ``False`` to pause. config: New full graph dict. Replaces the existing graph wholesale - for granular edits use the helpers above. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `action_id` | Yes | string | See the schema below for constraints and defaults. | | `name` | No | string / null | See the schema below for constraints and defaults. | | `description` | No | string / null | See the schema below for constraints and defaults. | | `category` | No | string / null | See the schema below for constraints and defaults. | | `enabled` | No | boolean / null | See the schema below for constraints and defaults. | | `config` | No | object / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "action_id": { "title": "Action Id", "type": "string" }, "name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Name" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Description" }, "category": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Category" }, "enabled": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "default": null, "title": "Enabled" }, "config": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Config" } }, "required": [ "action_id" ], "title": "update_workflowArguments", "type": "object" } ``` </details> ## g8_workflow_update_node > Update one node's config / label / position / connections in place. Pass only the fields you want to change. The node's ``type`` is NOT updatable here - switching types requires remove + re-add so the new config schema validates cleanly. ``connections`` and ``conditional_connections`` drive how the executor traverses: use ``connections`` for sequential nodes and ``conditional_connections`` (a dict ``{path_id: target_node_id}`` or ``{"true": target, "false": target}``) for branching ``condition`` / ``loop`` / ``human_approval`` nodes. Most callers let ``add_node`` / ``connect_nodes`` keep these in sync; touch them here only when rewiring a condition node by hand. Args: action_id: Workflow id. node_id: Existing node's id. config: New full per-node config dict (replaces, not merges). label: New display label. position: New ``{"x":..., "y":...}``. connections: New outgoing connections list (replaces). Empty list clears; omit to leave unchanged. conditional_connections: New conditional connections dict (replaces). Empty dict clears; omit to leave unchanged. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `action_id` | Yes | string | See the schema below for constraints and defaults. | | `node_id` | Yes | string | See the schema below for constraints and defaults. | | `config` | No | object / null | See the schema below for constraints and defaults. | | `label` | No | string / null | See the schema below for constraints and defaults. | | `position` | No | object / null | See the schema below for constraints and defaults. | | `connections` | No | array / null | See the schema below for constraints and defaults. | | `conditional_connections` | No | object / null | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "action_id": { "title": "Action Id", "type": "string" }, "node_id": { "title": "Node Id", "type": "string" }, "config": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "default": null, "title": "Config" }, "label": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "title": "Label" }, "position": { "anyOf": [ { "additionalProperties": { "type": "number" }, "type": "object" }, { "type": "null" } ], "default": null, "title": "Position" }, "connections": { "anyOf": [ { "items": { "type": "string" }, "type": "array" }, { "type": "null" } ], "default": null, "title": "Connections" }, "conditional_connections": { "anyOf": [ { "additionalProperties": { "type": "string" }, "type": "object" }, { "type": "null" } ], "default": null, "title": "Conditional Connections" } }, "required": [ "action_id", "node_id" ], "title": "update_nodeArguments", "type": "object" } ``` </details> ## g8_workflow_validate > Validate a workflow graph WITHOUT saving it. Returns ``{errors: [...], warnings: [...], missing_references: [...]}``. Call before ``g8_workflow_create`` or after a granular edit to confirm the graph is sound and every templated reference (``{{node1.output}}``) resolves. A workbench node whose ``workbench_id`` does not exist in the org is an error (``ref_type="workbench"`` in ``missing_references``). Args: config: The graph dict ``{metadata?, settings?, nodes, edges, start_node_id, trigger?}`` - same shape ``g8_workflow_get`` returns under ``response.config``. ### Inputs | Input | Required | Type | Description | |---|---|---|---| | `config` | Yes | object | See the schema below for constraints and defaults. | <details> <summary>Complete input schema</summary> ```json { "properties": { "config": { "additionalProperties": true, "title": "Config", "type": "object" } }, "required": [ "config" ], "title": "validate_workflowArguments", "type": "object" } ``` </details> --- # MCP tool reference > Find every registered graph8 MCP tool, its purpose, and complete input schema. Source: https://docs.graph8.com/developers/mcp-tools/ The graph8 MCP registry contains **661 tools** in this documentation version. Use `g8_tool_search` to discover tools beyond the initially visible set. If your client does not refresh its tool list after discovery, use `g8_execute` with the discovered tool name and arguments. ## Tool families | Family | Tools | |---|---:| | [Campaigns](https://docs.graph8.com/developers/mcp-reference/campaign/) | 65 | | [Clickhouse](https://docs.graph8.com/developers/mcp-reference/clickhouse/) | 2 | | [CRM records](https://docs.graph8.com/developers/mcp-reference/crm/) | 456 | | [Intent signals](https://docs.graph8.com/developers/mcp-reference/intent/) | 16 | | [Skill library](https://docs.graph8.com/developers/mcp-reference/library/) | 3 | | [Marketplace](https://docs.graph8.com/developers/mcp-reference/marketplace/) | 36 | | [Opensearch](https://docs.graph8.com/developers/mcp-reference/opensearch/) | 4 | | [Playbook](https://docs.graph8.com/developers/mcp-reference/playbook/) | 2 | | [Skill](https://docs.graph8.com/developers/mcp-reference/skill/) | 28 | | [Voice and dialer](https://docs.graph8.com/developers/mcp-reference/voice/) | 16 | | [Workflows](https://docs.graph8.com/developers/mcp-reference/workflow/) | 33 | ## Before calling a tool 1. Connect your client and confirm the active organization with `g8_current_org`. 2. Search for the capability and read the input schema. 3. Use IDs returned by the platform and check any requested confirmation. 4. Inspect the result and any action-required response. For writes, verify the intended record or destination. ## Connection compatibility Connecting an AI client to graph8 is different from configuring a third-party MCP server for a workflow or agent. The external-server configuration currently accepts SSE and stdio. Do not assume that a server requiring Streamable HTTP or an OAuth flow can use that configuration unchanged. ## Related - [MCP setup](https://docs.graph8.com/developers/mcp/) - [MCP examples](https://docs.graph8.com/developers/mcp-examples/) - [API and MCP coverage](https://docs.graph8.com/developers/api-mcp-coverage/) ## Contacts ### `g8_search_contacts` [Read the complete inputs for `g8_search_contacts`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_search_contacts). ### `g8_find_contacts` [Read the complete inputs for `g8_find_contacts`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_find_contacts). ### `g8_create_contact` [Read the complete inputs for `g8_create_contact`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_create_contact). ### `g8_enrich_contacts` [Read the complete inputs for `g8_enrich_contacts`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_enrich_contacts). ## Fields ### `g8_create_field` [Read the complete inputs for `g8_create_field`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_create_field). ## Companies ### `g8_search_companies` [Read the complete inputs for `g8_search_companies`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_search_companies). ### `g8_find_companies` [Read the complete inputs for `g8_find_companies`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_find_companies). ## Enrichment & Lookup ### `g8_lookup_person` [Read the complete inputs for `g8_lookup_person`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_lookup_person). ### `g8_lookup_company` [Read the complete inputs for `g8_lookup_company`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_lookup_company). ## Lists ### `g8_get_lists` [Read the complete inputs for `g8_get_lists`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_get_lists). ### `g8_create_list` [Read the complete inputs for `g8_create_list`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_create_list). ### `g8_add_to_list` [Read the complete inputs for `g8_add_to_list`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_add_to_list). ## Pipeline & CRM ### `g8_get_deals` [Read the complete inputs for `g8_get_deals`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_get_deals). ### `g8_get_deal` [Read the complete inputs for `g8_get_deal`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_get_deal). ### `g8_get_pipeline` [Read the complete inputs for `g8_get_pipeline`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_get_pipeline). ## Tasks ### `g8_get_tasks` [Read the complete inputs for `g8_get_tasks`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_get_tasks). ### `g8_create_task` [Read the complete inputs for `g8_create_task`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_create_task). ## Activity & Notes ### `g8_get_activities` [Read the complete inputs for `g8_get_activities`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_get_activities). ### `g8_create_note` [Read the complete inputs for `g8_create_note`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_create_note). ## Sequences ### `g8_list_sequences` [Read the complete inputs for `g8_list_sequences`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_list_sequences). ### `g8_create_sequence` [Read the complete inputs for `g8_create_sequence`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_create_sequence). ### `g8_get_sequence_preview` [Read the complete inputs for `g8_get_sequence_preview`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_get_sequence_preview). ### `g8_update_sequence` [Read the complete inputs for `g8_update_sequence`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_update_sequence). ### `g8_update_sequence_step` [Read the complete inputs for `g8_update_sequence_step`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_update_sequence_step). ### `g8_add_to_sequence` [Read the complete inputs for `g8_add_to_sequence`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_add_to_sequence). ### `g8_pause_sequence` [Read the complete inputs for `g8_pause_sequence`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_pause_sequence). ### `g8_resume_sequence` [Read the complete inputs for `g8_resume_sequence`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_resume_sequence). ### `g8_get_sequence_analytics` [Read the complete inputs for `g8_get_sequence_analytics`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_get_sequence_analytics). ### `g8_delete_sequence` [Read the complete inputs for `g8_delete_sequence`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_delete_sequence). ## Inbox ### `g8_list_inbox` [Read the complete inputs for `g8_list_inbox`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_list_inbox). ### `g8_get_reply` [Read the complete inputs for `g8_get_reply`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_get_reply). ### `g8_assign_reply` [Read the complete inputs for `g8_assign_reply`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_assign_reply). ### `g8_tag_reply` [Read the complete inputs for `g8_tag_reply`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_tag_reply). ### `g8_get_reply_draft` [Read the complete inputs for `g8_get_reply_draft`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_get_reply_draft). ### `g8_send_reply` [Read the complete inputs for `g8_send_reply`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_send_reply). ## Audience Sync ### `g8_list_audience_syncs` [Read the complete inputs for `g8_list_audience_syncs`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_list_audience_syncs). ### `g8_create_audience_sync` [Read the complete inputs for `g8_create_audience_sync`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_create_audience_sync). ### `g8_get_audience_sync` [Read the complete inputs for `g8_get_audience_sync`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_get_audience_sync). ### `g8_update_audience_sync` [Read the complete inputs for `g8_update_audience_sync`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_update_audience_sync). ### `g8_delete_audience_sync` [Read the complete inputs for `g8_delete_audience_sync`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_delete_audience_sync). ### `g8_trigger_audience_sync` [Read the complete inputs for `g8_trigger_audience_sync`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_trigger_audience_sync). ### `g8_get_audience_sync_runs` [Read the complete inputs for `g8_get_audience_sync_runs`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_get_audience_sync_runs). ### `g8_get_audience_sync_errors` [Read the complete inputs for `g8_get_audience_sync_errors`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_get_audience_sync_errors). ## CRM Sync ### `g8_list_crm_syncs` [Read the complete inputs for `g8_list_crm_syncs`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_list_crm_syncs). ### `g8_push_to_crm_contact` [Read the complete inputs for `g8_push_to_crm_contact`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_push_to_crm_contact). ### `g8_push_to_crm_company` [Read the complete inputs for `g8_push_to_crm_company`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_push_to_crm_company). ### `g8_push_to_crm_list` [Read the complete inputs for `g8_push_to_crm_list`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_push_to_crm_list). ### `g8_get_crm_fields` [Read the complete inputs for `g8_get_crm_fields`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_get_crm_fields). ### `g8_get_crm_status` [Read the complete inputs for `g8_get_crm_status`](https://docs.graph8.com/developers/mcp-reference/crm/#g8_get_crm_status). ## Tool Annotations Tool descriptions and schemas are listed in the [tool families](#tool-families). Your client can also inspect the annotations returned by the server. ## See Also See the [related documentation](#related). --- # Meetings > List and retrieve meetings — including attendees, transcript, and AI analysis — from the graph8 inbox Source: https://docs.graph8.com/developers/meetings/ > **Tip: Where meetings live** > Meetings are surfaced under `/inbox/meetings`. Every call requires `Authorization: Bearer $API_KEY`. The `X-Org-Id` header is optional — it defaults to the key's own org. The Meetings API lets you query scheduled, past, and upcoming meetings stored in the graph8 inbox, including attendees, transcript status, AI-generated summaries, and linked CRM data. --- ## List Meetings `GET /inbox/meetings` Returns a paginated list of meetings. All query parameters are optional and can be combined. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `participant_email` | string (repeatable) | — | Match meetings where the organizer, owner, or any attendee has this email. Repeat the parameter to match any of multiple emails (OR logic). | | `scope` | string | — | `my` — only meetings belonging to the authenticated user; `all` — all meetings in the org. | | `timeframe` | string | — | `all` \| `upcoming` \| `past` | | `has_transcript` | boolean | — | When `true`, return only meetings that have a linked transcript. | | `internal_mode` | string | — | `include_internal` — include internal-only meetings; `exclude_internal` — omit them. | | `search` | string | — | Full-text match against meeting title or transcript content. | | `page` | integer | `1` | Page number (1-indexed). | | `page_size` | integer | `50` | Items per page (max 100). | ### Example **cURL** ```bash # List upcoming meetings for the authenticated user curl "https://be.graph8.com/api/v1/inbox/meetings?scope=my&timeframe=upcoming" \ -H "Authorization: Bearer $API_KEY" # Filter by participant email and transcript presence curl "https://be.graph8.com/api/v1/inbox/meetings?participant_email=jane@acme.com&has_transcript=true&page=1&page_size=25" \ -H "Authorization: Bearer $API_KEY" # Search transcript content (exclude internal meetings) curl "https://be.graph8.com/api/v1/inbox/meetings?search=pricing&internal_mode=exclude_internal" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": [ { "id": "meeting_uuid", "channel": "meetings", "subject": "...", "organizer_email": "...", "user_email": "...", "start_time": "...", "end_time": "...", "meeting_url": "...", "attendees": [ { "email": "...", "name": "...", "response_status": "accepted", "organizer": false } ], "transcript_status": "linked", "transcript_id": "...", "transcript_source": "fathom", "transcript_summary": "...", "transcript_redacted": false, "audience_type": "prospect", "is_internal_only": false, "meeting_sensitivity": null, "matched_account": {}, "tags": [{}], "assignees": [{}], "preview": "...", "created_at": "...", "updated_at": "..." } ], "pagination": {} } ``` #### Response Fields | Field | Type | Description | |-------|------|-------------| | `id` | string | Unique meeting UUID. | | `channel` | string | Always `"meetings"`. | | `subject` | string | Meeting title. | | `organizer_email` | string | Email of the calendar organizer. | | `user_email` | string | Email of the graph8 user who owns this meeting record. | | `start_time` | string | ISO 8601 start timestamp. | | `end_time` | string | ISO 8601 end timestamp. | | `meeting_url` | string | Conferencing join URL. | | `attendees` | array | List of attendee objects (see below). | | `transcript_status` | string | Transcript link state — e.g. `"linked"`, `"none"`, `"processing"`. | | `transcript_id` | string | ID of the linked transcript record, if any. | | `transcript_source` | string | Provider that generated the transcript — e.g. `"fathom"`. | | `transcript_summary` | string | AI-generated summary of the transcript. | | `transcript_redacted` | boolean | Whether the transcript has been redacted. | | `audience_type` | string | Classification of attendees — e.g. `"prospect"`. | | `is_internal_only` | boolean | `true` if all attendees are internal (same org domain). | | `meeting_sensitivity` | string \| null | Sensitivity label, if set. | | `matched_account` | object | CRM account matched to this meeting, if any. | | `tags` | array | Tags attached to this meeting. | | `assignees` | array | Team members assigned to this meeting. | | `preview` | string | Short text preview of the meeting content. | | `created_at` | string | ISO 8601 record creation timestamp. | | `updated_at` | string | ISO 8601 record last-updated timestamp. | #### Attendee Object | Field | Type | Description | |-------|------|-------------| | `email` | string | Attendee email address. | | `name` | string | Attendee display name. | | `response_status` | string | Calendar response — e.g. `"accepted"`, `"declined"`, `"tentative"`. | | `organizer` | boolean | `true` if this attendee is the meeting organizer. | --- ## Get Meeting `GET /inbox/meetings/{meeting_id}` Returns full detail for a single meeting, including the full transcript text and AI analysis fields that are not returned in the list endpoint. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `meeting_id` | string | Meeting UUID. | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/inbox/meetings/meeting_uuid" \ -H "Authorization: Bearer $API_KEY" ``` ### Response All fields from the list endpoint are present, plus the following additional fields: | Field | Type | Description | |-------|------|-------------| | `transcript_text` | string | Full transcript body text. | | `key_topics` | array | AI-extracted key topics discussed in the meeting. | | `action_items` | array | AI-extracted action items from the meeting. | | `meeting_tasks` | array | Tasks created from this meeting. | | `campaign_mentions` | array | Campaigns or sequences mentioned during the meeting. | | `custom_fields` | object | Custom field values attached to this meeting record. | | `participant_links` | array | CRM links resolved for each participant (contact/company records). | | `owner_team_member` | object | graph8 team member who owns this meeting record. | | `organizer_team_member` | object | graph8 team member who is the calendar organizer. | --- # n8n Setup > Connect n8n to graph8 via MCP using the remote endpoint with a Bearer API key Source: https://docs.graph8.com/developers/n8n-setup/ Connect n8n to graph8 to automate workflows with your campaigns, accounts, contacts, and intelligence data. > **Tip:** > n8n runs headless, so it does **not** use the interactive browser OAuth flow. Instead it authenticates to the remote graph8 MCP server with a **personal or org API key** sent as an `Authorization: Bearer` header. There is no `session_id` to copy and no config file with a `session_id` — those belonged to a decommissioned setup. ## What is n8n? [n8n](https://n8n.io) is an open-source workflow automation tool. With the graph8 MCP integration you can: - Trigger workflows when campaigns, deals, or contacts change - Sync graph8 data into your CRM or data warehouse - Build custom automations against graph8's tools and index ## Prerequisites - An n8n instance (self-hosted or cloud) - A graph8 account ([get started free](https://app.graph8.com/signup) — 1,000 credits, no card required) - A graph8 API key: - **Personal API key** — **Profile → Developer** (scoped to you inside the active org), or - **Org API key** — **Settings → API** (admin only; for shared/service automations) > **Note:** > Keep the API key in n8n **Credentials**, not hard-coded in nodes. That way you rotate it in one place. graph8 API keys are long-lived — they do not expire after 24 hours of inactivity, so there is no per-run "session refresh" to manage. ## Setup There are two ways to call graph8 from n8n. Both point at the same endpoint: ``` https://be.graph8.com/mcp/ ``` **MCP Client Tool node** Recent n8n versions ship a native **MCP Client Tool** node. 1. **Add the MCP Client Tool node** to your workflow (or attach it to an AI Agent node as a tool). 2. **Set the server URL** ``` https://be.graph8.com/mcp/ ``` 3. **Set authentication to Header Auth** Add a header credential: | Field | Value | |-------|-------| | Name | `Authorization` | | Value | `Bearer YOUR_G8_API_KEY` | Replace `YOUR_G8_API_KEY` with your personal or org API key (they start with `g8_`). 4. **Select tools and run** The node lists the graph8 tools your role grants. Pick a tool (for example `g8_list_campaigns`) and execute the node to verify the connection. **HTTP Request node** If your n8n version has no MCP node, call the JSON-RPC endpoint with an **HTTP Request** node: ```json { "method": "POST", "url": "https://be.graph8.com/mcp/", "headers": { "Authorization": "Bearer YOUR_G8_API_KEY", "Content-Type": "application/json" }, "body": { "jsonrpc": "2.0", "method": "tools/call", "params": { "name": "g8_list_campaigns", "arguments": { "status": "active" } }, "id": 1 } } ``` Store the API key as an n8n **Header Auth** credential and reference it from the node rather than pasting it into the body or URL. ## Available Actions Once connected, your n8n workflows can call any graph8 MCP tool your role grants. Tool names are prefixed with `g8_`. A few common ones: > **Note:** > Exact tool names are **mode-dependent**. When the endpoint runs in combined `all` mode, GTM tools are namespaced — e.g. `g8_gtm_list_campaigns` instead of `g8_list_campaigns`. The unprefixed names below are the canonical form; the MCP node lists the exact names your role grants, and you can always confirm them in the [MCP Tool Reference](https://docs.graph8.com/developers/mcp-tools/). | Tool | Description | |------|-------------| | `g8_list_campaigns` | List campaigns with an optional status filter | | `g8_get_campaign` | Get details for a specific campaign | | `g8_search_companies` | Search companies already in your CRM | | `g8_find_companies` | Search graph8's company index for new accounts | | `g8_search_contacts` | Search contacts already in your CRM | | `g8_get_deals` | List pipeline deals with stage / text filters | | `g8_list_intelligence_data` | List intelligence data (scrapes, enrichment, competitor research) | | `g8_tool_search` | Discover and activate other tools by keyword | For the complete list with parameters and return shapes, see the [MCP Tool Reference](https://docs.graph8.com/developers/mcp-tools/). ## Example Workflows ### Sync New Campaigns to Slack 1. **Trigger**: Schedule (every hour) or Webhook 2. **graph8 tool**: `g8_list_campaigns` with `status="active"` 3. **Filter Node**: Only new campaigns since the last run 4. **Slack Node**: Post to #marketing ### Enrich CRM with Company Intelligence 1. **Trigger**: Webhook from CRM (new deal created) 2. **graph8 tool**: `g8_find_companies` (or `g8_search_companies`) by name 3. **graph8 tool**: `g8_list_intelligence_data` for the company 4. **HTTP Node**: Update the CRM record with the intelligence data ### Daily Pipeline Report 1. **Trigger**: Schedule (9am daily) 2. **graph8 tool**: `g8_get_deals` 3. **Code Node**: Summarize the pipeline by stage 4. **Email Node**: Send the report to the sales team ## Troubleshooting n8n specifics live here. Generic MCP issues (auth, permissions, tool names, timeouts) are answered in the [MCP FAQ](https://docs.graph8.com/developers/mcp-faq/). ### Workflow execution errors - Use n8n's **Execute Workflow** in single-step mode to isolate which node fails - Inspect the HTTP Request / MCP node response body for the actual server error - For self-hosted n8n, check the container logs for connection-level errors ### Other issues - Authentication failed → [FAQ](https://docs.graph8.com/developers/mcp-faq/#authentication-failed-or-401) (check the `Authorization: Bearer` header and that the key is not revoked) - Tool not found → [FAQ](https://docs.graph8.com/developers/mcp-faq/#tool-not-found) - Rate limiting (50 rps cap) → [FAQ](https://docs.graph8.com/developers/mcp-faq/#what-is-the-rate-limit) - Timeouts on long-running tools → [FAQ](https://docs.graph8.com/developers/mcp-faq/#why-does-my-tool-call-time-out) ## Credentials & Keys - Store the API key as an n8n **Header Auth** credential (`Authorization: Bearer g8_...`) so it can be rotated in one place. - Revoke or rotate keys from **Profile → Developer** (personal) or **Settings → API** (org). Revocation is immediate — the next call returns 401. - Prefer a dedicated org API key for production automations so it is decoupled from any one person's account. ## Frequently Asked Questions ### Can I use n8n Cloud or do I need self-hosted? Both work. n8n Cloud and self-hosted instances connect to graph8 the same way — the remote endpoint with a Bearer API key. The HTTP Request node method works universally if your version has no MCP node. ### Do I need to do the OAuth browser sign-in like Claude Desktop? No. The browser OAuth flow is for interactive clients (Claude, Cursor, etc.). n8n is headless and authenticates with an API key in the `Authorization` header instead. ### Are there rate limits on MCP calls from n8n? Yes — 50 requests per second and 1,000 per minute per org across REST, SDK, and MCP. Add delays in high-volume workflows; a burst over the cap returns HTTP 429 with a `Retry-After` header. ### Can I trigger n8n workflows from graph8? Use webhooks. Configure an outbound webhook in graph8 that calls your n8n webhook trigger URL when events occur (campaign created, intelligence complete, etc.). ### How do I test MCP calls before building full workflows? Run a single node with a read-only tool like `g8_list_campaigns` and use n8n's **Execute step** button to inspect the response before wiring it into the rest of the flow. ## Related - [MCP Overview](https://docs.graph8.com/developers/mcp) - [What You Can Ask](https://docs.graph8.com/developers/what-you-can-ask/) - [Automation Platforms](https://docs.graph8.com/developers/automation-platforms/) --- # Notes > Create, update, and manage notes on contacts Source: https://docs.graph8.com/developers/notes/ Notes let you attach free-text annotations to contacts for context, follow-ups, and team collaboration. --- ## List Notes `GET /contacts/{contact_id}/notes` Returns all notes on a contact, sorted by most recent first. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `contact_id` | string | Contact ID | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/contacts/12345/notes" \ -H "Authorization: Bearer $API_KEY" ``` **Python** ```python response = requests.get( f"{BASE_URL}/contacts/12345/notes", headers=HEADERS ) notes = response.json() ``` **TypeScript** ```typescript const response = await fetch(`${BASE_URL}/contacts/12345/notes`, { headers: { Authorization: `Bearer ${API_KEY}` } }); const notes = await response.json(); ``` ### Response ```json { "data": [ { "id": "note_uuid", "content": "Spoke with Jane about the Q2 renewal.", "entity_type": "contact", "entity_id": "12345", "created_by": "user_x", "created_by_name": "John", "created_at": "2026-02-25T14:30:00Z", "updated_at": "2026-02-25T14:30:00Z" } ] } ``` --- ## Create Note `POST /contacts/{contact_id}/notes` → `201 Created` Returns the full note object on success. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `contact_id` | string | Contact ID | ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `content` | string | Yes | Note text content | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/contacts/12345/notes" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"content": "Follow up next week about pricing."}' ``` **Python** ```python response = requests.post( f"{BASE_URL}/contacts/12345/notes", headers=HEADERS, json={"content": "Follow up next week about pricing."} ) ``` ### Response `201 Created` ```json { "data": { "id": "note_uuid", "content": "Follow up next week about pricing.", "entity_type": "contact", "entity_id": "12345", "created_by": "user_x", "created_by_name": "John", "created_at": "2026-02-25T14:30:00Z", "updated_at": "2026-02-25T14:30:00Z" } } ``` --- ## Update Note `PATCH /notes/{note_id}` ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `note_id` | string | Note ID | ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `content` | string | Yes | Updated note text | ### Example **cURL** ```bash curl -X PATCH "https://be.graph8.com/api/v1/notes/note_uuid" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"content": "Updated text"}' ``` --- ## Delete Note `DELETE /notes/{note_id}` Returns `204 No Content` on success. --- # OpenAI Agents SDK Setup > Connect OpenAI Agents SDK to graph8 via MCP using MCPServerStreamableHttp Source: https://docs.graph8.com/developers/openai-sdk-setup/ Connect the OpenAI Agents SDK to graph8's MCP server. The Agents SDK ships with native MCP support, so a graph8 connection is a single `MCPServerStreamableHttp` call away. > **Tip:** > Try the developer tier free - 1,000 credits, no card required. [Get started](https://app.graph8.com/signup). ## Prerequisites - Python 3.10+ - `openai-agents` SDK with the MCP extra ```bash pip install 'openai-agents[mcp]' ``` ## Hosted MCP (remote OAuth) ```python import asyncio import os from agents import Agent, Runner from agents.mcp import MCPServerStreamableHttp async def main() -> None: async with MCPServerStreamableHttp( name="graph8", params={"url": "https://be.graph8.com/mcp/"}, ) as server: agent = Agent( name="ProspectFinder", instructions=( "You find prospects for SaaS GTM teams. Use g8_find_contacts to " "preview. Never call g8_build_contact_list, g8_enrich_contacts, " "or g8_add_to_sequence without explicit user confirmation." ), mcp_servers=[server], model="gpt-4o", ) result = await Runner.run( agent, "Find 10 VP Engineering at Series B SaaS in the US. " "Return name, company, title, and linkedin URL.", ) print(result.final_output) if __name__ == "__main__": asyncio.run(main()) ``` The first run prompts for OAuth in a browser. The SDK caches the token. ## Self-hosted MCP (stdio) ```python import os from agents.mcp import MCPServerStdio server = MCPServerStdio( name="graph8", params={ "command": "uvx", "args": ["g8-mcp-server"], "env": { "G8_API_KEY": os.environ["G8_API_KEY"], "G8_MCP_MODE": "gtm", }, }, ) ``` ## Worked example: one-shot prospect list ```python import asyncio from agents import Agent, Runner from agents.mcp import MCPServerStreamableHttp async def build_prospect_list() -> None: async with MCPServerStreamableHttp( name="graph8", params={"url": "https://be.graph8.com/mcp/"}, ) as server: agent = Agent( name="GTMAgent", instructions=( "Step 1: g8_find_contacts to preview 50 VP Eng at Series B SaaS.\n" "Step 2: Pick the top 25 by signal score. Ask the user to confirm.\n" "Step 3: g8_build_contact_list named 'Series B SaaS VP Eng'.\n" "Step 4: g8_list_sequences to find 'New SaaS Outreach'.\n" "Step 5: g8_add_to_sequence after confirmation.\n" ), mcp_servers=[server], model="gpt-4o", ) result = await Runner.run(agent, "Run the full prospect-to-sequence flow.") print(result.final_output) asyncio.run(build_prospect_list()) ``` The Agents SDK's `Runner` handles tool-call loops automatically. graph8's confirmation rule for credit-charging tools surfaces as natural-language prompts to the user before any save. ## Related - [MCP Server overview](https://docs.graph8.com/developers/mcp/) - [MCP Tool Reference](https://docs.graph8.com/developers/mcp-tools/) - [MCP FAQ](https://docs.graph8.com/developers/mcp-faq/) - [End-to-end examples](https://docs.graph8.com/developers/mcp-examples/) --- # OpenSearch (Raw Data Access) > Structured search, count, aggregate, and bulk ID resolution against the graph8 mashup OpenSearch indices Source: https://docs.graph8.com/developers/opensearch/ > **Caution: RAW field names — not snake_case** > Filter, group-by, and sort fields on every OpenSearch endpoint use the **RAW index field names** — all UPPERCASE, e.g. `COMPANY_INDUSTRY`, `COMPANY_NAICS`, `COMPANY_COUNTRY`. These are **not** the friendly snake_case names used by `/search/contacts`. A typo in a field name returns **0 hits silently** rather than an error — double-check names before filtering. These endpoints provide generic, read-only, structured access to the graph8 mashup OpenSearch indices. They back the `g8_opensearch_*` MCP tool family. Use them when the higher-level CRM or prospecting endpoints (`/contacts`, `/companies`, `/enrichment/*`, `/search/contacts`) don't expose the field you need to filter on. The `search`, `count`, and `aggregate` routes require an active subscription (same gate as `/search/contacts` and `/enrichment/enrich`). The `resolve` route does not. > **Note: Index shape — contacts vs. companies** > `mashup_contacts` and `mashup_companies` are the **same physical, contact-grained index** distinguished by your filters. A `count` or `search` over `mashup_companies` counts contact rows, not unique companies. For a unique-company total, use `aggregate` with `uniqExact` on `COMPANY_ID`. > **Note: Observed value shapes** > - `COMPANY_COUNTRY` is free-text — both `"US"` and `"United States"` occur; OR both forms in your filter. > - `COMPANY_NAICS` is a multi-value array that may carry both `"3311"` and `"3311.00000"`. > - `COMPANY_EMPLOYEE_COUNT` and `COMPANY_REVENUE` are band strings (not numbers), so `sum`/`avg` on them is not meaningful. --- ## Search `POST /opensearch/search` Structured search across a whitelisted set of mashup indices. The request body declares filters, fields, sort, and limit as typed inputs — the handler translates them into a safe `bool/term` OpenSearch query. Raw OpenSearch DSL is not accepted. ### Request Body | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `index` | string | **Yes** | — | Index to search. One of: `mashup_contacts`, `mashup_companies`, `hem2contact`, `ip2company` | | `filters` | object | No | — | Dict of field name to value. A scalar value becomes a `term` filter; a list becomes a `terms` (OR) filter. Fields must be RAW UPPERCASE index names. | | `fields` | array | No | — | List of RAW field names to project from `_source`. Omit to return all fields. | | `sort` | array | No | — | List of `{"FIELD_NAME": "asc"}` or `{"FIELD_NAME": "desc"}` sort objects. | | `limit` | integer | No | `50` | Number of hits to return (1–200). | | `cursor` | array | No | `null` | `search_after` token from a prior response — pass to retrieve the next page. | | `track_total_hits` | integer (0–1000000) | No | `null` | Exact-count ceiling for the `total` field (e.g. `100000`). By default, OpenSearch caps `total` at **10000** for any larger result set. Pass a value in the range `0–1000000` to get an exact count up to that ceiling in the same round trip. Does not lift pagination depth — keep paging with `cursor`. For a count with no row payload, use `/opensearch/count` instead (never capped). | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/opensearch/search" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "index": "mashup_contacts", "filters": { "COMPANY_DOMAIN": "acme.com", "COMPANY_INDUSTRY": ["SaaS", "Fintech"] }, "fields": ["COMPANY_DOMAIN", "CONTACT_ID"], "sort": [{ "COMPANY_REVENUE": "desc" }], "limit": 50, "cursor": null, "track_total_hits": null }' ``` ### Response ```json { "data": { "index": "mashup_contacts", "total": 1, "hits": [ { "id": "abc", "score": 1.5, "source": { "COMPANY_DOMAIN": "acme.com" }, "sort": [42] } ], "cursor": [42], "took_ms": 7 } } ``` | Field | Description | |-------|-------------| | `index` | The index that was searched | | `total` | Hit count, capped at 10000 by default. Pass `track_total_hits` for a higher exact-count ceiling. | | `hits` | Array of matching documents. Each has `id`, `score`, `source` (projected fields), and `sort` (search_after token for this row). | | `cursor` | Pass as `cursor` on the next request to retrieve the following page. `null` when there are no more results. | | `took_ms` | OpenSearch query duration in milliseconds | --- ## Count `POST /opensearch/count` Exact document count for a whitelisted index using the native OpenSearch `_count` API — **not** subject to the 10000 `track_total_hits` ceiling. Uses the same `index` and `filters` shape as `search`; paging fields are intentionally absent because a count returns no rows. This is the correct call for TAM cell sizing. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `index` | string | **Yes** | Index to count. Same enum as `search`: `mashup_contacts`, `mashup_companies`, `hem2contact`, `ip2company` | | `filters` | object | No | Same semantics as `search` — scalar value becomes `term`, list becomes `terms` (OR). Fields must be RAW UPPERCASE index names. | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/opensearch/count" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "index": "mashup_companies", "filters": { "COMPANY_NAICS": ["3311", "3311.00000"], "COMPANY_COUNTRY": ["US", "United States"] } }' ``` ### Response ```json { "data": { "index": "mashup_companies", "count": 17842 } } ``` > **Note:** > This counts documents on the contact-grained index. For unique companies, use `aggregate` with `uniqExact` on `COMPANY_ID`. --- ## Aggregate `POST /opensearch/aggregate` Generic, allowlist-bounded GROUP BY — the TAM matrix-walk primitive. Translates `group_by` into nested OpenSearch `terms` aggregations with metric sub-aggregations, then applies `having`, `sort`, and `limit` to the flattened groups. ### Request Body | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `index` | string | **Yes** | — | Index to aggregate. Same enum as `search`. | | `filters` | object | No | — | Pre-aggregation filters. Same semantics as `search`. Applied before grouping. | | `group_by` | array | **Yes** | — | List of 1–3 RAW UPPERCASE keyword fields to group by (e.g. `["COMPANY_STATE", "COMPANY_EMPLOYEE_COUNT"]`). Capped at 3 dimensions. | | `agg` | array | No | `[{"function": "count"}]` | List of metric aggregation objects. Defaults to a single document count. See functions table below. | | `having` | object | No | — | Post-aggregation filter by metric alias. Operators: `eq`, `gte`, `lte`, `gt`, `lt`. Example: `{"count": {"gte": 50}}`. | | `sort` | array | No | — | Sort groups by a metric alias. Example: `[{"count": "desc"}]`. | | `limit` | integer | No | `100` | Maximum number of groups to return (1–1000). `total_groups` reflects the count of matching groups before this slice. | #### `agg` object fields | Field | Type | Required | Description | |-------|------|----------|-------------| | `function` | string | **Yes** | Aggregation function. One of: `count`, `uniqExact`, `sum`, `avg`, `min`, `max`. `count` uses the bucket document count and does not require a `field`. All others require a numeric `field`. | | `field` | string | Conditional | RAW UPPERCASE field name to aggregate. Required for all functions except `count`. | | `alias` | string | No | Name to use for this metric in `having`, `sort`, and response output. Defaults to the function name. | > **Note: Accuracy** > `terms` bucket counts and `uniqExact` (HLL) are **approximate** on a sharded index — treat them as well-calibrated estimates, not exact figures. Per-level bucket size and returned `total_groups` are bounded server-side so a single call cannot fan out into a full-cluster scan. ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/opensearch/aggregate" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "index": "mashup_companies", "filters": { "COMPANY_NAICS": ["3311", "3311.00000"] }, "group_by": ["COMPANY_STATE", "COMPANY_EMPLOYEE_COUNT"], "agg": [ { "function": "count" }, { "function": "uniqExact", "field": "COMPANY_DOMAIN", "alias": "unique_domains" } ], "having": { "count": { "gte": 50 } }, "sort": [{ "count": "desc" }], "limit": 500 }' ``` ### Response ```json { "data": { "index": "mashup_companies", "groups": [ { "key": { "COMPANY_STATE": "OH", "COMPANY_EMPLOYEE_COUNT": "11-50" }, "agg": { "count": 1247, "unique_domains": 1184 } } ], "total_groups": 312, "took_ms": 84 } } ``` | Field | Description | |-------|-------------| | `index` | The index that was aggregated | | `groups` | Array of group objects. Each has `key` (the group-by field values) and `agg` (metric values keyed by alias). | | `total_groups` | Count of groups matching `having` before the `limit` slice. | | `took_ms` | OpenSearch query duration in milliseconds | --- ## Resolve `POST /opensearch/resolve` Bulk HEM-to-contact-ID or IP-to-company-ID resolution, optimised for batches up to 500. The response includes **every** input as a key — `null` for misses — so callers can iterate results without re-querying. This endpoint does **not** require an active subscription. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `kind` | string | **Yes** | Resolution type. One of: `hem_to_contact`, `ip_to_company` | | `ids` | array | **Yes** | List of 1–500 string identifiers to resolve. For `hem_to_contact`, pass SHA-256 hashed email values. For `ip_to_company`, pass IP address strings. | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/opensearch/resolve" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "kind": "hem_to_contact", "ids": ["sha256hash1", "sha256hash2"] }' ``` ### Response ```json { "data": { "kind": "hem_to_contact", "results": { "sha256hash1": "C1", "sha256hash2": null }, "matched": 1, "requested": 2 } } ``` | Field | Description | |-------|-------------| | `kind` | The resolution type that was requested | | `results` | Object keyed by every input identifier. Value is the resolved ID string on a hit, or `null` on a miss. | | `matched` | Number of inputs that resolved to an ID | | `requested` | Total number of input identifiers submitted | --- # Pagination > Response envelope format and how to paginate through results Source: https://docs.graph8.com/developers/pagination/ All API responses use a standard JSON envelope. List endpoints include pagination metadata to help you iterate through large datasets. --- ## Response Envelope Every response wraps its payload in a `data` field: ```json { "data": { ... } } ``` List endpoints also include a `pagination` object: ```json { "data": [ ... ], "pagination": { "page": 1, "limit": 50, "total": 342, "has_next": true, "next_cursor": "bzo1MA" } } ``` ### Pagination Fields | Field | Type | Description | |-------|------|-------------| | `page` | integer | Current page number (1-indexed) | | `limit` | integer | Number of items per page | | `total` | integer | Total number of items across all pages | | `has_next` | boolean | `true` if more pages are available | | `next_cursor` | string \| null | Opaque cursor for the next page; `null` on the last page. Pass it back as `cursor`. | --- ## Query Parameters All list endpoints accept these pagination parameters: | Parameter | Type | Default | Range | Description | |-----------|------|---------|-------|-------------| | `page` | integer | `1` | 1+ | Page number to retrieve | | `limit` | integer | `50` | 1-200 | Number of items per page | | `cursor` | string | — | — | Opaque cursor from a prior response's `next_cursor`. Takes precedence over `page`. | You can paginate two ways: by **page number** (`page`) or by **opaque cursor** (`cursor`). They share the same `limit`. When you send a `cursor`, the `page` parameter is ignored. --- ## Iterating Through Pages Use `has_next` to determine when to stop paginating. **cURL** ```bash # Fetch page 1 curl "https://be.graph8.com/api/v1/contacts?page=1&limit=100" \ -H "Authorization: Bearer $API_KEY" # If has_next is true, fetch page 2 curl "https://be.graph8.com/api/v1/contacts?page=2&limit=100" \ -H "Authorization: Bearer $API_KEY" ``` **Python** ```python import requests API_KEY = "your_api_key_here" BASE_URL = "https://be.graph8.com/api/v1" HEADERS = {"Authorization": f"Bearer {API_KEY}"} all_contacts = [] page = 1 while True: response = requests.get( f"{BASE_URL}/contacts", headers=HEADERS, params={"page": page, "limit": 200} ) data = response.json() all_contacts.extend(data["data"]) if not data["pagination"]["has_next"]: break page += 1 print(f"Fetched {len(all_contacts)} contacts") ``` **TypeScript** ```typescript const API_KEY = "your_api_key_here"; const BASE_URL = "https://be.graph8.com/api/v1"; async function fetchAllContacts() { const allContacts = []; let page = 1; while (true) { const response = await fetch( `${BASE_URL}/contacts?page=${page}&limit=200`, { headers: { Authorization: `Bearer ${API_KEY}` } } ); const { data, pagination } = await response.json(); allContacts.push(...data); if (!pagination.has_next) break; page++; } return allContacts; } ``` --- ## Cursor Pagination For forward iteration you can follow `next_cursor` instead of incrementing `page`. The cursor is an **opaque token** — don't parse or construct it; just pass the last response's `next_cursor` back as the `cursor` parameter. When `next_cursor` is `null`, you've reached the end. This is the Stripe `starting_after` / Twilio `PageToken` equivalent, and is the recommended pattern for scripts that walk an entire list. **cURL** ```bash # First page — no cursor curl "https://be.graph8.com/api/v1/usage/transactions?limit=100" \ -H "Authorization: Bearer $API_KEY" # => { "data": [...], "pagination": { "next_cursor": "bzoxMDA", ... } } # Next page — pass the previous next_cursor curl "https://be.graph8.com/api/v1/usage/transactions?limit=100&cursor=bzoxMDA" \ -H "Authorization: Bearer $API_KEY" ``` **Python** ```python import requests API_KEY = "your_api_key_here" URL = "https://be.graph8.com/api/v1/usage/transactions" HEADERS = {"Authorization": f"Bearer {API_KEY}"} rows, cursor = [], None while True: params = {"limit": 200} if cursor: params["cursor"] = cursor page = requests.get(URL, headers=HEADERS, params=params).json() rows.extend(page["data"]) cursor = page["pagination"]["next_cursor"] if not cursor: # null -> done break print(f"Fetched {len(rows)} transactions") ``` **TypeScript** ```typescript const API_KEY = "your_api_key_here"; const URL = "https://be.graph8.com/api/v1/usage/transactions"; async function fetchAll() { const rows: unknown[] = []; let cursor: string | null = null; do { const qs = new URLSearchParams({ limit: "200" }); if (cursor) qs.set("cursor", cursor); const res = await fetch(`${URL}?${qs}`, { headers: { Authorization: `Bearer ${API_KEY}` }, }); const { data, pagination } = await res.json(); rows.push(...data); cursor = pagination.next_cursor; } while (cursor); return rows; } ``` A malformed or foreign `cursor` returns `400` with `{ "type": "bad_request", ... }`. --- ## Best Practices - **Prefer `cursor`** for walking an entire list — follow `next_cursor` until it's `null`. Use `page` only when you need to jump to a specific page number. - **Use the maximum `limit` (200)** when exporting large datasets to minimize requests. - **Check `has_next`** (or a `null` `next_cursor`) instead of computing from `page * limit < total` — this avoids off-by-one errors. - **Respect rate limits** — add a short delay between pages if fetching many pages. See [Rate Limits](https://docs.graph8.com/developers/rate-limits/). - **Don't assume order** — results are returned in the default order for each resource. If you need a specific order, sort client-side. --- # Stage Checklist Pipelines > REST reference for graph8 Stage Checklist v2 pipelines — multi-stage workflows with evidence requirements and channel scripts Source: https://docs.graph8.com/developers/pipelines/ A Stage Checklist pipeline is a multi-stage workflow with per-stage **evidence requirements** (e.g. "budget confirmed", "decision-maker identified") and **channel scripts** (call, email, LinkedIn) tied to each stage. Distinct from deal pipelines, which are the simple stage-list deals progress through. > **Tip:** > For deal pipelines (the lightweight stages your deals sit in), use `/api/v1/deals/pipelines` — documented under [Deals](https://docs.graph8.com/developers/deals/). This page covers the heavier Stage Checklist v2 surface. > **Note: Auth** > Every call needs `Authorization: Bearer $G8_API_KEY`. The `X-Org-Id` is bound to the key automatically. [Get a key in 30 seconds →](https://docs.graph8.com/developers/authentication/) ## 30-second start List your Stage Checklist pipelines: **cURL** ```bash curl 'https://be.graph8.com/api/v1/pipelines' \ -H "Authorization: Bearer $G8_API_KEY" ``` **TypeScript** ```typescript import { g8 } from '@graph8/sdk'; g8.init({ apiKey: process.env.G8_API_KEY! }); const { data } = await g8.pipelines.list(); console.log(data); // → StagePipeline[] with stages + evidence + channel scripts ``` **CLI** ```bash g8 list-stage-pipelines ``` **MCP** Ask your agent: *"List my stage-checklist pipelines"* → tool: `g8_list_pipelines`. ## Quick reference | Endpoint | Method | Purpose | |---|---|---| | `/pipelines` | GET | List pipelines | | `/pipelines` | POST | Create | | `/pipelines/{id}` | GET | Get a pipeline with all stages | | `/pipelines/{id}` | PUT | Update metadata (name, target) | | `/pipelines/{id}` | DELETE | Delete (no deals must reference) | | `/pipelines/evidence-library` | GET | Canonical 8-key evidence library | | `/pipelines/{id}/stages` | POST | Add a stage | | `/pipelines/{id}/stages/{stage_id}` | PUT | Update a stage | | `/pipelines/{id}/stages/{stage_id}` | DELETE | Delete a stage | | `/pipelines/{id}/stages/reorder` | PUT | Reorder stages | | `/pipelines/suggest` | POST | AI-suggested pipeline from org context | | `/pipelines/from-suggestion` | POST | Promote a suggestion into a real pipeline | ## Pipeline shape ```typescript { id: "pip_xyz", name: "Enterprise Outbound", target: "Land 5 new logos in Q3", is_default: false, stages: [ { id: "stg_discovery", name: "Discovery", position: 0, required_elements: ["budget_confirmed", "timeline_known"], recommended_elements: ["competitor_landscape"], channel_scripts: { email: "Hi {{first_name}}...", call: "Hello, calling about..." }, color: "#00CAEB", stage_type: "qualification" }, { id: "stg_demo", name: "Demo", position: 1, ... } ] } ``` ## Evidence library The canonical 8 evidence keys (returned by `GET /pipelines/evidence-library`) cover the typical qualification criteria: | Key | Label | |---|---| | `budget_confirmed` | Budget confirmed | | `timeline_known` | Decision timeline known | | `decision_maker_identified` | Decision maker identified | | `pain_point_validated` | Pain point validated | | `competitor_landscape` | Competitor landscape understood | | `success_criteria_agreed` | Success criteria agreed | | `legal_review_started` | Legal review started | | `commercial_terms_aligned` | Commercial terms aligned | Use these in `required_elements` / `recommended_elements` on each stage. ## Create ```bash # Default templated stages POST /api/v1/pipelines { "name": "Enterprise Outbound", "target": "Land 5 new logos in Q3" } # Or blank POST /api/v1/pipelines { "name": "Custom flow", "blank": true } ``` ## Update ```bash PUT /api/v1/pipelines/{pipeline_id} { "name": "Renamed", "target": "Updated target" } ``` ## Stages ```bash POST /api/v1/pipelines/{pipeline_id}/stages { "name": "Discovery", "position": 0, "required_elements": ["budget_confirmed"], "channel_scripts": { "email": "Hi {{first_name}}..." } } PUT /api/v1/pipelines/{pipeline_id}/stages/{stage_id} { "color": "#00CAEB", "recommended_elements": ["competitor_landscape"] } DELETE /api/v1/pipelines/{pipeline_id}/stages/{stage_id} PUT /api/v1/pipelines/{pipeline_id}/stages/reorder { "stage_ids": ["stg_qual", "stg_demo", "stg_negotiation", "stg_close"] } ``` ## AI suggestion ```bash POST /api/v1/pipelines/suggest # Returns: { data: { suggestion_id, name, target, stages: [...], rationale } } POST /api/v1/pipelines/from-suggestion { "suggestion_id": "sug_abc123", "name": "PLG Path" } ``` The suggestion engine reads the org's Studio context (brand brief, ICPs, personas, messaging house — see [Studio Context](https://docs.graph8.com/developers/sdk/#studio-context)) and proposes a pipeline tailored to the org's GTM motion. Apply via `from-suggestion`, optionally overriding the name / target. ## Credit costs All endpoints are free. AI suggestion runs an LLM internally — that consumes credits implicitly (~50-100 per suggestion, depending on context size). ## Errors | Status | Cause | Fix | |---|---|---| | `401` | Missing or invalid `Authorization: Bearer` header | [Get a key](https://docs.graph8.com/developers/authentication/) | | `402` | Out of credits (waterfall enrichment, AI generation, voice minutes) | Top up in **Settings → Billing**, or switch to Platform | | `404` | Resource ID doesn't exist | List first to verify the ID | | `422` | Validation error in request body | Inspect `error.message` + `error.field` in the response | | `429` | Rate limit (50 rps per org) | Backoff per `Retry-After` header. See [Rate Limits](https://docs.graph8.com/developers/rate-limits/) | | `5xx` | graph8 error | Retry with exponential backoff (5s → 30s → 120s) | The full error envelope shape: `{ "error": { "code": "...", "message": "...", "field": "...", "request_id": "..." } }`. Include the `request_id` in any support ticket. See [Errors](https://docs.graph8.com/developers/errors/) for the canonical reference. ## See also - [SDK pipelines methods](https://docs.graph8.com/developers/sdk/#stage-checklist-pipelines) - [CLI stage-pipeline commands](https://docs.graph8.com/developers/cli/#stage-checklist-pipelines) - [MCP pipeline tools](https://docs.graph8.com/developers/mcp/#pipelines-stage-checklist-v2--12-tools) - [Deals](https://docs.graph8.com/developers/deals/) — for deal pipelines (different surface) --- # Pricing > Plans, credit costs, the unlimited graph8-index bundle, and auto-CRM-capture for the graph8 Developer API Source: https://docs.graph8.com/developers/pricing/ graph8 has two paid plans plus a free tier. **Both paid plans give you the same graph8-owned data for free** within a 50 rps cap — contacts, B2B index search, intent signals, website visitors. Single-record lookups (`/enrichment/lookup/person`, `/enrichment/lookup/company`) are the exception: 2 credits per lookup, charged only when a match is found. Credits only deduct when you reach for third-party providers, AI generation, outreach sends, voice minutes, or paid integrations. > **Tip: Quick pick** > - **Just exploring or low-volume?** Start on **PAYG** - no card, 1,000 free credits. Free contact/B2B/intent/visitor data within 50 rps; credits only deduct on AI, voice, sends, and 3rd-party enrichment. > - **Running real GTM workflows?** **Platform** at $499/mo bundles 75,000 credits for those always-charged surfaces, same free graph8-data access. > **Note: Mental model** > **graph8-owned data is free on both paid plans. Third-party data, AI generation, voice minutes, outreach sends, and meeting bookings charge credits on both plans.** That's the whole pricing model. --- ## Plan Comparison | | Free trial | PAYG | Platform | |---|---|---|---| | **Price** | $0 | $0.05 / credit | $499 / month | | **Free credits on signup** | See below | 1,000 | 2,500 | | **Card required to start** | No | No | No | | **Included AI credits / month** | - | Pay-per-credit | 75,000 | | **graph8-owned data (contacts, B2B search, intent, visitors; not single-record lookups)** | Metered (1 cr / record) | **Free (50 rps)** | **Free (50 rps)** | | **Waterfall enrichment (3rd-party)** | Credits | Credits | Credits | | **AI generation (copilot, replies, copy, drafts, landing-page chat)** | Credits (per LLM tokens) | Credits (per LLM tokens) | Drawn from 75k bundle, then $0.00666/credit | | **Voice minutes** | 20 / min | 20 / min | Drawn from 75k bundle | | **Meeting bookings (charged when the booking is confirmed)** | 20 each | 20 each | Drawn from 75k bundle | | **Sequencer / campaign / newsletter sends** | Per step or recipient | Per step or recipient | Drawn from 75k bundle | | **Auto-CRM-capture** | Off | On (default) | On (default) | | **Rate limit** | 50 rps | 50 rps | 50 rps | | **Work email required** | Yes | Yes | Yes | | **Best for** | Trying graph8 | Developers, evaluators, low volume | Teams running real GTM | > **Note: Credit value** > Each credit is worth $0.05 on PAYG and $0.00666 on Platform overage. The Platform plan's 75,000 included credits work out to ~$3,750 in PAYG-equivalent value covering AI / voice / sends / 3rd-party enrichment. Both plans share the same free graph8-data tier (contacts, B2B, intent, visitors) within the 50 rps cap. --- ## Free Tier Two ways to start free, both without a card: - **PAYG signup**: 1,000 free credits, no expiration timer. - **Platform trial**: 2,500 free credits to evaluate the full Platform experience before subscribing. Personal email domains (gmail, yahoo, outlook, etc.) are blocked - signup requires a work email. > **Note: Auto-capture is off on free tier** > Free tier API calls are stateless probes - results are returned but **not** saved into your CRM. Capture turns on automatically when you upgrade to PAYG or Platform. --- ## Free on both PAYG and Platform graph8-owned data has **no third-party COGS**, so it is free for paid orgs within the 50 rps cap on both plans. ### Contact + B2B data (graph8's 700M+ / 100M+ index) | Endpoint | What it does | |---|---| | `POST /api/v1/search/contacts` | ICP contact search across 700M+ records | | `POST /api/v1/search/companies` | ICP company search across 100M+ records | | `POST /api/v1/search/contacts/save`, `/search/companies/save` | Save matching records to a list | ### Your CRM (first-party data) | Endpoint | What it does | |---|---| | `GET/POST/PATCH/DELETE /api/v1/contacts` | Full contact CRUD | | `GET/POST/PATCH/DELETE /api/v1/companies` | Full company CRUD | | `GET/POST/PATCH/DELETE /api/v1/lists` | Manage lists | | `GET/POST/PATCH/DELETE /api/v1/deals` | Manage deals | | `GET/POST/PATCH/DELETE /api/v1/tasks`, `/notes`, `/fields`, `/quotes` | Tasks, notes, custom fields, quotes (metadata) | ### Intent + website-visitor signals | Endpoint | What it does | |---|---| | `GET /api/v1/intent/stats` | Org-level intent rollups | | `POST /api/v1/intent/keywords/list` | Tracked keyword groups | | `POST /api/v1/intent/pages/visitors` | Visitor records for a page URL | | `POST /api/v1/intent/pages/contacts` | Contacts that visited a page | | `POST /api/v1/intent/keywords/{id}/companies\|contacts\|urls` | Per-keyword aggregations | | `GET /api/v1/public/visitors/score`, `/visitors/company` | Anonymous-visitor IP resolution | | `POST /api/v1/public/signals/company` | Aggregate company-level intent score | ### Workflow / Studio / webhook plumbing | Endpoint | What it does | |---|---| | Workflow / skill / pipeline / landing-page CRUD | Metadata management (execution charges separately — see below) | | Studio reads (ICPs, personas, brand brief, intelligence, research reports) | Read-only context for AI generation | | Webhook subscription mgmt + delivery | Configure + receive event callbacks | | Calendar widget load, slot lookup (`g8.calendar.slots`) | Browse availability without booking (booking confirmation charges — see below) | > **Tip: Why "free within 50 rps"** > The 50 rps cap is per-org and applies to all data API endpoints. There is **no monthly cap** on the graph8-data endpoints listed above for either paid plan — if you stay under 50 rps you can search and read intent/visitor data as much as you want. The cap exists to keep the platform stable, not to meter usage. > **Caution: Coming soon** > LinkedIn-keyed lookup endpoints (`POST /api/v1/linkedin/person`, `POST /api/v1/linkedin/company`) are not yet exposed on the public Developer API. Use `/enrichment/lookup/person` with a `linkedin_url` in the meantime. > **Note: Backend implementation note** > The per-call constants in [`developer_api/constants.py`](https://github.com/graph8-com/g8/blob/main/developer_api/constants.py) are what the Developer API charges today: `CREDITS_PERSON_LOOKUP = 2`, `CREDITS_COMPANY_LOOKUP = 2`, `CREDITS_EMAIL_VERIFY = 1`. A lookup is charged only when it finds a match; one that finds nothing costs 0. PAYG orgs always pay these rates. --- ## What consumes credits These call out to third-party providers, send real messages, run AI, or trigger paid services. They consume credits on **both** PAYG and Platform - the only difference is whether they draw from your pay-per-credit balance (PAYG) or your 75,000-credit monthly bundle (Platform). | Action | Credits | Where | |---|---|---| | Person or company lookup | 2 per lookup, charged only when a match is found | `POST /api/v1/enrichment/lookup/person`, `/lookup/company` | | Email verify | 1 per verify | `POST /api/v1/enrichment/verify-email` | | Waterfall enrichment (1 contact) | 0.5-20 per provider hit (typically 1-3) | `POST /api/v1/enrichment/enrich` — full per-provider matrix in [Enrichment](https://docs.graph8.com/developers/enrichment/#waterfall-credit-matrix) | | Copilot turn (chat message) | Per LLM tokens (~1 per 1,000) | `g8.copilot.ask()`, Studio Copilot, `/api/v1/public/copilot/chat` | | AI inbox draft | Per LLM tokens | `POST /api/v1/inbox/{id}/draft` | | AI generation — copy, brief, brand kit, intelligence | Per LLM tokens | Studio generation tasks | | Landing-page chat edit | Per LLM tokens | `POST /api/v1/pages/{id}/chat` | | Skill execute (`type=llm`) | Per LLM tokens | `POST /api/v1/skills/{id}/execute` | | Voice AI minute | 20 | Voice / dialer (call audio) | | **Meeting booked** (charged when the booking is confirmed) | 20 | `POST /appointments/bookings`, or `POST /appointments/bookings/{booking_uid}/confirm` for a booking that needs host approval. Once per booking: reschedules are free, rejections are never charged, cancels are not refunded. Calendar widget load + slot lookup are free | | Process 1 intent signal | 1 | Intent processing (background) | | Event stream ingest at scale | 1 per 10 events | Tracking / CDP | | Campaign step / sequence send [^seq-charge] | 1 per step | `/campaigns/launch`, sequencer engagement sends | | Newsletter send | 1 per recipient | Newsletter / nurture campaigns | | Bulk email verification (internal) | 1 per email | `POST /api/v1/contacts/verify-emails` | | Waterfall email verification step (ZeroBounce) | 2 per row it checks, not added on top of the row's dearest step | `email_verification` on a waterfall pipeline | | Skill execute (`type=api`) | Free (graph8-side) | Third-party HTTP may charge | | Audience-sync push | Per-record where the destination platform meters | `/audience-syncs/{id}/trigger` | Storing contacts and companies in your CRM is **free**. Credits only apply when data is enriched via third parties, used in outreach, processed by AI (copilot, drafts, generation), or pushed to a metered destination. > **Caution: Sequencer charge timing — confirmation pending** > The "1 credit per campaign step / sequence send" rate is the published marketing rate. As of this page revision, an internal code audit could not pinpoint whether the charge fires per send or once at campaign launch. Treat the per-step rate as authoritative for planning; we will update this row if the audit lands a different conclusion. [^seq-charge]: See the caution note above — per-step vs per-launch charge timing is being confirmed by the backend team. --- ## PAYG (Pay-as-you-go) The entry tier. Every feature is available - no feature walls. - **$0.05 per credit**, billed against your balance - **1,000 free credits** on signup, no card - **Full platform access** - search, enrichment, sequences, campaigns, copilot, intent, ads, voice, landing pages - **Free graph8-owned data within 50 rps** — contact CRM, B2B index search, intent + visitor signals — same as Platform (single-record lookups cost 2 credits each) - **Auto-CRM-capture is on** - every contact or company returned by the API is saved into your CRM - **50 requests/second** on data API endpoints Best for: developers, evaluators, scripts, low-volume integrations. --- ## Platform - $499/month For teams running real GTM workflows on graph8. - **2,500 free credits on signup** to trial before subscribing (no card) - **75,000 credits per month included** for AI generation, voice minutes, meeting bookings, waterfall enrichment, and outreach sends - **Free graph8-owned data within 50 rps** — contact CRM, B2B index search, intent + visitor signals — same as PAYG (single-record lookups cost 2 credits each) - **Additional credits at $0.00666 each** beyond the 75,000 included - **Auto-CRM-capture is on** - every API result lands in your CRM as a permanent record - Full access to sequences, campaigns, copilot, intent, ads, voice, and landing pages --- ## Auto-CRM-Capture Every contact and company returned by the API is automatically saved into your CRM. The next time you query `GET /contacts` or open the Contacts grid in the app, it's there. ### Defaults by plan | Plan | Capture default | |---|---| | Free trial | Off | | PAYG | On | | Platform | On | ### How captured records appear Captured records are tagged so you can find and audit them: - `source = 'api_lookup'` - `created_via = 'api'` - `api_endpoint` records which endpoint produced the record - `last_api_touched_at` updates on each subsequent lookup A second lookup of the same person updates `last_api_touched_at` rather than creating a duplicate. Dedup is per-org against the canonical `MashupContact` / `MashupCompany`. ### Opting out You can disable capture at three levels: 1. **Per call** - append `?capture=false` to a GET, or pass `"capture": false` in the JSON body of a POST. 2. **Per API key** - configure the key with capture disabled in **Settings → API**. 3. **Org-wide** - toggle `auto_capture_enabled` off in **Settings → API**. ```bash # Per-call opt-out via query string curl "https://be.graph8.com/api/v1/enrichment/lookup/person?capture=false" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"email": "jane@acme.com"}' # Per-call opt-out via JSON body (POST) curl -X POST "https://be.graph8.com/api/v1/enrichment/lookup/person" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"email": "jane@acme.com", "capture": false}' ``` > **Caution: Coming soon** > Auto-CRM-capture is approved and rolling out. The capture flag, the per-key setting, and the org-wide toggle are documented here so you can plan against them, but they are not yet wired into every endpoint. Until rollout is complete, treat lookup and search results as read-only and use [`/search/contacts/save`](https://docs.graph8.com/developers/search/#save-contacts-to-list) to persist records explicitly. ### Cost of capture Capture is bundled into the same request — there is no double-charge. On **both plans**, index-backed searches are free within the 50 rps cap, and capture adds nothing to a lookup's own price (2 credits per lookup, charged only when a match is found). Waterfall enrichment, AI drafts, voice minutes, meeting bookings, and message sends consume credits on both plans as documented above. --- ## Rate Limits All data API endpoints enforce **50 requests/second + 1,000 requests/minute per org**, on both PAYG and Platform. - The cap is the same on both plans. - On **both PAYG and Platform**, there is **no monthly cap** on graph8-owned data endpoints (contacts, B2B index, intent, visitors) — free within the 50 rps cap. - Exceeding the cap returns `429 Too Many Requests` with a `Retry-After` header. See [Rate Limits](https://docs.graph8.com/developers/rate-limits/) for retry strategies and headers. --- ## FAQ ### Do I need a credit card to start? No. Both PAYG (1,000 free credits) and the Platform trial (2,500 free credits) start without a card. ### Why is a work email required? graph8 is B2B - we use the work email domain to scope your org and prevent abuse. Personal email providers (gmail, yahoo, outlook, etc.) are blocked at signup. ### Is graph8-owned data really free on both plans? Yes, within the 50 rps cap. Contacts CRM, B2B index search, intent signals, and website-visitor data all hit graph8-owned infrastructure with no third-party COGS — both PAYG and Platform get unlimited access within the rate cap. There is no monthly volume limit on these endpoints. The 50 rps cap exists to keep the platform stable, not to meter usage. ### So what's actually different between PAYG and Platform? Only the always-charged surfaces (AI generation, voice minutes, meeting bookings, outreach sends, waterfall enrichment through 3rd-party providers). PAYG bills $0.05 / credit against your balance as you go; Platform bundles 75,000 of those credits into the $499 monthly fee and prices any overage at $0.00666 / credit. **Free graph8-data access is identical on both plans.** ### What counts as a "third-party" enrichment call? Anything under `POST /enrichment/enrich` that runs the waterfall through an external provider (Prospeo, Dropcontact, Cognism, Apollo, Hunter, Kickbox, ZeroBounce, and others). The waterfall consumes credits on both plans because graph8 pays the provider per record. Single-record `lookup/person` and `lookup/company` also cost credits on both plans: **2 credits per lookup, charged only when a match is found**. ### What happens to my saved data if I downgrade? Your CRM data stays. Captured records remain in your workspace regardless of plan. You only lose the ability to make new credit-consuming calls if your balance hits zero on PAYG. ### Can I switch between PAYG and Platform? Yes. Switching to Platform is instant. Switching from Platform back to PAYG takes effect at the end of your billing cycle. ### Are MCP and CLI calls billed the same way? Yes. The [MCP server](https://docs.graph8.com/developers/mcp/) and [CLI](https://docs.graph8.com/developers/cli/) call the same Developer API under the hood — the same credit costs, rate limits, and per-plan rules apply (index search is free within 50 rps on both paid plans; single-record lookups cost 2 credits each, charged only when a match is found). --- ## What's next - **[Authentication](https://docs.graph8.com/developers/authentication/)** - create an API key - **[Rate Limits](https://docs.graph8.com/developers/rate-limits/)** - retry headers and backoff - **[Search](https://docs.graph8.com/developers/search/)** - the index-backed search endpoints - **[Enrichment](https://docs.graph8.com/developers/enrichment/)** - lookup vs. waterfall, in detail --- # Public Endpoints > Browser-safe endpoints authenticated by write key: copilot chat, intent signals, visitor identity Source: https://docs.graph8.com/developers/public-endpoints/ The `/public/*` endpoints are designed to be called **from a browser** by the [JavaScript SDK](https://docs.graph8.com/developers/sdk/). They authenticate with a **write key** (not an API key) and validate the request `Origin` against the workspace's allowed domains. | Auth header | Where to use | |-------------|--------------| | `X-Write-Key: <key>` | All public endpoints | The write key is org-scoped (it resolves to one organization) and the `Origin` header must match a domain registered on the write key's stream config. Server-side calls without an `Origin` header bypass the domain check but still need a valid key. These endpoints are also wrapped by the SDK: | Endpoint | SDK wrapper | |----------|-------------| | `POST /public/enrich/lookup` | `<G8Form />` progressive form | | `GET /public/visitors/company` | `g8.visitors.identify()` | | `GET /public/visitors/score` | `g8.visitors.score()` | | `GET /public/signals/company` | `g8.signals.company()` | | `POST /public/copilot/chat` | `g8.copilot.ask()` | For machine-readable schemas see the [interactive API docs](https://be.graph8.com/api/v1/docs). --- ## Authentication All requests must include the write key: ```bash curl "https://be.graph8.com/api/v1/public/visitors/company" \ -H "X-Write-Key: $WRITE_KEY" \ -H "Origin: https://yoursite.com" ``` ### Errors | Status | Meaning | |--------|---------| | `401` | Missing `X-Write-Key`, invalid key, or key not linked to an org | | `403` | `Origin` not in the workspace's allowed domains | --- ## Lookup Contact (Progressive Form) `POST /public/enrich/lookup` Look up a contact by email and report which form fields graph8 already knows. Used by `<G8Form />` to hide pre-filled fields and only ask for what's missing. The supported form-field set is fixed: `name`, `company`, `phone`, `title`, `website`. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `email` | string | **Yes** | Email to look up | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/public/enrich/lookup" \ -H "X-Write-Key: $WRITE_KEY" \ -H "Origin: https://yoursite.com" \ -H "Content-Type: application/json" \ -d '{"email": "jane@acme.com"}' ``` ### Response ```json { "data": { "found": true, "known_fields": { "name": "Jane Smith", "company": "Acme Inc", "phone": "+1 (555) 234-5678", "title": "VP of Engineering", "website": "https://acme.com" }, "missing_fields": ["phone"] } } ``` If no match is found, `found` is `false` and `missing_fields` lists all 5 fields. --- ## Identify Visitor Company `GET /public/visitors/company` Resolve the caller's IP address to a company using the `ip_2_company` table. graph8 reads the IP from `X-Forwarded-For`, then `X-Real-IP`, then the direct connection. If a domain match is found, the response is enriched with industry, employee count, city, and country from the OpenSearch mashup index. ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/public/visitors/company" \ -H "X-Write-Key: $WRITE_KEY" \ -H "Origin: https://yoursite.com" ``` ### Response ```json { "data": { "company_name": "Acme Inc", "company_domain": "acme.com", "industry": "Technology", "employee_count": "500", "city": "San Francisco", "country": "US", "confidence": 0.85 } } ``` If the IP cannot be resolved, all fields except `confidence` (which is `0.0`) are `null`. --- ## Score Visitor `GET /public/visitors/score` Return an engagement score (0-100) for the current visitor based on their last 7 days of events. ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/public/visitors/score" \ -H "X-Write-Key: $WRITE_KEY" \ -H "Origin: https://yoursite.com" ``` ### Response ```json { "data": { "engagement": 75, "intent": "high", "signals": ["multi_page_visit", "high_activity"] } } ``` `intent` is `high` (>=70), `medium` (>=30), or `low`. Signals are derived from page diversity and event volume. --- ## Company Intent Signals `GET /public/signals/company` Return buying-intent signals for a **company domain** (not the current visitor). Aggregates the last 30 days of events from any visitors resolved to that domain. ### Query Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `domain` | string | **Yes** | Company domain (e.g. `acme.com`) | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/public/signals/company?domain=acme.com" \ -H "X-Write-Key: $WRITE_KEY" \ -H "Origin: https://yoursite.com" ``` ### Response ```json { "data": { "domain": "acme.com", "score": 82, "intent": "high", "signals": [ "pricing_page", "demo_page", "case_study", "multiple_visitors", "deep_exploration", "high_activity" ], "last_seen": "2026-04-15T14:30:00Z" } } ``` The `signals[]` list is derived from page paths (`/pricing`, `/demo`, `/book`, `/case-stud*`), unique visitor count (>=3), unique page count (>=5), and total event count (>=20). --- ## Copilot Chat `POST /public/copilot/chat` Send a message to graph8 copilot scoped to the org's knowledge base (global context docs, brand brief, FAQs). Unlike the authenticated copilot, this endpoint has **no admin tools** - it can only read from the org's RAG index. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `message` | string | **Yes** | User message | | `session_id` | string | No | Conversation session ID (echoed back) | | `context` | object | No | Optional client-side context | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/public/copilot/chat" \ -H "X-Write-Key: $WRITE_KEY" \ -H "Origin: https://yoursite.com" \ -H "Content-Type: application/json" \ -d '{"message": "Do you offer SOC 2 compliance?"}' ``` ### Response ```json { "data": { "response": "Yes - Acme is SOC 2 Type II compliant as of...", "session_id": null, "sources": [ {"title": "Security FAQ", "type": "faq"} ] } } ``` When the RAG search or AI completion fails, the endpoint returns a graceful fallback string instead of a 5xx - check `sources` to see whether real context was retrieved. --- # Pydantic AI Setup > Connect Pydantic AI agents to graph8 via MCP with typed agent outputs Source: https://docs.graph8.com/developers/pydantic-ai-setup/ Wire Pydantic AI into graph8's MCP server. Pydantic AI's typed agent model makes it a clean fit for graph8 - your structured prospect data flows through Pydantic models end-to-end. > **Tip:** > Try the developer tier free - 1,000 credits, no card required. [Get started](https://app.graph8.com/signup). ## Prerequisites - Python 3.10+ - `pydantic-ai` with the MCP extra ```bash pip install 'pydantic-ai[mcp]' ``` ## Hosted MCP (remote OAuth) ```python import asyncio import os from pydantic import BaseModel from pydantic_ai import Agent from pydantic_ai.mcp import MCPServerHTTP class Prospect(BaseModel): name: str company: str title: str linkedin_url: str | None = None class ProspectList(BaseModel): prospects: list[Prospect] server = MCPServerHTTP(url="https://be.graph8.com/mcp/") agent = Agent( "anthropic:claude-sonnet-4", mcp_servers=[server], output_type=ProspectList, system_prompt=( "You are a SaaS prospector. Use g8_find_contacts to preview prospects. " "Never call credit-charging tools without explicit user confirmation." ), ) async def main() -> None: async with agent.run_mcp_servers(): result = await agent.run( "Find 10 VP Engineering at Series B SaaS startups in the US." ) for p in result.output.prospects: print(f"{p.name} - {p.title} @ {p.company}") if __name__ == "__main__": asyncio.run(main()) ``` The `output_type=ProspectList` constraint forces the agent to return a validated, typed list - no parsing or guessing. ## Self-hosted MCP (stdio) ```python import os from pydantic_ai.mcp import MCPServerStdio server = MCPServerStdio( command="uvx", args=["g8-mcp-server"], env={ "G8_API_KEY": os.environ["G8_API_KEY"], "G8_MCP_MODE": "gtm", }, ) # Same `agent = Agent(...)` as the hosted example. ``` ## Worked example: typed prospect-to-sequence flow ```python class SequenceEnrollment(BaseModel): sequence_name: str enrolled_count: int confirmation_required: bool agent = Agent( "anthropic:claude-sonnet-4", mcp_servers=[server], output_type=SequenceEnrollment, system_prompt=( "Find prospects, save them to a list, and enroll in a sequence. " "Always set confirmation_required=true before calling " "g8_build_contact_list or g8_add_to_sequence." ), ) async def main() -> None: async with agent.run_mcp_servers(): result = await agent.run( "Enroll 25 VP Eng at Series B SaaS in the 'New SaaS Outreach' sequence." ) if result.output.confirmation_required: input(f"Enroll {result.output.enrolled_count} contacts? [y/N] ") ``` Typed outputs make it trivial to gate credit-charging steps behind explicit confirmation logic in your code rather than relying on the model to surface a prompt. ## Related - [MCP Server overview](https://docs.graph8.com/developers/mcp/) - [MCP Tool Reference](https://docs.graph8.com/developers/mcp-tools/) - [MCP FAQ](https://docs.graph8.com/developers/mcp-faq/) - [End-to-end examples](https://docs.graph8.com/developers/mcp-examples/) --- # Quotes > Create, send, sign, and manage quotes through graph8's quote-to-cash lifecycle Source: https://docs.graph8.com/developers/quotes/ Manage the full quote-to-cash lifecycle in graph8 — draft quotes with line items, send them for signature, duplicate, and convert accepted or sent quotes back to draft for revision. > **Caution:** > `POST /quotes/{quote_id}/send` triggers a real email to the recipient with a signing link. Always verify the recipient and line items before sending. --- ## List Quotes `GET /quotes` Returns a paginated list of quotes. All query parameters are optional. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `status` | string | — | Filter by status: `draft`, `sent`, `viewed`, `accepted`, `declined`, `voided`, or `expired` | | `mashup_company_id` | integer | — | Filter by company ID | | `deal_id` | string | — | Filter by deal UUID | | `owner_id` | string | — | Filter by owner email address | | `page` | integer | `1` | Page number (1-indexed) | | `limit` | integer | `50` | Items per page (max 200) | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/quotes?status=sent&limit=10" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": { "items": [ { "id": "quo_abc123", "quote_number": "Q-001", "title": "Acme Q3 Platform", "status": "draft", "total": 5000, "currency": "USD", "created_at": "2026-06-01T10:00:00Z", "sent_at": null, "accepted_at": null } ], "total": 1, "page": 1, "limit": 50 } } ``` --- ## Get Quote `GET /quotes/{quote_id}` Returns the full quote record including line items, activity log, and envelope data. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `quote_id` | string | Quote ID | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/quotes/quo_abc123" \ -H "Authorization: Bearer $API_KEY" ``` ### Response The response includes all `CBQuote` columns plus: | Field | Description | |-------|-------------| | `line_items` | Array of line item objects | | `activity` | Array of activity log entries | | `envelope` | Signing envelope data | --- ## List Quotable Products `GET /quotable-products` Returns a paginated list of products that can be used as line items on quotes. All query parameters are optional. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `currency` | string | — | Filter by 3-character currency code (e.g. `USD`) | | `page` | integer | `1` | Page number (1-indexed) | | `limit` | integer | `100` | Items per page (max 500) | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/quotable-products?currency=USD" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": { "items": [{}], "total": 0, "page": 1, "limit": 100 } } ``` --- ## Get Quote Settings `GET /quote-settings` Returns org-level quote settings: branding, default terms, and sender identity. Auto-creates defaults if not yet configured. ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/quote-settings" \ -H "Authorization: Bearer $API_KEY" ``` --- ## Get Quotes by Company `GET /companies/{company_id}/quotes` Returns the full quote history for a company as an array of `QuoteDetail` objects. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `company_id` | integer | Company ID | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/companies/42/quotes" \ -H "Authorization: Bearer $API_KEY" ``` --- ## Get Quotes by Contact `GET /contacts/{contact_id}/quotes` Returns all quotes where the contact is the signer, as an array of `QuoteDetail` objects. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `contact_id` | integer | Contact ID | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/contacts/101/quotes" \ -H "Authorization: Bearer $API_KEY" ``` --- ## Create Quote `POST /quotes` → `201 Created` Creates a new draft quote. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `title` | string | **Yes** | Quote title (1–500 characters) | | `contract_start_date` | string | **Yes** | Contract start date in ISO format (e.g. `2026-09-01`). Required for every quote. | | `line_items` | array | **Yes** | Line items (at least 1 required — see Line Items below) | | `mashup_company_id` | integer | No | Company to associate with the quote | | `signer_contact_id` | integer | No | Contact ID of the signer. At least one of `signer_contact_id` or `signer_email` is required. | | `signer_email` | string | No | Signer email address (max 255 characters, must be a valid email). At least one of `signer_contact_id` or `signer_email` is required. | | `signer_name` | string | No | Signer display name (max 255 characters) | | `billing_contact_id` | integer | No | Contact ID for billing | | `billing_legal_name` | string | No | Customer legal entity name shown on the quote (max 255 characters) | | `billing_primary_email` | string | No | Primary billing contact email (max 255 characters, must be a valid email) | | `billing_email` | string | No | Secondary billing email (max 255 characters, must be a valid email) | | `billing_address` | string | No | Billing address (max 2000 characters) | | `deal_id` | string | No | Deal UUID to associate with the quote | | `owner_id` | string | No | Quote owner / AE email (max 255 characters). Omit to default to the API caller. | | `template_id` | string | No | Template UUID | | `currency` | string | No | 3-character currency code. Default `USD`. | | `payment_terms` | string | No | One of: `due_on_receipt`, `net_15`, `net_30`, `net_45`, `net_60`, `net_90` | | `valid_until` | string | No | Quote expiry date in ISO format (e.g. `2026-09-30`) | | `terms_content` | string | No | Terms and conditions text (max 50 000 characters) | | `privacy_content` | string | No | Privacy policy text (max 50 000 characters) | | `notes` | string | No | Internal notes (max 10 000 characters) | | `public_notes` | string | No | Notes visible to the recipient (max 10 000 characters) | | `tax_amount` | integer | No | Tax amount in cents (≥ 0) | | `contract_months` | float | No | Contract length in months (0.1–120) | | `contract_duration_value` | float | No | Numeric duration value (0.1–3650) | | `contract_duration_unit` | string | No | Duration unit: `months`, `weeks`, or `days` | | `contract_end_date` | string | No | Contract end date in ISO format | | `accepted_payment_methods` | array | No | Accepted payment methods, e.g. `["ach", "credit_card", "wire"]` | | `marketing_rights` | array | No | Marketing rights granted, e.g. `["logo", "quote", "case_study"]` | #### Line Items Each object in `line_items` supports: | Field | Type | Required | Description | |-------|------|----------|-------------| | `product_name` | string | **Yes** | Product name (1–255 characters) | | `unit_amount` | integer | **Yes** | Unit price in cents (0–1 000 000 000) | | `stripe_product_id` | string | No | Stripe product ID. Pass one of `stripe_product_id`, `stripe_price_id`, or `manual_product_id`, or none for a custom line item. | | `stripe_price_id` | string | No | Stripe price ID | | `manual_product_id` | string | No | Manual product UUID from `/quotable-products` | | `description` | string | No | Line item description (max 2000 characters) | | `quantity` | integer | No | Quantity (1–10 000, default `1`) | | `discount_pct` | float | No | Discount percentage (0–100, default `0`) | | `billing_frequency` | string | No | One of: `one_time`, `month`, `year`, `quarter` | | `sort_order` | integer | No | Display order | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/quotes" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "title": "Acme Q3", "contract_start_date": "2026-09-01", "signer_contact_id": 101, "signer_email": "jane@acme.com", "signer_name": "Jane Doe", "mashup_company_id": 1, "billing_legal_name": "Acme Inc.", "billing_primary_email": "ap@acme.com", "billing_email": "ar@acme.com", "billing_address": "1 Acme Way, San Francisco, CA", "deal_id": "deal_uuid", "owner_id": "rep@example.com", "currency": "USD", "payment_terms": "net_30", "valid_until": "2026-09-30", "tax_amount": 0, "contract_months": 12, "contract_end_date": "2027-09-01", "accepted_payment_methods": ["ach", "credit_card", "wire"], "marketing_rights": ["logo", "quote", "case_study"], "line_items": [ { "stripe_product_id": "prod_xxx", "stripe_price_id": "price_xxx", "product_name": "Pro Plan", "description": "Monthly subscription", "quantity": 1, "unit_amount": 50000, "discount_pct": 0, "billing_frequency": "month", "sort_order": 0 } ] }' ``` ### Validation Errors (`422`) Invalid input returns `422` with a field-level error list. Common rejections: | Cause | Error message | |-------|---------------| | `contract_start_date` missing or not a valid ISO date | `"contract_start_date is required for quotes."` | | No signer provided | Must pass at least one of `signer_contact_id` or `signer_email` | | Invalid email for `signer_email`, `billing_primary_email`, or `billing_email` | Field-level validation error | | `line_items` is empty or missing | Must be a non-empty list (≥ 1 item) | --- ## Update Quote `PUT /quotes/{quote_id}` · `PATCH /quotes/{quote_id}` Both verbs are accepted and behave identically. All fields are optional — only the fields you send are changed. Draft quotes edit in place. A `sent` or `viewed` quote is recalled to draft first (its live signing link is voided) while keeping the same row and quote number. Terminal quotes (`accepted`, `declined`, `voided`, `expired`) reject the update with `400`. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `quote_id` | string | Quote ID | ### Request Body All fields are optional. Same fields as [Create Quote](#create-quote). > **Note:** > Omit `contract_start_date` to leave the existing value unchanged. Sending it explicitly as empty, non-ISO, or `null` returns `422` — it cannot be cleared on an existing quote. > > `owner_id` reassigns the quote owner/AE. A blank value is ignored; an existing owner is never nulled out. > > `line_items`, if provided, replaces the full set and re-runs quotable-product authorization. ### Example **cURL** ```bash curl -X PATCH "https://be.graph8.com/api/v1/quotes/quo_abc123" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "billing_legal_name": "Acme Corp.", "valid_until": "2026-10-31" }' ``` --- ## Duplicate Quote `POST /quotes/{quote_id}/duplicate` Creates a new draft quote by copying the source quote. No request body required. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `quote_id` | string | Quote ID to duplicate | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/quotes/quo_abc123/duplicate" \ -H "Authorization: Bearer $API_KEY" ``` ### Response Returns the new draft `QuoteDetail`. --- ## Edit Quote as Draft `POST /quotes/{quote_id}/edit-as-draft` Converts a `sent` quote back to a new draft for revision. Voids the existing signing envelope. No request body required. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `quote_id` | string | Quote ID. Must be in `sent` status. | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/quotes/quo_abc123/edit-as-draft" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": { "new_quote_id": "quo_def456", "new_quote_number": "Q-002", "voided_envelope_id": "env_xyz789", "source_quote_id": "quo_abc123" } } ``` --- ## Send Quote `POST /quotes/{quote_id}/send` Sends the quote to the recipient via email with a signing link. Idempotent for resend. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `quote_id` | string | Quote ID | ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `subject` | string | No | Email subject line. Falls back to the quote title if omitted. | | `message` | string | No | Email body message. Falls back to the default cover line if omitted. | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/quotes/quo_abc123/send" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "subject": "Your Q3 quote from graph8", "message": "Hi Jane, please review and sign the attached quote." }' ``` ### Response Returns the full `QuoteDetail` after sending. --- # Rate Limits > API rate limits and how to handle them Source: https://docs.graph8.com/developers/rate-limits/ The API enforces rate limits per org to ensure fair usage and platform stability. > **Tip: Pricing context** > The rate cap is the same on every plan. On the **Platform** plan, graph8 index-backed endpoints (search, lookup, internal email validator) are unlimited *within this cap* - there is no monthly volume cap. See [Pricing](https://docs.graph8.com/developers/pricing/) for the full bucket map. --- ## Limits | Window | Limit | |--------|-------| | Per second | 50 requests | | Per minute | 1,000 requests | Applied per org (keyed off your API key) across all data API endpoints. The per-second cap absorbs bursts; the per-minute cap governs sustained throughput. Exceeding either returns `429 Too Many Requests`. > **Note: What "unlimited within the cap" means on Platform** > The Platform plan removes the per-record credit cost on graph8's index-backed endpoints, but the 50 rps / 1,000 rpm ceiling still applies. There is no monthly volume cap - if you stay under the cap, you can call those endpoints as much as you want. --- ## Rate Limit Headers Every response carries your current quota state — on success **and** on `429` — so you can self-pace before you ever get throttled: | Header | Description | |--------|-------------| | `X-RateLimit-Limit-Second` | Per-second ceiling (`50`) | | `X-RateLimit-Limit-Minute` | Per-minute ceiling (`1000`) | | `X-RateLimit-Remaining` | Requests left in the current minute window | | `X-RateLimit-Reset` | Unix epoch (seconds) when the minute window next frees a slot | | `Retry-After` | Seconds to wait before retrying (sent on `429` only) | ### Example `429` Response ``` HTTP/1.1 429 Too Many Requests Retry-After: 1 X-RateLimit-Limit-Second: 50 X-RateLimit-Limit-Minute: 1000 X-RateLimit-Remaining: 0 X-RateLimit-Reset: 1716624957 Content-Type: application/json {"detail": "Rate limit exceeded. Please slow down."} ``` --- ## Retry Strategy Use exponential backoff with the `Retry-After` header: **Python** ```python import time import requests def api_request(url, headers, max_retries=3): for attempt in range(max_retries): response = requests.get(url, headers=headers) if response.status_code != 429: return response retry_after = int(response.headers.get("Retry-After", 2 ** attempt)) print(f"Rate limited. Retrying in {retry_after}s...") time.sleep(retry_after) return response # Return last response if all retries exhausted ``` **TypeScript** ```typescript async function apiRequest(url: string, headers: HeadersInit, maxRetries = 3) { for (let attempt = 0; attempt < maxRetries; attempt++) { const response = await fetch(url, { headers }); if (response.status !== 429) return response; const retryAfter = parseInt(response.headers.get("Retry-After") || String(2 ** attempt)); console.log(`Rate limited. Retrying in ${retryAfter}s...`); await new Promise(resolve => setTimeout(resolve, retryAfter * 1000)); } } ``` **cURL** ```bash # Simple retry with backoff for i in 1 2 3; do response=$(curl -s -w "\n%{http_code}" \ "https://be.graph8.com/api/v1/contacts" \ -H "Authorization: Bearer $API_KEY") status=$(echo "$response" | tail -1) if [ "$status" != "429" ]; then echo "$response" | head -n -1 break fi echo "Rate limited, retrying in ${i}s..." sleep $i done ``` --- ## Best Practices - **Use pagination with high limits** - fetch 200 items per page instead of making 200 individual requests. - **Batch per-contact reads** - instead of one `GET /contacts/{contact_id}/sequences` (or `/lists`) per contact, send up to 100 contact ids to `POST /contacts/related` with `{"contact_ids": [...], "include": ["sequences", "lists"]}`. One request, one rate-limit slot; each contact comes back with `found: true|false`. - **Watch `X-RateLimit-Remaining`** - back off as it approaches `0` instead of waiting for a `429`; `X-RateLimit-Reset` tells you when the window frees up. - **Cache responses** - if you fetch the same data repeatedly, cache it locally to reduce API calls. - **Use backoff, not tight loops** - when rate-limited, always respect the `Retry-After` header instead of retrying immediately. > **Tip:** > At the sustained limit of 1,000 requests/minute with a page size of 200, you can read ~200,000 records per minute - enough to export a full workspace in seconds. --- # Request Logs > Inspect your organization's most recent API requests for debugging Source: https://docs.graph8.com/developers/request-logs/ `GET /api/v1/logs` returns your organization's **most recent API requests** — method, path, status code, and the `request_id` — so you can debug an integration without adding logging on your side. It's the developer equivalent of the request log in a payments dashboard. Each entry corresponds to one authenticated API call your keys made, newest first. ## Endpoint ``` GET /api/v1/logs?limit=50 ``` | Parameter | Type | Default | Range | Description | |-----------|------|---------|-------|-------------| | `limit` | integer | `50` | 1–200 | Max entries to return (most recent first) | ### Response ```json { "data": [ { "request_id": "a1b2c3d4e5f6...", "method": "POST", "path": "/api/v1/contacts", "status": 201, "timestamp": 1717000000 }, { "request_id": "f6e5d4c3b2a1...", "method": "GET", "path": "/api/v1/usage", "status": 200, "timestamp": 1716999990 } ] } ``` | Field | Type | Description | |-------|------|-------------| | `request_id` | string | Correlation id — matches the `X-Request-Id` header on the original response | | `method` | string | HTTP method | | `path` | string | Request path | | `status` | integer | HTTP status code of the response | | `timestamp` | integer | Unix seconds when the request completed | ## Examples **cURL** ```bash curl "https://be.graph8.com/api/v1/logs?limit=20" \ -H "Authorization: Bearer $API_KEY" ``` **Python** ```python import httpx r = httpx.get( "https://be.graph8.com/api/v1/logs", headers={"Authorization": f"Bearer {API_KEY}"}, params={"limit": 20}, ) for entry in r.json()["data"]: print(entry["status"], entry["method"], entry["path"], entry["request_id"]) ``` **TypeScript** ```ts const res = await fetch("https://be.graph8.com/api/v1/logs?limit=20", { headers: { Authorization: `Bearer ${API_KEY}` }, }); const { data } = await res.json(); data.forEach((e) => console.log(e.status, e.method, e.path, e.request_id)); ``` ## Correlating with `X-Request-Id` Every API response carries an `X-Request-Id` header. The same id appears as `request_id` here and in any error envelope's `request_id` field — so when a call fails, you can grab the id from the response and find the exact entry in your logs (or quote it to support). ## Notes & limits - **Scope** — logs are per-organization and cover requests authenticated with your org's API keys. - **Retention** — a rolling window of the most recent requests (the latest ~100), retained for up to 7 days. This is a debugging aid, not a permanent audit trail — export anything you need to keep. - **Best-effort** — logging never blocks or slows your API calls. In the rare event logging is briefly unavailable, an entry may be missing; the original request still succeeds normally. - **No bodies** — only request metadata is stored (method, path, status, id). Request and response bodies are never logged. --- # JavaScript SDK > The most comprehensive GTM SDK - 25+ modules at full MCP parity. Tracking, visitor intel, AI copilot, webchat, booking, enrichment, sequences, campaigns, CRM, deals, tasks, notes, fields, quotes, pipelines, workflows, skills, voice, intent, studio. Source: https://docs.graph8.com/developers/sdk/ The `@graph8/sdk` SDK gives you typed access to every graph8 capability from any JavaScript or TypeScript project. **25+ modules**, full MCP parity, one `g8.init()` call. > **Tip:** > Try the developer tier free - 1,000 credits, no card required. [Get started](https://app.graph8.com/signup). > > AI agents: fetch the full doc index at [https://docs.graph8.com/llms.txt](https://docs.graph8.com/llms.txt). ## Installation ```bash npm install @graph8/sdk ``` ## Auth Modes The SDK has two auth modes depending on what you're building: | Mode | Key | Use case | Safe in browser? | |------|-----|----------|-----------------| | **Client-side** | `writeKey` | Tracking, visitor ID, copilot, chat, calendar, forms | Yes | | **Server-side** | `apiKey` | Enrichment, sequences, campaigns, integrations, voice, pages | No | ```typescript // Client-side (browser) g8.init({ writeKey: 'YOUR_WRITE_KEY' }); // Server-side (Node.js) g8.init({ apiKey: 'YOUR_API_KEY' }); // Both (full access) g8.init({ writeKey: 'YOUR_WRITE_KEY', apiKey: 'YOUR_API_KEY' }); ``` ## Quick Start > **Note: Built-in CDP** > Every `g8.track()` and `g8.identify()` call is stored in graph8's built-in CDP automatically. Contacts, enrichment, sequences, and campaigns work out of the box. Optionally route a copy to your own warehouse (BigQuery, Snowflake, etc.) via [Destinations](https://docs.graph8.com/developers/destinations/). See [Data Pipeline](https://docs.graph8.com/developers/data-pipeline/). ```typescript import { g8 } from '@graph8/sdk'; g8.init({ writeKey: 'YOUR_WRITE_KEY' }); // Track events g8.track('signup', { plan: 'pro', source: 'landing_page' }); // Identify users g8.identify('user@acme.com', { name: 'Jane Smith', company: 'Acme Corp', job_title: 'VP Engineering', }); // Know who's visiting const visitor = await g8.visitors.identify(); // { company_name: 'Acme Corp', industry: 'SaaS', employee_count: '200-500' } // Open AI copilot g8.copilot.open({ greeting: 'How can I help?' }); // Show booking widget g8.calendar.show({ username: 'thomas', eventType: 'demo-30min' }); ``` ## Error Handling & Reliability The SDK ships a hardened HTTP core: typed errors, automatic retries, idempotency, and auto-pagination. These are exported as building blocks and used internally by a growing set of resource clients — `contacts`, `companies`, `lists`, `deals`, `notes`, `tasks`, `audiences`, `search`, `agency`, `marketplace`, `snippet`, `meetings`, `integrations`, `pages`, `enrich`, `fields`, `campaigns`, `inbox`, `quotes`, `intent`, `studio`, `pipelines`, `skills`, `workflows`, `voice`, `sequences`, and `analytics` — i.e. **every server-side (API-key) resource client**. Only the browser/`writeKey` widget clients (`visitors`, `copilot`, `calendar`, `signals`, `forms`) remain on raw `fetch` by design (DOM/tracking surface). The hardened clients **throw a `G8Error`** on a failed request instead of returning the error body as data. ### Typed errors — `G8Error` Any non-2xx response throws a `G8Error` carrying the standard envelope fields, so you can branch on `status`/`type` and quote `requestId` to support. ```ts import { g8, G8Error } from '@graph8/sdk'; try { await g8.contacts.create({ work_email: 'ada@example.com' }); } catch (err) { if (err instanceof G8Error) { console.error(err.status, err.type, err.code, err.requestId); // e.g. 400 "bad_request" "400" "req_abc123" if (err.status === 404) {/* handle not-found */} } else { throw err; // non-HTTP error } } ``` | Field | Type | Description | |-------|------|-------------| | `status` | number | HTTP status (`0` for a network failure) | | `type` | string | Machine-readable category (`bad_request`, `not_found`, …) | | `code` | string | Short error code (HTTP status by default) | | `requestId` | string \| undefined | Correlation id — matches the `X-Request-Id` header | | `detail` | unknown | Original error detail (validation errors, upstream payloads) | | `retryable` | boolean | Whether the failure class was transient (429/5xx/network) | ### Automatic retries Transient failures — `429` and `5xx` responses, plus network errors — are retried automatically with **exponential backoff + jitter**, honoring the `Retry-After` header on `429`. Defaults: up to 2 retries (3 total attempts), 200 ms base. 4xx errors (except 429) are **never** retried — they won't succeed on a retry. ### Idempotency Pass an idempotency key to make a create safe to retry — the same key returns the first result instead of creating a duplicate (see [Idempotency](https://docs.graph8.com/developers/idempotency/)). ```ts import { randomUUID } from 'node:crypto'; const key = randomUUID(); await g8.contacts.create({ work_email: 'ada@example.com' }, key); // a retry with the same key never double-creates ``` ### Auto-pagination `paginate()` is an async iterator that follows `next_cursor` (see [Pagination](https://docs.graph8.com/developers/pagination/)) so you can walk an entire list without cursor bookkeeping. ```ts import { paginate, request } from '@graph8/sdk'; const base = 'https://be.graph8.com'; for await (const tx of paginate((cursor) => request(base, '/api/v1/usage/transactions', apiKey, { query: { limit: 200, cursor } }) )) { console.log(tx); } ``` ## Reference app: AI-SDR Want to see all of this in one place? [`examples/ai-sdr/`](https://github.com/graph8-com/g8/tree/qa/examples/ai-sdr) is a ~160-line, end-to-end "AI SDR" built on the SDK: it **prospects** with `search`, **imports** contacts idempotently, **organizes** them into a list, **enriches** account context, and **enrolls** them into a sequence — with centralized `G8Error` handling throughout. A `DRY_RUN=1` mode runs the read-only prospecting path with no writes. ## React / Next.js ```tsx // app/layout.tsx import { G8Provider } from '@graph8/sdk/react'; export default function RootLayout({ children }) { return ( <G8Provider writeKey="YOUR_WRITE_KEY"> {children} </G8Provider> ); } ``` ```tsx // Any component import { useG8 } from '@graph8/sdk/react'; function PricingPage() { const { track, visitors, copilot, calendar } = useG8(); useEffect(() => { visitors.identify().then(v => { if (v.company_name) setCompany(v.company_name); }); }, []); return ( <button onClick={() => { track('book_demo', { page: 'pricing' }); calendar.show({ username: 'thomas', eventType: 'demo' }); }}> Book a Demo </button> ); } ``` The hook returns: `track`, `identify`, `page`, `reset`, `visitors`, `copilot`, `chat`, `calendar`, `forms`, `signals`, and `g8` (the full client). --- ## Visitor Intelligence Identify anonymous visitors by IP address - know which company is on your site before they fill out a form. ```typescript // Resolve visitor's company const visitor = await g8.visitors.identify(); // { // company_name: 'Acme Corp', // company_domain: 'acme.com', // industry: 'SaaS', // employee_count: '200-500', // city: 'San Francisco', // country: 'United States', // confidence: 0.92 // } // Get engagement score const score = await g8.visitors.score(); // { engagement: 82, intent: 'high', signals: ['pricing_page', 'case_study'] } // React to high-intent visitors in real-time const stop = g8.visitors.onIntent('high', (visitor) => { showBanner(`${visitor.company_name} is checking us out!`); g8.copilot.open(); // proactively open copilot }); // Stop listening stop(); ``` --- ## AI Copilot Embed an AI assistant in your product that knows your org's knowledge base. ```typescript // Open as a floating widget g8.copilot.open({ greeting: 'Hi! How can I help you today?', position: 'bottom-right', // or 'bottom-left' theme: 'dark', // 'light', 'dark', or 'auto' }); // Send a message programmatically const answer = await g8.copilot.ask('What integrations do you support?'); // Register custom actions the AI can trigger g8.copilot.registerAction('book_demo', async (params) => { router.push(`/book?date=${params.date}`); }); // Listen for events g8.copilot.on('tool_used', (data) => { analytics.track('copilot_action', data); }); // Close g8.copilot.close(); ``` --- ## Webchat Live chat + AI chat embedded in your product. ```typescript // Open chat widget g8.chat.open({ position: 'bottom-right', theme: 'auto', greeting: 'Hi! Ask me anything.', }); // Send a message g8.chat.send('I need help with billing'); // Listen for events g8.chat.on('message', (msg) => { /* new message from agent */ }); g8.chat.on('human_transfer', () => { /* transferred to human */ }); // Configure appearance g8.chat.configure({ position: 'bottom-left', avatar: '/logo.png', }); // Close g8.chat.close(); ``` --- ## Calendar Booking Embed scheduling directly in your product - no Calendly redirect. ```typescript // Show as a modal overlay g8.calendar.show({ username: 'thomas', eventType: 'demo-30min', prefill: { name: 'Jane', email: 'jane@acme.com' }, }); // Embed inline in a container g8.calendar.embed('#booking-container', { username: 'thomas', eventType: 'discovery-call', }); // Get available slots programmatically const slots = await g8.calendar.slots('thomas', 'demo-30min', { start: '2026-04-07', end: '2026-04-11', }); // Book programmatically const booking = await g8.calendar.book({ event_type_id: 123, slot: '2026-04-08T10:00:00Z', attendee: { name: 'Jane', email: 'jane@acme.com' }, }); // Listen for bookings g8.calendar.on('booked', (booking) => { showConfirmation(booking); }); ``` --- ## Progressive Forms Smart forms that skip fields graph8 already knows - higher conversion, same data. ```typescript // Check what's already known const result = await g8.forms.lookup('user@acme.com'); if (result.found) { console.log('Known:', result.known_fields); // { name: 'Jane Smith', company: 'Acme Corp' } console.log('Still need:', result.missing_fields); // ['phone', 'job_title'] } ``` --- ## Enrichment > **Caution:** > Enrichment requires an API key (server-side only). Never expose your API key in browser code. ```typescript // Initialize with API key g8.init({ apiKey: 'YOUR_API_KEY' }); // Look up a person (2 credits, charged only when a match is found) const person = await g8.enrich.person({ email: 'jane@acme.com' }); // { found: true, confidence: 0.95, data: { name, title, company, phone, linkedin, ... } } // Look up a company (2 credits, charged only when a match is found) const company = await g8.enrich.company({ domain: 'acme.com' }); // { found: true, data: { name, industry, revenue, headcount, tech_stack, ... } } // Verify an email (1 credit) const result = await g8.enrich.verifyEmail('jane@acme.com'); // { email: 'jane@acme.com', valid: true, deliverable: true, catch_all: false } // Search 700M+ contacts with filters (free on both PAYG and Platform within 50 rps) const leads = await g8.enrich.search([ { field: 'seniority_level', operator: 'any_of', value: ['VP', 'Director'] }, { field: 'company_industry', operator: 'contains', value: ['SaaS'] }, { field: 'company_employee_count', operator: 'between', value: [50, 500] }, ], 1, 25); ``` --- ## Search Prospect the open-data graph for new contacts and companies by filter, and optionally save the matches straight into a list. Requires an API key. Built on the hardened HTTP core — every method throws a [`G8Error`](#typed-errors--g8error) on failure and retries transient errors. ```typescript // Find VPs of Engineering at mid-size SaaS companies const { data: people } = await g8.search.contacts({ filters: [ { field: 'job_title', operator: 'any_of', value: ['VP Engineering', 'Head of Engineering'] }, { field: 'company_industry', operator: 'any_of', value: ['SaaS'] }, ], page: 1, limit: 25, }); // Find companies by firmographics const { data: companies } = await g8.search.companies({ filters: [{ field: 'employee_count', operator: 'between', value: [50, 500] }], }); // Search and save the matches into a new list (async — returns an estimate immediately) const job = await g8.search.saveContacts({ list_title: 'Q3 SaaS VPs', filters: [{ field: 'seniority_level', operator: 'any_of', value: ['VP', 'C-Level'] }], max_results: 2000, }); // { list_id, list_title, estimated_total, status: 'processing' } ``` | Method | Description | |--------|-------------| | `g8.search.contacts(params?)` | Search open-data contacts. Returns `{ data: SearchContactItem[] }`. | | `g8.search.companies(params?)` | Search open-data companies. Returns `{ data: SearchCompanyItem[] }`. | | `g8.search.saveContacts(params)` | Search contacts and save matches to a new list. Returns a `SearchSaveResult`. | | `g8.search.saveCompanies(params)` | Search companies and save matches to a new list. | Operators: `any_of`, `all_of`, `none_of`, `contains`, `between`, `exists`, `is_empty`, `is_not_empty`. Filters combine with AND. --- ## Agency For **agency-scoped API keys** (minted with `is_agency: true`). Discover the agency credential and the client orgs it may operate. Requires an API key — a non-agency key gets a `403`. ```typescript const me = await g8.agency.me(); // { agency_org_id, agency_org_name, is_agency: true, client_count, target_header: 'X-Target-Org-Id' } const { data: clients } = await g8.agency.clients(); // [{ org_id: 'org_abc' }, { org_id: 'org_def' }] ``` To operate a client org, set the `X-Target-Org-Id` header (the value of `target_header`) to that client's `org_id` on your subsequent requests — the agency key is then scoped to that one client for the call. | Method | Description | |--------|-------------| | `g8.agency.me()` | Describe the agency credential — agency org + authorized client count. Returns `AgencyInfo`. | | `g8.agency.clients()` | List the client orgs this key may target. Returns `{ data: AgencyClient[] }`. | See [Agency Keys](https://docs.graph8.com/developers/agency-keys/) for how agency keys and per-request targeting work. --- ## Marketplace For **SDR/AE talent** on the graph8 marketplace: view your profile, see pending hire offers and respond to them, and list your active hirings. Requires a personal API key. Built on the hardened HTTP core — every method throws a [`G8Error`](#typed-errors--g8error) on failure and retries transient errors. ```typescript const profile = await g8.marketplace.profile(); // { id, email, marketplace_role, monthly_rate_usd, availability_status, is_complete, ... } const { offers, count } = await g8.marketplace.offers(); for (const offer of offers) { if (offer.monthly_rate_usd && Number(offer.monthly_rate_usd) >= 4000) { await g8.marketplace.acceptOffer(offer.id); } else { await g8.marketplace.rejectOffer(offer.id); } } const { hirings } = await g8.marketplace.hirings(); // active contracts ``` | Method | Description | |--------|-------------| | `g8.marketplace.profile()` | Your marketplace SDR profile. Returns `MarketplaceProfile`. | | `g8.marketplace.offers()` | Pending hire offers. Returns `{ offers: MarketplaceOffer[]; count }`. | | `g8.marketplace.acceptOffer(hiringId)` | Accept a pending offer. | | `g8.marketplace.rejectOffer(hiringId)` | Reject a pending offer. | | `g8.marketplace.hirings()` | Your active hiring contracts. Returns `{ hirings: MarketplaceHiring[]; count }`. | --- ## Snippet Fetch your org's graph8 tracking snippet programmatically — the write key, a React component, a vanilla `<script>` tag, allowed domains, and the full config — to embed the tracker into an app or a customer's site. Requires an API key. Built on the hardened HTTP core (typed [`G8Error`](#typed-errors--g8error) + retries). ```typescript const snippet = await g8.snippet.get(); // { write_key, tracking_host, domains, react_snippet, script_tag, config } // Inject the vanilla tag into a customer's site customerSite.head.append(snippet.script_tag); ``` | Method | Description | |--------|-------------| | `g8.snippet.get()` | Get the org tracking snippet. Returns `Snippet` (`write_key`, `react_snippet`, `script_tag`, `domains`, `config`). | --- ## Intent Signals Know when target accounts are in-market. ```typescript // Get signals for a specific company const signals = await g8.signals.company('acme.com'); // { domain: 'acme.com', score: 87, intent: 'high', signals: ['pricing_3x', 'case_study'] } // Stream signals for multiple domains (polls every 30s) const stop = g8.signals.stream(['acme.com', 'startup.io'], (signals) => { const hot = signals.filter(s => s.intent === 'high'); if (hot.length > 0) notifySDR(hot); }); // Stop streaming stop(); ``` > **Note:** > Signals work with both write key (public, limited) and API key (full access). --- ## Sequences > **Caution:** > Requires API key. Adding contacts to a sequence triggers real outreach. ```typescript // List available sequences const sequences = await g8.sequences.list(); // Add contacts to a sequence await g8.sequences.add({ sequenceId: sequences[0].id, contactIds: [5028106, 5028105], listId: 637, }); // The mutating methods — add, create, run, pause, resume — accept an optional // trailing idempotency key so a retry never double-triggers the side effect // (see Error Handling & Reliability → Idempotency). await g8.sequences.run(sequences[0].id, 'run-seq-2026-06-30'); ``` --- ## Campaigns > **Caution:** > Requires API key. Launching a campaign sends real messages. ```typescript // List campaigns const campaigns = await g8.campaigns.list(); // Create a campaign const campaign = await g8.campaigns.create({ name: 'Q2 Enterprise Push', category: 'Outbound', target_persona: 'VP Engineering at SaaS 200-1000', }); // Launch await g8.campaigns.launch(campaign.id); // Get stats const stats = await g8.campaigns.stats(campaign.id); // { sent: 500, opened: 230, replied: 45, meetings: 12 } ``` --- ## Contacts > **Caution:** > Requires API key. Operates on first-party CRM data in your workspace. Full CRUD for contacts in your graph8 workspace - the same records that power sequences, campaigns, and CRM sync. No credit cost; these endpoints only read and write data you already own. ```typescript import { g8 } from '@graph8/sdk'; g8.init({ apiKey: 'YOUR_API_KEY' }); ``` ### `list(params?)` List contacts with optional filters. Returns `{ data: Contact[]; total: number }`. ```typescript const { data, total } = await g8.contacts.list({ company_name: 'Acme Corp', seniority_level: 'VP', limit: 25, page: 1, }); ``` Filterable fields: `email`, `name`, `job_title`, `seniority_level`, `company_name`, `country`, `list_id`, plus `page` and `limit`. ### `get(contactId)` Fetch a single contact by ID. Returns a `Contact`. ```typescript const contact = await g8.contacts.get(5028106); ``` ### `create(contact, idempotencyKey?)` Create a contact. `work_email` is required; everything else is optional. Pass `list_id` to add the contact to a list at the same time. Returns the created `Contact`. Throws [`G8Error`](#typed-errors--g8error) on failure. Pass an optional `idempotencyKey` to make a retry safe — the same key returns the first result instead of creating a duplicate (see [Error Handling & Reliability](#idempotency)). ```typescript const contact = await g8.contacts.create({ work_email: 'jane@acme.com', first_name: 'Jane', last_name: 'Smith', job_title: 'VP Engineering', company_domain: 'acme.com', list_id: 637, }, 'create-jane-acme-2026-06-29'); // optional idempotency key ``` ### `update(contactId, fields)` Partial update - send only the fields you want to change. Returns `{ updated: number }`. ```typescript await g8.contacts.update(5028106, { job_title: 'SVP Engineering', mobile_phone: '+14155551234', }); ``` ### `delete(contactId)` Soft-delete a contact. Returns `{ deleted: boolean }`. ```typescript await g8.contacts.delete(5028106); ``` --- ## Companies > **Caution:** > Requires API key. Operates on first-party CRM data in your workspace. Read and update companies in your workspace. Companies are created automatically when contacts are saved or enriched, so you typically `list`, `get`, and `update` rather than create. To get a company's contacts in one call, use `companies.contacts(id)`. ```typescript import { g8 } from '@graph8/sdk'; g8.init({ apiKey: 'YOUR_API_KEY' }); ``` ### `list(params?)` List companies with optional filters. Returns `{ data: Company[]; total: number }`. ```typescript const { data } = await g8.companies.list({ industry: 'SaaS', country: 'United States', limit: 50, }); ``` Filterable fields: `domain`, `industry`, `name`, `country`, plus `page` and `limit`. ### `get(companyId)` Fetch a single company by ID. Returns a `Company`. ```typescript const company = await g8.companies.get(8821); // { id: 8821, name: 'Acme Corp', domain: 'acme.com', industry: 'SaaS', employee_count: 320, ... } ``` ### `contacts(companyId, limit?, offset?)` Get the contacts associated with a company. Defaults to 50 per page. Returns `{ data: CompanyContact[]; total: number }`. ```typescript const { data: people } = await g8.companies.contacts(8821, 25, 0); // [{ id: 5028106, first_name: 'Jane', last_name: 'Smith', work_email: 'jane@acme.com', job_title: 'VP Engineering' }, ...] ``` ### `update(companyId, fields)` Partial update. Returns `{ updated: number }`. ```typescript await g8.companies.update(8821, { industry: 'B2B SaaS', description: 'Workflow automation for revenue teams.', }); ``` ### `delete(companyId)` Soft-delete a company. Returns `{ deleted: boolean }`. ```typescript await g8.companies.delete(8821); ``` --- ## Lists > **Caution:** > Requires API key. Lists feed sequences, campaigns, and exports. Lists organize contacts (or companies) into groups for campaigns, exports, and segmentation. A list created here can be passed straight to `g8.sequences.add()` or used as a target audience for a campaign. ```typescript import { g8 } from '@graph8/sdk'; g8.init({ apiKey: 'YOUR_API_KEY' }); ``` ### `list(page?, limit?)` Page through every list in the workspace. Defaults to page 1 / limit 50. Returns `{ data: ContactList[]; total: number }`. ```typescript const { data: lists } = await g8.lists.list(1, 100); // [{ id: 637, title: 'Q2 Outbound', type: 'contacts', contact_count: 1240, created_at: '2026-04-01T...' }, ...] ``` ### `create(title, type?)` Create a new list. `type` defaults to `'contacts'`; use `'companies'` for an account list. Returns the created `ContactList`. ```typescript const list = await g8.lists.create('Q2 Enterprise Push', 'contacts'); ``` ### `delete(listId)` Soft-delete a list. Returns `{ deleted: boolean }`. ```typescript await g8.lists.delete(637); ``` ### `contacts(listId, page?, limit?)` Page through the contacts in a list. Returns `{ data: ListContact[]; total: number }`. ```typescript const { data } = await g8.lists.contacts(637, 1, 200); ``` ### `addContacts(listId, contactIds)` Add one or more existing contacts to a list. Returns `{ added: number }`. ```typescript await g8.lists.addContacts(637, [5028106, 5028105, 5028104]); ``` ### `removeContacts(listId, contactIds)` Remove contacts from a list (does not delete the contacts themselves). Returns `{ removed: number }`. ```typescript await g8.lists.removeContacts(637, [5028104]); ``` --- ## Deals > **Caution:** > Requires API key. Operates on first-party CRM data in your workspace. Full CRUD for deals plus pipeline introspection and contact / company graph queries. Reads and writes against your CRM — no credit cost. ```typescript import { g8 } from '@graph8/sdk'; g8.init({ apiKey: 'YOUR_API_KEY' }); ``` ### `pipelines()` List every deal pipeline and its stages. Returns `{ data: Pipeline[] }`. ```typescript const { data: pipelines } = await g8.deals.pipelines(); // [{ id: 'pip_default', name: 'Default', is_default: true, stages: [...] }] ``` ### `list(params?)` List deals with optional filters and pagination. Returns `{ data: Deal[]; pagination?: PaginationMeta }`. ```typescript const { data: deals } = await g8.deals.list({ stage_id: 'stg_proposal', search: 'Acme', limit: 50, }); ``` Filterable: `stage_id`, `pipeline_id`, `search`, plus `page` and `limit`. ### `get(dealId)` Fetch a single deal by ID. ```typescript const deal = await g8.deals.get('deal_abc123'); ``` ### `create(deal)` Create a deal. `name` is required; stage / pipeline default to the org defaults if omitted. ```typescript const deal = await g8.deals.create({ name: 'Acme Q3 expansion', company_id: 8821, amount: 12500, currency: 'USD', close_date: '2026-09-30', }); ``` ### `update(dealId, fields)` Partial update — most often used to change stage as the deal progresses. ```typescript await g8.deals.update('deal_abc123', { stage_id: 'stg_proposal', amount: 15000, }); ``` ### `delete(dealId)` Delete a deal. Returns `{ data: { deleted: boolean } }`. Irreversible — prefer moving the deal to a `closed_lost` stage if you need an audit trail. ```typescript await g8.deals.delete('deal_abc123'); ``` ### `forContact(contactId)` / `forCompany(companyId)` Get the trimmed deal list associated with a contact or company. ```typescript const { data: contactDeals } = await g8.deals.forContact(5028106); const { data: companyDeals } = await g8.deals.forCompany(8821); ``` --- ## Tasks > **Caution:** > Requires API key. Operates on first-party CRM data in your workspace. CRUD for tasks linked to CRM contacts. ```typescript import { g8 } from '@graph8/sdk'; g8.init({ apiKey: 'YOUR_API_KEY' }); ``` ### `list(params?)` List tasks org-wide. Returns `{ data: Task[] }`. ```typescript const { data } = await g8.tasks.list({ status: 'open', priority: 'high', limit: 50, }); ``` Filterable: `status`, `priority`, `assignee_id`, `search`, `limit`, `offset`. ### `listForContact(contactId, status?)` List tasks for one contact. ```typescript const { data } = await g8.tasks.listForContact(5028106, 'open'); ``` ### `create(contactId, task)` Create a task linked to a contact. ```typescript const task = await g8.tasks.create(5028106, { title: 'Send proposal', due_date: '2026-06-15', priority: 'high', }); ``` ### `update(taskId, fields)` Partial update. ```typescript await g8.tasks.update('task_abc', { status: 'completed' }); ``` ### `delete(taskId)` Delete a task. Irreversible. ```typescript await g8.tasks.delete('task_abc'); ``` --- ## Notes > **Caution:** > Requires API key. Operates on first-party CRM data in your workspace. CRUD for notes attached to contacts. ```typescript import { g8 } from '@graph8/sdk'; g8.init({ apiKey: 'YOUR_API_KEY' }); ``` ### `list(contactId)` List every note on a contact. ```typescript const { data: notes } = await g8.notes.list(5028106); ``` ### `create(contactId, content)` Add a note to a contact. ```typescript const note = await g8.notes.create(5028106, 'Discovery call: budget approved, decision by Q3' ); ``` ### `update(noteId, content)` Replace a note's content. ```typescript await g8.notes.update('note_xyz', 'Updated: budget approved for Q4'); ``` ### `delete(noteId)` Delete a note. Irreversible. ```typescript await g8.notes.delete('note_xyz'); ``` --- ## Custom Fields > **Caution:** > Requires API key. Operates on first-party CRM data in your workspace. Manage custom columns on contacts or companies. Columns can be **global** (entity-wide) or **list-scoped** (only on contacts / companies inside one list). ```typescript import { g8 } from '@graph8/sdk'; g8.init({ apiKey: 'YOUR_API_KEY' }); ``` ### `listContactFields(listId?)` / `listCompanyFields(listId?)` List field definitions for an entity. Returns base + custom columns. ```typescript const { data: contactFields } = await g8.fields.listContactFields(); const { data: listFields } = await g8.fields.listContactFields(637); ``` ### `create(params)` Create a custom column. Omit `list_id` for a global column. ```typescript // Global column on contacts const field = await g8.fields.create({ title: 'Lead score', entity: 'contacts', data_type: 'number', }); // List-scoped column const listField = await g8.fields.create({ title: 'Q3 priority', entity: 'contacts', data_type: 'boolean', list_id: 637, }); ``` ### `setValue(columnId, recordId, value, entity?)` Set a custom column value on a single contact or company row. ```typescript await g8.fields.setValue(42, 5028106, 85, 'contacts'); ``` ### `delete(columnId, params?)` Soft-delete a custom column. ```typescript await g8.fields.delete(42, { entity: 'contacts' }); ``` --- ## Quotes > **Caution:** > Requires API key. `send()` triggers a real email to the recipient. Full quote-to-cash lifecycle — draft, edit, send (with signing + payment links), duplicate, convert back to draft. ```typescript import { g8 } from '@graph8/sdk'; g8.init({ apiKey: 'YOUR_API_KEY' }); ``` ### `list(params?)` List quotes with optional filters and pagination. ```typescript const { data, pagination } = await g8.quotes.list({ status: 'sent', contact_id: 5028106, limit: 50, }); ``` Filterable: `status` (`draft`, `sent`, `signed`, `declined`, `expired`, `void`), `contact_id`, `company_id`, `page`, `limit`. ### `get(quoteId)` ```typescript const quote = await g8.quotes.get('quo_abc123'); ``` ### `create(quote)` Create a new quote. `line_items` is required. ```typescript const quote = await g8.quotes.create({ contact_id: 5028106, line_items: [ { product_id: 'prod_pro', quantity: 1, unit_price: 499, recurring: true }, { name: 'Onboarding', quantity: 1, unit_price: 1500 }, ], currency: 'USD', expires_at: '2026-07-15T00:00:00Z', }); ``` ### `update(quoteId, fields)` Partial update on a draft quote. ```typescript await g8.quotes.update('quo_abc123', { notes: 'Updated to 12-month commit' }); ``` ### `delete(quoteId)` Delete a draft quote. Irreversible. ```typescript await g8.quotes.delete('quo_abc123'); ``` ### `duplicate(quoteId)` Copy an existing quote into a new draft. ```typescript const copy = await g8.quotes.duplicate('quo_abc123'); ``` ### `editAsDraft(quoteId)` Convert a signed / sent quote back into editable draft state. ```typescript await g8.quotes.editAsDraft('quo_abc123'); ``` ### `send(quoteId, params)` Send the quote via email. Includes signing link by default. ```typescript await g8.quotes.send('quo_abc123', { recipient_email: 'jane@acme.com', send_signing_link: true, }); ``` > **Caution:** > Sends a real email. Verify recipient + content before invoking. ### `products()` / `settings()` ```typescript const { data: products } = await g8.quotes.products(); const settings = await g8.quotes.settings(); // { default_currency, default_tax_rate, payment_providers, logo_url, signature_required } ``` ### `forContact(contactId)` / `forCompany(companyId)` ```typescript const { data } = await g8.quotes.forContact(5028106); const { data: companyQuotes } = await g8.quotes.forCompany(8821); ``` --- ## Stage Checklist Pipelines > **Caution:** > Requires API key. Separate from `g8.deals.pipelines()` (which lists deal pipelines). Workflow pipelines with per-stage evidence requirements and channel scripts. Used for structured outbound flows (Stage Checklist v2). ```typescript import { g8 } from '@graph8/sdk'; g8.init({ apiKey: 'YOUR_API_KEY' }); ``` ### `list()` / `get(pipelineId)` ```typescript const { data: pipelines } = await g8.pipelines.list(); const pipeline = await g8.pipelines.get('pip_xyz'); ``` ### `evidenceLibrary()` Get the canonical evidence-key library (used when defining stage requirements). ```typescript const { data: keys } = await g8.pipelines.evidenceLibrary(); // [{ key: 'budget_confirmed', label: 'Budget confirmed', category: 'qualification', ... }] ``` ### `create(params)` Create a new pipeline. Defaults to a templated stage set; pass `blank: true` for an empty pipeline. ```typescript const pipeline = await g8.pipelines.create({ name: 'Enterprise Outbound', target: 'Land 5 new logos in Q3', }); const empty = await g8.pipelines.create({ name: 'Custom flow', blank: true }); ``` ### `update(pipelineId, fields)` ```typescript await g8.pipelines.update('pip_xyz', { name: 'Renamed', target: 'New goal' }); ``` ### `delete(pipelineId)` Only allowed when no deals reference the pipeline. ```typescript await g8.pipelines.delete('pip_xyz'); ``` ### `createStage(pipelineId, stage)` ```typescript const stage = await g8.pipelines.createStage('pip_xyz', { name: 'Discovery', required_elements: ['budget_confirmed', 'timeline_known'], channel_scripts: { email: 'Hi {{first_name}}...', call: 'Hello, calling about...', }, }); ``` ### `updateStage(pipelineId, stageId, fields)` ```typescript await g8.pipelines.updateStage('pip_xyz', 'stg_discovery', { color: '#00CAEB' }); ``` ### `deleteStage(pipelineId, stageId)` ```typescript await g8.pipelines.deleteStage('pip_xyz', 'stg_discovery'); ``` ### `reorderStages(pipelineId, stageIds)` Pass the full ordered list of stage IDs. ```typescript await g8.pipelines.reorderStages('pip_xyz', [ 'stg_qual', 'stg_demo', 'stg_negotiation', 'stg_close', ]); ``` ### `suggest()` / `fromSuggestion(suggestionId, overrides?)` AI-suggested pipeline based on org context (brand, ICP, messaging). Two-step: suggest, then promote to a real pipeline. ```typescript const { data: suggestion } = await g8.pipelines.suggest(); // { suggestion_id, name, target, stages: [...], rationale } const pipeline = await g8.pipelines.fromSuggestion(suggestion.suggestion_id, { name: 'PLG Path', }); ``` --- ## Workflows > **Caution:** > Requires API key. `execute()` runs real automations — sequence enrollments, dialer sessions, Slack messages, etc. Multi-node automation graphs with execution lifecycle. The whole workflow definition is treated as a single record — node-level CRUD is performed client-side by mutating `config.nodes` / `config.connections` and submitting via `update()`. ```typescript import { g8 } from '@graph8/sdk'; g8.init({ apiKey: 'YOUR_API_KEY' }); ``` ### `list(params?)` / `get(workflowId)` / `delete(workflowId)` ```typescript const { data: workflows } = await g8.workflows.list({ is_active: true }); const workflow = await g8.workflows.get('wf_abc123'); await g8.workflows.delete('wf_abc123'); ``` ### `create(params)` ```typescript const wf = await g8.workflows.create({ name: 'Inbound demo follow-up', description: 'Triggered when a form is submitted', config: { nodes: [ { id: 'n1', type: 'form_trigger', config: { form_id: 'demo_request' } }, { id: 'n2', type: 'agent', config: { skill_id: 'sk_qualify' } }, { id: 'n3', type: 'slack_message', config: { channel: 'C0123' } }, ], connections: [ { from_node_id: 'n1', to_node_id: 'n2' }, { from_node_id: 'n2', to_node_id: 'n3' }, ], }, }); ``` ### `update(workflowId, fields)` Pass the full `config` to edit nodes / connections. ```typescript const wf = await g8.workflows.get('wf_abc123'); wf.config.nodes.push({ id: 'n4', type: 'delay', config: { hours: 24 } }); wf.config.connections.push({ from_node_id: 'n3', to_node_id: 'n4' }); await g8.workflows.update('wf_abc123', { config: wf.config }); ``` ### `validate({ config, trigger_config? })` Validate a workflow definition (catches orphans, dangling connections, missing required fields). ```typescript const { data } = await g8.workflows.validate({ config: wf.config }); if (!data.valid) { console.log(data.errors); } ``` ### Execution lifecycle ```typescript // Kick off const exec = await g8.workflows.execute('wf_abc123', { contact_id: 5028106, source: 'manual_run', }); // Poll status const status = await g8.workflows.getExecution(exec.id); // Mid-flight control await g8.workflows.pauseExecution(exec.id); await g8.workflows.resumeExecution(exec.id); await g8.workflows.stopExecution(exec.id); // Trigger management (event-stream triggers) await g8.workflows.getTriggerStatus('wf_abc123'); await g8.workflows.resetTrigger('wf_abc123'); ``` ### Node-type discovery + integration listings ```typescript // Schema for the config UI const { data: nodeTypes } = await g8.workflows.nodeTypes(); const { data: agentNode } = await g8.workflows.nodeTypes({ type: 'agent' }); // Pickers for action recipients const { data: slackChannels } = await g8.workflows.listSlackChannels(); const { data: roamGroups } = await g8.workflows.listRoamGroups(); const { data: mcpServers } = await g8.workflows.listMcpServers(); const { data: dispositions } = await g8.workflows.listDispositions(); const { data: fields } = await g8.workflows.listFormFields('form_demo_request'); ``` --- ## Skills > **Caution:** > Requires API key. `execute()` calls real LLMs or HTTP endpoints and consumes credits / quota. LLM and API building blocks that workflows compose into runs. Two kinds: **LLM skills** (prompt + model) and **API skills** (HTTP request wrappers). ```typescript import { g8 } from '@graph8/sdk'; g8.init({ apiKey: 'YOUR_API_KEY' }); ``` ### Read ```typescript const { data: skills } = await g8.skills.list({ type: 'llm' }); const skill = await g8.skills.get('sk_abc123'); const { data: vars } = await g8.skills.getVariables('sk_abc123'); const { data: models } = await g8.skills.listModels(); const { data: templates } = await g8.skills.listTemplates(); ``` ### Create an LLM skill ```typescript const skill = await g8.skills.createLLM({ title: 'Qualify lead', description: 'Score and tag inbound leads', prompt: 'You are a SDR. Given the contact: {{contact}} and the company: {{company}}, return JSON with { tier: "A"|"B"|"C", reason: string }.', model: 'claude-sonnet-4-6', input_schema: { fields: [ { name: 'contact', type: 'object', required: true }, { name: 'company', type: 'object', required: true }, ], }, }); ``` ### Create an API skill ```typescript const apiSkill = await g8.skills.createAPI({ title: 'Enrich domain via Clearbit', method: 'GET', url: 'https://company.clearbit.com/v2/companies/find?domain={{domain}}', headers: { 'Authorization': 'Bearer sk_xxx' }, input_schema: { fields: [{ name: 'domain', type: 'string', required: true }] }, }); ``` ### From templates / from a node ```typescript const fromTemplate = await g8.skills.createFromTemplate({ title: 'Reply summarizer', template_id: 'summarize_reply', }); const lifted = await g8.skills.createFromNode({ title: 'Lifted draft-reply node', node_id: 'n_draft_reply_456', }); ``` ### Update / delete / validate ```typescript await g8.skills.updateLLM('sk_abc123', { model: 'claude-opus-4-7' }); await g8.skills.delete('sk_abc123'); const { data } = await g8.skills.validate({ type: 'llm', title: 'Test', prompt: 'Hello {{name}}', model: 'claude-sonnet-4-6', input_schema: { fields: [{ name: 'name', type: 'string', required: true }] }, }); ``` ### Execute (test / preview) ```typescript const result = await g8.skills.execute('sk_abc123', { contact: { name: 'Jane Smith', title: 'VP Eng' }, company: { name: 'Acme', domain: 'acme.com' }, }); // result.data → { output, latency_ms, tokens, error } ``` --- ## Intent Tracking > **Caution:** > Requires API key. Distinct from `g8.signals` (which is summary scoring) — this module gives you the underlying keyword groups, page-level visitor data, and account-level intent. Track keywords against your site or competitor URLs, then query the visitor + contact graph by keyword or page. ```typescript import { g8 } from '@graph8/sdk'; g8.init({ apiKey: 'YOUR_API_KEY' }); ``` ### Org-level stats + keyword management ```typescript const { data: stats } = await g8.intent.stats(); // { total_keywords, total_pages, total_visitors_30d, total_companies_30d } const { data: keywords } = await g8.intent.listKeywords({ search: 'pricing' }); const newKw = await g8.intent.createFromDomain('competitor.com'); await g8.intent.deleteKeyword('kw_abc123'); ``` ### Per-keyword data ```typescript const { data: companies } = await g8.intent.keywordCompanies('kw_abc123', { limit: 50 }); const { data: contacts } = await g8.intent.keywordContacts('kw_abc123'); const { data: urls } = await g8.intent.keywordUrls('kw_abc123'); ``` ### Page-level visitor data ```typescript const { data: pages } = await g8.intent.pagesByDomain('competitor.com'); const { data: searchResults } = await g8.intent.searchPages('pricing'); const { data: visitors } = await g8.intent.pageVisitors('https://example.com/pricing'); const { data: ppl } = await g8.intent.pageContacts('https://example.com/pricing'); const { data: counts } = await g8.intent.pageVisitorCounts([ 'https://example.com/pricing', 'https://example.com/docs', ]); ``` ### Account-level intent search ```typescript const { data } = await g8.intent.urlCompanies('https://competitor.com/pricing', { date_from: '2026-04-01', }); ``` --- ## Studio Context > **Caution:** > Requires API key. Read-only access to the 44-doc intelligence engine that powers campaign generation. Org-level Studio documents: brand brief, value props, messaging house, ICPs, personas, intelligence (scrapes + enrichment), and AI research reports. ```typescript import { g8 } from '@graph8/sdk'; g8.init({ apiKey: 'YOUR_API_KEY' }); ``` ### `globalContext(params?)` / `icps(params?)` / `personas(params?)` ```typescript const { data: docs } = await g8.studio.globalContext({ category: 'brand_brief' }); const { data: icps } = await g8.studio.icps({ status: 'published' }); const { data: personas } = await g8.studio.personas(); ``` ### `intelligenceData(params?)` / `researchReports(params?)` ```typescript const { data: intel } = await g8.studio.intelligenceData({ source_type: 'website_scrape' }); const { data: reports } = await g8.studio.researchReports({ category: 'competitive_teardown' }); ``` --- ## Meetings > **Caution:** > Requires API key. Read-only — booking happens via `g8.calendar.*` (browser SDK) or the embedded widget. Read scheduled, completed, and cancelled meetings with attendees, recordings, transcripts, and AI analysis. ```typescript import { g8 } from '@graph8/sdk'; g8.init({ apiKey: 'YOUR_API_KEY' }); ``` ### `list(params?)` List meetings with optional filters. Returns summary rows (without transcript / analysis). ```typescript const { data } = await g8.meetings.list({ status: 'completed', date_from: '2026-05-01', contact_id: 5028106, limit: 25, }); ``` Filterable: `status`, `date_from`, `date_to`, `attendee_id`, `event_type`, `contact_id`, `page`, `limit`. ### `get(meetingId)` Get the full meeting record including transcript and AI analysis. ```typescript const meeting = await g8.meetings.get('mtg_abc123'); // → { id, status, attendees, conferencing_url, recording_url, transcript: [...], analysis: { sentiment, summary, next_steps, objections, talk_listen_ratio } } ``` The transcript is available 1-5 minutes after the meeting ends. While transcription is in progress, `transcript` and `analysis` may be `null`. --- ## CRM Integrations > **Caution:** > Requires API key. Connecting and syncing moves real customer data. Connect graph8 to your CRM (HubSpot, Salesforce, Pipedrive, etc.) and trigger one-off or bidirectional syncs. ```typescript // List connected CRMs const integrations = await g8.integrations.list(); // [{ id: 'int_123', provider: 'hubspot', status: 'connected', connected_at: '2026-03-15T...' }, ...] // Connect a new CRM await g8.integrations.connect('hubspot', { apiKey: 'pat-na1-...' }); // Trigger a sync ('inbound' | 'outbound' | 'bidirectional') await g8.integrations.sync('hubspot', { direction: 'bidirectional' }); ``` ### Methods | Method | Description | |--------|-------------| | `g8.integrations.list()` | List connected CRMs. Returns `Integration[]`. | | `g8.integrations.connect(provider, config?)` | Connect a CRM. Provider-specific credentials go in `config`. | | `g8.integrations.sync(provider, { direction? })` | Trigger a sync. `direction` defaults to provider settings. | --- ## Audiences Sync an audience list to ad platforms (Meta, LinkedIn, Google, X) and keep it refreshed. Requires an API key. Built on the hardened HTTP core — every method throws a [`G8Error`](#typed-errors--g8error) on failure and retries transient errors. ```typescript // Create a sync from an audience list to a Meta Custom Audience const sync = await g8.audiences.create({ audience_id: 1234, platform: "meta", mode: "mirror", // or "append_only" refresh_cadence_hours: 24, platform_config: { ad_account_id: "act_123" }, }); await g8.audiences.trigger(sync.id); // run it now const runs = await g8.audiences.runs(sync.id); console.log(runs.data[0].members_added); ``` | Method | Description | |--------|-------------| | `g8.audiences.list()` | List all audience syncs. Returns `{ data: AudienceSync[] }`. | | `g8.audiences.create(params)` | Create a sync. Returns the `AudienceSync`. | | `g8.audiences.get(configId)` | Get one sync by ID. | | `g8.audiences.update(configId, fields)` | Update mode / cadence / active / suppression lists. | | `g8.audiences.delete(configId)` | Delete a sync. | | `g8.audiences.trigger(configId)` | Trigger an immediate sync run. | | `g8.audiences.runs(configId)` | List recent sync runs (members added/removed, status). | | `g8.audiences.errors(configId)` | List recent sync errors. | Platforms: `meta`, `linkedin`, `google`, `x`. Modes: `mirror` (full two-way membership) or `append_only` (only ever add). --- ## Analytics ```typescript const overview = await g8.analytics.overview({ period: '30d' }); // { visitors: 12500, contacts_created: 340, emails_sent: 2100, replies: 89, meetings_booked: 23 } ``` --- ## Voice AI The `g8.voice` surface has two layers: 1. **Top-level preview** (`g8.voice.start()`, `g8.voice.analysis()`) — legacy single-call API. 2. **`g8.voice.dialer`** — the production parallel-dialer surface backed by `/api/v1/voice/dialer/*`. ```typescript // Preview single-call API (kept for backwards compat) const session = await g8.voice.start({ agent: 'sales-discovery', contactId: 123 }); const analysis = await g8.voice.analysis(session.id); // { sentiment, summary, next_steps, objections, transcript } ``` ### Dialer sessions ```typescript const { data: sessions } = await g8.voice.dialer.listSessions({ status: 'active' }); const session = await g8.voice.dialer.createSession({ list_id: 637, agent_id: 'ag_default', from_number: '+14155551234', campaign_id: 'cmp_abc', }); // Pause / resume / stop await g8.voice.dialer.updateSessionStatus(session.session_id, 'paused'); await g8.voice.dialer.resumeSession(session.session_id, 4); ``` ### Calls + transcripts + grading ```typescript const { data: calls } = await g8.voice.dialer.listCalls({ campaign_id: 'cmp_abc' }); const forContact = await g8.voice.dialer.listCallsForContact(5028106); const forSdr = await g8.voice.dialer.listCallsForSdr('jane@company.com', { date_from: '2026-05-01', }); const transcript = await g8.voice.dialer.callTranscript('room_abc123'); const grading = await g8.voice.dialer.callGrading('room_abc123'); ``` ### Stats + numbers + agents ```typescript const { data: stats } = await g8.voice.dialer.stats({ aggregation: 'daily' }); const { data: numbers } = await g8.voice.dialer.numbers(); const { data: agents } = await g8.voice.dialer.agents(); const { data: callbacks } = await g8.voice.dialer.missedCallbacks(50); ``` > **Caution:** > Resuming a dialer session places real outbound calls and consumes voice credits (20 per minute). Always confirm before invoking `resumeSession()`. --- ## Landing Pages ```typescript // Clone from any URL const page = await g8.pages.clone('https://competitor.com/pricing'); // Create from template const page = await g8.pages.create({ template: 'lead_magnet', title: 'The Future of Sales', }); // Publish to CDN const { url } = await g8.pages.publish(page.id); // https://g8-lp-abc12345.pages.dev ``` --- ## Webhooks Listen for events from graph8 (server-side). ```typescript g8.webhooks.on('reply_received', (event) => { slack.send(`${event.contact} replied to ${event.sequence}`); }); g8.webhooks.on('meeting_booked', (event) => { crm.updateDeal(event.dealId, { stage: 'meeting' }); }); // Stop all listeners g8.webhooks.stop(); ``` **Available events:** `reply_received`, `meeting_booked`, `contact_enriched`, `contact_created`, `sequence_completed`, `campaign_launched`, `form_submitted`, `visitor_identified` --- ## Privacy ```typescript g8.init({ writeKey: 'YOUR_WRITE_KEY', privacy: { dontSend: true, // Disable all cookies and event sending dontStoreUserIds: true, // Don't store user identifiers ipPolicy: 'remove', // 'keep' | 'stripLastOctet' | 'remove' }, }); ``` ## SSR Support All methods are no-ops on the server and execute on the client after hydration. Safe in Next.js, Nuxt, Remix, and any SSR framework. ## Config Options | Option | Type | Default | Description | |--------|------|---------|-------------| | `writeKey` | `string` | - | Write key for client-side features | | `apiKey` | `string` | - | API key for server-side features | | `host` | `string` | `https://t.graph8.com` | Tracking host URL | | `apiUrl` | `string` | `https://be.graph8.com` | Backend API URL | | `debug` | `boolean` | `false` | Enable debug logging | | `privacy.dontSend` | `boolean` | `false` | Disable cookies and event sending | | `privacy.dontStoreUserIds` | `boolean` | `false` | Don't store user identifiers | | `privacy.ipPolicy` | `string` | `keep` | `keep`, `stripLastOctet`, or `remove` | ## Get Your Keys 1. Go to [graph8 Settings](https://app.graph8.com/profile) > MCP & API 2. **Write key** is in the tracking snippet section (client-side) 3. **API key** is in the API tab (server-side) --- # SDK Examples > End-to-end worked workflows using @graph8/sdk — prospecting, sequences, quotes, workflows, intent-driven outreach Source: https://docs.graph8.com/developers/sdk-examples/ Six worked workflows you can copy. Each one chains multiple SDK modules to do something real. See [SDK overview](https://docs.graph8.com/developers/sdk/) for the full module reference, [SDK FAQ](https://docs.graph8.com/developers/sdk-faq/) for common questions. > **Tip:** > Most of these examples require an API key. Set it once: `g8.init({ apiKey: process.env.G8_API_KEY })`. Server-side only. ## 1. Prospect, enrich, save to list Find VPs of Engineering at SaaS companies sized 50-500, enrich them, save the qualified ones to a campaign list. ```typescript import { g8 } from '@graph8/sdk'; g8.init({ apiKey: process.env.G8_API_KEY! }); // Search graph8's 700M+ contact index — free on both PAYG and Platform within 50 rps const leads = await g8.enrich.search([ { field: 'seniority_level', operator: 'any_of', value: ['VP'] }, { field: 'job_title', operator: 'contains', value: ['Engineering'] }, { field: 'company_industry', operator: 'contains', value: ['SaaS'] }, { field: 'company_employee_count', operator: 'between', value: [50, 500] }, ], 1, 50); // Create a target list const list = await g8.lists.create('Q3 VP Engineering — SaaS SMB', 'contacts'); // Save the top results into the list (each save creates the contact in your CRM) for (const lead of leads.data) { await g8.contacts.create({ work_email: lead.work_email!, first_name: lead.first_name!, last_name: lead.last_name!, job_title: lead.job_title!, company_domain: lead.company_domain!, list_id: list.id, }); } console.log(`Saved ${leads.data.length} contacts to list ${list.id}`); ``` --- ## 2. Multi-step sequence enrollment List sequences, enroll a saved list of contacts into the right one, monitor analytics. ```typescript import { g8 } from '@graph8/sdk'; g8.init({ apiKey: process.env.G8_API_KEY! }); // Find the sequence by name const sequences = await g8.sequences.list(); const target = sequences.find(s => s.name === 'Q3 VP Engineering Outbound'); if (!target) throw new Error('Sequence not found'); // Enroll a list (this triggers real sends — confirm before running) await g8.sequences.add({ sequenceId: target.id, contactIds: [5028106, 5028105, 5028104], listId: 637, }); // 24 hours later — check engagement const analytics = await fetch(`/api/v1/sequences/${target.id}/analytics`, { headers: { Authorization: `Bearer ${process.env.G8_API_KEY}` }, }).then(r => r.json()); console.log(`Sent: ${analytics.data.sent}, opened: ${analytics.data.opened}, replied: ${analytics.data.replied}`); ``` --- ## 3. Quote-to-cash flow Generate a quote from a contact, send it for signature with a payment link, listen for the signed event. ```typescript import { g8 } from '@graph8/sdk'; g8.init({ apiKey: process.env.G8_API_KEY! }); // Build the quote const quote = await g8.quotes.create({ contact_id: 5028106, company_id: 8821, line_items: [ { product_id: 'prod_platform', quantity: 1, unit_price: 499, recurring: true }, { name: 'Onboarding (one-time)', quantity: 1, unit_price: 2500 }, ], currency: 'USD', expires_at: '2026-07-15T00:00:00Z', notes: 'Includes 90-day money-back guarantee.', }); // Send for signature await g8.quotes.send(quote.id, { recipient_email: 'jane@acme.com', send_signing_link: true, }); // Listen for the signed event g8.webhooks.on('contact_enriched', (event) => { // ...optional: track when the prospect engages }); ``` --- ## 4. Inbound: form submit → intent check → priority routing Visitor fills out a form. SDR-route them based on company intent score. ```typescript import { g8 } from '@graph8/sdk'; g8.init({ writeKey: process.env.NEXT_PUBLIC_G8_WRITE_KEY!, apiKey: process.env.G8_API_KEY }); // On submit (Next.js server action) export async function onDemoSubmit(formData: FormData) { const email = formData.get('work_email') as string; const domain = email.split('@')[1]; // Upsert the contact const contact = await g8.contacts.create({ work_email: email, first_name: formData.get('first_name') as string, company_domain: domain, }); // Check intent for the company domain const signals = await g8.signals.company(domain); const isHotAccount = signals.intent === 'high' || signals.score >= 75; // Route based on intent if (isHotAccount) { await g8.tasks.create(contact.id, { title: `🔥 Hot inbound demo — ${signals.score} score`, priority: 'high', assignee_id: 'user_enterprise_ae', due_date: new Date(Date.now() + 60 * 60 * 1000).toISOString(), }); } else { // Standard nurture sequence await g8.sequences.add({ sequenceId: 'seq_demo_nurture', contactIds: [contact.id], listId: 0, }); } } ``` --- ## 5. Build a workflow with an LLM skill Author an LLM skill that qualifies leads, then bake it into a workflow triggered by form submissions. ```typescript import { g8 } from '@graph8/sdk'; g8.init({ apiKey: process.env.G8_API_KEY! }); // 1. Author the LLM skill const qualifySkill = await g8.skills.createLLM({ title: 'Qualify inbound lead', description: 'Returns tier A/B/C and a reason', prompt: `You are an SDR triaging an inbound lead. Contact: {{contact}} Company: {{company}} Return JSON: {"tier": "A"|"B"|"C", "reason": "<one sentence>"}`, model: 'claude-sonnet-4-6', input_schema: { fields: [ { name: 'contact', type: 'object', required: true }, { name: 'company', type: 'object', required: true }, ], }, }); // 2. Build the workflow const workflow = await g8.workflows.create({ name: 'Inbound qualification + routing', config: { nodes: [ { id: 'n_trigger', type: 'form_trigger', config: { form_id: 'demo_request' } }, { id: 'n_qualify', type: 'skill', config: { skill_id: qualifySkill.id } }, { id: 'n_branch', type: 'branch', config: { condition: '{{n_qualify.output.tier}} == "A"' } }, { id: 'n_slack', type: 'slack_message', config: { channel_id: 'C_alerts', text: 'New A-tier lead: {{n_trigger.contact.work_email}} — {{n_qualify.output.reason}}', } }, { id: 'n_nurture', type: 'sequence_enroll', config: { sequence_id: 'seq_demo_nurture' } }, ], connections: [ { from_node_id: 'n_trigger', to_node_id: 'n_qualify' }, { from_node_id: 'n_qualify', to_node_id: 'n_branch' }, { from_node_id: 'n_branch', to_node_id: 'n_slack', condition: 'true' }, { from_node_id: 'n_branch', to_node_id: 'n_nurture', condition: 'false' }, ], }, }); // 3. Validate before activating const { data: check } = await g8.workflows.validate({ config: workflow.config }); if (!check.valid) throw new Error('Workflow invalid: ' + JSON.stringify(check.errors)); // 4. Activate await g8.workflows.update(workflow.id, { is_active: true }); ``` --- ## 6. Account-level intent → outreach Find companies showing intent on a competitor URL, find their contacts, add to a "competitive displacement" sequence. ```typescript import { g8 } from '@graph8/sdk'; g8.init({ apiKey: process.env.G8_API_KEY! }); // 1. Companies visiting competitor pricing const { data: hotAccounts } = await g8.intent.urlCompanies( 'https://competitor.com/pricing', { date_from: '2026-05-01', limit: 50 }, ); // 2. For each hot account, find decision-makers via search const allContacts: number[] = []; for (const acct of hotAccounts) { if (!acct.domain) continue; const results = await g8.enrich.search([ { field: 'company_domain', operator: 'any_of', value: [acct.domain] }, { field: 'seniority_level', operator: 'any_of', value: ['C-Suite', 'VP', 'Director'] }, ], 1, 5); for (const lead of results.data) { const c = await g8.contacts.create({ work_email: lead.work_email!, first_name: lead.first_name!, last_name: lead.last_name!, job_title: lead.job_title!, company_domain: lead.company_domain!, }); allContacts.push(c.id); } } // 3. Enroll into competitive displacement sequence await g8.sequences.add({ sequenceId: 'seq_competitive_displacement', contactIds: allContacts, listId: 0, }); console.log(`Enrolled ${allContacts.length} contacts from ${hotAccounts.length} intent-active accounts.`); ``` --- ## See also - [SDK overview](https://docs.graph8.com/developers/sdk/) — full module reference - [SDK FAQ](https://docs.graph8.com/developers/sdk-faq/) — install, auth, parity, errors - [CLI Examples](https://docs.graph8.com/developers/cli-examples/) — same workflows from the terminal - [MCP Examples](https://docs.graph8.com/developers/mcp-examples/) — same workflows from an AI agent - [Pricing](https://docs.graph8.com/developers/pricing/) — what each call costs --- # SDK FAQ > Common questions about @graph8/sdk — write keys vs API keys, SSR, React, bundling, parity with MCP, and troubleshooting Source: https://docs.graph8.com/developers/sdk-faq/ Common questions about `@graph8/sdk` — graph8's JavaScript / TypeScript SDK. If your question isn't here, see the [SDK overview](https://docs.graph8.com/developers/sdk/), [SDK Examples](https://docs.graph8.com/developers/sdk-examples/), or file an issue on the SDK repo. > **Tip:** > Try the developer tier free - 1,000 credits, no card required. [Get started](https://app.graph8.com/signup). ## Plans, credits, and parity ### Is the SDK free to use? Yes. The SDK itself is free and open-source. API calls bill against your graph8 credit balance the same way MCP, CLI, or REST calls do. PAYG ships with 1,000 free credits on signup, no card. See [Pricing](https://docs.graph8.com/developers/pricing/). ### Does the SDK have parity with the MCP server? Yes for server-side operations. The SDK ships **25+ modules** that cover every CRM, prospecting, sequence, campaign, quote, pipeline, workflow, skill, voice, intent, and studio operation that MCP exposes — plus widgets and tracking which MCP doesn't have (copilot, chat, calendar, forms, page tracking). The two surfaces target different consumers (MCP = AI agents over stdio/HTTP, SDK = JS/TS code), but both hit the same `/api/v1/*` backend, so feature parity is intentional and complete. ### Which operations cost credits? Mental model: **graph8-owned data is free on both plans. Third-party data, AI generation, voice, outreach sends, and meeting bookings charge credits on both plans.** Full per-operation matrix in [Pricing](https://docs.graph8.com/developers/pricing/). **Always free on both paid plans (within the 50 rps cap)** graph8-owned data: - B2B index search (`g8.enrich.search`) - Save-to-list from a search - Intent reads (`g8.intent.stats`, keyword aggregations, page visitors) - Website-visitor identification (`g8.visitors.identify`, `score`, `company`) - Public signals (`g8.signals.company`) First-party CRM + metadata: - Contacts / companies / lists / deals / tasks / notes / fields / quotes — full CRUD - Workflow / skill / pipeline / landing-page CRUD (the metadata, not the execution) - Studio reads (`g8.studio.icps`, `personas`, `globalContext`, `intelligenceData`, `researchReports`) - Webhook subscription management + delivery - Calendar widget load, slot lookup (`g8.calendar.show`, `slots`) - Form widget load (`g8.forms.lookup`) - Tracking from the browser (`g8.track`, `g8.identify`, `g8.page`) **Always charges credits (both plans — drawn from your balance or the 75k bundle)** - **Copilot turns** (`g8.copilot.ask`, `chat`) — per LLM tokens. The widget loads free; every message is billed. - AI inbox draft (`g8.inbox.draft`) — per LLM tokens - Studio AI generation (brief, brand kit, intelligence) — per LLM tokens - Landing-page chat edit (`g8.pages.chat`) — per LLM tokens - `g8.skills.execute()` with `type=llm` — per LLM tokens - Waterfall enrichment (`g8.enrich.run` / `/enrichment/enrich`) — 0.5-20 per 3rd-party provider hit (Prospeo, Apollo, Hunter, Cognism, etc.) - Voice minutes — 20 credits / minute - **Meeting booking** (charged when the booking is confirmed) — 20 credits flat per booking - Sequencer / campaign step (real send) — 1 credit per step - Newsletter / nurture send — 1 credit per recipient - Intent signal processing (background ingest) — 1 credit / 10 events - Tracking event ingest at scale — 1 credit / 10 events - Person / company lookup (`g8.enrich.person`, `g8.enrich.company`) — 2 credits per lookup, charged only when a match is found - Email verify (`g8.enrich.verifyEmail`, `/enrichment/verify-email`) — 1 credit / verify - Audience-sync push — per record where the destination platform meters *If you stay on graph8-owned data and CRUD on records you already own, you spend zero credits on either plan.* ## Installation and bundling ### How do I install it? ```bash npm install @graph8/sdk # or pnpm add @graph8/sdk yarn add @graph8/sdk ``` Works in any modern bundler (Vite, Webpack, esbuild, Turbopack, Rollup). Tree-shakable — modules you don't import don't ship. ### Does it work in Node? Yes. The SDK detects the runtime — tracking and widget modules become no-ops on the server, and the API modules (`enrich`, `sequences`, `campaigns`, etc.) work the same on both. ### Does it work with React / Next.js? Yes. There's a dedicated React adapter at `@graph8/sdk/react`: ```tsx import { G8Provider, useG8 } from '@graph8/sdk/react'; <G8Provider writeKey="YOUR_WRITE_KEY">{children}</G8Provider> ``` `useG8()` returns the same surface as the singleton `g8` — see [React / Next.js section](https://docs.graph8.com/developers/sdk/#react--nextjs). ### How big is the bundle? About 12 KB gzipped for the full client (`g8.init()` + tracking). Each additional module is small enough that tree-shaking takes it out if unused. ## Auth modes ### What's the difference between `writeKey` and `apiKey`? | Mode | Key | Use case | Safe in browser? | |------|-----|----------|-----------------| | Client-side | `writeKey` | Tracking, visitors, copilot, chat, calendar, forms, public signals | **Yes** | | Server-side | `apiKey` | Everything else (enrichment, sequences, campaigns, CRM CRUD, voice, quotes, workflows, etc.) | **No** | ```typescript g8.init({ writeKey: 'pk_xxx' }); // browser g8.init({ apiKey: 'sk_xxx' }); // node / server g8.init({ writeKey: 'pk_xxx', apiKey: 'sk_xxx' });// both (full access) ``` The SDK will throw a clear error if you try to call a server-side module without an `apiKey`. ### Where do I get my keys? - **Write key**: tracking snippet section in **Profile -> Developer** (or **Settings -> Sources**). - **API key**: **Profile -> Developer** for a personal key, **Settings -> API** for an org key. ### Can I expose `apiKey` in browser code? No. It grants full access to your workspace. Always proxy server-side methods through your own backend (e.g. Next.js API routes, Cloudflare Workers). ## React / Next.js / SSR ### Will the SDK break on SSR? No. All tracking and widget methods (`g8.track`, `g8.identify`, `g8.copilot.open`, etc.) are no-ops on the server and hydrate normally on the client. API methods that need an `apiKey` work the same in any Node environment. ### How do I gate widget code so it only runs in the browser? The SDK handles this internally — calls return safely. If you specifically want to skip computation, check `typeof window !== "undefined"` or use a `useEffect`. ### Can I use it in Edge runtimes (Cloudflare Workers, Vercel Edge)? Yes — the API modules use `fetch`, which is native in Edge runtimes. Tracking / widget modules will simply no-op. ## Common errors ### `g8.<module> requires an API key` You called a server-side module (e.g. `g8.deals`, `g8.enrich`, `g8.workflows`) without passing `apiKey` to `g8.init()`. Pass an `apiKey` or only call from server code. ### `401 Unauthorized` on an API call Your `apiKey` is missing, malformed, or revoked. Run `g8 whoami` from the CLI to verify, or check **Profile -> Developer** in the app. ### `429 Too Many Requests` You hit the 50 rps per-org rate limit. The response includes a `Retry-After` header — back off and retry. See [Rate Limits](https://docs.graph8.com/developers/rate-limits/). ### `402 Payment Required` You ran out of credits on PAYG (waterfall enrichment, AI generation, voice minutes, etc.). Top up at **Settings -> Billing**, or switch to Platform for the bundled 75k credits/month. ### TypeScript can't find a type from `@graph8/sdk` Make sure you imported from the entry point: `import type { Deal, Quote, Workflow } from "@graph8/sdk"`. Sub-paths like `@graph8/sdk/src/deals` are internal. ## Migration ### Coming from `@graph8/sdk` v0.3 or earlier? The package name has never changed — it is and has always been `@graph8/sdk`. (If older docs or examples ever pointed you at `@graph8/js`, that package never existed on npm; `@graph8/sdk` is the only official package.) Upgrading from v0.3 or earlier: 1. Update the package: `pnpm add @graph8/sdk@latest`. 2. Imports are unchanged: `import { g8 } from "@graph8/sdk"`. 3. `g8.voice.start(...)` and `g8.voice.analysis(...)` still work (kept for backwards compat); new dialer code uses `g8.voice.dialer.*`. 4. Module surface is now ~5x larger — see the [SDK overview](https://docs.graph8.com/developers/sdk/) for the full module list. ### Coming from a custom REST wrapper? The SDK is a thin typed layer over the same `/api/v1/*` REST surface, so swap-in is mostly mechanical. Pricing, rate limits, and auto-capture all work the same. See [SDK Examples](https://docs.graph8.com/developers/sdk-examples/) for typical usage patterns. ## See also - [SDK overview](https://docs.graph8.com/developers/sdk/) — full module reference - [SDK Examples](https://docs.graph8.com/developers/sdk-examples/) — six worked workflows - [Authentication](https://docs.graph8.com/developers/authentication/) — key creation + scoping - [Rate Limits](https://docs.graph8.com/developers/rate-limits/) — retry headers and backoff - [Pricing](https://docs.graph8.com/developers/pricing/) — free tier, credit costs, plans --- # Search > Search graph8's global B2B database for contacts and companies with advanced filters Source: https://docs.graph8.com/developers/search/ > **Tip: Using the SDK or CLI?** > The [JavaScript SDK](https://docs.graph8.com/developers/sdk/#enrichment) wraps search as `g8.enrich.search(filters)`. The [CLI](https://docs.graph8.com/developers/cli/#g8-find-contacts) supports `g8 find-contacts --filters '...'` and `g8 build-list`. > **Note: Free on both plans** > Search endpoints are **free on both PAYG and Platform within a 50 rps cap** — they hit graph8's owned indexes (700M+ contacts / 100M+ companies) with no third-party COGS, so there is no monthly cap on either plan. Save-to-list is also free. Credits only deduct when you later run waterfall enrichment, AI generation, voice minutes, meeting bookings, or outreach sends on the saved records. See [Pricing](https://docs.graph8.com/developers/pricing/) for the full matrix. The Search endpoints query graph8's **open data index** — a global B2B database of millions of contacts and companies aggregated from multiple data providers. Use it to discover new prospects that match your ideal customer profile (ICP), then save them to your workspace. This is different from the [`/contacts`](https://docs.graph8.com/developers/contacts/) and [`/companies`](https://docs.graph8.com/developers/companies/) endpoints, which only return data you've already saved — your first-party CRM data. > **Note: Organization deliverability policy** > Open-data searches and save-to-list operations automatically apply your organization's default-on deliverability protection policy. The policy can exclude account categories associated with higher spam-report risk. Users with settings access can inspect or change it under **Settings → Compliance → Deliverability**; it is not a request-body filter and cannot be overridden by an API call. > **Tip: Which endpoint should I use?** > - **I want to find NEW contacts or companies I don't have yet** → Use Search (this page) > - **I want to query contacts or companies I've already saved** → Use [Contacts](https://docs.graph8.com/developers/contacts/) or [Companies](https://docs.graph8.com/developers/companies/) (free, no credits) > - **I know a specific person's email or LinkedIn and want their profile** → Use [Enrichment Lookup](https://docs.graph8.com/developers/enrichment/) (single-record lookup) ### What you can search for - **Contacts**: Filter by job title, seniority, department, skills, location, company attributes, and more (~28 filterable fields) - **Companies**: Filter by industry, employee count, revenue, geography, founded year, LinkedIn presence, and more (~15 filterable fields) ### How it connects to your data Search results come from the open data index and are **read-only**. To work with them in your workflows: 1. **Search** — find contacts or companies matching your criteria 2. **Save to list** — use `/search/contacts/save` or `/search/companies/save` to copy matching records into your workspace 3. **Manage** — once saved, they appear in [`/contacts`](https://docs.graph8.com/developers/contacts/) and [`/companies`](https://docs.graph8.com/developers/companies/) as your data > **Note:** > Pricing depends on your plan - see the [credit costs](#credit-costs) section below. --- ## Credit Costs | Operation | PAYG | Platform | |-----------|------|----------| | Contact search | **Free** (within 50 rps) | **Free** (within 50 rps) | | Company search | **Free** (within 50 rps) | **Free** (within 50 rps) | | Save to list | **Free** (within 50 rps) | **Free** (within 50 rps) | Search and save-to-list are part of the free graph8-owned data tier on **both plans** — the only ceiling is the [50 rps rate cap](https://docs.graph8.com/developers/rate-limits/) and there is no monthly volume cap. Credits only deduct when you act on the saved records: waterfall enrichment via 3rd-party providers, AI generation, message sends, voice minutes, and meeting bookings. > **Tip: Iterate fast** > Paginate freely and refine filters without burning credits — save-to-list is also free on both plans. Credits only deduct when you run waterfall enrichment (`g8 enrich`), generate AI content, send outreach, dial, or book meetings on the saved records. --- ## Filter Operators All search endpoints accept a `filters` array in the request body. Each filter has a `field`, `operator`, and `value`. | Operator | Description | Example Value | |----------|-------------|---------------| | `any_of` | Exact match — field matches any value in the list | `["CEO", "CTO"]` | | `contains` | Fuzzy/substring match | `["sales"]` | | `all_of` | Field must contain ALL values | `["python", "javascript"]` | | `none_of` | Field must NOT match any value | `["intern", "student"]` | | `is_empty` | Field has no value | `[]` | | `is_not_empty` | Field has a value | `[]` | | `between` | Numeric range (inclusive) | `[2015, 2025]` | | `exists` | Field exists in the record | `[]` | --- ## Search Contacts `POST /search/contacts` Search the open data index for contacts matching the given filters. Returns paginated results with up to 100 results per page. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `filters` | array | **Yes** | Filter conditions (AND logic). At least one filter is required. See [filter operators](#filter-operators) | | `page` | integer | No | Page number, 1-indexed (default: 1, max: 100) | | `limit` | integer | No | Results per page (default: 25, max: 100) | > **Note:** > Pagination is limited to 10,000 total results (`page × limit ≤ 10,000`). For larger result sets, use the [save to list](#save-contacts-to-list) endpoint. ### Filterable Contact Fields | Field | Description | |-------|-------------| | `first_name` | First name | | `last_name` | Last name | | `work_email` | Work email address | | `personal_emails` | Personal email addresses | | `direct_phone` | Direct phone number | | `mobile_phone` | Mobile phone number | | `job_title` | Job title | | `job_department` | Department | | `seniority_level` | Seniority level (e.g., "VP", "Director", "Manager") | | `role` | Role | | `linkedin_url` | LinkedIn profile URL | | `gender` | Gender | | `skills` | Skills | | `education_degree` | Education degree | | `education_field` | Field of study | | `education_university_name` | University name | | `city` | City | | `state` | State/province | | `country` | Country | | `confidence_score` | Data confidence score | | `company_name` | Company name | | `company_domain` | Company domain | | `company_industry` | Company industry | | `company_employee_count` | Company employee count | | `company_country` | Company country | | `company_state` | Company state | | `company_city` | Company city | | `company_revenue` | Company revenue | | `company_founded_year` | Company founded year | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/search/contacts" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "filters": [ {"field": "job_title", "operator": "any_of", "value": ["CEO", "CTO", "VP Engineering"]}, {"field": "company_industry", "operator": "contains", "value": ["Technology"]}, {"field": "company_employee_count", "operator": "between", "value": [50, 500]} ], "page": 1, "limit": 25 }' ``` **Python** ```python response = requests.post( f"{BASE_URL}/search/contacts", headers=HEADERS, json={ "filters": [ {"field": "job_title", "operator": "any_of", "value": ["CEO", "CTO", "VP Engineering"]}, {"field": "company_industry", "operator": "contains", "value": ["Technology"]}, {"field": "company_employee_count", "operator": "between", "value": [50, 500]} ], "page": 1, "limit": 25 } ) ``` **TypeScript** ```typescript const response = await fetch(`${BASE_URL}/search/contacts`, { method: "POST", headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json" }, body: JSON.stringify({ filters: [ { field: "job_title", operator: "any_of", value: ["CEO", "CTO", "VP Engineering"] }, { field: "company_industry", operator: "contains", value: ["Technology"] }, { field: "company_employee_count", operator: "between", value: [50, 500] } ], page: 1, limit: 25 }), }); ``` ### Response ```json { "data": [ { "first_name": "Jane", "last_name": "Doe", "middle_name": null, "work_email": "jane@acme.com", "personal_emails": null, "direct_phone": "+1-555-0100", "mobile_phone": null, "job_title": "CEO", "job_department": "Executive", "seniority_level": "C-Suite", "role": "Chief Executive Officer", "linkedin_url": "https://linkedin.com/in/janedoe", "linkedin_headline": "CEO at Acme Inc", "city": "San Francisco", "state": "California", "country": "United States", "company_name": "Acme Inc", "company_domain": "acme.com", "company_industry": "Technology", "company_employee_count": "200", "company_country": "United States", "confidence_score": 95 } ], "pagination": { "page": 1, "limit": 25, "total": 142, "has_next": true } } ``` --- ## Search Companies `POST /search/companies` Search the open data index for companies matching the given filters. ### Request Body Same structure as contact search. See [request body](#request-body) above. ### Filterable Company Fields | Field | Description | |-------|-------------| | `name` | Company name | | `domain` | Company domain | | `website` | Website URL | | `description` | Company description | | `industry` | Industry | | `industry_group` | Industry group | | `employee_count` | Number of employees | | `revenue` | Annual revenue | | `founded_year` | Year founded | | `country` | Country | | `state` | State/province | | `city` | City | | `phone` | Phone number | | `linkedin_url` | LinkedIn URL | | `linkedin_followers` | LinkedIn follower count | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/search/companies" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "filters": [ {"field": "industry", "operator": "contains", "value": ["Banking"]}, {"field": "employee_count", "operator": "between", "value": [100, 1000]}, {"field": "country", "operator": "any_of", "value": ["United States"]} ], "page": 1, "limit": 50 }' ``` **Python** ```python response = requests.post( f"{BASE_URL}/search/companies", headers=HEADERS, json={ "filters": [ {"field": "industry", "operator": "contains", "value": ["Banking"]}, {"field": "employee_count", "operator": "between", "value": [100, 1000]}, {"field": "country", "operator": "any_of", "value": ["United States"]} ], "page": 1, "limit": 50 } ) ``` **TypeScript** ```typescript const response = await fetch(`${BASE_URL}/search/companies`, { method: "POST", headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json" }, body: JSON.stringify({ filters: [ { field: "industry", operator: "contains", value: ["Banking"] }, { field: "employee_count", operator: "between", value: [100, 1000] }, { field: "country", operator: "any_of", value: ["United States"] } ], page: 1, limit: 50 }), }); ``` ### Response ```json { "data": [ { "name": "First National Bank", "domain": "fnb.com", "website": "https://www.fnb.com", "description": "Regional banking institution...", "industry": "Banking", "industry_group": "Financial Services", "employee_count": "450", "revenue": "$100M-$500M", "founded_year": 1987, "phone": "+1-555-0200", "address": "100 Main St", "city": "Pittsburgh", "state": "Pennsylvania", "country": "United States", "zip": 15222, "linkedin_url": "https://linkedin.com/company/fnb", "linkedin_followers": "12500", "facebook_url": "https://facebook.com/fnb", "twitter_url": "https://twitter.com/fnb", "crunchbase_url": null, "logo_url": "https://logo.clearbit.com/fnb.com" } ], "pagination": { "page": 1, "limit": 50, "total": 89, "has_next": true } } ``` --- ## Save Contacts to List `POST /search/contacts/save` Search contacts and save matching results to a new list in your organization. Results are saved asynchronously. Returns `202 Accepted`. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `filters` | array | **Yes** | Filter conditions (AND logic). At least one filter is required. | | `list_title` | string | **Yes** | Title for the new list | | `max_results` | integer | No | Maximum results to save (default: 1,000, max: 10,000) | | `page` | integer | No | Page number (default: 1; ignored on save, kept for request symmetry) | | `limit` | integer | No | Results per page (default: 25; ignored on save, kept for request symmetry) | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/search/contacts/save" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "filters": [ {"field": "job_title", "operator": "any_of", "value": ["VP Sales", "Head of Sales"]}, {"field": "company_industry", "operator": "contains", "value": ["SaaS"]} ], "list_title": "Sales Leaders in SaaS", "max_results": 5000 }' ``` **Python** ```python response = requests.post( f"{BASE_URL}/search/contacts/save", headers=HEADERS, json={ "filters": [ {"field": "job_title", "operator": "any_of", "value": ["VP Sales", "Head of Sales"]}, {"field": "company_industry", "operator": "contains", "value": ["SaaS"]} ], "list_title": "Sales Leaders in SaaS", "max_results": 5000 } ) list_id = response.json()["data"]["list_id"] ``` **TypeScript** ```typescript const response = await fetch(`${BASE_URL}/search/contacts/save`, { method: "POST", headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json" }, body: JSON.stringify({ filters: [ { field: "job_title", operator: "any_of", value: ["VP Sales", "Head of Sales"] }, { field: "company_industry", operator: "contains", value: ["SaaS"] } ], list_title: "Sales Leaders in SaaS", max_results: 5000 }), }); const listId = (await response.json()).data.list_id; ``` ### Response ```json { "data": { "list_id": 42, "list_title": "Sales Leaders in SaaS", "estimated_total": 3200, "status": "processing" } } ``` > **Tip: What happens next?** > Saving moves contacts from graph8's open data index into your workspace. Once processing completes, the saved contacts become **your data** — you can find them via [`GET /contacts`](https://docs.graph8.com/developers/contacts/) with `list_id` filter, manage list membership via [`/lists`](https://docs.graph8.com/developers/lists/), and enrich them via [`/enrichment`](https://docs.graph8.com/developers/enrichment/). --- ## Save Companies to List `POST /search/companies/save` Search companies and save matching results to a new list. Returns `202 Accepted`. ### Request Body Same structure as [save contacts to list](#save-contacts-to-list) — uses company filter fields instead. ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/search/companies/save" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "filters": [ {"field": "industry", "operator": "any_of", "value": ["Financial Services"]}, {"field": "employee_count", "operator": "between", "value": [500, 5000]} ], "list_title": "Mid-Market Financial Services", "max_results": 2000 }' ``` **Python** ```python response = requests.post( f"{BASE_URL}/search/companies/save", headers=HEADERS, json={ "filters": [ {"field": "industry", "operator": "any_of", "value": ["Financial Services"]}, {"field": "employee_count", "operator": "between", "value": [500, 5000]} ], "list_title": "Mid-Market Financial Services", "max_results": 2000 } ) ``` **TypeScript** ```typescript const response = await fetch(`${BASE_URL}/search/companies/save`, { method: "POST", headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json" }, body: JSON.stringify({ filters: [ { field: "industry", operator: "any_of", value: ["Financial Services"] }, { field: "employee_count", operator: "between", value: [500, 5000] } ], list_title: "Mid-Market Financial Services", max_results: 2000 }), }); ``` ### Response ```json { "data": { "list_id": 43, "list_title": "Mid-Market Financial Services", "estimated_total": 890, "status": "processing" } } ``` > **Tip: What happens next?** > Saved companies become part of your workspace data and are accessible via [`GET /companies`](https://docs.graph8.com/developers/companies/) and [`/lists`](https://docs.graph8.com/developers/lists/). --- # Sequences > List, run, pause, and resume email sequences Source: https://docs.graph8.com/developers/sequences/ > **Tip: Using the SDK or CLI?** > The [JavaScript SDK](https://docs.graph8.com/developers/sdk/#sequences) provides `g8.sequences.list()` and `g8.sequences.add()`. The [CLI](https://docs.graph8.com/developers/cli/#g8-sequences) supports `g8 sequences` and `g8 add-to-sequence`. Sequences are automated multi-step email campaigns. Use these endpoints to list sequences, control their execution, and manage enrolled contacts. For authoring (create / preview / update steps / archive / analytics) see [Sequence Lifecycle](https://docs.graph8.com/developers/sequences-lifecycle/). --- ## List Sequences `GET /sequences` Returns all sequences with optional status filter and pagination. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `page` | integer | 1 | Page number | | `limit` | integer | 50 | Items per page (max 200) | | `status` | string | — | Filter by status (`drafted`, `live`, `paused`, `completed`) | | `sequence_kind` | string | — | Filter by kind — `cold_outbound` or `nurture`. Omit to list every kind. | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/sequences?status=live" \ -H "Authorization: Bearer $API_KEY" ``` **Python** ```python response = requests.get( f"{BASE_URL}/sequences", headers=HEADERS, params={"status": "live"} ) ``` ### Response ```json { "data": [ { "id": "seq_uuid", "name": "Q2 Outbound", "status": "live", "user_email": "you@org.com", "step_count": 3, "contact_count": 200, "sequence_kind": "cold_outbound", "associated_list_id": 12345, "created_at": "2026-02-15T10:00:00", "updated_at": "2026-02-20T14:30:00" } ], "pagination": { "page": 1, "limit": 50, "total": 1, "has_next": false } } ``` --- ## Get Sequence `GET /sequences/{sequence_id}` Returns detailed information about a sequence, including configuration flags. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `sequence_id` | string | Sequence ID | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/sequences/seq-abc" \ -H "Authorization: Bearer $API_KEY" ``` **Python** ```python response = requests.get( f"{BASE_URL}/sequences/seq-abc", headers=HEADERS ) ``` ### Response ```json { "data": { "id": "seq-abc", "name": "Q2 Outbound", "description": "Multi-touch outbound campaign for Q2 targets", "status": "live", "user_email": "rep@company.com", "associated_list_id": 12345, "finish_on_reply": true, "send_in_same_thread": false, "wait_for_new_contacts": false, "schedule_id": "sched_uuid", "appointment_id": 1, "textual_agent_name": "Outbound SDR", "voice_agent_name": null, "sequence_kind": "cold_outbound", "pinned_mailbox_id": null, "paused_at": null, "resumed_at": null, "created_at": "2026-02-15T10:00:00", "updated_at": "2026-02-20T14:30:00" } } ``` --- ## Run Sequence `POST /sequences/{sequence_id}/run` Start a drafted sequence. Transitions its status from `drafted` to `live`. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `sequence_id` | string | Sequence ID | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/sequences/seq-abc/run" \ -H "Authorization: Bearer $API_KEY" ``` **Python** ```python response = requests.post( f"{BASE_URL}/sequences/seq-abc/run", headers=HEADERS ) ``` ### Response ```json { "data": { "sequence_id": "seq-abc", "status": "live", "contacts_affected": 200 } } ``` --- ## Pause Sequence `POST /sequences/{sequence_id}/pause` Pause a live sequence. No request body required. Scheduled sends are held until the sequence is resumed. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `sequence_id` | string | Sequence ID | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/sequences/seq-abc/pause" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": { "sequence_id": "seq-abc", "status": "paused", "contacts_affected": 150 } } ``` --- ## Resume Sequence `POST /sequences/{sequence_id}/resume` Resume a paused sequence. No request body required. Held sends are rescheduled. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `sequence_id` | string | Sequence ID | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/sequences/seq-abc/resume" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": { "sequence_id": "seq-abc", "status": "live", "contacts_affected": 150 } } ``` --- ## List Sequence Contacts `GET /sequences/{sequence_id}/contacts` Returns contacts enrolled in a sequence with their current state. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `sequence_id` | string | Sequence ID | ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `page` | integer | 1 | Page number | | `limit` | integer | 50 | Items per page (max 200) | | `state` | string | — | Filter by contact state | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/sequences/seq-abc/contacts?state=active" \ -H "Authorization: Bearer $API_KEY" ``` **Python** ```python response = requests.get( f"{BASE_URL}/sequences/seq-abc/contacts", headers=HEADERS, params={"state": "active"} ) ``` ### Response ```json { "data": [ { "id": "sc-uuid", "contact_id": 101, "state": "waiting", "current_step_order": 1, "created_at": "2026-02-15T10:00:00", "updated_at": "2026-02-18T09:00:00" } ], "pagination": { "page": 1, "limit": 50, "total": 1, "has_next": false } } ``` --- ## Add Contacts to Sequence `POST /sequences/{sequence_id}/contacts` Add contacts to a live or drafted sequence. Returns `201 Created`. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `sequence_id` | string | Sequence ID | ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `contact_ids` | integer[] | Yes | Contact IDs to add | | `list_id` | integer | Yes | List ID the contacts belong to | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/sequences/seq-abc/contacts" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"contact_ids": [101, 102], "list_id": 12345}' ``` **Python** ```python response = requests.post( f"{BASE_URL}/sequences/seq-abc/contacts", headers=HEADERS, json={"contact_ids": [101, 102], "list_id": 12345} ) ``` ### Response ```json { "data": { "sequence_id": "seq-abc", "status": "contacts_added", "contacts_affected": 2 } } ``` --- # Sequence Lifecycle > Create, preview, update, archive, and read analytics for sequences Source: https://docs.graph8.com/developers/sequences-lifecycle/ The [Sequences](https://docs.graph8.com/developers/sequences/) page covers basic operations (list, get, run, pause/resume, manage enrolled contacts). This page covers the **lifecycle** endpoints used to author and inspect a sequence end-to-end - typically what a CLI or build tool needs. All endpoints share the `/sequences` prefix and require a Bearer API key. | Endpoint | Method | Description | |----------|--------|-------------| | `/sequences` | POST | Create a sequence (with optional steps + channels) | | `/sequences/{id}/preview` | GET | Read sequence + steps + channels (no enrollment) | | `/sequences/{id}` | PATCH | Update sequence metadata | | `/sequences/{id}/steps/{step_id}` | PATCH | Update a single step | | `/sequences/{id}/pause` | POST | Pause a live sequence | | `/sequences/{id}/resume` | POST | Resume a paused sequence | | `/sequences/{id}/analytics` | GET | Comprehensive analytics report | | `/sequences/{id}` | DELETE | Soft-delete (archive) a sequence | A sequence in a **transitional** status (`scheduling`, `pausing`, `resuming`, `terminating`) cannot be PATCHed - you'll get `409 Conflict`. Wait for the operation to settle. For machine-readable schemas see the [interactive API docs](https://be.graph8.com/api/v1/docs). ### Enum reference - **`step_type`** (case-insensitive): `EMAIL | PHONE | SMS | WHATSAPP | HEYREACH | MANUAL_DIALER`. The alias `linkedin` maps to `HEYREACH`. For Unipile-based LinkedIn pass `UNIPILE` explicitly. - **`input_type`**: `ON_DEMAND | MANUAL_TEMPLATE | AI_GENERATED_TEMPLATE`. Aliases: `template` / `manual` → `MANUAL_TEMPLATE`; `ai` / `ai_template` → `AI_GENERATED_TEMPLATE`. - **`channel_type`**: `SMTP | GMAIL | INBOXKIT | PHONE | LINKEDIN | SMS | WHATSAPP`. ### `step_data` by channel `step_data` is a free-form object whose meaningful keys depend on `step_type`. Unknown keys are silently ignored — a key that belongs to another channel has no effect. Pick the keys for your step's channel from the sections below. > **Caution: LinkedIn copy does NOT use `body` / `subject`** > Those are EMAIL-only keys. A HEYREACH step that only sets `body` is accepted but sends no copy. Use the `linkedin_*` keys described below. **EMAIL** ```json { "subject": "...", "body": "...", "instructions": "...", "email_type": "plain" } ``` | Key | Type | Description | |-----|------|-------------| | `subject` | string | Email subject line | | `body` | string | Email body — supports `{{merge_tokens}}` | | `instructions` | string | Used when `input_type` is `AI_GENERATED_TEMPLATE` | | `email_type` | string | `"plain"` (default) or `"html"` | **HEYREACH / LinkedIn** `step_type: "HEYREACH"` (or the `linkedin` alias). `linkedin_action_type` is **required** — a LinkedIn step without it is accepted at create time but fails at send time. | `linkedin_action_type` | Copy / config keys | |---|---| | `CONNECTION_REQUEST` | `linkedin_connection_message` (string), `linkedin_connection_fallback` (optional string), `linkedin_connection_withdraw_days` (optional integer, default `25`) | | `MESSAGE` | `linkedin_message_text` (string) — supports `{{merge_tokens}}` | | `INMAIL` | `linkedin_inmail_subject` (string), `linkedin_inmail_body` (string) | | `FOLLOW` | — (no copy) | | `VIEW_PROFILE` | — (no copy) | | `LIKE_POST` | `linkedin_like_reaction_type` (optional integer: `0`=Like, `1`=Celebrate, `2`=Support, `3`=Love, `4`=Insightful, `5`=Funny; default `1`), `linkedin_like_react_before_days` (optional integer, default `3`) | ```json { "linkedin_action_type": "MESSAGE", "linkedin_message_text": "Hi {{first_name}}, ..." } ``` For AI-generated LinkedIn copy: set `input_type: "AI_GENERATED_TEMPLATE"` on the step, plus `step_data.linkedin_input_mode: "instructions"` and `step_data.linkedin_instructions: "<prompt>"`. Use `linkedin_input_mode: "manual"` with the explicit copy keys above for fixed text. > **Note: HeyReach vs Unipile** > The `linkedin` alias maps to HeyReach. For Unipile-based LinkedIn, pass `step_type: "UNIPILE"` explicitly — it uses the same `linkedin_*` `step_data` keys and the same `linkedin_action_type` requirement. **PHONE / MANUAL_DIALER** ```json { "dial_dnc": false, "phone_options": ["mobile", "direct"] } ``` | Key | Type | Description | |-----|------|-------------| | `dial_dnc` | boolean | Dial Do-Not-Call numbers (default `false`) | | `phone_options` | string[] | Phone types to dial | **SMS / WHATSAPP** ```json { "message_body": "...", "media_urls": ["https://..."], "whatsapp_template_sid": "...", "whatsapp_template_variables": {} } ``` | Key | Type | Description | |-----|------|-------------| | `message_body` | string | Message text — supports `{{merge_tokens}}` | | `media_urls` | string[] | WhatsApp media attachment URLs | | `whatsapp_template_sid` | string | WhatsApp Business template SID | | `whatsapp_template_variables` | object | Values for the template variables | --- > **Note: Ids in these examples** > The request examples use shell variables (`$SEQUENCE_ID` and `$STEP_ID`) for ids. Set them from the `id` fields returned when you create or list sequences and their steps. Ids shown in sample responses (`seq_abc`, `stp_1`) are illustrative only and do not exist. ## Create Sequence `POST /sequences` Create a sequence in `drafted` status. You can optionally include `steps[]` and `channels[]` in the same call. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | string | **Yes** | Sequence name | | `user_email` | string | **Yes** | Owner email address | | `description` | string | No | Free-form description | | `finish_on_reply` | boolean | No (default `true`) | Stop contacting on reply | | `send_in_same_thread` | boolean | No (default `false`) | Send follow-ups in the same email thread | | `wait_for_new_contacts` | boolean | No (default `false`) | Keep sequence waiting for new enrollments | | `associated_list_id` | integer | No | Contact list to associate | | `campaign_id` | string | No | AI Studio campaign UUID to link | | `sequence_kind` | string | No (default `"cold_outbound"`) | `"cold_outbound"` or `"nurture"`. Immutable after creation. | | `pinned_mailbox_id` | integer | No | Required when `sequence_kind` is `"nurture"`; ignored otherwise. Immutable after creation. | | `steps` | object[] | No | Step configs — see step config table below | | `channels` | object[] | No | Channel configs — see channel config table below | #### Step config | Field | Type | Required | Description | |-------|------|----------|-------------| | `step_order` | integer | **Yes** | Order within the sequence (1-indexed) | | `step_type` | string | **Yes** | `EMAIL`, `PHONE`, `SMS`, `WHATSAPP`, `HEYREACH`, `MANUAL_DIALER` (or alias `linkedin`) | | `input_type` | string | No (default `ON_DEMAND`) | `ON_DEMAND`, `MANUAL_TEMPLATE`, or `AI_GENERATED_TEMPLATE` | | `time_interval` | integer | No (default `0`) | Delay in seconds after the previous step | | `step_data` | object | No | Step content — see `step_data` by channel above. LinkedIn steps use `linkedin_*` keys, not `body`. | #### Channel config | Field | Type | Required | Description | |-------|------|----------|-------------| | `channel_id` | integer | **Yes** | Mailbox or phone resource ID | | `channel_value` | string | **Yes** | The address or number (e.g. `rep@org.com`) | | `channel_type` | string | **Yes** | `SMTP`, `GMAIL`, `INBOXKIT`, `PHONE`, `LINKEDIN`, `SMS`, or `WHATSAPP` | | `channel_data` | object | No | Optional metadata | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/sequences" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Q2 Outbound", "user_email": "rep@graph8.com", "associated_list_id": 5, "steps": [ { "step_order": 1, "step_type": "EMAIL", "input_type": "MANUAL_TEMPLATE", "time_interval": 0, "step_data": {"subject": "Quick intro", "body": "Hi {{first_name}}..."} } ], "channels": [ { "channel_id": 12, "channel_value": "rep@graph8.com", "channel_type": "SMTP" } ] }' ``` ### Response `201 Created` ```json { "data": { "id": "seq_abc", "name": "Q2 Outbound", "status": "drafted", "created_at": "2026-04-15T10:00:00Z" } } ``` > **Note: Nurture sequences** > A nurture is an ordinary sequence with `sequence_kind: "nurture"` — the same step types and structure as a cold-outbound sequence, created through this same endpoint. There is no separate `/nurtures` endpoint. Key differences: > > - `pinned_mailbox_id` is **required**, and every email channel must use that one mailbox (no multi-mailbox load-balancing). A nurture with a missing or mismatched mailbox returns **422**. > - Email steps send at up to **500/day per mailbox** (vs the standard cold-outbound cap). Non-email channels keep their standard caps. > - Nurture creation is gated by the **`ENABLE_NURTURE`** feature flag. > - `sequence_kind` and `pinned_mailbox_id` are **immutable** — set once at creation, never patchable. > > Enrollment, run/pause/resume, analytics, and contact listing are identical to cold-outbound sequences. ### Errors | Status | Meaning | |--------|---------| | `400` | Invalid step or channel config | | `422` | Nurture sequence missing or mismatched `pinned_mailbox_id` | --- ## Preview Sequence `GET /sequences/{sequence_id}/preview` Read-only view of a sequence with all steps and channels. Does not enroll anyone. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `sequence_id` | string | Sequence UUID | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/sequences/$SEQUENCE_ID/preview" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": { "id": "seq_abc", "name": "Q2 Outbound", "status": "drafted", "description": null, "steps": [ { "id": "stp_1", "step_order": 1, "step_type": "EMAIL", "input_type": "MANUAL_TEMPLATE", "time_interval": 0, "step_data": {"subject": "Quick intro", "body": "..."} } ], "channels": [ {"id": "ch_1", "channel_id": 12, "channel_value": "rep@graph8.com", "channel_type": "SMTP"} ] } } ``` --- ## Update Sequence `PATCH /sequences/{sequence_id}` Update sequence metadata. Send only the fields you want to change. Returns `409` if the sequence is in a transitional status (`scheduling`, `pausing`, `resuming`, `terminating`). ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `sequence_id` | string | Sequence UUID | ### Request Body All fields are optional. | Field | Type | Description | |-------|------|-------------| | `name` | string | Sequence name | | `description` | string | Description | | `is_shared` | boolean | Share with the org | | `finish_on_reply` | boolean | Finish on reply | | `send_in_same_thread` | boolean | Same-thread follow-ups | | `wait_for_new_contacts` | boolean | Wait for new contacts | | `schedule_id` | string | UUID from `GET /schedules` | | `appointment_id` | integer | Integer from `GET /event-types` | | `textual_agent_name` | string | Agent name from `GET /voice/dialer/agents` (since 0.9.7) | | `voice_agent_name` | string | Same agent picker — pass the same value as `textual_agent_name` for the common "one agent, all channels" case | > **Caution: `sequence_kind` and `pinned_mailbox_id` are immutable** > These fields are intentionally not accepted here. They are set once at creation (including the defaults `cold_outbound` / `null`). To change either, delete and recreate the sequence. ### Example **cURL** ```bash curl -X PATCH "https://be.graph8.com/api/v1/sequences/$SEQUENCE_ID" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "Q2 Outbound v2", "send_in_same_thread": true}' ``` ### Errors | Status | Meaning | |--------|---------| | `404` | Sequence not found | | `409` | Sequence is in a transitional status (`scheduling`, `pausing`, `resuming`, `terminating`) | --- ## Update Step `PATCH /sequences/{sequence_id}/steps/{step_id}` Update a single step. The same `409` rule applies if the parent sequence is transitioning. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `sequence_id` | string | Sequence UUID | | `step_id` | string | Step UUID | ### Request Body All fields are optional. | Field | Type | Description | |-------|------|-------------| | `step_data` | object | Step content — see `step_data` by channel above. **Replaced wholesale, not merged.** Send the complete object every time; any key you omit is dropped. | | `time_interval` | integer | Delay in seconds after the previous step | | `step_type` | string | `EMAIL`, `PHONE`, `SMS`, `WHATSAPP`, `HEYREACH`, `MANUAL_DIALER` | | `input_type` | string | `ON_DEMAND`, `MANUAL_TEMPLATE`, or `AI_GENERATED_TEMPLATE` | > **Caution: `step_data` is replaced wholesale, not merged** > Send the **complete** `step_data` object every time — any key you omit is dropped. For example, patching only `linkedin_message_text` on a LinkedIn step will erase its `linkedin_action_type` and break the step. Re-send all keys the step needs. ### Example **cURL** ```bash curl -X PATCH "https://be.graph8.com/api/v1/sequences/$SEQUENCE_ID/steps/$STEP_ID" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"step_data": {"subject": "Quick intro v2", "body": "Updated..."}}' ``` --- ## Pause / Resume `POST /sequences/{sequence_id}/pause` `POST /sequences/{sequence_id}/resume` Pause a live sequence (or resume a paused one). Returns the number of contacts affected. ### Response ```json { "data": { "sequence_id": "seq_abc", "status": "paused", "contacts_affected": 150 } } ``` --- ## Analytics `GET /sequences/{sequence_id}/analytics` Returns a comprehensive analytics report - overview KPIs, performance over time, engagement breakdown, contact distribution, per-step metrics, and sender distribution. The shape mirrors the internal `SequenceReports` model. ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/sequences/$SEQUENCE_ID/analytics" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": { "overview": {"sent": 320, "opened": 180, "replied": 22}, "performance": {"open_rate": 0.56, "reply_rate": 0.07}, "engagement": {"by_day": [...]}, "timeline": [...], "contact_distribution": {"active": 150, "completed": 100, "stopped": 70}, "step_breakdown": [ {"step_id": "stp_1", "sent": 320, "opened": 180} ], "step_creation_methods": [...], "sender_distribution": [...] } } ``` The full schema lives at [/api/v1/docs](https://be.graph8.com/api/v1/docs) under `SequenceAnalyticsResponse`. --- ## Delete (Archive) `DELETE /sequences/{sequence_id}` Soft-deletes a sequence by setting `is_archived = true`. Already-archived sequences return `400`. Sequences in certain non-terminal states cannot be archived and return `409`. ### Example **cURL** ```bash curl -X DELETE "https://be.graph8.com/api/v1/sequences/$SEQUENCE_ID" \ -H "Authorization: Bearer $API_KEY" ``` ### Errors | Status | Meaning | |--------|---------| | `400` | Sequence is already archived | | `404` | Sequence not found | | `409` | Sequence cannot be archived in its current state | --- # Skills (LLM + API) > Create, manage, validate, and execute reusable LLM and API skills in your graph8 workspace Source: https://docs.graph8.com/developers/skills/ Skills are reusable building blocks — either an LLM prompt template or an HTTP API wrapper — that workflows compose into multi-step automation runs. Single-brace `{variable}` placeholders are interpolated at execution time; double-brace `{{...}}` patterns are ignored. --- ## List Skills `GET /skills` Returns skills in your workspace, filtered by the query parameters below. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `runtime_type` | string | — | Filter by runtime: `llm` or `api` | | `enabled` | boolean | — | Filter by enabled state | | `category` | string | — | Filter by category | | `object_type` | string | — | Filter by associated object: `Company`, `Contact`, `Deal`, or `Campaign` | | `include_system` | boolean | `false` | When `true`, includes system-managed skills | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/skills?runtime_type=llm&enabled=true" \ -H "Authorization: Bearer $API_KEY" ``` --- ## Get Skill `GET /skills/{action_id}` Returns the full skill record, including `llm_config` (for LLM skills) or `api_config` (for API skills). ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `action_id` | string | Skill (action) UUID | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/skills/sk_uuid" \ -H "Authorization: Bearer $API_KEY" ``` --- ## Get Skill Variables `GET /skills/{action_id}/variables` Extracts the input variable names from a skill's prompt or API template. Only single-brace `{var}` references are returned; `{{...}}` patterns are ignored. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `action_id` | string | Skill (action) UUID | ### Response ```json { "action_id": "sk_uuid", "runtime_type": "llm", "variables": ["name", "company"], "total": 2 } ``` --- ## List Models `GET /skills/models` Returns the LLM models available for use in LLM skills. ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/skills/models" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "models": [ { "id": "gpt-4o", "provider": "openai", "label": "GPT-4o" }, { "id": "claude-sonnet-4-5", "provider": "anthropic", "label": "..." } ], "total": 9 } ``` --- ## List Templates `GET /skills/templates` Returns built-in skill templates filtered to `llm` and `api` runtimes only. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `category` | string | — | Optional. Filter templates by category | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/skills/templates" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "actions": [ { "id": "tpl_uuid", "name": "Summarize meeting", "runtime_type": "llm", "category": "summarization" } ], "total_count": 1 } ``` --- ## Create Skill `POST /skills` → `201 Created` Creates a new skill. The required fields differ by `runtime_type`. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | string | **Yes** | Display name for the skill | | `description` | string | No | Optional description | | `runtime_type` | string | **Yes** | `llm` or `api` | | `object_type` | string | No | Associated object: `Company`, `Contact`, `Deal`, `Campaign`, etc. | | `category` | string | No | Category label | | `requires_approval` | boolean | No | Whether execution requires approval (default `false`) | | `approval_type` | string | No | Approval flow type (nullable) | | `knowledge_scope` | string | No | Knowledge scope (nullable) | | `llm_config` | object | **Yes** when `runtime_type=llm` | LLM configuration (see below) | | `api_config` | object | **Yes** when `runtime_type=api` | API configuration (see below) | #### `llm_config` fields | Field | Type | Description | |-------|------|-------------| | `model` | string | LLM model ID (e.g. `claude-sonnet-4-5`) | | `prompt_template` | string | Prompt with `{variable}` placeholders | | `temperature` | number | Sampling temperature | #### `api_config` fields | Field | Type | Description | |-------|------|-------------| | `endpoint` | string | URL with optional `{variable}` placeholders | | `method` | string | HTTP method (e.g. `GET`, `POST`) | | `headers` | object | Request headers; values may contain `{variable}` placeholders | | `query_params` | object | Query parameters | | `body_template` | string | Request body template with `{variable}` placeholders | ### Example — LLM skill **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/skills" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Summarize meeting", "description": "Summarize a meeting transcript into key points", "runtime_type": "llm", "object_type": "Meeting", "requires_approval": false, "llm_config": { "model": "claude-sonnet-4-5", "prompt_template": "Summarize: {transcript}", "temperature": 0.2 } }' ``` ### Example — API skill **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/skills" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Lookup zip", "runtime_type": "api", "api_config": { "endpoint": "https://api.example.com/zip/{zip}", "method": "GET", "headers": { "Authorization": "Bearer {api_key}" }, "query_params": {}, "body_template": "" } }' ``` --- ## Update Skill `PUT /skills/{action_id}` Partial update. Supply only the fields you want to change. The `runtime_type` field is silently dropped server-side — you cannot change a skill's runtime after creation. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `action_id` | string | Skill (action) UUID | ### Example **cURL** ```bash curl -X PUT "https://be.graph8.com/api/v1/skills/sk_uuid" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Summarize meeting v2", "llm_config": { "temperature": 0.1 } }' ``` --- ## Validate Skill `POST /skills/validate` Validates a prompt template string or a full skill definition without saving. Returns extracted variables and any warnings. ### Request Body Two accepted shapes: **Template string validation:** ```json { "template": "Hello {name}" } ``` **Full definition validation:** ```json { "runtime_type": "llm", "llm_config": { "model": "claude-sonnet-4-5", "prompt_template": "Hello {name}", "temperature": 0.2 } } ``` ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/skills/validate" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "template": "Hello {name}" }' ``` ### Response ```json { "valid": true, "variables": ["name"], "variable_count": 1, "warnings": [] } ``` --- ## Create Skill from Template `POST /skills/from-template` → `201 Created` Creates a new skill from a built-in template, with optional overrides. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `template_id` | string | **Yes** | UUID of the built-in template | | `name` | string | **Yes** | Display name for the new skill | | `overrides` | object | No | Fields to override from the template defaults | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/skills/from-template" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "template_id": "tpl_uuid", "name": "My Skill", "overrides": { "llm_config": { "temperature": 0.5 } } }' ``` --- ## Create Skill from Workflow Node `POST /skills/from-node` → `201 Created` Lifts an existing workflow action node into a standalone reusable skill. The source node must be of type `action`. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `workflow_id` | string | **Yes** | UUID of the source workflow | | `node_id` | string | **Yes** | Node ID within the workflow — must be an `action` node | | `name` | string | No | Display name for the new skill. Defaults to `"<source node name> (Copy)"` | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/skills/from-node" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "workflow_id": "wf_uuid", "node_id": "n1", "name": "Draft reply skill" }' ``` --- ## Execute Skill `POST /skills/{action_id}/execute` Runs the skill with the provided input data. Use for testing and preview; production execution happens inside workflow runs. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `action_id` | string | Skill (action) UUID | ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `input_data` | object | No | Key-value pairs for `{variable}` placeholders in the skill | | `triggered_by` | string | No | User identifier. Defaults to the API key owner | | `trigger_type` | string | No | Trigger context. Defaults to `"manual"` | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/skills/sk_uuid/execute" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "input_data": { "name": "Jane" }, "triggered_by": "user_x", "trigger_type": "manual" }' ``` --- # Snippet > Retrieve the org-level tracking snippet and embeddable lead-capture form configuration Source: https://docs.graph8.com/developers/snippet/ The snippet endpoints return the org-level tracking code and embeddable lead-capture form that you place on your website. They use the organisation's first stream, so no stream ID is required. --- ## Get Tracking Snippet `GET /snippet` Returns the same payload shape as `GET /repos/{repo_id}/snippet` but automatically resolves to the organisation's first stream. Use this when you do not need to target a specific stream. ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/snippet" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": { "write_key": "wk_abc123...", "tracking_host": "https://t.graph8.com", "domains": ["example.com"], "react_snippet": "import { G8Analytics } from '@graph8/nextjs'\n...", "script_tag": "<script src=\"https://t.graph8.com/p.js\" data-write-key=\"...\" data-tracking-host=\"...\" defer></script>", "config": { "write_key": "wk_abc123...", "tracking_host": "https://t.graph8.com", "project_id": "proj_xyz", "domains": ["example.com"] } } } ``` | Field | Type | Description | |-------|------|-------------| | `write_key` | string | Public write key used to authenticate tracking events | | `tracking_host` | string | Base URL that the snippet sends events to | | `domains` | array of strings | Domains associated with this stream | | `react_snippet` | string | Ready-to-use React/Next.js import snippet | | `script_tag` | string | HTML `script` tag for non-React sites | | `config` | object | Machine-readable config object for custom integrations | | `config.write_key` | string | Write key (same as top-level `write_key`) | | `config.tracking_host` | string | Tracking host (same as top-level `tracking_host`) | | `config.project_id` | string | Internal project identifier | | `config.domains` | array of strings | Domains list (same as top-level `domains`) | --- ## Get Form Snippet `GET /snippet/form` Returns the embeddable HTML lead-capture form along with its configuration — write key, tracking host, field list, and event name — ready to drop into any page. ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/snippet/form" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": { "html": "<form id=\"g8-lead-form\">...</form><script>...</script>", "write_key": "...", "tracking_host": "https://t.graph8.com", "fields": ["name", "email", "company"], "event_name": "form_submitted", "notes": "..." } } ``` | Field | Type | Description | |-------|------|-------------| | `html` | string | Ready-to-embed HTML string containing the form markup and inline script | | `write_key` | string | Public write key used by the snippet to authenticate tracking events | | `tracking_host` | string | Base URL that the snippet sends events to | | `fields` | array of strings | Ordered list of field names rendered in the form | | `event_name` | string | Event name fired when the form is submitted | | `notes` | string | Optional implementation notes or instructions | > **Note:** > The `X-Org-Id` header is optional and defaults to the organisation associated with your API key. Pass it explicitly only when targeting a different org. --- # Tasks > Create and manage tasks with typed related records and source context Source: https://docs.graph8.com/developers/tasks/ Tasks let you create follow-up reminders and action items linked to one or more business records. --- ## List Contact Tasks `GET /contacts/{contact_id}/tasks` Returns tasks linked to a specific contact. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `status` | string | — | Filter by status: `open` or `completed` | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/contacts/12345/tasks?status=open" \ -H "Authorization: Bearer $API_KEY" ``` **Python** ```python response = requests.get( f"{BASE_URL}/contacts/12345/tasks", headers=HEADERS, params={"status": "open"} ) ``` ### Response ```json { "data": [ { "id": "task_uuid", "entity_type": "contact", "entity_id": "101", "title": "Follow up", "description": "...", "due_date": "2026-06-01T17:00:00Z", "assignee_id": "user_x", "assignee_name": "John", "status": "open", "priority": 2, "task_type": "...", "created_by": "user_y", "created_at": "...", "updated_at": "...", "parent_task_id": null, "subtask_sort_order": 0, "subtask_count": 0, "completed_subtask_count": 0 } ] } ``` --- ## List All Tasks `GET /tasks` Returns the tasks the caller can read, with optional filters and authoritative pagination. Use `pagination.has_next` and advance the requested `offset` to read beyond the first page. `pagination.total` is the filtered total, not the page length. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `status` | string | — | Filter by status | | `priority` | integer | — | Filter by priority (0=none, 1=urgent, 2=high, 3=normal, 4=low) | | `assignee_id` | string | — | Filter by assignee | | `search` | string | — | Partial match on task title | | `entity_type` | string | — | Related record type; pair with `entity_id` | | `entity_id` | string | — | Exact related record ID | | `limit` | integer | 100 | Page size, from 1 to 200 | | `offset` | integer | 0 | Number of matching tasks to skip | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/tasks?status=open&priority=2&assignee_id=user_x" \ -H "Authorization: Bearer $API_KEY" ``` --- ## Create Task `POST /contacts/{contact_id}/tasks` ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `title` | string | Yes | Task title | | `description` | string | No | Task details | | `due_date` | string | No | ISO 8601 due date | | `assignee_id` | string | No | User ID to assign | | `priority` | integer | No | 0=none, 1=urgent, 2=high, 3=normal, 4=low | | `parent_task_id` | string | No | ID of parent task — makes this a subtask (one level deep) | | `subtask_sort_order` | integer | No | Sort order among sibling subtasks | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/contacts/12345/tasks" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"title": "Send proposal", "due_date": "2026-03-05", "priority": 2}' ``` ### Response `201 Created` Returns the full task object — same shape as the items in `GET /contacts/{contact_id}/tasks`. --- ## Update Task `PATCH /tasks/{task_id}` Supports partial updates. Send only the fields you want to change. ### Request Body All fields are optional. | Field | Type | Description | |-------|------|-------------| | `title` | string | Task title | | `description` | string | Task details | | `due_date` | string | ISO 8601 due date | | `assignee_id` | string | User ID to assign | | `status` | string | Task status (`open` or `completed`) | | `priority` | integer | 0=none, 1=urgent, 2=high, 3=normal, 4=low | | `parent_task_id` | string | ID of parent task (or `null` to remove subtask relationship) | | `subtask_sort_order` | integer | Sort order among sibling subtasks | ### Example **cURL** ```bash curl -X PATCH "https://be.graph8.com/api/v1/tasks/task_uuid" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"status": "completed", "priority": 1}' ``` ## Get an exact task `GET /tasks/{task_id}` returns one task, including subtasks that do not appear in the top-level list. Missing tasks and tasks outside the caller's access return HTTP 404. ## Create a task with multiple records `POST /tasks` accepts the usual task fields plus `records`, an array of objects containing `entity_type` and `entity_id`. Supported types are `contact`, `company`, `deal`, `team_member`, `lead`, and `application`. The first record is the primary association. Alternatively, supply a primary `entity_type` and `entity_id` pair. ```json { "title": "Review the lead with its company", "records": [ { "entity_type": "lead", "entity_id": "789" }, { "entity_type": "company", "entity_id": "456" } ] } ``` For a restricted API key, creating a contact-linked task requires both `tasks:write` and `contacts:read`. A key with task-write access alone receives HTTP 404 for an unavailable contact. Other newly attached records require their corresponding read access. Linking a record does not modify that record, so source-record write access is not required. Legacy keys without an explicit scope list retain their existing behavior. Use the native lead ID and application UUID. A contact ID is not interchangeable with a lead ID; an application ID identifies one submission, not the candidate's first application. Task responses preserve `entity_type`, `entity_id`, `entity_label`, `links`, and source context such as `source_url`, `source_meeting_id`, and `source_transcript_id`. Each related link includes its availability and canonical record ID when accessible. A task-read grant does not grant access to its records: the API key also needs the applicable record-read scopes. Unavailable records do not expose labels. When using `PATCH /tasks/{task_id}`, omit `records` to leave existing associations unchanged. Supply `records` only when deliberately replacing the association list; retain every link you intend to keep. Ordinary title or due-date updates preserve unfamiliar record types. The MCP tools `g8_create_task`, `g8_update_task`, `g8_get_task`, and `g8_get_tasks` retain these typed records and source fields. Paginated MCP results expose `total`, `offset`, `limit`, and `has_next`; do not treat one capped page as the full task count. ## Assign an executor without starting work `PUT /tasks/{task_id}/assignment` requires `tasks:write` and the same task and agent access checks as the browser. Send `agent_id` (an eligible Work profile UUID, or `null` for human execution) and `expected_updated_at` from the current task. A stale version returns a conflict. Agent assignment requires an active human assignee in the organization. The response reports `execution_started: false`. ## Explicitly start execution `POST /tasks/{task_id}/start` requires `tasks:run` and a UUID `Idempotency-Key` header. Reuse that key for retries of the same logical request. Assignment never implies execution. Active runs cannot be duplicated or reassigned, and completed tasks must be reopened before a new run. A dispatch that cannot be confirmed returns 503; retry with the same key to check the existing run. ## Read execution history `GET /tasks/{task_id}/execution` requires `tasks:read` and permission to view the task. It returns the latest 50 runs, including distinct lifecycle states and timestamps. `details_available` indicates whether private progress, next action and session links are visible to the caller. Run completion does not automatically approve operator work or complete the CRM task. MCP clients use `g8_assign_task_executor`, `g8_start_task_execution` and `g8_get_task_execution` for these operations. Pass a stable UUID `request_id` to Start and retain it through uncertain retries. Ordinary update tools cannot approve or reject operator approval tasks. --- # Usage & Metering > Read your organization's credit balance and transaction ledger over the API Source: https://docs.graph8.com/developers/usage/ Every metered operation in graph8 (enrichment, AI calls, dialer minutes, sends, …) draws down your organization's **credit balance**. The Usage API lets you read that balance and the full transaction ledger programmatically — so you can build budget alerts, cost dashboards, or reconcile spend without leaving your own tooling. All endpoints are authenticated with an org API key: `Authorization: Bearer <api_key>`. They are **read-only**. --- ## Get current balance `GET /usage` Returns the organization's credit balance, held credits, available credits, and lifetime totals. **cURL** ```bash curl "https://be.graph8.com/api/v1/usage" \ -H "Authorization: Bearer $API_KEY" ``` **Python** ```python import requests resp = requests.get( "https://be.graph8.com/api/v1/usage", headers={"Authorization": f"Bearer {API_KEY}"}, ) balance = resp.json()["data"] print(balance["available_credits"]) ``` **TypeScript** ```ts const resp = await fetch("https://be.graph8.com/api/v1/usage", { headers: { Authorization: `Bearer ${apiKey}` }, }); const { data } = await resp.json(); console.log(data.available_credits); ``` ### Response ```json { "data": { "customer_id": "org_aBc123", "credits": 10000, "held_credits": 150, "available_credits": 9850, "total_earned": 50000, "total_used": 40000 } } ``` | Field | Description | |-------|-------------| | `credits` | Total credit balance | | `held_credits` | Credits reserved by in-flight holds (e.g. dialer minutes) | | `available_credits` | Spendable now (`credits` − `held_credits`) | | `total_earned` / `total_used` | Lifetime totals | --- ## List transactions `GET /usage/transactions` Returns the credit-transaction ledger, most recent first, paginated. ### Query parameters | Param | Default | Description | |-------|---------|-------------| | `page` | `1` | Page number (1-indexed) | | `limit` | `50` | Items per page (max `200`) | **cURL** ```bash curl "https://be.graph8.com/api/v1/usage/transactions?page=1&limit=50" \ -H "Authorization: Bearer $API_KEY" ``` **Python** ```python import requests resp = requests.get( "https://be.graph8.com/api/v1/usage/transactions", headers={"Authorization": f"Bearer {API_KEY}"}, params={"page": 1, "limit": 50}, ) body = resp.json() for tx in body["data"]: print(tx["created_at"], tx["service"], tx["amount"]) print("more pages:", body["pagination"]["has_next"]) ``` **TypeScript** ```ts const resp = await fetch( "https://be.graph8.com/api/v1/usage/transactions?page=1&limit=50", { headers: { Authorization: `Bearer ${apiKey}` } }, ); const body = await resp.json(); for (const tx of body.data) console.log(tx.created_at, tx.service, tx.amount); ``` ### Response ```json { "data": [ { "id": "9f2c…", "type": "usage", "amount": -10, "service": "ai_enrichment", "quantity": 1, "llm_tier": "g8_t2", "description": "Bulk enrichment", "created_at": "2026-06-29T12:00:00Z" } ], "pagination": { "page": 1, "limit": 50, "total": 1240, "has_next": true } } ``` | Field | Description | |-------|-------------| | `amount` | Credit delta — **negative = spend**, positive = top-up/grant | | `service` | The service charged (e.g. `ai_enrichment`, `dialer`, `web_visitor`). Group by this for a per-feature cost breakdown. | | `llm_tier` | `g8_t1` / `g8_t2` / `g8_t3` when the charge was an LLM call | | `type` | Transaction type (`usage`, `purchase`, …) | > **Tip: Cost breakdown** > There's no server-side category rollup yet — aggregate `amount` grouped by `service` client-side to get spend-per-feature. A `by-category` summary on `GET /usage` is on the roadmap. --- ## Errors Failures return the standard [error envelope](https://docs.graph8.com/developers/errors/) with a `request_id`. A `404` means the org has no credit customer record yet (no usage incurred). --- # Voice & Dialer > REST reference for graph8's parallel dialer — sessions, calls, transcripts, AI grading, agents, and phone numbers Source: https://docs.graph8.com/developers/voice/ > **Caution: Live calls** > `POST /voice/dialer/sessions/{session_id}/resume` places **real outbound calls** and consumes credits at 20 per minute of call audio. Always confirm intent before invoking write endpoints. > **Note: Auth** > Every request requires `Authorization: Bearer $API_KEY`. The `X-Org-Id` is bound to the API key automatically. The Voice API exposes graph8's parallel dialer — session management, real-time call records, transcripts, AI grading, agent rosters, and phone number pools. All endpoints are under `/api/v1/voice/dialer/`. --- ## List Dialer Sessions `GET /voice/dialer/sessions` Returns a paginated list of dialer sessions for the org. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `page` | integer | `1` | Page number (1-indexed) | | `page_size` | integer | `50` | Items per page (max 200) | | `status` | string | — | Filter by status — comma-separated values (e.g. `ACTIVE,PAUSED`) | | `user_email` | string | — | Filter by the SDR's email address | | `name` | string | — | Filter by session name | | `campaign_id` | string | — | Filter by campaign ID | | `list_id` | string | — | Filter by list ID | | `list_name` | string | — | Filter by list name | | `date_from` | string (ISO) | — | Return sessions created on or after this date | | `date_to` | string (ISO) | — | Return sessions created on or before this date | | `sort_by` | string | — | Field to sort by | | `sort_order` | string | `desc` | Sort direction — `asc` or `desc` | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/voice/dialer/sessions?status=ACTIVE,PAUSED&page_size=20" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": { "sessions": [ { "session_id": "...", "status": "PAUSED", "user_id": "...", "user_email": "...", "org_id": "...", "from_phone": "+1...", "campaign_id": null, "campaign_builder_campaign_id": null, "name": "Q3 Outbound", "session_metadata": { "list_id": "12345", "list_title": "..." }, "total_calls": 0, "created_at": "...", "updated_at": "...", "contact_ids": [] } ], "pagination": { "total_items": 1, "total_pages": 1, "current_page": 1, "page_size": 50 } } } ``` --- ## Create Dialer Session `POST /voice/dialer/sessions` → `201 Created` Create a new dialer session. Sessions are created in **PAUSED** state. Call `POST /voice/dialer/sessions/{session_id}/resume` to begin dialing. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | string | **Yes** | Session display name (1–200 characters) | | `from_phone` | string | **Yes** | Caller ID phone number in E.164 format (9–16 characters) | | `list_id` | string | No | ID of the contact list to dial | | `list_title` | string | No | Display title of the contact list | | `agent_id` | string | No | UUID of the voice agent to use | | `agent_name` | string | No | Agent name — V1 fallback when `agent_id` is not supplied | | `entity_type` | string | No | `"agent"` or `"twin"` | | `studio_campaign_id` | string | No | Link to a Studio campaign | | `user_timezone` | string | No | IANA timezone string (e.g. `"America/New_York"`) | | `skip_voicemails` | boolean | No | When `true`, skip contacts that go to voicemail | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/voice/dialer/sessions" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Q3 Outbound", "from_phone": "+15551234567", "list_id": "12345", "list_title": "Q3 Targets", "agent_id": "agent_uuid", "entity_type": "agent", "user_timezone": "America/New_York", "skip_voicemails": true }' ``` ### Response ```json { "data": { "session_id": "...", "status": "PAUSED", "name": "Q3 Outbound", "org_id": "...", "user_id": "...", "user_email": "...", "from_phone": "+15551234567", "session_metadata": { "list_id": "12345", "list_title": "Q3 Targets" }, "total_calls": 0, "created_at": "...", "message": "..." } } ``` --- ## Update Session Status `PATCH /voice/dialer/sessions/{session_id}/status` Pause, resume, or stop a dialer session. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `session_id` | string | Session UUID | ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `status` | string | **Yes** | New status — `"ACTIVE"` (resume), `"PAUSED"` (pause), or `"COMPLETED"` (stop). `"FAILED"` is rejected. | ### Example **cURL** ```bash curl -X PATCH "https://be.graph8.com/api/v1/voice/dialer/sessions/ses_abc123/status" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "status": "PAUSED" }' ``` ### Response ```json { "data": { "session_id": "ses_abc123", "status": "PAUSED", "name": "Q3 Outbound", "org_id": "...", "user_id": "...", "user_email": "...", "from_phone": "+15551234567", "session_metadata": { "list_id": "12345", "list_title": "Q3 Targets" }, "total_calls": 0, "created_at": "...", "updated_at": "..." } } ``` --- ## Resume Dialer Session `POST /voice/dialer/sessions/{session_id}/resume` Resume a paused session and dial the next batch of contacts. This endpoint places **real outbound calls** immediately. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `session_id` | string | Session UUID | ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `max_contacts` | integer | No | Maximum number of contacts to dial concurrently (1–4, default `4`) | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/voice/dialer/sessions/ses_abc123/resume" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "max_contacts": 4 }' ``` ### Response ```json { "data": { "session_id": "ses_abc123", "status": "ACTIVE", "from_phone": "+15551234567", "session_metadata": { "list_id": "12345", "list_title": "Q3 Targets" }, "total_calls": 0, "dialed_count": 4, "created_at": "...", "updated_at": "...", "message": "..." } } ``` --- ## Dialer Stats `GET /voice/dialer/stats` Returns aggregated dialer analytics. Supports daily or total aggregation with optional date and session filters. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `session_id` | string | — | Filter metrics to a specific session | | `user_email` | string | — | Filter metrics to a specific SDR | | `date_from` | string (YYYY-MM-DD) | — | Start date (inclusive) | | `date_to` | string (YYYY-MM-DD) | — | End date (inclusive) | | `aggregation` | string | `DAILY` | Aggregation level — `DAILY` or `TOTAL` | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/voice/dialer/stats?aggregation=DAILY&date_from=2026-05-01&date_to=2026-05-31" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": { "filters": { "session_id": null, "user_email": null, "org_id": "...", "date_from": "...", "date_to": "...", "aggregation": "DAILY" }, "metrics": [ { "date": "2026-05-26", "sdr_name": "...", "org_id": "...", "list_title": "...", "total_dials": 100, "total_connections": 30, "connection_rate": 0.3, "total_voicemails": 10, "voicemail_rate": 0.1, "talk_time_minutes": 45.5, "avg_call_duration_minutes": 1.5, "dispositions": { "booked": 2, "callback": 5 }, "success_rate": 0.02, "unique_sessions": 1, "avg_calls_per_session": 100, "redial_count": 0, "redial_success_rate": null, "peak_calling_hour": 14, "total_callbacks": 0 } ], "summary": {} } } ``` --- ## List Phone Numbers `GET /voice/dialer/numbers` Returns the phone numbers provisioned for the org, with usage and health stats. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `user_email` | string | — | Filter numbers assigned to a specific user | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/voice/dialer/numbers" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": { "numbers": [ { "number": "+15551234", "inbound_type": "ai", "telephony_provider": "twilio", "calls_today": 5, "calls_7d": 40, "connect_rate_7d": 0.3, "is_valid": true, "daily_limit": 40, "assigned_to": "rep@org.com", "created_at": "..." } ] } } ``` --- ## List Missed Callbacks `GET /voice/dialer/missed-callbacks` Returns inbound calls that arrived while no SDR was available to answer. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `limit` | integer | `50` | Maximum number of records to return (max 200) | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/voice/dialer/missed-callbacks?limit=50" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": { "missed_callbacks": [ { "id": 1, "caller_phone": "...", "inbound_number": "...", "first_name": "...", "last_name": "...", "email": "...", "contact_id": 101, "parallel_session_id": "...", "created_at": "...", "call_duration": null, "is_callback": false } ], "count": 1 } } ``` --- ## Get Call Grading `GET /voice/dialer/calls/{room_name}/grading` Returns the AI-graded talk-track feedback for a call. If grading is still being computed, `status` will be `"pending"`. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `room_name` | string | LiveKit room name for the call | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/voice/dialer/calls/room_abc123/grading" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": { "room_name": "...", "status": "ready", "grading": {}, "reviewed": false, "reviewed_at": null } } ``` `status` is `"pending"` while grading is still being computed. --- ## Get Call Transcript `GET /voice/dialer/calls/{room_name}/transcript` Returns the full transcript for a call, along with the summary, disposition, and recording URL. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `room_name` | string | LiveKit room name for the call | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/voice/dialer/calls/room_abc123/transcript" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": { "room_name": "...", "status": "ready", "transcript": [ { "speaker": "SDR", "content": "...", "timestamp": "..." } ], "summary": "...", "disposition": "booked", "contact_id": "101", "call_duration": 90, "recording_url": "...", "parallel_session_id": "...", "created_at": "..." } } ``` --- ## List Calls `GET /voice/dialer/calls` Returns calls matching the given filters. At least one of `contact_id`, `user_email`, or `parallel_session_id` is required. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `contact_id` | string | — | Filter by contact ID (at least one filter required) | | `user_email` | string | — | Filter by SDR email (at least one filter required) | | `parallel_session_id` | string | — | Filter by session ID (at least one filter required) | | `created_after` | string (ISO) | — | Return calls created after this timestamp | | `limit` | integer | `20` | Maximum number of records to return (max 100) | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/voice/dialer/calls?contact_id=101&limit=20" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": { "contact_id": "101", "user_email": null, "parallel_session_id": null, "count": 1, "calls": [ { "room_name": "...", "created_at": "...", "disposition": "booked", "summary": "...", "call_duration": 90, "recording_url": "...", "parallel_session_id": "...", "contact_id": "101", "from_": "+1...", "to": "+1...", "has_transcript": true } ] } } ``` --- ## List Voice Agents `GET /voice/dialer/agents` Returns available voice agents and digital twins for the org. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `role` | string | — | Filter by agent role (e.g. `SDR`) | | `agent_status` | string | — | Filter by agent status (e.g. `active`) | | `entity_type` | string | — | Filter by entity type — `agent` or `twin` | | `search` | string | — | Search agents by name or description | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/voice/dialer/agents?entity_type=agent&agent_status=active" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": { "agents": [ { "agent_id": "uuid", "agent_name": "...", "role": "SDR", "agent_status": "active", "entity_type": "agent", "description": "...", "phone": "+1...", "is_template": false } ], "total_count": 1 } } ``` --- ## Credit Costs | Operation | Credits | |-----------|---------| | Read endpoints (sessions, calls, transcripts, grading, stats, numbers, agents) | Free | | Outbound call audio | 20 per minute | | Booked meeting | 20 per booking | | Inbound callback (handled by AI receptionist) | 20 per minute | --- ## Errors | Status | Cause | Fix | |--------|-------|-----| | `401` | Missing or invalid `Authorization: Bearer` header | [Get a key](https://docs.graph8.com/developers/authentication/) | | `402` | Out of credits | Top up in **Settings → Billing** | | `404` | Resource ID does not exist | List first to verify the ID | | `422` | Validation error in request body | Inspect `error.message` and `error.field` in the response | | `429` | Rate limit (50 rps per org) | Back off per `Retry-After` header | | `5xx` | graph8 error | Retry with exponential backoff (5s → 30s → 120s) | The full error envelope: `{ "error": { "code": "...", "message": "...", "field": "...", "request_id": "..." } }`. Include `request_id` in any support ticket. --- # VS Code Setup > Connect VS Code to graph8 via native MCP support using .vscode/mcp.json Source: https://docs.graph8.com/developers/vscode-setup/ VS Code has native MCP support starting in 1.86. Drop a workspace-scoped `.vscode/mcp.json` file in your repo and graph8 tools become available to GitHub Copilot Agent Mode and any other MCP-aware extension. > **Tip:** > Try the developer tier free - 1,000 credits, no card required. [Get started](https://app.graph8.com/signup). ## Prerequisites - VS Code 1.86 or later - GitHub Copilot with Agent Mode enabled (or another MCP-aware extension) - For stdio: Python 3.10+ and a personal API key from **Profile -> Developer** ## Hosted MCP (remote OAuth) Create `.vscode/mcp.json` in your workspace: ```json { "servers": { "graph8": { "type": "http", "url": "https://be.graph8.com/mcp/" } } } ``` 1. Save the file and reload the VS Code window (Cmd-Shift-P -> Developer: Reload Window). 2. Open the Copilot chat panel and switch to Agent Mode. 3. Ask: "Find 10 VP Engineering at Series B SaaS using graph8." 4. VS Code opens a browser tab for OAuth on first use. Sign in and return to VS Code. ## Self-hosted MCP (stdio) For users who prefer local credential storage or work in air-gapped environments, run the MCP server as a child process. ```json { "servers": { "graph8": { "type": "stdio", "command": "uvx", "args": ["g8-mcp-server"], "env": { "G8_API_KEY": "${input:g8-api-key}", "G8_MCP_MODE": "dev" } } }, "inputs": [ { "id": "g8-api-key", "type": "promptString", "description": "graph8 personal API key (Profile -> Developer)", "password": true } ] } ``` VS Code prompts for the API key on first use and stores it securely. Switch `G8_MCP_MODE` between `dev` (17 tools for codebase work), `gtm` (23 tools for campaign work), and `all` depending on what you are building. See [Modes](https://docs.graph8.com/developers/mcp/#modes). ## Workspace vs user config - `.vscode/mcp.json` in the workspace - shared with the repo, ideal for team-wide setups (no secrets in this file). - `~/.vscode/mcp.json` (or VS Code's user config) - personal, applies across all workspaces. For a SaaS team that wants every engineer connected to graph8 from the same repo, commit the hosted-OAuth `.vscode/mcp.json` to the repo. Each engineer signs in with their own graph8 account on first use. ## Worked example: install graph8 tracking in your Next.js app With `dev` mode set, ask Copilot Agent Mode: > Connect this repo to graph8, scan it, and install the tracking snippet for Next.js. Then generate a progressive email-capture form for the pricing page. The agent will read your code, then use `g8_connect_repo`, `g8_scan_repo`, `g8_get_tracking_snippet`, and `g8_get_form_template` in sequence. See the [End-to-end examples](https://docs.graph8.com/developers/mcp-examples/) page for the full walkthrough. ## Related - [MCP Server overview](https://docs.graph8.com/developers/mcp/) - [MCP Tool Reference](https://docs.graph8.com/developers/mcp-tools/) - [MCP FAQ](https://docs.graph8.com/developers/mcp-faq/) - [Claude Code setup](https://docs.graph8.com/developers/claude-code-setup/) --- # Webhooks > Subscribe to events and manage webhook deliveries Source: https://docs.graph8.com/developers/webhooks/ > **Tip: Using the SDK?** > The [JavaScript SDK](https://docs.graph8.com/developers/sdk/#webhooks) provides `g8.webhooks.on(event, callback)` for programmatic event listening. Webhooks let you receive real-time HTTP callbacks when events occur in your organization. You can subscribe to specific event types and graph8 will POST a signed JSON payload to your endpoint. --- ## Available Events `GET /webhooks/events` Returns all event types you can subscribe to, each with a **category** and a human-readable **description** — the catalog you browse to decide what to listen for. ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/webhooks/events" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": [ { "event": "campaign.launched", "category": "Campaign", "description": "A campaign was launched and is now sending." }, { "event": "meeting.booked", "category": "Meetings", "description": "A meeting was booked." } ] } ``` | Field | Type | Description | |-------|------|-------------| | `event` | string | The event type to subscribe to | | `category` | string | Grouping for the event (e.g. `Campaign`, `Engagement`) | | `description` | string \| null | What the event means / when it fires | ### Event catalog | Event | Category | Description | |-------|----------|-------------| | `campaign.created` | Campaign | A campaign was created. | | `campaign.updated` | Campaign | A campaign's configuration was updated. | | `campaign.deleted` | Campaign | A campaign was deleted. | | `campaign.launched` | Campaign | A campaign was launched and is now sending. | | `campaign.paused` | Campaign | A campaign was paused. | | `campaign.completed` | Campaign | A campaign finished all of its steps. | | `campaign.status_changed` | Campaign | A campaign's status changed. | | `document.generated` | Content | A campaign document finished generating. | | `document.failed` | Content | A campaign document failed to generate. | | `campaign.content_ready` | Content | All content for a campaign is ready. | | `intelligence.completed` | Intelligence | An intelligence/research run completed. | | `intelligence.failed` | Intelligence | An intelligence/research run failed. | | `company.enriched` | Enrichment | A company record was enriched. | | `company_intelligence.completed` | Enrichment | Company intelligence gathering completed. | | `audience.ready` | Audience | An audience finished building and is ready to use. | | `audience.failed` | Audience | An audience failed to build. | | `sequence.draft_created` | Sequence | A campaign created a Sequencer draft; nothing has started sending. | | `sequence.started` | Sequence | A sequence started running for a contact. | | `sequence.paused` | Sequence | A sequence was paused. | | `sequence.completed` | Sequence | A sequence completed all steps for a contact. | | `engagement.email_sent` | Engagement | An email was sent to a contact. | | `engagement.email_replied` | Engagement | A contact replied to an email. | | `engagement.email_bounced` | Engagement | An email to a contact bounced. | | `engagement.email_skipped` | Engagement | An email step was skipped for a contact. | | `engagement.call_dispatched` | Engagement | A call task was dispatched to the dialer. | | `engagement.sms_sent` | Engagement | An SMS was sent to a contact. | | `engagement.sms_replied` | Engagement | A contact replied to an SMS. | | `engagement.whatsapp_sent` | Engagement | A WhatsApp message was sent to a contact. | | `engagement.linkedin_connection_sent` | Engagement | A LinkedIn connection request was sent. | | `engagement.linkedin_message_sent` | Engagement | A LinkedIn message was sent. | | `engagement.linkedin_inmail_sent` | Engagement | A LinkedIn InMail was sent. | | `engagement.linkedin_reply_received` | Engagement | A LinkedIn reply was received from a contact. | | `engagement.linkedin_connection_accepted` | Engagement | A LinkedIn connection request was accepted. | | `meeting.booked` | Meetings | A meeting was booked. | | `meeting.cancelled` | Meetings | A booked meeting was cancelled. | | `meeting.rescheduled` | Meetings | A booked meeting was rescheduled. | | `engagement.email_clicked` | Engagement | A contact first clicked a tracked link in a sequence email (email-security scanner clicks are filtered out). | | `engagement.link_clicked` | Engagement | A contact first clicked a tracked link sent on a non-email channel (SMS, LinkedIn and others). | | `meeting.no_show` | Meetings | A booked meeting's host or attendee was marked as a no-show. | | `voice_ai.call_started` | Voice AI | A Voice AI agent call was placed. | | `voice_ai.call_completed` | Voice AI | A Voice AI agent call ended, with its duration, disposition and sentiment. | | `voice_ai.voicemail_left` | Voice AI | A Voice AI agent left a voicemail. | | `engagement.call_recording_ready` | Engagement | A dialer call recording is available. Fetch it with `GET /voice/calls/{call_id}/recording`. | | `deal.created` | Deals | A deal was created. | | `deal.updated` | Deals | A deal's fields were updated. | | `deal.stage_changed` | Deals | A deal moved to a different pipeline stage. | | `deal.deleted` | Deals | A deal was deleted. | | `quote.created` | Quotes | A quote was created. | | `quote.updated` | Quotes | A quote was updated. | | `quote.sent` | Quotes | A quote was sent to the buyer. | | `quote.resent` | Quotes | A quote was sent to the buyer again. | | `quote.resend_failed` | Quotes | Re-sending a quote to the buyer failed. | | `quote.link_generated` | Quotes | A shareable quote link was generated. | | `quote.viewed` | Quotes | The buyer opened a quote. | | `quote.accepted` | Quotes | The buyer accepted a quote. | | `quote.declined` | Quotes | The buyer declined a quote. | | `quote.expired` | Quotes | A quote passed its expiry date. | | `quote.voided` | Quotes | A quote was voided. | | `quote.superseded` | Quotes | A quote was replaced by a newer version. | | `quote.archived` | Quotes | A quote was archived. | | `quote.unarchived` | Quotes | An archived quote was restored. | | `quote.payment_received` | Quotes | A payment for a quote was received. | | `quote.payment_failed` | Quotes | A payment for a quote failed. | | `quote.subscription_canceled` | Quotes | The subscription created from a quote was canceled. | | `quote.provisioned` | Quotes | An accepted quote was provisioned. | | `quote.approval_requested` | Quotes | A quote broke the discount or margin policy and was submitted for approval. | | `quote.approved` | Quotes | An approver approved a quote, so it can be sent. | | `quote.rejected` | Quotes | An approver rejected a quote, with a reason. | | `quote.forwarded_for_signature` | Quotes | The buyer forwarded a quote to someone else to sign. | | `quote.forwarded_for_payment` | Quotes | The buyer forwarded a quote to someone else to pay. | | `quote.signer_change_requested` | Quotes | The buyer asked to hand the signature to someone else. Nothing changes until the rep approves. | | `quote.signer_change_approved` | Quotes | The rep approved the buyer's signer change; the new signer was sent the signing link. | | `quote.signer_change_rejected` | Quotes | The rep rejected the buyer's signer change; the signer is unchanged. | | `form.submitted` | Website | A visitor submitted a tracked website or booking form. | | `visitor.identified` | Website | A website visitor was identified as a contact (at most once per visitor per day). | | `intent.signal` | Website | A contact showed buyer intent for one of your tracked keywords. | | `task.created` | Tasks | A task was created. | | `task.updated` | Tasks | A task was updated. | | `task.completed` | Tasks | A task was marked as completed. | | `task.deleted` | Tasks | A task was deleted. | | `enrichment.job_completed` | Enrichment | An enrichment job finished processing all of its records. | | `enrichment.job_failed` | Enrichment | An enrichment job failed. | | `workflow.execution_completed` | Workflows | A workflow run finished. | | `workflow.execution_failed` | Workflows | A workflow run failed. | --- ## List Webhooks `GET /webhooks` Returns all webhook subscriptions for your organization. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `is_active` | boolean | — | Filter by active status | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/webhooks" \ -H "Authorization: Bearer $API_KEY" ``` **Python** ```python response = requests.get( f"{BASE_URL}/webhooks", headers=HEADERS ) ``` ### Response ```json { "data": [ { "id": "wh-abc", "name": "CRM Sync", "url": "https://example.com/webhooks/graph8", "events": ["campaign.created", "campaign.updated"], "is_active": true, "secret": null, "created_at": "2026-02-20T10:00:00", "updated_at": "2026-02-20T10:00:00" } ] } ``` --- ## Create Webhook `POST /webhooks` Create a new webhook subscription. The signing `secret` is returned only once in the response — store it securely. Maximum 10 active webhooks per organization. Returns `201 Created`. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `url` | string | Yes | Target URL for delivery | | `events` | string[] | Yes | Event types to subscribe to | | `name` | string | No | Human-readable name | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/webhooks" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/webhooks/graph8", "events": ["campaign.created", "campaign.launched"], "name": "CRM Sync" }' ``` **Python** ```python response = requests.post( f"{BASE_URL}/webhooks", headers=HEADERS, json={ "url": "https://example.com/webhooks/graph8", "events": ["campaign.created", "campaign.launched"], "name": "CRM Sync" } ) secret = response.json()["data"]["secret"] # Store this! ``` ### Response ```json { "data": { "id": "wh-abc", "name": "CRM Sync", "url": "https://example.com/webhooks/graph8", "events": ["campaign.created", "campaign.launched"], "is_active": true, "secret": "whsec_a1b2c3d4e5f6..." } } ``` ### Verifying Signatures Each delivery includes an `X-Studio-Signature` header (format: `sha256=<hex>`). Verify it with HMAC-SHA256 over `{X-Studio-Timestamp}.{raw_body}`: ```python import hashlib, hmac def verify_signature(body: bytes, timestamp: str, signature: str, secret: str) -> bool: signed = f"{timestamp}.".encode() + body expected = "sha256=" + hmac.new( secret.encode(), signed, hashlib.sha256 ).hexdigest() return hmac.compare_digest(expected, signature) ``` --- ## Get Webhook `GET /webhooks/{webhook_id}` Returns webhook details. The `secret` is **not** included in the response. --- ## Update Webhook `PATCH /webhooks/{webhook_id}` Partial update — send only the fields you want to change. ### Request Body | Field | Type | Description | |-------|------|-------------| | `url` | string | Target URL | | `events` | string[] | Event types | | `name` | string | Human-readable name | | `is_active` | boolean | Enable or disable | --- ## List Deliveries `GET /webhooks/{webhook_id}/deliveries` Returns delivery attempts for a webhook, with pagination. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `page` | integer | 1 | Page number | | `limit` | integer | 50 | Items per page (max 200) | | `status` | string | — | Filter by delivery status (`success`, `failed`, `pending`) | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/webhooks/wh-abc/deliveries" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "data": [ { "id": "del-1", "event": "campaign.created", "status": "success", "attempts": 1, "max_attempts": 3, "response_code": 200, "error_message": null, "created_at": "2026-02-25T10:00:00", "completed_at": "2026-02-25T10:00:01" } ], "pagination": { "page": 1, "limit": 50, "total": 1, "has_next": false } } ``` Failed deliveries are retried up to 3 times with increasing delays (10s, 60s, 300s). --- ## Payload Envelope Every webhook delivery uses the same outer envelope: ```json { "event": "campaign.launched", "timestamp": "2026-05-25T12:34:56.789000Z", "org_id": "org_xxx123", "data": { /* event-specific payload below */ } } ``` ## Delivery Headers graph8 sets the following headers on every webhook POST: | Header | Example | Purpose | |---|---|---| | `Content-Type` | `application/json` | — | | `X-Studio-Signature` | `sha256=8a91...c2f4` | HMAC-SHA256 over `{timestamp}.{raw_body}` | | `X-Studio-Timestamp` | `1716624896` | Unix epoch seconds, also part of the signed message | | `X-Studio-Event` | `campaign.launched` | Quick filter without parsing body | | `X-Studio-Delivery-Id` | `del-abc-uuid` | Unique per delivery attempt (idempotency) | ## HMAC Verification Verify every webhook before processing. The signed message is `{X-Studio-Timestamp}.{raw_body}`. Compare against `X-Studio-Signature` using a constant-time comparator. **Python** ```python import hmac, hashlib def verify(body: bytes, timestamp: str, signature: str, secret: str) -> bool: signed = f"{timestamp}.".encode() + body expected = "sha256=" + hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, signature) ``` **Node.js** ```javascript import crypto from 'crypto'; function verify(body, timestamp, signature, secret) { const signed = `${timestamp}.${body}`; const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(signed).digest('hex'); return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature)); } ``` The `body` here is the **raw request body** (bytes / string), not the parsed JSON. Parse only after the signature passes. ## Replay Protection Reject deliveries where `|now - X-Studio-Timestamp| > 5 minutes` to prevent replay attacks. graph8 retries failed deliveries but never resigns — every retry carries the original timestamp. ## Event Payload Schemas ### `campaign.launched` Fires when a campaign transitions to `launched` / `active`. ```json { "id": "camp_abc123", "name": "Q2 Outreach Campaign", "slug": "q2-outreach", "concept_slug": "saas-retargeting", "goal": "Drive trial signups", "target_persona": "VP Sales, B2B SaaS", "status": "launched", "created_at": "2026-05-20T10:00:00Z" } ``` ### `campaign.created` / `campaign.updated` / `campaign.deleted` / `campaign.paused` / `campaign.completed` Same shape as `campaign.launched`, with `status` reflecting the new state. ### `company.enriched` Fires after a successful Apollo (or fallback provider) enrichment of a company record. ```json { "mashup_company_id": 12345, "company_ext_id": "cext_xyz789", "account_name": "Acme Corp", "domain": "acme.com", "fields_updated": ["phone", "industry", "headcount"], "enriched_at": "2026-05-25T12:34:56.789000Z" } ``` ### `intelligence.completed` Global context or per-company intelligence generation finishes. ```json { "website_url": "https://acme.com", "task_id": "task_uuid", "status": "completed", "success_count": 12, "fail_count": 0, "completed_at": "2026-05-25T12:34:56.789000Z" } ``` ### `audience.ready` / `audience.failed` Audience build finishes. ```json { "audience_id": 42, "list_id": 637, "platform": "meta", "status": "ready", "record_count": 8421 } ``` ### `sequence.draft_created` ```json { "sequence_id": "seq_abc123", "sequence_name": "Q2 Cold Outreach", "campaign_id": "camp_def456", "status": "drafted", "steps_created": 4, "created_at": "2026-05-25T12:34:56.789000Z" } ``` Creating a draft does not activate the sequence or send anything. Activation is reported separately as `sequence.started`. ### `sequence.started` / `sequence.paused` / `sequence.completed` ```json { "sequence_id": "seq_abc123", "campaign_id": "camp_def456", "contacts_completed": 247, "completed_at": "2026-05-25T12:34:56.789000Z" } ``` ### `engagement.email_replied` (subscribers ask for this as `reply_received`) Fires when a reply is detected on an outbound message via AI Inbox. ```json { "contact_id": "contact_uuid", "email": "john@example.com", "reply_subject": "Re: Your Q2 offer", "sequence_id": "seq_abc123", "campaign_id": "camp_def456", "replied_at": "2026-05-25T12:34:56.789000Z", "is_positive": true } ``` Related engagement events follow the same per-channel shape: - `engagement.email_sent` / `email_bounced` / `email_skipped` - `engagement.call_dispatched` - `engagement.sms_sent` / `sms_replied` - `engagement.whatsapp_sent` - `engagement.linkedin_connection_sent` / `linkedin_message_sent` / `linkedin_inmail_sent` / `linkedin_reply_received` / `linkedin_connection_accepted` ### `meeting.booked` / `meeting.cancelled` / `meeting.rescheduled` ```json { "contact_id": "contact_uuid", "email": "john@example.com", "meeting_id": "meeting_uuid", "meeting_title": "Discovery Call", "scheduled_at": "2026-05-28T14:00:00Z", "duration_minutes": 30, "sequence_id": "seq_abc123", "campaign_id": "camp_def456", "booked_at": "2026-05-25T12:34:56.789000Z" } ``` ### Event data for bridged events Events that come from graph8's event pipeline (engagement, meetings, deals, quotes, tasks, enrichment jobs, workflow runs, Voice AI calls, forms and visitors) deliver the producer's message as `data`, plus `eda_event` (the internal event name), `g8_correlation` and `idempotency_key`. Each of them can also be read through `GET /events` under its `eda_event` name, with the same message (filter with `name`, `type`, `contact_id`, `since` and `cursor`). ### `form.submitted` Fires once per submission of a tracked website form or booking form, after duplicate submissions from the tracker are collapsed. ```json { "eda_event": "form_submitted", "form_id": "demo-request", "form_group_id": null, "form_kind": "web", "url": "https://acme.com/demo", "referrer": "https://www.google.com/", "submitter_email": "john@example.com", "fields": [{ "name": "email", "value": "john@example.com" }, { "name": "company", "value": "Acme" }], "submission_id": "3f1c9a2b7d4e5f60718293a4b5c6d7e8" } ``` ### `visitor.identified` / `intent.signal` `visitor.identified` fires when a website visitor is matched to a contact, at most once per contact per day. `intent.signal` fires when a contact shows intent for one of your tracked keywords, at most once per contact, keyword and page per day. ```json { "eda_event": "visitor_identified", "contact_id": 46699, "company_id": 812, "signal_type": "visitor_identified", "signal_group": "website" } ``` ### `engagement.email_clicked` / `engagement.link_clicked` The first human click on a tracked link. Clicks from email-security scanners are filtered out. `email_clicked` is for sequence emails; `link_clicked` covers SMS, WhatsApp, LinkedIn and other channels. ```json { "eda_event": "sequence_email_clicked", "utm_id": "AbCd1234", "contact_id": 46699, "campaign_id": "8f0d0b52-5a1e-4a55-9c3e-2f7d1b0c9e11", "step_id": "step_uuid", "channel": "email", "sent_at": "2026-09-24T14:58:12+00:00", "clicked_at": "2026-09-25T09:02:44+00:00", "detected_by": "click_resolver" } ``` Email opens are not tracked. graph8 does not put a tracking pixel in outbound mail, so there is no open event. ### `engagement.call_recording_ready` A dialer call recording is available. It carries no recording URL: download the audio with `GET /voice/calls/{call_id}/recording`. The event includes that path. ```json { "eda_event": "sdr.recording_ready", "call_id": "dialer_46699_sess_1790261892", "session_id": "sess_uuid", "contact_id": "46699", "recording_sid": "RE0123456789abcdef", "recording_duration_seconds": 37, "recording_api_path": "/voice/calls/dialer_46699_sess_1790261892/recording" } ``` ### `quote.approval_requested` / `quote.approved` / `quote.rejected` A quote that breaks your discount or margin policy (set in Quote settings) must be approved before it can be sent. `quote.approval_requested` fires when it is submitted, `quote.approved` and `quote.rejected` when an approver decides. The data is the same as every other `quote.*` event, plus one `approval` object: | Field | Type | Description | |-------|------|-------------| | `approval.status` | string | `pending_approval`, `approved` or `rejected` | | `approval.requested_by` | string | User ID of the rep who submitted the quote | | `approval.requested_at` | string | When it was submitted (ISO 8601, UTC) | | `approval.approver` | string | User ID of the approver who decided; empty on `quote.approval_requested` | | `approval.reason` | string | The approver's comment on `quote.approved` (optional) or the rejection reason on `quote.rejected`; empty on `quote.approval_requested` | | `approval.thresholds_hit` | array | Each policy rule the quote broke: `rule` (`discount_pct` or `margin_pct`), the enforced `threshold` and the quote's `value`, in percent | ```json { "eda_event": "quote.rejected", "quote_id": "5b0e7c1a-6f0e-4d7a-9a51-3f2b9e8d4c10", "quote_number": "Q-0042", "action": "rejected", "status": "draft", "previous_status": "draft", "deal_id": "812", "owner_id": "user_uuid", "total_cents": 1275000, "subtotal_cents": 1500000, "currency": "usd", "source": "quote_approval", "invoked_by": "approver_user_uuid", "approval": { "status": "rejected", "requested_by": "user_uuid", "requested_at": "2026-10-12T09:15:02.123456", "approver": "approver_user_uuid", "reason": "Max 15% for this segment", "thresholds_hit": [{ "rule": "discount_pct", "threshold": 15.0, "value": 22.5 }] } } ``` ### `test` Verification ping. Use this when first wiring a subscription to confirm signature validation works. ```json { "test": true, "message": "This is a test webhook from Graph8 Studio" } ``` ## Full Event List For the complete catalog (campaign lifecycle, content generation, intelligence, enrichment, audience, sequence deployment, engagement per channel, meetings), call `GET /webhooks/events` — it returns the live registry. Subscribe to all events via `events: ["*"]` if you want a single firehose listener. --- # What You Can Ask > Everything you can do with MCP in graph8 Studio Source: https://docs.graph8.com/developers/what-you-can-ask/ Once connected via MCP, your AI client can help you with campaigns, accounts, deals, intelligence, and more. Here's everything you can ask. ## Campaigns Ask about your marketing campaigns and get details instantly. ### List Campaigns ``` Show me my campaigns ``` ``` What campaigns are currently active? ``` ``` List all draft campaigns ``` ``` Show me completed campaigns ``` ### Get Campaign Details ``` Tell me about the Q1 Product Launch campaign ``` ``` What's the core concept for [campaign name]? ``` ``` Show me the details for campaign [ID] ``` ### Search Campaigns ``` Find campaigns targeting CMOs ``` ``` Search for campaigns about enterprise pricing ``` --- ## Companies Find companies and their key contacts. ### List Companies ``` Show me my companies ``` ``` List all companies ``` ``` How many companies do I have? ``` ### Search Companies ``` Find companies in the healthcare industry ``` ``` Search for companies named "Acme" ``` ``` Show me technology companies ``` ### Get Company Details ``` Tell me about Acme Corp ``` ``` What do we know about [company name]? ``` ``` Show me the details for company [ID] ``` ### Find Contacts ``` Who are the key contacts at Acme Corp? ``` ``` Show me stakeholders for [account name] ``` ``` List contacts at [company] ``` --- ## Deals Check your sales pipeline and deal status. ### List Deals ``` Show me my deals ``` ``` What deals are in the pipeline? ``` ``` List deals in the negotiation stage ``` ### Get Deal Details ``` Tell me about the Acme Corp deal ``` ``` What's the status of deal [ID]? ``` ``` Show me deal details for [company name] ``` ### Pipeline Overview ``` What's my pipeline value? ``` ``` How many deals are closing this month? ``` ``` Show me deals by stage ``` --- ## Intelligence Access research and competitive analysis. ### Global Intelligence ``` Show me our global intelligence status ``` ``` What competitive analysis do we have? ``` ``` Get the market research overview ``` ### Company Intelligence ``` What's the intelligence on Acme Corp? ``` ``` Show me research for [company name] ``` ``` Get the competitive intel for [company] ``` > **Note:** > Company intelligence is only available for companies that have been enriched. You can trigger enrichment from the Companies page. --- ## Documents Access your global context documents and research reports. ### List Documents ``` Show me our global context documents ``` ``` What documents do we have? ``` ``` List all context documents ``` ### Get Document Content ``` Show me the brand messaging document ``` ``` What does our value proposition say? ``` ``` Get the competitive positioning document ``` ### Search Documents ``` Search documents for "enterprise pricing" ``` ``` Find content about our target personas ``` ``` What do our documents say about [topic]? ``` --- ## Search Search across everything in graph8 Studio. ### Unified Search ``` Search for "enterprise" across everything ``` ``` Find anything related to healthcare ``` ``` Search G8 Studio for [keyword] ``` ### Specific Searches ``` Search campaigns for [keyword] ``` ``` Search accounts for [company name] ``` ``` Search documents for [topic] ``` --- ## Tips for Better Results ### Be Specific Instead of: `"Show me stuff"` Try: `"Show me active campaigns targeting CMOs"` ### Use Names Instead of: `"Get account details"` Try: `"Tell me about Acme Corp"` ### Ask Follow-ups After getting a list, ask for details: 1. "Show me my deals" 2. "Tell me more about the Acme Corp deal" ### Combine Actions You can ask multi-part questions: ``` Show me accounts in healthcare and any deals associated with them ``` ## Permissions MCP respects your graph8 permissions: | If you can... | MCP can... | |---------------|-----------| | View campaigns | List and search campaigns | | Edit campaigns | Create campaign ideas | | View accounts | List and search accounts | | Run workflows | Trigger intelligence gathering | > **Caution:** > If you get a "permission denied" error, check with your admin about your role permissions. ## Frequently Asked Questions ### Why doesn't the AI find my data? Ensure you're connected and your session is active. Check that the data exists in graph8 Studio—the AI can only access what's actually in your organization. Try more specific queries. ### Can I create new campaigns or deals via MCP? Currently, MCP provides read access to campaigns, accounts, deals, and intelligence. Creating new records requires using the graph8 Studio UI or API directly. ### How do I search across everything at once? Use the unified search: "Search for [keyword] across everything" or "Search G8 Studio for [term]". The AI will search campaigns, accounts, documents, and intelligence. ### Why am I getting permission errors? MCP respects your graph8 role permissions. If you can't access something in the UI, you can't access it via MCP. Check with your admin about your assigned role. ### Can I ask follow-up questions? Yes, the AI maintains conversation context. After getting results, you can ask "tell me more about the first one" or "show me details for that account" without repeating yourself. ## Related - [MCP Overview](https://docs.graph8.com/developers/mcp) - [Cursor Setup](https://docs.graph8.com/developers/cursor-setup/) - [Claude Desktop Setup](https://docs.graph8.com/developers/claude-desktop-setup/) --- # Workflows > Create, update, execute, and manage automation workflow graphs in your graph8 workspace Source: https://docs.graph8.com/developers/workflows/ > **Note:** > Workflows are a voice-proxy surface. `org_id` and `runtime_type=workflow` are pinned server-side — you do not need to pass them. Build automation graphs from typed nodes (triggers, actions, delays, branches, agents, skills, loops) connected into directed graphs. The entire workflow graph lives in the `config` field. Node-level edits are made client-side by mutating `config.nodes` / `config.edges` and submitting via `PUT`. > **Caution:** > `POST /workflows/{action_id}/execute` and any node that calls outreach actions (sequence enroll, dialer call, Slack message, email send) triggers real activity. Always validate first with `POST /workflows/validate`. --- > **Note: Ids in these examples** > The request examples use shell variables (`$WORKFLOW_ID` and `$EXECUTION_ID`) for ids. Set them from the `id` returned when you create or list workflows, and the `execution_id` returned by an execute call. Ids shown in sample responses (`wf_abc123`, `exec_xyz789`) are illustrative only and do not exist. ## Get Node-Type Schemas `GET /workflows/node-types/schema` Returns schema definitions for all available node types, or a single node type when filtered. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `detail` | string | `slim` | Schema verbosity — `slim` (field names only) or `full` (field names + types + constraints) | | `node_type` | string | — | Optional. Filter to a single node type (e.g. `trigger`, `agent`, `delay`) | ### Example **cURL** ```bash # Slim schemas for all node types curl "https://be.graph8.com/api/v1/workflows/node-types/schema" \ -H "Authorization: Bearer $API_KEY" # Full schema for a single node type curl "https://be.graph8.com/api/v1/workflows/node-types/schema?detail=full&node_type=agent" \ -H "Authorization: Bearer $API_KEY" ``` --- ## List Workflows `GET /workflows` Returns a paginated list of workflows. The response uses the key `actions` (not `workflows`) and renames `skill_config` to `config` on each item. ### Query Parameters All parameters are optional. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `enabled` | boolean | — | Filter by enabled/disabled status | | `category` | string | — | Filter by workflow category | | `is_template` | boolean | — | Filter to template workflows only | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/workflows" \ -H "Authorization: Bearer $API_KEY" # Filter to enabled workflows in a category curl "https://be.graph8.com/api/v1/workflows?enabled=true&category=outreach" \ -H "Authorization: Bearer $API_KEY" # List templates only curl "https://be.graph8.com/api/v1/workflows?is_template=true" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "actions": [ { "id": "wf_abc123", "name": "Lead nurture", "description": "Triggered on form submit", "category": "outreach", "object_type": "Contact", "is_template": false, "config": { "nodes": [], "edges": [], "start_node_id": "n1", "metadata": {}, "settings": {} } } ], "total_count": 1 } ``` --- ## Get Workflow `GET /workflows/{action_id}` Returns the full workflow record including the `config` graph (nodes, edges, start node, metadata, settings). ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `action_id` | string | Workflow ID | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/workflows/$WORKFLOW_ID" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "id": "wf_abc123", "name": "Lead nurture", "description": "Triggered on form submit", "category": "outreach", "object_type": "Contact", "config": { "nodes": [ { "node_id": "n1", "node_type": "trigger", "name": "On Submit", "config": {}, "position": { "x": 0, "y": 0 } } ], "edges": [{ "source": "n1", "target": "n2" }], "start_node_id": "n1", "metadata": { "name": "Lead nurture", "version": 1 }, "settings": { "stop_on_failure": true } } } ``` --- ## Get Trigger Status `GET /workflows/{action_id}/trigger-status` Returns the current state of an event-stream (cursor-based) trigger for a workflow. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `action_id` | string | Workflow ID | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/workflows/$WORKFLOW_ID/trigger-status" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "has_trigger": true, "trigger_type": "event_stream", "last_cursor": "2026-06-24T10:00:00Z" } ``` --- ## Create Workflow `POST /workflows` → 201 Creates a new workflow. The proxy normalizes camelCase, ReactFlow, and hybrid graph shapes to canonical snake_case automatically. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | string | **Yes** | Workflow name | | `description` | string | No | Human-readable description | | `category` | string | No | Category label for grouping | | `object_type` | string | No | Primary object type this workflow operates on (e.g. `Contact`) | | `config` | object | **Yes** | Workflow graph — see structure below | #### `config` object | Field | Type | Required | Description | |-------|------|----------|-------------| | `nodes` | array | **Yes** | Array of node objects (see node shape below) | | `edges` | array | **Yes** | Array of `{ "source": "<node_id>", "target": "<node_id>" }` objects | | `start_node_id` | string | **Yes** | ID of the first node to execute | | `metadata` | object | No | Arbitrary metadata (e.g. `name`, `version`) | | `settings` | object | No | Execution settings (e.g. `{ "stop_on_failure": true }`) | #### Node shape | Field | Type | Required | Description | |-------|------|----------|-------------| | `node_id` | string | **Yes** | Unique node identifier within this workflow | | `node_type` | string | **Yes** | Node type (e.g. `trigger`, `agent`, `delay`, `branch`, `loop`) | | `name` | string | **Yes** | Display name for this node | | `config` | object | **Yes** | Node-type-specific configuration | | `position` | object | No | Visual position `{ "x": 0, "y": 0 }` for the canvas | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/workflows" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Lead nurture", "description": "Enroll new form submissions in a sequence", "category": "outreach", "object_type": "Contact", "config": { "nodes": [ { "node_id": "n1", "node_type": "trigger", "name": "On Submit", "config": {}, "position": { "x": 0, "y": 0 } } ], "edges": [{ "source": "n1", "target": "n2" }], "start_node_id": "n1", "metadata": { "name": "Lead nurture", "version": 1 }, "settings": { "stop_on_failure": true } } }' ``` ### Response `201 Created` ```json { "id": "wf_abc123", "name": "Lead nurture", "config": { "nodes": [...], "edges": [...], "start_node_id": "n1" } } ``` --- ## Update Workflow `PUT /workflows/{action_id}` Partial update. All body fields are optional — pass only the fields you want to change. Same field set as create. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `action_id` | string | Workflow ID | ### Request Body All fields are optional. | Field | Type | Description | |-------|------|-------------| | `name` | string | Workflow name | | `description` | string | Human-readable description | | `category` | string | Category label | | `object_type` | string | Primary object type | | `config` | object | Full workflow graph (replaces the existing config) | ### Example **cURL** ```bash curl -X PUT "https://be.graph8.com/api/v1/workflows/$WORKFLOW_ID" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Lead nurture v2", "config": { "nodes": [...], "edges": [...], "start_node_id": "n1" } }' ``` --- ## Validate Workflow `POST /workflows/validate` Validates a workflow graph without saving it. Checks for orphan nodes, dangling edges, missing required fields per node type, and other structural issues. Call before `PUT` to fail fast. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `config` | object | **Yes** | Workflow graph to validate — same shape as the `config` field in create/update | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/workflows/validate" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "config": { "nodes": [ { "node_id": "n1", "node_type": "trigger", "name": "On Submit", "config": {} } ], "edges": [], "start_node_id": "n1" } }' ``` ### Response ```json { "errors": [], "warnings": ["Node n2 is referenced in edges but not defined in nodes"], "missing_references": [] } ``` --- ## Execute Workflow `POST /workflows/{action_id}/execute` Runs a workflow immediately. Returns an `execution_id` you can poll via `GET /workflows/executions/{execution_id}`. > **Caution:** > Executing a workflow triggers real outreach if any node calls sequence enroll, dialer, Slack, or email actions. Always validate the workflow graph before executing. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `action_id` | string | Workflow ID | ### Request Body All fields are optional. | Field | Type | Default | Description | |-------|------|---------|-------------| | `input_data` | object | `{}` | Input payload passed to the first node | | `triggered_by` | string | caller user id | ID of the user triggering this execution | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/workflows/$WORKFLOW_ID/execute" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "input_data": { "contact_id": 5028106, "source": "manual_run" }, "triggered_by": "user_x" }' ``` ### Response ```json { "execution_id": "exec_xyz789" } ``` --- ## Reset Trigger Cursor `POST /workflows/{action_id}/trigger-reset` Resets the event-stream trigger cursor to the current time (`now()`). No request body. Use this to make the trigger start processing new events from this moment forward. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `action_id` | string | Workflow ID | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/workflows/$WORKFLOW_ID/trigger-reset" \ -H "Authorization: Bearer $API_KEY" ``` --- ## Get Execution `GET /workflows/executions/{execution_id}` Returns the current status of an execution, its output data, and token/cost telemetry. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `execution_id` | string | Execution ID returned by `POST /workflows/{action_id}/execute` | ### Example **cURL** ```bash curl "https://be.graph8.com/api/v1/workflows/executions/$EXECUTION_ID" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "execution_id": "exec_xyz789", "workflow_id": "wf_abc123", "status": "completed", "output_data": { "result": "enrolled" }, "tokens_used": 1240, "cost_credits": 2 } ``` --- ## Pause Execution `POST /workflows/executions/{execution_id}/pause` Pauses an in-flight execution. The execution state is preserved and can be resumed. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `execution_id` | string | Execution ID | ### Request Body All fields are optional. | Field | Type | Description | |-------|------|-------------| | `reason` | string | Internal reason code for the pause | | `message` | string | Human-readable message explaining why the execution was paused | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/workflows/executions/$EXECUTION_ID/pause" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "reason": "manual_review", "message": "Needs human approval before continuing" }' ``` --- ## Resume Execution `POST /workflows/executions/{execution_id}/resume` Resumes a paused execution, optionally skipping from a specific step or skipping failed nodes. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `execution_id` | string | Execution ID | ### Request Body All fields are optional. | Field | Type | Default | Description | |-------|------|---------|-------------| | `from_step` | string | — | Node ID to resume from (skips all preceding steps) | | `skip_failed` | boolean | `false` | When `true`, skips nodes that previously failed and continues from the next node | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/workflows/executions/$EXECUTION_ID/resume" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "from_step": "n3", "skip_failed": false }' ``` --- ## Stop Execution `POST /workflows/executions/{execution_id}/stop` Permanently stops an execution. This is a terminal action — the execution cannot be resumed after stopping. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `execution_id` | string | Execution ID | ### Request Body All fields are optional. | Field | Type | Default | Description | |-------|------|---------|-------------| | `reason` | string | — | Internal reason code for the stop | | `save_partial_results` | boolean | `true` | When `true`, persists any output data collected before the stop | | `message` | string | — | Human-readable message explaining why the execution was stopped | ### Example **cURL** ```bash curl -X POST "https://be.graph8.com/api/v1/workflows/executions/$EXECUTION_ID/stop" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "reason": "cancelled", "save_partial_results": true, "message": "Stopped by user" }' ``` --- ## Resolver Helpers A set of read-only GET endpoints that return lookup data for populating node configuration UIs (autocompletes, dropdowns, pickers). No request body for any of these. ### List Roam Users `GET /workflows/integrations/roam/users` **cURL** ```bash curl "https://be.graph8.com/api/v1/workflows/integrations/roam/users" \ -H "Authorization: Bearer $API_KEY" ``` --- ### List Roam Groups `GET /workflows/integrations/roam/groups` **cURL** ```bash curl "https://be.graph8.com/api/v1/workflows/integrations/roam/groups" \ -H "Authorization: Bearer $API_KEY" ``` --- ### List Slack Users `GET /workflows/integrations/slack/users` **cURL** ```bash curl "https://be.graph8.com/api/v1/workflows/integrations/slack/users" \ -H "Authorization: Bearer $API_KEY" ``` --- ### List Slack Channels `GET /workflows/integrations/slack/channels` **cURL** ```bash curl "https://be.graph8.com/api/v1/workflows/integrations/slack/channels" \ -H "Authorization: Bearer $API_KEY" ``` --- ### List MCP Servers `GET /workflows/mcp-servers` Returns available MCP servers that can be used as targets in Agent nodes. **cURL** ```bash curl "https://be.graph8.com/api/v1/workflows/mcp-servers" \ -H "Authorization: Bearer $API_KEY" ``` --- ### List Dispositions `GET /workflows/dispositions` Returns the 17-value call disposition enum used in voice/dialer nodes. **cURL** ```bash curl "https://be.graph8.com/api/v1/workflows/dispositions" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json [ "booked", "callback", "not_interested", "dnc", "not_icp", "has_solution", "gate_keeper", "wrong_number", "left_org", "voicemail", "hangup", "no_voice", "dial_tree", "answering_machine", "not_answered", "sdr_hangup", "failed" ] ``` --- ### Get Form Fields `GET /workflows/forms/{form_id}/fields` Returns the field schema for a specific form, used to configure form-trigger nodes. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `form_id` | string | Form ID | **cURL** ```bash curl "https://be.graph8.com/api/v1/workflows/forms/demo_request/fields" \ -H "Authorization: Bearer $API_KEY" ``` ### Response ```json { "form_id": "demo_request", "fields": ["first_name", "last_name", "work_email", "company"], "sample_size": 42 } ``` --- # Connect Your Calendar > Link your calendar to graph8 for scheduling, availability detection, and automatic meeting logging. Source: https://docs.graph8.com/getting-started/connect-calendar/ Connect your calendar so graph8 can check your availability, create booking links, and automatically log meetings with prospects. ## Supported Calendar Providers - **Google Calendar** (Google Workspace and personal Gmail) - **Microsoft Outlook** (Microsoft 365 and Exchange) - **Apple Calendar** (iCloud) You can connect multiple calendars from different providers at the same time. ## Connecting Your Calendar 1. Go to **Settings → Calendar** 2. Click **Connect Calendar** 3. Select your provider (Google, Microsoft, or Apple) 4. Authorize graph8 to access your calendar via OAuth 5. Once connected, you'll see your calendars listed in the settings panel ### Select Calendars for Conflict Checking After connecting, choose which calendars graph8 should check for conflicts. For example, you might select: - Your **work calendar** — so meetings aren't booked during work events - Your **personal calendar** — so personal appointments block availability too - A **team calendar** — to avoid conflicts with team-wide events graph8 aggregates busy times across all selected calendars. If any selected calendar shows you as busy during a time slot, that slot won't be offered to prospects. ### Set Your Destination Calendar Choose which calendar receives new bookings. When a prospect books a meeting through your link, the event is automatically created in your destination calendar with all the meeting details. ## Set Your Availability Define when you're available for meetings: 1. Go to **Settings → Calendar → Availability** 2. Set your **working hours** for each day of the week (e.g., Monday–Friday, 9:00 AM – 5:00 PM) 3. Set your **timezone** — graph8 automatically converts time slots to the prospect's timezone 4. Optionally create **multiple schedules** for different event types (e.g., a "Demo" schedule with afternoon-only slots) > **Tip:** > Set buffer time between meetings to avoid back-to-back calls. A 15-minute buffer gives you time to prep notes and take a break. ## Create Event Types Event types define the meetings prospects can book with you. Each event type has its own booking link. | Setting | Description | Example | |---------|-------------|---------| | **Title** | Meeting name shown to prospects | "30-Minute Demo" | | **Duration** | Length of the meeting | 15, 30, or 60 minutes | | **Location** | Where the meeting happens | Zoom, Google Meet, phone call | | **Description** | Details shown on the booking page | Agenda or prep instructions | | **Availability** | Which schedule to use | "Default" or a custom schedule | | **Buffer time** | Padding before/after meetings | 10 minutes before, 15 after | | **Booking window** | How far in advance prospects can book | 2–30 days out | | **Minimum notice** | Minimum lead time before a booking | 4 hours | ### Creating an Event Type 1. Go to **Settings → Calendar → Event Types** 2. Click **New Event Type** 3. Configure the settings above 4. Copy your booking link and use it in sequences, emails, or your email signature ## Booking Links in Sequences graph8 can auto-insert your booking link into outreach sequences: - Use the `{{booking_link}}` merge tag in any email step to insert your default booking link - Prospects click the link, see your real-time availability, pick a slot, and the meeting is confirmed instantly - The meeting appears in your destination calendar and is logged to the contact's record in graph8 ## Round-Robin Scheduling Distribute meetings across your team automatically: 1. Create a **team event type** in **Settings → Calendar → Event Types** 2. Add team members to the event type 3. Set the distribution method: - **Round-robin** — rotates evenly across team members based on availability - **Collective** — finds a time when all selected members are free (for group meetings) 4. Share the team booking link — graph8 checks each member's connected calendars and assigns the booking to the next available person > **Tip:** > Round-robin scheduling considers each team member's individual availability and connected calendars. If someone is on PTO or has a full day, they're automatically skipped. ## How Calendar Sync Works When a prospect checks your booking page or graph8 evaluates your availability: 1. graph8 fetches busy times from all your selected calendars 2. Busy times are excluded from available slots 3. Your availability schedule is applied (working hours, buffer time, booking window) 4. Only genuinely free slots are shown to the prospect When a booking is created: 1. An event is added to your destination calendar 2. The prospect receives a confirmation email with meeting details 3. The meeting is automatically logged to the contact's record in graph8 ## Meetings Channel Connected calendars also feed the **Meetings** channel in the Inbox. Once events are synced, graph8 can show those meetings in a review queue and enrich them with transcript summaries, participant context, and follow-up tasks when transcript data is available. ## Privacy and Permissions graph8 uses OAuth to connect to your calendar provider. It: - **Reads** calendar events to check for conflicts (busy/free status only) - **Creates** events in your destination calendar when bookings are made - Does **not** modify or delete your existing calendar events - Does **not** share your calendar data with other users You can disconnect your calendar at any time from **Settings → Calendar → Disconnect**. ## Troubleshooting **Calendar not syncing?** - Ensure you authorized the correct account (check the email address shown in Settings) - Corporate Google Workspace or Microsoft 365 admins may block third-party calendar access — ask your IT team to allow graph8 - Try disconnecting and reconnecting the calendar **Availability showing incorrectly?** - Verify you selected the right calendars for conflict checking — unselected calendars won't block time slots - Events marked as "Free" in your calendar won't block availability (only "Busy" events count) - Check your timezone setting matches your actual timezone **Booking link not working?** - Make sure you have at least one event type created - Verify your availability schedule has open hours for the days prospects are trying to book ## You're All Set With your domain, website, and calendar connected, your graph8 workspace is fully configured. Start exploring: - [Studio →](https://docs.graph8.com/studio/global/) — Build AI-powered campaigns - [Data →](https://docs.graph8.com/data/prospecting/) — Search prospects and manage lists - [Engage →](https://docs.graph8.com/engage/sequencer/) — Create outreach sequences - [Meetings →](https://docs.graph8.com/engage/meetings/) — Review meetings and transcript context --- # Connect Your Website > Add the graph8 tracking snippet to your website for real-time visitor tracking, form fills, and analytics. Source: https://docs.graph8.com/getting-started/connect-website/ Connect your website to graph8 by adding a small JavaScript snippet. This enables real-time visitor identification, form submission tracking, and website analytics. ## What You Get Once connected, graph8 automatically captures: - **Real-time visitors** — See which companies and contacts are browsing your site right now - **Form submissions** — Capture leads from any form on your site without extra configuration - **Page views** — Track which pages prospects visit and how long they stay - **Visitor identification** — Match anonymous visitors to known contacts and companies using graph8's data ## Adding the Snippet 1. Go to **Settings → Website Tracking** 2. Copy the JavaScript snippet provided 3. Paste it into your website's `<head>` tag, just before the closing `</head>` The snippet looks like this: ```html <script> (function(g,r,a,p,h){g.g8=g.g8||function(){(g.g8.q=g.g8.q||[]).push(arguments)}; h=r.createElement(a);h.async=1;h.src=p;r.head.appendChild(h) })(window,document,'script','https://cdn.graph8.com/tracking.js'); g8('init', 'YOUR_TRACKING_ID'); </script> ``` Your unique tracking ID is pre-filled when you copy from the Settings page. ## Installation by Platform ### Google Tag Manager 1. Create a new **Custom HTML** tag 2. Paste the snippet 3. Set the trigger to **All Pages** 4. Publish your container ### WordPress Add the snippet to your theme's `header.php` file, or use a plugin like **Insert Headers and Footers** to add it without editing code. ### Next.js / React Add the snippet to your `_document.tsx` or `layout.tsx` `<head>` section. ## Verifying the Connection After adding the snippet: 1. Return to **Settings → Website Tracking** 2. graph8 will show a green **Connected** status once it detects the snippet 3. Visit your own website to generate a test event 4. Check the **Visitors** section to confirm data is flowing > **Tip:** > It may take a few minutes for the first events to appear. If you don't see data after 10 minutes, check that the snippet is loading correctly using your browser's developer tools (Network tab). ## Next Steps - [Connect Your Calendar →](https://docs.graph8.com/getting-started/connect-calendar/) — Enable meeting scheduling --- # Feature guide > Find user guides for graph8 workflows, records, channels, settings and developer tools. Source: https://docs.graph8.com/getting-started/feature-guide/ Find the guide for the work you want to do. The articles below cover product workflows, settings, and developer tools. Available controls depend on your role, plan, organization configuration and product version. ## How to use this guide 1. Choose the product area or task below. 2. Follow the article from setup through its result checks. 3. If a control is not present, check the article prerequisites and your access before asking an administrator. ## Product areas ### Data | Task or feature | What the guide covers | |---|---| | [Companies](https://docs.graph8.com/data/companies/) | View and organize company records with firmographic data, technographics, and account-level signals. | | [Contacts](https://docs.graph8.com/data/contacts/) | View, manage, and enrich individual contact records with activities, intelligence documents, and AI actions. | | [Custom Fields](https://docs.graph8.com/data/custom-fields/) | Create custom fields (dynamic columns) on contacts and companies to track data specific to your business. | | [Custom objects and attributes](https://docs.graph8.com/data/custom-objects/) | Define record types and typed fields for information that does not fit standard CRM records. | | [Customer Data Platform](https://docs.graph8.com/data/customer-data-platform/) | How graph8 stores, unifies, and activates customer data — contacts, companies, events, audiences, enrichment, and identity resolution. | | [Enrichment & Formulas](https://docs.graph8.com/data/enrichment/) | Fill in missing data with waterfall enrichment, AI research, and computed formula columns — like a spreadsheet that enriches itself. | | [Import & Export](https://docs.graph8.com/data/import-export/) | Upload contacts from CSV or CRM, and export prospect lists from graph8. | | [Managing Lists](https://docs.graph8.com/data/managing-lists/) | Create prospect lists, import from CSV, filter and segment, and run bulk actions. | | [Prospecting](https://docs.graph8.com/data/prospecting/) | Search graph8's contact database to find and save prospects to targeted lists. | | [Workbench Pipelines](https://docs.graph8.com/data/staging-pipelines/) | Automate multi-step data processing in the staging workbench — chain AI extraction, waterfall enrichment, formulas, and branching into reusable pipelines. | | [Staging Workbench](https://docs.graph8.com/data/staging-workbench/) | Import, structure, enrich, and prepare raw data before adding it to your contact lists. | | [Suppressions](https://docs.graph8.com/data/suppressions/) | Exclude contacts from outreach across email, phone, and LinkedIn, and reinstate them when needed. | | [Tasks](https://docs.graph8.com/data/tasks/) | Create, assign, and track tasks across contacts, companies, and deals for structured follow-up management. | ### Signals | Task or feature | What the guide covers | |---|---| | [Buying Committees](https://docs.graph8.com/signals/buying-committees/) | See account-level buying intent rolled up across the people who actually make the decision - the economic buyer, champion, end user, and technical evaluator. | | [Forms](https://docs.graph8.com/signals/forms/) | Capture leads from website form submissions and automatically add them to your contact lists. | | [Hiring Signals](https://docs.graph8.com/signals/hiring/) | Track companies that are actively hiring and people who change jobs - including talent moving in and out of your competitors. | | [Intent Tracking](https://docs.graph8.com/signals/intent-tracking/) | Detect buying intent from your own website and from across the web using keyword and topic tracking. | | [Signals Overview](https://docs.graph8.com/signals/overview/) | Understand buying signals — how graph8 detects intent from website visitors, form submissions, and keyword tracking. | | [Social Listener](https://docs.graph8.com/signals/social-listener/) | Monitor X (Twitter) for brand mentions, competitors, keywords, and high-intent buying signals - on graph8's shared X API or your own X Developer App. | | [Trends](https://docs.graph8.com/signals/trends/) | Track industry trends with growth scores, company correlations, and category breakdowns to identify demand signals. | | [Website Visitors](https://docs.graph8.com/signals/website-visitors/) | Track anonymous website visitors, identify companies, and turn traffic into actionable leads. | ### Studio | Task or feature | What the guide covers | |---|---| | [Companies](https://docs.graph8.com/studio/accounts/) | Run AI-powered company intelligence, generate deal strategy documents, and manage your sales pipeline. | | [Google Ads Setup](https://docs.graph8.com/studio/ads-google/) | Set up a Google Ads account, connect it to graph8, launch a Google campaign, and verify your ads inside Google Ads. | | [LinkedIn Ads Setup](https://docs.graph8.com/studio/ads-linkedin/) | Set up LinkedIn Campaign Manager, connect it to graph8, launch a LinkedIn campaign, and verify your ads inside LinkedIn. | | [Ads](https://docs.graph8.com/studio/ads/) | Design on-brand ad creatives with AI and launch paid campaigns to Meta, LinkedIn, Google Ads, and X from one workspace. | | [Campaigns](https://docs.graph8.com/studio/campaigns/) | Create AI-powered campaigns — from ideation through document generation to launching into your outreach sequences. | | [Content](https://docs.graph8.com/studio/content/) | Manage your website content inventory, analyze SEO health, and use AI to refresh or create content at scale. | | [Copilot](https://docs.graph8.com/studio/copilot/) | Use Studio Copilot to create, edit, and analyze your campaign documents with AI-powered skills. | | [Custom Records](https://docs.graph8.com/studio/custom-records/) | Model custom object types beyond contacts and companies — accounts, opportunities, territories, products, or anything specific to your business. | | [Run Actions on Contacts and Companies](https://docs.graph8.com/studio/entity-action-execution/) | Execute any AI action directly from a contact or company detail page, with that record's data used as context. | | [Global Context](https://docs.graph8.com/studio/global/) | Set up your organization's brand, personas, ICPs, and intelligence — the strategic foundation that powers all AI-generated content. | | [Intelligence](https://docs.graph8.com/studio/intelligence/) | Research competitors, scrape websites, discover keywords, and enrich company data — all from Studio's Intelligence module. | | [Organization knowledge](https://docs.graph8.com/studio/knowledge/) | Browse, search, and contribute the knowledge your organization uses. | | [Landing Pages](https://docs.graph8.com/studio/landing-pages/) | Create, edit, and publish landing pages using AI - clone from your website, start from a template, or build from scratch. | | [Security Questionnaires](https://docs.graph8.com/studio/security-questionnaires/) | Upload security questionnaires, let AI draft answers from your approved responses, and ship completed questionnaires faster. | | [Skills](https://docs.graph8.com/studio/skills/) | Create, version, and deploy reusable AI skills — structured prompts that run across contacts, companies, campaigns, and documents. | | [Team](https://docs.graph8.com/studio/team/) | Set up team profiles, AI Twins, and thought leadership content — generate personalized content in each team member's voice. | ### Revenue | Task or feature | What the guide covers | |---|---| | [AE Cockpit](https://docs.graph8.com/deals/ae-cockpit/) | AI-powered decision queue for account executives - prioritized actions, meeting prep, deal risk alerts, and coaching insights. | | [Customers](https://docs.graph8.com/deals/customers/) | Manage closed-won accounts - track post-sale activity, spot upsell opportunities, and monitor customer health. | | [Deal Contacts & Notes](https://docs.graph8.com/deals/deal-contacts/) | Manage buying committee contacts on deals - assign roles like champion, decision maker, and blocker. Add notes with @mentions for team collaboration. | | [Leads](https://docs.graph8.com/deals/leads/) | Unqualified to qualifying records — work the lead lifecycle with status, lifecycle stage, scoring, and bulk actions. | | [Deals Overview](https://docs.graph8.com/deals/overview/) | Manage your sales pipeline from lead to close with deals, forecasting, revenue tracking, and AI-powered deal management. | | [Pipeline Settings](https://docs.graph8.com/deals/pipeline-settings/) | Create and customize deal pipelines - add stages, set probabilities, and configure multiple pipelines for different sales processes. | | [Products & Services](https://docs.graph8.com/deals/products/) | Manage your product catalog - connect Stripe, track subscriptions, and attach products to deals for accurate revenue tracking. | | [Prospects](https://docs.graph8.com/deals/prospects/) | Companies with active deals in the pipeline - track progress from qualified to closed. | | [Quotes](https://docs.graph8.com/deals/quotes/) | Create and send quotes to prospects — line items pulled from Stripe, e-signature flow, expiration tracking, and contract terms. | ### Engage | Task or feature | What the guide covers | |---|---| | [Appointments](https://docs.graph8.com/engage/appointments/) | Schedule and manage appointments — automatic contact creation, CRM meeting sync, and owner assignment. | | [Connections](https://docs.graph8.com/engage/connections/) | Connect and sync your tools — manage data streams, destinations, transformations, and real-time event tracking. | | [Dialer](https://docs.graph8.com/engage/dialer/) | Power dialer for outbound calls — parallel dialing, call recording, voicemail detection, AI voice agents, and SDR analytics. | | [HeyReach Campaign Overview](https://docs.graph8.com/engage/heyreach-campaign-overview/) | Monitor LinkedIn campaign performance, lead progress, and engagement metrics directly from your sequence step cards. | | [Inbox](https://docs.graph8.com/engage/inbox/) | Manage all conversations in one place — email, LinkedIn, SMS, chat, calls, and meetings with AI-powered response and review tools. | | [LinkedIn Publishing](https://docs.graph8.com/engage/linkedin-publishing/) | Compose, schedule, and publish organic LinkedIn posts from graph8, then turn the people who engage into pipeline. | | [Meetings](https://docs.graph8.com/engage/meetings/) | Review scheduled meetings in the Inbox — filter customer, prospect, and internal calls, manage transcript links, and work from AI-generated meeting context. | | [Newsletter](https://docs.graph8.com/engage/newsletter/) | Send branded newsletters to your subscriber lists with full engagement tracking. | | [Nurture](https://docs.graph8.com/engage/nurture/) | Engage consented audiences at scale with multi-step nurture campaigns, built for warm leads, customers, and opted-in subscribers. | | [Sequencer](https://docs.graph8.com/engage/sequencer/) | Build automated multi-step outreach sequences across email, LinkedIn, phone, SMS, and WhatsApp. | | [Web Chat](https://docs.graph8.com/engage/web-chat/) | Engage website visitors with a customizable live chat widget — AI agents, action buttons, preset questions, and calendar booking. | | [Work: conversations and agent work](https://docs.graph8.com/engage/work/) | Use Work to coordinate conversations, review agent activity, and schedule recurring work. | ### Agents | Task or feature | What the guide covers | |---|---| | [Copilot Bridge](https://docs.graph8.com/ai-features/bridge/) | Access graph8 Copilot from WhatsApp, Slack, Roam, and iMessage - ask questions and run actions from your messaging apps. | | [AI Content Generation](https://docs.graph8.com/ai-features/content-generation/) | Create personalized content with AI assistance | | [Copilot](https://docs.graph8.com/ai-features/copilot/) | AI assistant built into graph8 - ask questions, look up contacts, manage deals, run web searches, and take actions across your CRM using natural language. | | [Enrichment](https://docs.graph8.com/ai-features/enrichment/) | Fill missing contact data with waterfall enrichment - emails, phones, job titles, and company info from multiple data providers. | | [AI Features](https://docs.graph8.com/ai-features/) | AI capabilities across enrichment, inbox, campaigns, and sales. | | [AI Lead Scoring](https://docs.graph8.com/ai-features/lead-scoring/) | Prioritize leads with AI-powered scoring | | [AI Features Overview](https://docs.graph8.com/ai-features/overview/) | AI capabilities built into every part of graph8 - enrichment, content generation, copilot, voice agents, lead scoring, and sales coaching. | | [AI Research Assistant](https://docs.graph8.com/ai-features/research/) | Research prospects and companies with AI | | [Sales Coach](https://docs.graph8.com/ai-features/sales-coach/) | AI-powered sales guidance - get real-time coaching on deals, objections, and next steps based on your pipeline data and competitive intel. | | [Agents & Actions](https://docs.graph8.com/ai-features/skills/) | Build AI agents with custom personas, attach reusable actions, connect knowledge bases, and deploy across phone, email, and chat. | | [Voice Agents](https://docs.graph8.com/ai-features/voice-agents/) | Create AI agents that handle phone calls, generate personalized outreach, and automate voice interactions. | | [Workflows](https://docs.graph8.com/ai-features/workflows/) | Automate multi-step AI workflows that chain skills, triggers, and actions together. | ### Analytics | Task or feature | What the guide covers | |---|---| | [Acquisition](https://docs.graph8.com/analytics/acquisition/) | Understand where your visitors come from — traffic channels, campaigns, referrals, and AI-sourced traffic. | | [Behavior](https://docs.graph8.com/analytics/behavior/) | Analyze visitor behavior on your website — page performance, landing pages, exit pages, and custom event tracking. | | [Conversions](https://docs.graph8.com/analytics/conversions/) | Track goals, form submissions, and conversion funnels — measure what matters with completion rates, form performance, and step-by-step funnel analysis. | | [Dialer Analytics](https://docs.graph8.com/analytics/dialer-analytics/) | Call volume, connection rate, talk time, dispositions, redial performance, and team leaderboard — built into the Dialer. | | [Marketing Intelligence](https://docs.graph8.com/analytics/marketing-intelligence/) | Measure the web-to-outbound conversion funnel — qualified traffic, campaign attribution, content performance, and pipeline impact. | | [Overview](https://docs.graph8.com/analytics/overview/) | High-level snapshot of website traffic and engagement — visitors, sessions, page views, bounce rate, and top traffic sources. | | [Realtime](https://docs.graph8.com/analytics/realtime/) | Monitor live website activity — active visitors, page views, traffic sources, visitor locations, and company identification in real time. | | [SDR Analytics](https://docs.graph8.com/analytics/sdr-analytics/) | Personal SDR dashboard — execution health, outcomes, next-best actions, and your reply rate vs the team. | ### Settings | Task or feature | What the guide covers | |---|---| | [API, MCP & CLI](https://docs.graph8.com/settings/api/) | Create and manage API keys, connect MCP tools, and set up the CLI. | | [Audience Sync](https://docs.graph8.com/settings/audience-sync/) | Sync contact lists to ad platforms - push audiences to Meta, LinkedIn, Google Ads, and X for targeted advertising. | | [Billing](https://docs.graph8.com/settings/billing/) | Manage subscription, credits, and payment. | | [Bug Reports](https://docs.graph8.com/settings/bug-reports/) | Report bugs directly from graph8 - attach screenshots, track status, and communicate with the team. | | [Company Settings](https://docs.graph8.com/settings/company/) | Configure your organization's core settings - name, domain, timezone, industry, and branding defaults. | | [Compliance](https://docs.graph8.com/settings/compliance/) | Email compliance and suppression management. | | [Deals](https://docs.graph8.com/settings/deals/) | Configure deal stages in pipelines. | | [Docs](https://docs.graph8.com/settings/docs/) | Control default document generation and meeting transcript extraction by scope. | | [Domains](https://docs.graph8.com/settings/domains/) | Manage email sending domains and DNS. | | [Email Warmup](https://docs.graph8.com/settings/email-warmup/) | Build sender reputation for new mailboxes with automated email warmup - configure daily limits, ramp-up speed, and monitor deliverability. | | [Integrations](https://docs.graph8.com/settings/integrations/) | Connect third-party tools and services. | | [Mailboxes](https://docs.graph8.com/settings/mailboxes/) | Connect and manage email mailboxes. | | [Phone Numbers](https://docs.graph8.com/settings/phone-numbers/) | Manage phone numbers for calls and SMS. | | [Pipedrive Integration](https://docs.graph8.com/settings/pipedrive/) | Connect Pipedrive to graph8 to pull and push contacts, organizations, deals, and leads using Pipedrive labels. | | [Purchase Domain & Mailboxes](https://docs.graph8.com/settings/purchase-domains/) | Buy domains and set up mailboxes directly in graph8. | | [Roles](https://docs.graph8.com/settings/roles/) | Define custom roles and permissions. | | [Schedule & Availability](https://docs.graph8.com/settings/schedule-availability/) | Set your working hours and availability for meetings, appointments, and sequence scheduling. | | [Scraping](https://docs.graph8.com/settings/scraping/) | Configure website scraping behavior and exclusions. | | [SSO](https://docs.graph8.com/settings/sso/) | Single sign-on configuration (Enterprise). | | [Users](https://docs.graph8.com/settings/team/) | Invite and manage the users in your graph8 organization - roles, status, contact details, and invitations. | ### Marketplace | Task or feature | What the guide covers | |---|---| | [Browsing & Filtering Talent](https://docs.graph8.com/marketplace/clients/browse/) | Search, sort, and filter the talent catalog to find the right SDRs, AEs, and GTM Engineers — and save searches you want to reuse. | | [Hiring Talent](https://docs.graph8.com/marketplace/clients/hiring/) | Send a hire offer with custom rates and a start date, and follow it from pending acceptance to an active contract. | | [Managing Hired Talent](https://docs.graph8.com/marketplace/clients/managing/) | Work with the talent you've hired — message them, track meetings, and review and pay their statements. | | [Opening the Marketplace](https://docs.graph8.com/marketplace/clients/overview/) | Find the Marketplace in graph8 and get oriented with the six tabs clients use to hire and manage talent. | | [Talent Profiles & Match Scores](https://docs.graph8.com/marketplace/clients/profiles/) | Read a talent card, understand certification tiers, and interpret the match-score radar before you hire. | | [How It Works](https://docs.graph8.com/marketplace/how-it-works/) | How the graph8 Marketplace connects clients and talent — vetting, certification, rates, and the commission model. | | [graph8 Marketplace](https://docs.graph8.com/marketplace/) | Hire vetted GTM talent or join as a sales professional — the graph8 Marketplace connects companies with freelance SDRs, AEs, and GTM Engineers. | | [Setting Up Your AI Twin](https://docs.graph8.com/marketplace/talent/ai-twin/) | Complete your AI Twin so graph8 can write and act on your behalf — career details, voice, and a LinkedIn import. | | [Joining the Marketplace](https://docs.graph8.com/marketplace/talent/join/) | Sign up as graph8 Marketplace talent, accept your profile, and start the certification that gets you listed. | | [Your Listing & Getting Scored](https://docs.graph8.com/marketplace/talent/listing/) | Set up your public Marketplace Profile — upload your resume to get scored, set your rate card, and control your availability. | | [Messages & Meetings](https://docs.graph8.com/marketplace/talent/messages-meetings/) | Stay in touch with hiring clients and track the meetings you book from the Messages and Meetings tabs. | | [Receiving & Accepting Offers](https://docs.graph8.com/marketplace/talent/offers/) | Review hire offers from clients, check the order form, and accept or decline. | | [Getting Paid](https://docs.graph8.com/marketplace/talent/payouts/) | Submit statements for your work, set up a payout method, and track your earnings from the Invoices and Payouts tabs. | | [Switching Between Accounts](https://docs.graph8.com/marketplace/talent/switching-accounts/) | Move between your talent profile and a client organization using the org switcher. | ### Developer Platform | Task or feature | What the guide covers | |---|---| | [Agency Keys & Cross-Org Targeting](https://docs.graph8.com/developers/agency-keys/) | Mint a single agency-scoped API key to operate any client org across the entire Developer API without one key per client. | | [REST API FAQ](https://docs.graph8.com/developers/api-faq/) | Common questions about the graph8 Developer REST API — auth headers, pagination, rate limits, errors, idempotency, webhooks, and credits | | [API Keys: Live & Test Mode](https://docs.graph8.com/developers/api-keys/) | Mint live and test API keys with g8_live_/g8_test_ prefixes | | [API and MCP coverage](https://docs.graph8.com/developers/api-mcp-coverage/) | Understand which operations are exposed through MCP and when to use the API. | | [Appointments Management](https://docs.graph8.com/developers/appointments/) | Manage event types, bookings, schedules, availability, routing forms, workflows, and calendar integrations via the graph8 API | | [Assert / Upsert](https://docs.graph8.com/developers/assert/) | Idempotent create-or-update for contacts, companies, and deals | | [Audience Syncs](https://docs.graph8.com/developers/audience-syncs/) | Mirror contact lists to ad platforms (Meta, LinkedIn, Google Ads, X) on a recurring schedule | | [Authentication](https://docs.graph8.com/developers/authentication/) | Authenticate API requests with org API keys | | [Automation Platforms](https://docs.graph8.com/developers/automation-platforms/) | Connect workflow automation tools to graph8 Studio via MCP | | [Claude Code Setup](https://docs.graph8.com/developers/claude-code-setup/) | Connect Claude Code to graph8 via MCP using ~/.claude/mcp.json with OAuth or stdio | | [Claude Desktop Setup](https://docs.graph8.com/developers/claude-desktop-setup/) | Connect Claude Desktop to graph8 Studio via MCP | | [CLI Campaigns](https://docs.graph8.com/developers/cli-campaigns/) | List, create, update, and launch repo-scoped campaigns via the graph8 Developer API | | [CLI Examples](https://docs.graph8.com/developers/cli-examples/) | Worked terminal workflows using the g8 CLI — prospect, enrich, sequence, quote, workflow, intent-driven outreach | | [CLI FAQ](https://docs.graph8.com/developers/cli-faq/) | Common questions about the g8 CLI — install, auth, scripting, output formats, parity with MCP and SDK | | [CLI Pipelines (Deal Pipelines)](https://docs.graph8.com/developers/cli-pipelines/) | Create and manage deal pipelines — the lightweight stage lists that deals progress through in graph8 | | [Repos & Spine](https://docs.graph8.com/developers/cli-spine/) | Connect repositories, run scans, install tracking, manage patch sets, and search the per-repo knowledge base via the graph8 Developer API. | | [CLI](https://docs.graph8.com/developers/cli/) | graph8 developer CLI for tracking snippets and progressive forms | | [ClickHouse](https://docs.graph8.com/developers/clickhouse/) | Read-only enrichment and per-org visitor analytics against graph8's ClickHouse instances | | [Companies](https://docs.graph8.com/developers/companies/) | List, update, and delete companies and their contacts in your workspace | | [Contacts](https://docs.graph8.com/developers/contacts/) | List, create, update, and delete contacts in your workspace | | [CrewAI Setup](https://docs.graph8.com/developers/crewai-setup/) | Connect CrewAI multi-agent crews to graph8 via MCPServerAdapter | | [CRM Syncs](https://docs.graph8.com/developers/crm-syncs/) | Push contacts, companies, and list memberships to HubSpot, Salesforce, Pipedrive, Zoho, or SugarCRM | | [Cursor IDE Setup](https://docs.graph8.com/developers/cursor-setup/) | Connect Cursor IDE to graph8 Studio via MCP | | [Data Pipeline](https://docs.graph8.com/developers/data-pipeline/) | How events flow from your app to destinations - streams, functions, and real-time routing | | [Deals](https://docs.graph8.com/developers/deals/) | Create, manage, and track deals across your pipeline stages | | [Destinations](https://docs.graph8.com/developers/destinations/) | Route events to data warehouses, analytics tools, and CRMs automatically | | [Enrichment](https://docs.graph8.com/developers/enrichment/) | Lookup contacts and companies, run waterfall enrichment, and verify emails | | [Errors](https://docs.graph8.com/developers/errors/) | HTTP status codes and error response format | | [Fields](https://docs.graph8.com/developers/fields/) | List, create, and write custom field definitions and values for contacts and companies | | [Functions](https://docs.graph8.com/developers/functions/) | Transform, filter, and enrich events with JavaScript before they reach destinations | | [Getting Started](https://docs.graph8.com/developers/getting-started/) | Choose how to integrate with graph8 - SDK, MCP, CLI, or REST API | | [GTM Campaigns](https://docs.graph8.com/developers/gtm-campaigns/) | Create and manage AI Studio campaigns, documents, and sequence step catalogs | | [GTM Context](https://docs.graph8.com/developers/gtm-context/) | Read and update global context documents, personas, ICPs, mailboxes, intelligence data, research reports, and activities | | [GTM Knowledge Base](https://docs.graph8.com/developers/gtm-knowledge-base/) | Search and list org-scoped knowledge base documents used by GTM campaigns and AI agents | | [Idempotency](https://docs.graph8.com/developers/idempotency/) | Safely retry create requests without duplicating data using Idempotency-Key | | [Inbox](https://docs.graph8.com/developers/inbox/) | Multi-channel inbox: list threads, generate AI drafts, send replies via email, SMS, or LinkedIn | | [REST API Overview](https://docs.graph8.com/developers/) | Programmatic access to graph8 CRM, enrichment, and prospecting data | | [Intent & Signals](https://docs.graph8.com/developers/intent-signals/) | REST reference for graph8's intent tracking — keywords, page visitors, account-level intent search, signal scoring | | [Restricted Keys (Scopes)](https://docs.graph8.com/developers/key-scopes/) | Limit an API key to specific resource:action scopes | | [Landing Pages](https://docs.graph8.com/developers/landing-pages/) | REST reference for graph8 landing pages — clone from URL or homepage, edit, publish to Cloudflare Pages | | [LangChain Setup](https://docs.graph8.com/developers/langchain-setup/) | Connect LangChain agents to graph8 via MCP using langchain-mcp-adapters and LangGraph | | [Launch Helpers](https://docs.graph8.com/developers/launch-helpers/) | Discovery endpoints for the pre-launch fields used when launching campaigns and updating sequences | | [Lists](https://docs.graph8.com/developers/lists/) | Create, manage, and populate contact lists | | [Live Events](https://docs.graph8.com/developers/live-events/) | Real-time event debugger - see events flowing through your pipeline as they happen | | [LlamaIndex Setup](https://docs.graph8.com/developers/llamaindex-setup/) | Connect LlamaIndex agents to graph8 via MCP using llama-index-tools-mcp | | [MCP Examples](https://docs.graph8.com/developers/mcp-examples/) | End-to-end worked examples for graph8 MCP across prospecting, inbox, ads, tracking install, voice, and full GTM setup | | [MCP FAQ](https://docs.graph8.com/developers/mcp-faq/) | Answers to common questions about the graph8 MCP server - free tier, modes, errors, security, and multi-org | | [MCP tool reference](https://docs.graph8.com/developers/mcp-tools/) | Find every registered graph8 MCP tool, its purpose, and complete input schema. | | [MCP Server](https://docs.graph8.com/developers/mcp/) | Connect AI coding agents to graph8 via the Model Context Protocol | | [Meetings](https://docs.graph8.com/developers/meetings/) | List and retrieve meetings — including attendees, transcript, and AI analysis — from the graph8 inbox | | [n8n Setup](https://docs.graph8.com/developers/n8n-setup/) | Connect n8n to graph8 Studio via MCP | | [Notes](https://docs.graph8.com/developers/notes/) | Create, update, and manage notes on contacts | | [OpenAI Agents SDK Setup](https://docs.graph8.com/developers/openai-sdk-setup/) | Connect OpenAI Agents SDK to graph8 via MCP using MCPServerStreamableHttp | | [OpenSearch (Raw Data Access)](https://docs.graph8.com/developers/opensearch/) | Structured search, count, aggregate, and bulk ID resolution against the graph8 mashup OpenSearch indices | | [Pagination](https://docs.graph8.com/developers/pagination/) | Response envelope format and how to paginate through results | | [Stage Checklist Pipelines](https://docs.graph8.com/developers/pipelines/) | REST reference for graph8 Stage Checklist v2 pipelines — multi-stage workflows with evidence requirements and channel scripts | | [Pricing](https://docs.graph8.com/developers/pricing/) | Plans, credit costs, the unlimited graph8-index bundle, and auto-CRM-capture for the graph8 Developer API | | [Public Endpoints](https://docs.graph8.com/developers/public-endpoints/) | Browser-safe endpoints authenticated by write key: copilot chat, intent signals, visitor identity | | [Pydantic AI Setup](https://docs.graph8.com/developers/pydantic-ai-setup/) | Connect Pydantic AI agents to graph8 via MCP with typed agent outputs | | [Quotes](https://docs.graph8.com/developers/quotes/) | Create, send, sign, and manage quotes through graph8's quote-to-cash lifecycle | | [Rate Limits](https://docs.graph8.com/developers/rate-limits/) | API rate limits and how to handle them | | [Request Logs](https://docs.graph8.com/developers/request-logs/) | Inspect your organization's most recent API requests for debugging | | [SDK Examples](https://docs.graph8.com/developers/sdk-examples/) | End-to-end worked workflows using @graph8/sdk — prospecting, sequences, quotes, workflows, intent-driven outreach | | [SDK FAQ](https://docs.graph8.com/developers/sdk-faq/) | Common questions about @graph8/sdk — write keys vs API keys, SSR, React, bundling, parity with MCP, and troubleshooting | | [JavaScript SDK](https://docs.graph8.com/developers/sdk/) | Use the graph8 JavaScript SDK modules for application integrations. Tracking, visitor intel, AI copilot, webchat, booking, enrichment, sequences, campaigns, CRM, deals, tasks, notes, fields, quotes, pipelines, workflows, skills, voice, intent, studio. | | [Search](https://docs.graph8.com/developers/search/) | Search graph8's global B2B database for contacts and companies with advanced filters | | [Sequence Lifecycle](https://docs.graph8.com/developers/sequences-lifecycle/) | Create, preview, update, archive, and read analytics for sequences | | [Sequences](https://docs.graph8.com/developers/sequences/) | List, run, pause, and resume email sequences | | [Skills (LLM + API)](https://docs.graph8.com/developers/skills/) | Create, manage, validate, and execute reusable LLM and API skills in your graph8 workspace | | [Snippet](https://docs.graph8.com/developers/snippet/) | Retrieve the org-level tracking snippet and embeddable lead-capture form configuration | | [Tasks](https://docs.graph8.com/developers/tasks/) | Create, manage, and track tasks on contacts | | [Usage & Metering](https://docs.graph8.com/developers/usage/) | Read your organization's credit balance and transaction ledger over the API | | [Voice & Dialer](https://docs.graph8.com/developers/voice/) | REST reference for graph8's parallel dialer — sessions, calls, transcripts, AI grading, agents, and phone numbers | | [VS Code Setup](https://docs.graph8.com/developers/vscode-setup/) | Connect VS Code to graph8 via native MCP support using .vscode/mcp.json | | [Webhooks](https://docs.graph8.com/developers/webhooks/) | Subscribe to events and manage webhook deliveries | | [What You Can Ask](https://docs.graph8.com/developers/what-you-can-ask/) | Everything you can do with MCP in graph8 Studio | | [Workflows](https://docs.graph8.com/developers/workflows/) | Create, update, execute, and manage automation workflow graphs in your graph8 workspace | ### Training | Task or feature | What the guide covers | |---|---| | [Account Executive Training](https://docs.graph8.com/training/account-executives/) | Maximize deal velocity through AI-powered company intelligence | | [Mission 3: Leverage AI for Selling](https://docs.graph8.com/training/ae/ai-selling/) | Use Copilot for deep research, generate AI-powered emails, and leverage meeting transcripts | | [Mission 4: Close Deals Faster](https://docs.graph8.com/training/ae/closing/) | Track deal velocity, keep your pipeline current, and export deal summaries for stakeholders | | [AE Training Missions](https://docs.graph8.com/training/ae/) | Four hands-on missions to master graph8 studio as an Account Executive | | [Mission 2: Build Your Pipeline](https://docs.graph8.com/training/ae/pipeline/) | Create deals, set values and close dates, and move opportunities through pipeline stages | | [Mission 1: Master Company Research](https://docs.graph8.com/training/ae/research/) | Run Company Intelligence, map stakeholders, and prepare for calls in under 5 minutes | | [Mission 3: Connect Your Tools](https://docs.graph8.com/training/general/connect/) | Link your CRM and email so graph8 can sync data and send outreach | | [Mission 2: Get Your Data In](https://docs.graph8.com/training/general/first-data/) | Import contacts and create your first list | | [Getting Started Training](https://docs.graph8.com/training/general/) | A guided walkthrough of graph8 for new users | | [Mission 1: Navigate graph8](https://docs.graph8.com/training/general/navigate/) | Learn the layout — data, studio, engage, signals, and settings | | [Mission 3: Create Your First Campaign](https://docs.graph8.com/training/growth-manager/first-campaign/) | Generate campaign ideas from AI, push the best one to a full campaign, and refine content in Canvas | | [Mission 2: Generate Global Context](https://docs.graph8.com/training/growth-manager/global-context/) | Trigger Context Generation and review the Personas, ICPs, and Research Reports that power every campaign | | [Growth Manager Training](https://docs.graph8.com/training/growth-manager/) | Five hands-on missions that take you from first login to optimized campaign delivery | | [Mission 1: Onboard Your First Client](https://docs.graph8.com/training/growth-manager/onboarding/) | Set up the Company Profile, run the Intelligence Workflow, and approve the profile to unlock downstream features | | [Mission 5: Monitor and Optimize](https://docs.graph8.com/training/growth-manager/optimize/) | Use the Campaign Dashboard, document health scores, export features, and sequence analytics to optimize performance | | [Mission 4: Master the Sequencer](https://docs.graph8.com/training/growth-manager/sequencer/) | Create an email sequence from your campaign, configure steps with timing and templates, add contacts, and launch | | [Growth Manager Training](https://docs.graph8.com/training/growth-managers/) | Deliver high-quality, AI-powered campaigns while maximizing efficiency | | [Role-Based Training](https://docs.graph8.com/training/) | Interactive, mission-based training to get you productive with graph8 fast | | [Sales Manager Training](https://docs.graph8.com/training/sales-managers/) | Pipeline visibility, deal velocity, and campaign health metrics | | [Mission 5: Build Your Daily Workflow](https://docs.graph8.com/training/sdr/daily-workflow/) | Establish a repeatable daily routine for morning prep, working accounts, and logging activities | | [Mission 2: Launch Your First Sequence](https://docs.graph8.com/training/sdr/first-sequence/) | Create a multi-step outreach sequence, add contacts, and activate it | | [Mission 1: Set Up Your Foundation](https://docs.graph8.com/training/sdr/foundation/) | Import contacts, create lists, enrich data, and review ICP scores | | [SDR Training Missions](https://docs.graph8.com/training/sdr/) | Five hands-on missions to get you fully productive in graph8 as an SDR | | [Mission 3: Master Company Intelligence](https://docs.graph8.com/training/sdr/intelligence/) | Run intelligence on companies, review research categories, and use Copilot for quick insights | | [Mission 4: Connect Your Tools](https://docs.graph8.com/training/sdr/tools/) | Link your CRM, mailbox, and calendar to graph8 for a seamless workflow | | [SDR Manager Training](https://docs.graph8.com/training/sdr-managers/) | Conversion funnel visibility and team performance metrics | | [SDR Training](https://docs.graph8.com/training/sdrs/) | Maximize outreach efficiency through AI-powered prioritization and research | ### By role | Task or feature | What the guide covers | |---|---| | [graph8 for Account Manager](https://docs.graph8.com/roles/account-manager/) | graph8 field guide for Account Manager. Live URLs, real surfaces, screenshots from a public-data showcase tenant. | | [graph8 for Account Executive](https://docs.graph8.com/roles/ae/) | graph8 field guide for Account Executive. Live URLs, real surfaces, screenshots from a public-data showcase tenant. | | [graph8 for AI Agent Operator](https://docs.graph8.com/roles/agent-op/) | graph8 field guide for AI Agent Operator. Live URLs, real surfaces, screenshots from a public-data showcase tenant. | | [graph8 for Campaign Manager](https://docs.graph8.com/roles/campaign-manager/) | graph8 field guide for Campaign Manager. Live URLs, real surfaces, screenshots from a public-data showcase tenant. | | [graph8 for Content Marketer](https://docs.graph8.com/roles/content/) | graph8 field guide for Content Marketer. Live URLs, real surfaces, screenshots from a public-data showcase tenant. | | [graph8 for CRO](https://docs.graph8.com/roles/cro/) | graph8 field guide for CRO. Live URLs, real surfaces, screenshots from a public-data showcase tenant. | | [graph8 for CSM](https://docs.graph8.com/roles/csm/) | graph8 field guide for CSM. Live URLs, real surfaces, screenshots from a public-data showcase tenant. | | [graph8 for Finance](https://docs.graph8.com/roles/finance/) | graph8 field guide for Finance. Live URLs, real surfaces, screenshots from a public-data showcase tenant. | | [graph8 for Founder / CEO](https://docs.graph8.com/roles/founder/) | graph8 field guide for Founder / CEO. Live URLs, real surfaces, screenshots from a public-data showcase tenant. | | [graph8 for GTM Engineer](https://docs.graph8.com/roles/gtm-engineer/) | graph8 field guide for GTM Engineer. Live URLs, real surfaces, screenshots from a public-data showcase tenant. | | [graph8 by role](https://docs.graph8.com/roles/) | Pick your role to see the exact surfaces, URLs, and screenshots you'll touch every day. 22 field guides. | | [graph8 for Marketing / Demand Gen](https://docs.graph8.com/roles/marketing/) | graph8 field guide for Marketing / Demand Gen. Live URLs, real surfaces, screenshots from a public-data showcase tenant. | | [graph8 for Marketing Operations](https://docs.graph8.com/roles/mops/) | graph8 field guide for Marketing Operations. Live URLs, real surfaces, screenshots from a public-data showcase tenant. | | [graph8 for Onboarding / Implementation](https://docs.graph8.com/roles/onboarding/) | graph8 field guide for Onboarding / Implementation. Live URLs, real surfaces, screenshots from a public-data showcase tenant. | | [graph8 for Product Marketing](https://docs.graph8.com/roles/pmm/) | graph8 field guide for Product Marketing. Live URLs, real surfaces, screenshots from a public-data showcase tenant. | | [graph8 for Recruiter](https://docs.graph8.com/roles/recruiter/) | graph8 field guide for Recruiter. Live URLs, real surfaces, screenshots from a public-data showcase tenant. | | [graph8 for RevOps](https://docs.graph8.com/roles/revops/) | graph8 field guide for RevOps. Live URLs, real surfaces, screenshots from a public-data showcase tenant. | | [graph8 for Sales Engineer](https://docs.graph8.com/roles/sales-engineer/) | graph8 field guide for Sales Engineer. Live URLs, real surfaces, screenshots from a public-data showcase tenant. | | [graph8 for Sales Manager](https://docs.graph8.com/roles/sales-manager/) | graph8 field guide for Sales Manager. Live URLs, real surfaces, screenshots from a public-data showcase tenant. | | [graph8 for SDR](https://docs.graph8.com/roles/sdr/) | graph8 field guide for SDR. Live URLs, real surfaces, screenshots from a public-data showcase tenant. | | [graph8 for SDR Manager](https://docs.graph8.com/roles/sdr-manager/) | graph8 field guide for SDR Manager. Live URLs, real surfaces, screenshots from a public-data showcase tenant. | | [graph8 for Trial Specialist](https://docs.graph8.com/roles/trial/) | graph8 field guide for Trial Specialist. Live URLs, real surfaces, screenshots from a public-data showcase tenant. | | [graph8 for VP Sales / Head of Sales](https://docs.graph8.com/roles/vp-sales/) | graph8 field guide for VP Sales / Head of Sales. Live URLs, real surfaces, screenshots from a public-data showcase tenant. | ## More feature guides - [Recurring work](https://docs.graph8.com/engage/work-routines/) - [SEO and AI visibility](https://docs.graph8.com/signals/seo/) - [Competitive Radar](https://docs.graph8.com/signals/radar/) - [NPS and CSAT surveys](https://docs.graph8.com/studio/surveys/) --- # Initial Setup > Enter your domain, generate company intelligence, and pick one of two guided paths to start working. Source: https://docs.graph8.com/getting-started/initial-setup/ The first time you log in, graph8 guides you through an automated setup that populates your workspace with intelligence about your company, market, and competitors — then drops you onto a personal Home dashboard that picks one of two starting paths based on what you want to do first. ## Step 1: Enter Your Domain After creating your account, you'll be asked to enter your company's website domain (e.g., `yourcompany.com`). graph8 uses this to: - Scan your website content, messaging, and positioning - Identify your products, services, and value propositions - Research your competitors and market landscape - Analyze your target audience and industry ## Step 2: Auto-Generated Intelligence Once you submit your domain, graph8 generates **40+ documents** about your company in the background. This typically takes 3–5 minutes and includes: | Document Type | What It Contains | |---------------|-----------------| | **Company Profile** | Your mission, products, key differentiators | | **Competitor Analysis** | Direct and indirect competitors with positioning comparison | | **Market Overview** | Industry trends, market size, growth drivers | | **ICP Profiles** | Ideal customer profiles based on your positioning | | **Buyer Personas** | Detailed personas with pain points, motivations, and objections | | **Messaging Frameworks** | Value props, elevator pitches, and email angles | | **Battle Cards** | Competitive positioning for sales conversations | These documents power graph8's AI across the entire platform — from campaign generation to email personalization to lead scoring. ## Step 3: Pick a Starting Path Once intelligence finishes generating, your Home dashboard shows a **Welcome** card with the two most common first moves: | Path | Best for | What it does | |------|----------|--------------| | **Build a contact list** | "I want to find people to reach out to" | Walks you into prospecting with filters seeded from your ICP — you end up with a saved list ready for sequences | | **Create a campaign idea** | "I want to plan an outbound motion before I prospect" | Walks you into campaign generation — graph8 proposes 5 campaigns aligned to your positioning, you pick one and graph8 builds the supporting documents | You can also dismiss the welcome and explore the platform on your own. The Welcome card only shows once — your second login lands you on a daily-briefing version of Home instead. > **Tip:** > If you're not sure which path to start with: **Build a contact list** is the right answer for most teams. Campaign planning becomes much sharper once you have a real ICP-aligned list to plan around. ## Step 4: Review and Refine the Intelligence Your generated documents are good starting drafts — they get better with edits and your own context. 1. Navigate to **Studio → Global** to review the generated documents 2. Edit any documents that need corrections or additions 3. Add your own context (case studies, product briefs, top-performing emails) — graph8's AI uses everything in Global Context when it generates campaigns, emails, and replies The more context you provide, the better graph8's outputs get. See [Global Context](https://docs.graph8.com/studio/global/) for the full document catalog. ## Step 5: Invite Your Team Go to **Settings → Team → Invite** to add team members. Assign roles: - **Admin** — Full access to settings, billing, and team management - **Manager** — Access to analytics, team reporting, and campaign management - **SDR** — Access to prospecting, sequences, and inbox ## Next Steps - [Connect Your Website →](https://docs.graph8.com/getting-started/connect-website/) — Track visitors and form submissions - [Connect Your Calendar →](https://docs.graph8.com/getting-started/connect-calendar/) — Enable meeting scheduling - [Global Context →](https://docs.graph8.com/studio/global/) — Review and refine the documents that drive AI quality --- # What is graph8? > An introduction to graph8 — what it does and what to expect. Source: https://docs.graph8.com/getting-started/what-is-graph8/ graph8 is an AI-powered revenue platform that helps B2B teams find, engage, and close their ideal customers. It combines prospecting data, multi-channel outreach, campaign intelligence, and pipeline management into a single workspace. ## What You Can Do - **Find prospects** — Search a database of 200M+ contacts, enrich your existing data, and build targeted lists - **Run outreach** — Create multi-step sequences across email, phone, and LinkedIn - **Build campaigns** — Use AI to generate campaign content grounded in your company's intelligence and market context - **Track signals** — Manage deals, pipeline stages, and forecasting in one view - **Leverage AI agents** — AI-powered enrichment, inbox management, and content generation ## How It Works When you first log in, graph8 walks you through a quick setup: 1. **Enter your domain** — graph8 scans your website and automatically generates 40+ intelligence documents about your company, market, competitors, and positioning 2. **Connect your website** — Add a small JavaScript snippet so graph8 can track real-time visitors, form submissions, and page views 3. **Connect your calendar** — Link your calendar to enable meeting scheduling and availability tracking After setup, you have a fully populated workspace ready for prospecting, outreach, and campaign creation. ## What to Expect - **Setup takes 5-10 minutes** — Most of it is automated - **Intelligence generation runs in the background** — Your company docs are ready within minutes - **No technical skills required** — The platform is built for sales and marketing teams - **Your team can join immediately** — Invite colleagues from Settings at any time ## Next Steps - [Initial Setup →](https://docs.graph8.com/getting-started/initial-setup/) — Configure your workspace and generate intelligence - [Connect Your Website →](https://docs.graph8.com/getting-started/connect-website/) — Start tracking visitors - [Connect Your Calendar →](https://docs.graph8.com/getting-started/connect-calendar/) — Enable scheduling --- # AI Features > AI capabilities across enrichment, inbox, campaigns, and sales. Source: https://docs.graph8.com/ai-features/ Explore the AI capabilities built into every part of graph8, from data enrichment and inbox management to campaign creation and sales coaching. ## In This Section **AI Overview** How AI is integrated throughout the graph8 platform. **Content Generation** AI-powered email copy, sequences, and campaign content. **Lead Scoring** AI-driven lead scoring and prioritization. **Research** AI-powered account and market research. --- # What's New > Weekly release notes for graph8 Source: https://docs.graph8.com/changelog/ <p class="cl-subtitle">Stay up to date with the latest features and improvements shipped to graph8.</p> <div class="cl-timeline"> <div class="cl-entry"> <div class="cl-date-col"> <div class="cl-date-sticky"> <span class="cl-date-range">Oct 12-18</span> <span class="cl-date-year">2026</span> </div> </div> <div class="cl-content"> <div class="cl-item"> <div class="cl-item-header"> <h3>Schedule Buy: sender identities required</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> Every new Schedule Buy plan now needs at least one sender identity, in the app and on the API. `POST /mailboxes/schedule-buy/plans` requires `persona_ids`; manual `registrants` and `mailbox_aliases` are no longer accepted and return a 422. Existing plans are unaffected. <span class="cl-links"><a data-parent-nav="/developers">Open Developers →</a></span> </div> </div> </div> <div class="cl-entry"> <div class="cl-date-col"> <div class="cl-date-sticky"> <span class="cl-date-range">Jun 20-26</span> <span class="cl-date-year">2026</span> </div> </div> <div class="cl-content"> <div class="cl-item"> <div class="cl-item-header"> <h3>Trial Command Center with Activity Replay</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> Revenue ops gains a Stripe-anchored Trial Command Center that ranks trials by signup, expiry, and ghost status. Pill-aware KPI strips drill into Active, Expired, and Ghost cohorts, contact and company members carry their own sessions, and Activity Replay plays back every trial visit inside graph8 with a clean scrubber and gated platform scope. <span class="cl-links"><a data-parent-nav="/analytics">Open Analytics →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Two-sided marketplace job board</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> Marketplace becomes a two-sided platform. Operators publish open roles with PDF job descriptions, talent applies, recruiters export shortlists into Data Lists, unclaimed and incognito profiles surface for sourcing, and service-statement billing handles proration, line-item seeding, and client edits end to end. <span class="cl-links"><a data-parent-nav="/marketplace">Open Marketplace →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Radar LinkedIn intelligence expansion</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> Radar adds five LinkedIn intelligence features: a competitor corpus feed, steal-format templates, audience heist matching, warm-account alerts, and an AEO sentinel. Competitor stat cards become clickable drill-downs, Ads & keywords pull live capture and run-dates, and company pages gain a LinkedIn posts tab plus a Jobs feed. <span class="cl-links"><a data-parent-nav="/studio?mode=content">Open Studio content →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Pipedrive CRM integration</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> Pipedrive joins HubSpot, Salesforce, and Zoho with full parity. Push and pull every entity (contacts, companies, deals, leads), drive segments by Tags, map rich custom fields both directions, link contacts to organizations on push and pull, and browse pulled records in a persistent Pull tab. <span class="cl-links"><a data-parent-nav="/integrations">Open Integrations →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Personal booking pages</h3> <span class="cl-tag cl-tag-new">New</span> </div> Anyone in the org can host personal booking pages under their own username. Phase 0 wires the profile and slug; Phase 1 ships "For myself" event types with profile pictures, a colorful event-scope toggle, and a My events default. Slug-less orgs fall back to legacy org id so links keep resolving cross-tenant. <span class="cl-links"><a data-parent-nav="/appointments/availability">Open Appointments →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Newsletter calendar invites</h3> <span class="cl-tag cl-tag-new">New</span> </div> Newsletter issues can carry a calendar event. A reusable calendar event resource powers a Sheet inside the editor with intelligent CTA linking; outgoing emails attach the .ics, calendar invites carry the proper online-meeting URL, and Newsletter relocates into the Engage menu with its own router layer. <span class="cl-links"><a data-parent-nav="/studio?mode=content">Open Newsletter →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Dialer enhancements</h3> <span class="cl-tag cl-tag-new">New</span> </div> The dialer ships a unified filter queue where the grid IS the queue, plus contact notes in Call Outcome, a unified past-call panel, manual-dial ringback, save-unknown-caller as contact, international number search and inbound, restored Session User column with admin-only cross-user visibility, and server-side caller-ID daily limit with auto-rotate. <span class="cl-links"><a data-parent-nav="/dialer">Open Dialer →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Voice recordings on durable cloud storage</h3> <span class="cl-tag cl-tag-new">New</span> </div> Call recordings now write to graph8's durable object store with read-fallback to legacy storage. Older recordings keep streaming while every new call lands directly on the new path, with no cutover gap for review or sales coach playback. <span class="cl-links"><a data-parent-nav="/sales-coach/all-calls">Open Sales coach →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Contact avatars and company logos throughout</h3> <span class="cl-tag cl-tag-new">New</span> </div> Profile photos render in the Contacts grid and on contact detail pages, and company logos show in the Companies grid, company lists, and the company contacts rail. Loading is async per page so scrolling stays smooth even on big lists. <span class="cl-links"><a data-parent-nav="/contacts">Open Contacts →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Email Validation as an email verification waterfall</h3> <span class="cl-tag cl-tag-new">New</span> </div> Email Validation graduates to a dedicated email verification waterfall column. Configure provider order, persist verdicts to a real grid column, reuse the column to re-verify existing work emails, and watch live-stream progress with cleaner active-path display. <span class="cl-links"><a data-parent-nav="/enrichment">Open Enrichment →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Unified credit pricing</h3> <span class="cl-tag cl-tag-improved">Improved</span> <span class="cl-tag cl-tag-major">Major</span> </div> A single credit pool replaces the legacy per-category model. Every paid plan gets a welcome grant and data services on the house, Team plan joins the lineup as a tier, and roughly forty previously unbilled AI and LLM calls across enrichment, copilot, image generation, landing pages, and the campaign builder now flow through the meter. <span class="cl-links"><a data-parent-nav="/studio/settings?tab=billing">Open Billing →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Workflow builder additions</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> The visual workflow builder gains a Create Dialer Session node, a Latest AI Inbox Email node with an array-aware contains operator, and a Pull Workbench Rows node for bulk reads. The FieldPicker now exposes dynamic workbench columns and nested-loop item fields, the workbench picker shows real data, and Test run surfaces a clear "workflow disabled" message instead of a raw 400. <span class="cl-links"><a data-parent-nav="/agents/workflows/visual">Open Workflows →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Web chat reliability and handoff polish</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> Internal bot-assist sends a five-minute nudge instead of repeating "review shortly", visitor identity is captured and surfaced on the Visitors chat tab, the Hide/Show Assistant toggle no longer sticks, widgets open straight to an unread conversation, and visitor capture hardens rate limits, IP spoof handling, and PII suppression. <span class="cl-links"><a data-parent-nav="/webchat">Open Web Chat →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Bulk add to suppressions from lists</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> Lists gain a bulk "Add to suppressions" action with a confirm modal so reps can shield contacts from outreach in a single step. The action ships to qa and prod together. <span class="cl-links"><a data-parent-nav="/lists">Open Lists →</a></span> </div> </div> </div> <div class="cl-entry"> <div class="cl-date-col"> <div class="cl-date-sticky"> <span class="cl-date-range">Jun 13-19</span> <span class="cl-date-year">2026</span> </div> </div> <div class="cl-content"> <div class="cl-item"> <div class="cl-item-header"> <h3>Radar competitive intelligence in Studio</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> Radar lands as a full Studio Content surface. Pick competitor pages to track, see a clean diff of every meaningful change, browse Traffic and market intelligence built on real own-site analytics, and turn flagged moments into idea-stage content across five formats. <span class="cl-links"><a data-parent-nav="/studio?mode=content">Open Studio content →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Performance Reports anchored calendar</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> Twenty Performance reports now share an anchored date-range calendar with consistent window math, plus thirty data-accuracy fixes spanning SDR heatmaps, sequence status, MRR, won/lost dating, meeting pipeline, cost per meeting, and dialer dispositions. <span class="cl-links"><a data-parent-nav="/analytics">Open Analytics →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Master Lists for union-of-lists rollups</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> Master Lists merge any set of child lists into a single rollup with mirror sync, plus an include-all mode that auto-pulls every list in the workspace. Add or remove children and the master stays in lockstep. <span class="cl-links">[Managing lists →](https://docs.graph8.com/data/managing-lists/) · <a data-parent-nav="/lists">Open Lists →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>AI Inbox always-on with conversation pause</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> Healthy mailboxes now sync continuously, the per-mailbox enable toggle is gone, and every channel flows into the AI Inbox by default. Conversation-level auto-respond pauses per thread when you want to take over manually, and mailbox signatures auto-populate everywhere you reply. <span class="cl-links">[AI Inbox →](https://docs.graph8.com/ai-features/ai-inbox/) · <a data-parent-nav="/inbox">Open Inbox →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Grounded newsletter AI generation</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> Newsletter AI now generates against your real positioning, voice, and active campaign, runs a deterministic email-safe quality gate before drafts land, and keeps template parity across the Hub run to run. <span class="cl-links"><a data-parent-nav="/studio?mode=content">Open Newsletter →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>New workflow nodes for sequences and revenue</h3> <span class="cl-tag cl-tag-new">New</span> </div> The visual workflow builder gains four new nodes. Create Deal and Create Quote push records into the revenue pipeline, Lookup Sequences with remove-from-all-sequences cleans contacts out of running flows, and contact-in-list checks membership by work email or contact id. <span class="cl-links"><a data-parent-nav="/agents/workflows/visual">Open Workflows →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Appointments calendar overlay and drag-to-reschedule</h3> <span class="cl-tag cl-tag-new">New</span> </div> Bookings tab gets an interactive calendar view with drag-and-drop reschedule. Connected Google and Outlook calendars overlay as a read-only layer so you see every existing meeting next to graph8 bookings. <span class="cl-links"><a data-parent-nav="/appointments">Open Appointments →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Marketplace contracts redesigned</h3> <span class="cl-tag cl-tag-new">New</span> </div> Talent contracts ship with a redesigned PDF that streams on demand and regenerates automatically when storage is briefly unreachable. Browse now lets reps save a match-grade filter alongside the rest of the filter chips. <span class="cl-links"><a data-parent-nav="/marketplace">Open Marketplace →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Web chat theme controls and agent avatars</h3> <span class="cl-tag cl-tag-new">New</span> </div> Web Chat ships admin-editable theme tokens (font size, separate widget and message corner radius), an agent avatar gallery that renders in the widget header, bot messages, and the Agents grid, plus a brand mirror fallback that keeps existing widgets looking right by default. <span class="cl-links"><a data-parent-nav="/webchat">Open Web Chat →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Agency-scoped Developer API keys</h3> <span class="cl-tag cl-tag-new">New</span> </div> Agency-scoped API keys can now target any sub-org via the X-Target-Org-Id header. MCP tool calls and Developer API requests stream into the analytics warehouse, and the MCP server trims its connect-time context so tool counts stay honest. <span class="cl-links"><a data-parent-nav="/studio/settings?tab=api">Open API keys →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Dialer reliability and custom dispositions</h3> <span class="cl-tag cl-tag-improved">Improved</span> <span class="cl-tag cl-tag-major">Major</span> </div> The dialer gains org-level custom dispositions, a unified bottom drawer with past-call review, persistent grid filters, a single self-clearing ring-tone reconciler, and fixes for sessions self-pausing and the wrong contact appearing in the active-call drawer. <span class="cl-links"><a data-parent-nav="/dialer">Open Dialer →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Enrichment scale and resilience</h3> <span class="cl-tag cl-tag-improved">Improved</span> <span class="cl-tag cl-tag-major">Major</span> </div> Bulk enrichment now batches with a global rate limiter, byte-aware chunking, audience-membership dedup, credential-aware waterfall defaults, and a direct live event stream so progress shows up the moment work starts. <span class="cl-links">[Learn about enrichment →](https://docs.graph8.com/data/enrichment/) · <a data-parent-nav="/enrichment">Open Enrichment →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Permissions enforced across the platform</h3> <span class="cl-tag cl-tag-improved">Improved</span> <span class="cl-tag cl-tag-major">Major</span> </div> Role permissions now gate Contacts, Companies, Deals, Lists, Sequences, Nurtures, the dialer, the copilot, the MCP, and the agency Manage Orgs surface. Non-admins see empty states where appropriate, and copilot resolves user permissions by PropelAuth id with a master kill switch for emergencies. <span class="cl-links"><a data-parent-nav="/studio/settings?tab=roles">Open Roles →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Entity search 400x faster</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> Contact and company search migrates to index-driven arms, dropping average response time from roughly 1.3 seconds to about 3 milliseconds. Each hit shows relevance, provenance, an exact-match indicator, and a Related footer. <span class="cl-links"><a data-parent-nav="/search">Open Search →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>CRM sync parity for linked companies</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> Contact pulls from HubSpot, Salesforce, and Zoho now also create the linked Company in graph8, with Salesforce field-mapping defaults auto-populated, batched CampaignMember add and remove, and a fix that bypasses the 2000-record offset cap on campaign pulls. <span class="cl-links"><a data-parent-nav="/integrations">Open Integrations →</a></span> </div> </div> </div> <div class="cl-entry"> <div class="cl-date-col"> <div class="cl-date-sticky"> <span class="cl-date-range">Jun 6-12</span> <span class="cl-date-year">2026</span> </div> </div> <div class="cl-content"> <div class="cl-item"> <div class="cl-item-header"> <h3>Operator agents for autonomous RevOps</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> Operators are a new kind of agent that detect, decide, and act on revenue ops issues. The first cohort ships 13 live checks covering CRM hygiene, deliverability, pipeline quality, inbox assignment, admin sprawl, and token expiry, each running on a per-org schedule with a configurable autonomy ceiling and approval bridge. <span class="cl-links"><a data-parent-nav="/agents">Open Agents →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>SDR command center in the universal inbox</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> The universal inbox becomes a multi-channel command center. Queue view splits replies into three priority lanes (Hot, Awaiting, New), Board view shows the funnel across four columns, the Hot lane ranks by intent score, and a visible Mine/Team scope toggle, AI Reply drafts, and round-robin auto-assign on first inbound reply round out the workflow. <span class="cl-links">[AI Inbox →](https://docs.graph8.com/ai-features/ai-inbox/) · <a data-parent-nav="/inbox">Open Inbox →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Auto-compose workspaces</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> Auto-compose proposes complete workspaces in one click. The AI clusters prospects, picks the right agent for each segment, and creates workspaces with channels and sequences already attached, with v2 parallelising creation and pulling only campaigns that carry sending channels. <span class="cl-links"><a data-parent-nav="/inbox">Open Inbox →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Content calendar and LinkedIn publishing</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> Studio gains a unified content calendar with autopilot scheduling, repurposing, approvals, and signal mining. LinkedIn organic publishing ships end to end: compose, schedule, publish, analytics, and lead capture, plus an ideation drawer that surfaces live mining progress and turns evidence into draft content. <span class="cl-links"><a data-parent-nav="/studio?mode=content">Open Studio content →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Ads campaign sets and living creative</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> Ads now models persona by platform matrices through reusable campaign sets and templates. The living creative pipeline supports in-image SVG editing, inline copy revisions, persona lens previews, element-targeted point editing (v2), a concept board with pin-and-describe, size sets, export, and a dedicated Drafts tab. <span class="cl-links"><a data-parent-nav="/ads">Open Ads →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Newsletter Templates Hub</h3> <span class="cl-tag cl-tag-new">New</span> </div> Newsletters get a Templates Hub with intelligent template generation, layout and custom AI options, one-click clone, a unified template picker with live preview, a live-status dashboard, and an unsubscribe-link status indicator with one-click Fix. <span class="cl-links"><a data-parent-nav="/studio?mode=content">Open Newsletter →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Sales report Wave 2</h3> <span class="cl-tag cl-tag-new">New</span> </div> Sales report Wave 2 adds a per-motion objection picker, motion-scoped objection filter, the unhidden Grading Rubrics tab with three default rubrics seeded on org creation, a per-stage SLA editor, multi-path attribution including direct deal id, a data-quality diagnostic banner, and a per-rep Bookings table. <span class="cl-links"><a data-parent-nav="/analytics">Open Analytics →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Marketplace contracts, video intros, and mobile</h3> <span class="cl-tag cl-tag-new">New</span> </div> Marketplace closes its contract loop. Talents see and sign contracts on a real document page, download signed PDFs, get visibility after acceptance, and the 5% platform fee is removed per v2.3.0 terms. Video intros ship end to end with native in-browser recording, and the whole experience now responds on mobile with hamburger nav and a slide-in filter sheet. <span class="cl-links"><a data-parent-nav="/marketplace">Open Marketplace →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Sequencer on-demand model and auto-pause</h3> <span class="cl-tag cl-tag-new">New</span> </div> Sequences now pick the email model per step on demand, auto-pause on credit or AI billing failure with owner and admin notifications, and offer opt-in global suppression on hard bounce as a Channels preference. The Channels area also gains a cold-email unsubscribe header toggle. <span class="cl-links">[Sequencer →](https://docs.graph8.com/engage/sequencer/) · <a data-parent-nav="/sequencer">Open Sequencer →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>AI receptionist auto-provisioned on signup</h3> <span class="cl-tag cl-tag-new">New</span> </div> Every new org signup now auto-provisions a phone number and an AI receptionist ready to take calls on day one. A new Intake agent role lands first for claims intake and whistleblower hotline use cases. <span class="cl-links"><a data-parent-nav="/dialer">Open Dialer →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Developer API and MCP extensions</h3> <span class="cl-tag cl-tag-new">New</span> </div> The Developer API and MCP add full appointments-lifecycle access, custom-field value reads, sequences-nurture exposure, and a new send-meeting-invite workflow node that emails an .ics calendar invite. The MCP catalog standardises around a single appointments naming convention. <span class="cl-links"><a data-parent-nav="/studio/settings?tab=api">Open API settings →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Role-based access control overhaul</h3> <span class="cl-tag cl-tag-new">New</span> </div> Role permissions are now enforced end to end. Route guards on the settings surface for non-admins, a non-escalation guard on role create and assign, a new ads permission category enforced on every ads route, gated affordances for mailbox and phone-number creation, dialing role bundles with the right voice permissions, and a pre-flip rollout impact report. <span class="cl-links"><a data-parent-nav="/studio/settings?tab=roles">Open roles →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Dialer SDR controls</h3> <span class="cl-tag cl-tag-improved">Improved</span> <span class="cl-tag cl-tag-major">Major</span> </div> Dialers can edit contact info from the active-call drawer with instant feedback, skip and jump for non-linear dialing, see the contact's job title in the call header, and own a single audio session per tab so two browser windows never fight for the mic. Call notes are unified across the panel, drawer, grid, and CSV export. <span class="cl-links">[Dialer →](https://docs.graph8.com/engage/dialer/) · <a data-parent-nav="/dialer">Open Dialer →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Performance and search speed</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> The frontend now lazy-loads grid and chart engines on 75 routes, trimming roughly 4.6MB at first paint, then idle-prefetches them after first paint. The hiring intent signals search migrates to a search-engine backend that runs roughly 330x faster with a native pager, exact totals, and a Job Details drawer. <span class="cl-links"><a data-parent-nav="/intent-search">Open Intent search →</a></span> </div> </div> </div> <div class="cl-entry"> <div class="cl-date-col"> <div class="cl-date-sticky"> <span class="cl-date-range">May 30 - Jun 5</span> <span class="cl-date-year">2026</span> </div> </div> <div class="cl-content"> <div class="cl-item"> <div class="cl-item-header"> <h3>Instant signup with warm pool provisioning</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> New signups now drop into a fully provisioned workspace in seconds. A background pool keeps fresh orgs ready in advance, signup claims one instantly, and the analytics, billing, and tracking layers spin up in parallel so the first session is live before the welcome page loads. <span class="cl-links"><a data-parent-nav="/studio">Open Studio →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Day-one live onboarding rebuild</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> Onboarding pre-warms the Studio pipeline at sign-up, narrows ICP filters with an LLM pass, summarises the brief into person and company snapshots, and now survives transient errors without freezing the timeline. New users move from sign-up to running campaigns in a single session instead of waiting through a multi-day setup. <span class="cl-links">[Studio overview →](https://docs.graph8.com/studio/global/) · <a data-parent-nav="/studio">Open Studio →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Ads creative studio in Content</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> A new Ads surface inside Studio Content turns a short brief into designed image creatives with on-brand layout guard rails, manages drafts and a gallery in grid or board view, shows live "generating" status while assets render, and links a finished creative directly into the campaign launcher. <span class="cl-links"><a data-parent-nav="/studio?mode=content">Open Studio content →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Newsletter durable send pipeline</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> Newsletters now send through a throttled, rate-limited pipeline with pause and resume, webhook deduplication, bot-event guard rails, and a live per-subscriber analytics grid that shows status and timestamps as the campaign progresses. Subscribers also link to contacts with the central suppression list. <span class="cl-links">[Newsletter →](https://docs.graph8.com/engage/newsletter/) · <a data-parent-nav="/studio?mode=content">Open Newsletter →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Workspaces overhaul across inbox channels</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> Workspaces now route messages across all four inbox channels with a per-workspace agent, a color identity, and a single-modal inbox-style editor. The All Activity view adds a workspace context band with metrics, sparklines, and unread badges, AI Inbox settings move into Studio Settings under a dedicated Inbox tab, and workspace admins (not just creators) can now edit routing and members. <span class="cl-links">[AI Inbox →](https://docs.graph8.com/ai-features/ai-inbox/) · <a data-parent-nav="/inbox">Open Inbox →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Central suppression engine</h3> <span class="cl-tag cl-tag-new">New</span> </div> A new suppression service is the single source of truth for opt-outs across sequencer, newsletter, and audiences. Sequencer enforces suppression at execution time (not just enrollment), newsletter subscribers link directly to contacts, and a new Contact Suppress Control replaces ad-hoc list creation. <span class="cl-links">[Contacts →](https://docs.graph8.com/data/contacts/) · <a data-parent-nav="/contacts">Open Contacts →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Sequencer per-step models and one-click unsubscribe</h3> <span class="cl-tag cl-tag-new">New</span> </div> Sequences now ship with org-wide one-click unsubscribe via the List-Unsubscribe header, per-step on-demand email model selection with cached generation and a credit guard, a unified engagement cell across nurture and sequencer lists, and the ability to swap the attached list on a draft sequence with automatic contact unlock. <span class="cl-links">[Sequencer →](https://docs.graph8.com/engage/sequencer/) · <a data-parent-nav="/sequencer">Open Sequencer →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Sales Coach per-motion objections</h3> <span class="cl-tag cl-tag-new">New</span> </div> Sales Coach now grades meetings against a per-motion objection catalog with 27 seeded codes, exposes an Objections cohort filter, surfaces an ICP-docs completeness widget, and adds Excel export with workspace names in the report metadata. <span class="cl-links">[SDR analytics →](https://docs.graph8.com/analytics/sdr-analytics/) · <a data-parent-nav="/analytics">Open Analytics →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Developer API and MCP parity expansion</h3> <span class="cl-tag cl-tag-new">New</span> </div> The Developer API and MCP gain full-text search and analytics data-access tools, multi-org switching, batch field creation, a family-aware tool discovery search, and acting-org stamping on credit-consuming operations. Marketplace, Deals (with contact and owner associations), and Quotes (with billing identity) all reach full parity on the public surface. <span class="cl-links">[Developer SDK →](https://docs.graph8.com/developers/sdk/) · <a data-parent-nav="/studio/settings?tab=api">Open API settings →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Multi-bot webchat with audience routing</h3> <span class="cl-tag cl-tag-new">New</span> </div> Webchat now supports multiple bots per org with separate Marketing and Support pools, per-bot routing, a curated agent pool, presence-check fallback so handoff never false-promises a human, threaded chat DMs with clickable inbox links in handoff alerts, and per-bot theme and config isolation. <span class="cl-links"><a data-parent-nav="/webchat">Open Webchat →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Bridge approvals and per-platform formatting</h3> <span class="cl-tag cl-tag-new">New</span> </div> Copilot bridge users can now approve pending tasks by replying YES or NO from WhatsApp, iMessage, Slack, or Roam, see messages rendered with per-platform markdown, and receive enriched completion details when an agent finishes background work in their chat. <span class="cl-links">[Bridge connections →](https://docs.graph8.com/ai-features/bridge/) · <a data-parent-nav="/profile">Open Apps tab →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Free-data role pages and competitor landings</h3> <span class="cl-tag cl-tag-new">New</span> </div> graph8.com adds /roles/builders for MCP-native builders, /vs/apollo as a competitor-replacement landing, /ai-gtm with role landing pages plus free session registration, a /platform/data-pipelines feature page, an agency-console partner deep-dive, and a day-one live reframe across role strips. Spanish mirrors, footer alignment, abstract AI-agent avatars, and self-hosted customer wordmarks ship alongside. <span class="cl-links">[Role field guides →](https://docs.graph8.com/roles/)</span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Quote-to-cash polish across the surface</h3> <span class="cl-tag cl-tag-improved">Improved</span> <span class="cl-tag cl-tag-major">Major</span> </div> Quotes gain list search, expiring-soon flags and filters, deep links, typed date and number column filters with friendly presets, a signed-PDF-only view, accent color picker, shared Legal section with inline upload, default-template line items, branded FROM identity (name, address, phone, email, logo), shareable links with a Link generated status, in-place edit of viewed quotes, and a Documents tab that bundles attachments into the signed PDF. <span class="cl-links">[Quotes →](https://docs.graph8.com/deals/quotes/) · <a data-parent-nav="/deals">Open Deals →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Lifecycle Revenue grids rebuilt for scale</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> Revenue Deals and Contacts grids now page over millions of rows server-side instead of materialising the full set, add credit-gated PII unlock on lifecycle contacts, restore correct "Does not contain" filter behaviour, index Suspects filtering, support one-shot multi-record entity merge, and return a clearer 402 message when an org has no active subscription. <span class="cl-links"><a data-parent-nav="/studio/settings?tab=revenue">Open Revenue settings →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Voice and dialer flow refinements</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> Outbound calls greet callers via a deterministic turn-1 line, cutting 1-3 seconds of dead air. Dialer enables Book Meeting between calls with an availability-aware calendar, surfaces the campaign call script in the active-call drawer, keeps disposition Update enabled for previously-dialed calls, retries transient database hiccups on the dial worker, and the on-demand email model picker exposes additional provider options. <span class="cl-links">[Dialer →](https://docs.graph8.com/engage/dialer/) · <a data-parent-nav="/dialer">Open Dialer →</a></span> </div> </div> </div> <div class="cl-entry"> <div class="cl-date-col"> <div class="cl-date-sticky"> <span class="cl-date-range">May 23-29</span> <span class="cl-date-year">2026</span> </div> </div> <div class="cl-content"> <div class="cl-item"> <div class="cl-item-header"> <h3>First-launch onboarding flow</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> New orgs now move through a four-act narrative reveal that drip-streams a confirmed company snapshot, brand voice, hot contacts, competitors, a draft sequence, and pixel install, then offers a bridge picker so the next handoff happens in WhatsApp, iMessage, Slack, or Roam. The wow flow replaces the static demo with a guided, content-rich first session. <span class="cl-links">[Studio overview →](https://docs.graph8.com/studio/global/) · <a data-parent-nav="/studio">Open Studio →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>MCP and SDK developer platform</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> The MCP server now reaches full feature parity with the in-app surface, gains a 12-tool quote-to-cash kit, a campaign builder suite, always-on tasks, and a plan-aware rate limiter. The SDK and CLI ship matching coverage alongside 13 new developer doc pages spanning pricing, pipelines, public endpoints, and host-specific setup guides. <span class="cl-links">[Developer SDK →](https://docs.graph8.com/developers/sdk/) · <a data-parent-nav="/studio/settings?tab=api">Open API settings →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>22 role marketing and field guides</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> graph8.com now has a /roles tree with 22 individual pages tuned to SDR, sales manager, RevOps, CSM, finance, content, sales engineer, and 15 more, plus matching field guides in the help center with public-data screenshots. Every role page is wired into the new role-page-builder skill so future refreshes stay consistent. <span class="cl-links">[Role field guides →](https://docs.graph8.com/roles/)</span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Commission engine</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> Comp plans, monthly compute, rep earnings, and a finance console now run end-to-end. Sales managers configure per-rep, per-period payouts in Revenue settings, finance reviews the computed payouts in the new console, and reps see their earnings without leaving their workspace. <span class="cl-links">[Revenue settings →](https://docs.graph8.com/settings/deals/) · <a data-parent-nav="/studio/settings?tab=revenue">Open Revenue settings →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>AI Pipeline Builder via Copilot</h3> <span class="cl-tag cl-tag-new">New</span> </div> Copilot adds a dedicated pipeline builder mode that turns a conversational prompt into a full revenue pipeline with stages, gates, and SLAs ready to apply. The flow lands alongside a Deal Intelligence sidebar toggle and a tighter Deal Room company context. <span class="cl-links">[Copilot →](https://docs.graph8.com/studio/copilot/) · <a data-parent-nav="/studio">Open Studio →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>iMessage bridge replatform</h3> <span class="cl-tag cl-tag-new">New</span> </div> The iMessage copilot bridge moves to a hosted provider with typing indicators, reliable replies, and correct inbound sender routing. Reply sends that previously failed now land, and inbound iMessage now reaches copilot every time. <span class="cl-links">[Bridge connections →](https://docs.graph8.com/ai-features/bridge/) · <a data-parent-nav="/profile">Open Apps tab →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Unified Hiring and Social signals</h3> <span class="cl-tag cl-tag-new">New</span> </div> Hiring signals and the social listener now live under one Signals area with a Talent Moves view on competitors, tweet engagement loops, a source-list registry, contact-level Social tabs, and a consolidated X API connection. Signals also gains its own megamenu entry with every submenu now alphabetized. <span class="cl-links">[Hiring signals →](https://docs.graph8.com/signals/hiring/) · [Social listener →](https://docs.graph8.com/signals/social-listener/)</span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Dual-provider mailbox warmup</h3> <span class="cl-tag cl-tag-new">New</span> </div> Mailboxes now warm up across two providers in parallel for resilience, with 1,717 historical accounts backfilled to the dual-provider model. A new warmup detection framework keeps tag and slug-set classification consistent across providers. <span class="cl-links">[Mailboxes →](https://docs.graph8.com/settings/mailboxes/) · <a data-parent-nav="/studio/settings?tab=mailboxes">Open Mailbox settings →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Sales Reports v2 completion</h3> <span class="cl-tag cl-tag-improved">Improved</span> <span class="cl-tag cl-tag-major">Major</span> </div> Phase 2 through 4 close out: agency cross-org scoping, a mashup-snapshot daily aggregator, an objection catalog, a grading section, stuck-deal SLA radar, cohort drill-down owner resolution, tz-aware trend bucketing, channel filters, section-aware export, a Tier filter, an ICP section reading enrichment data, and honest empty-state disclosures on data-gapped sections. <span class="cl-links">[SDR analytics →](https://docs.graph8.com/analytics/sdr-analytics/) · <a data-parent-nav="/analytics">Open Analytics →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>SDR marketplace pipeline sealed</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> The resume to AI to marketplace pipeline unblocks roughly 250 stuck profiles, ships inbox conversation management, anonymous talent cards, an activation flag for direct visibility backfill, and drops Wise-unsupported currencies from the bank details form. <span class="cl-links"><a data-parent-nav="/marketplace">Open Marketplace →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Newsletter composer polish</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> The chat editor now keeps session memory across edits, auto-creates a draft on the first send from the New Issue surface, derives per-issue analytics from events to kill counter drift, and drops the Variables button and Brand Mode chip rows on the cleaner editor surface. <span class="cl-links">[Newsletter →](https://docs.graph8.com/engage/newsletter/) · <a data-parent-nav="/studio?mode=content">Open Studio content →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Voice and dialer reliability</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> The dialer adds a Book Meeting button with live availability slots in the call panel, personalized voicemail that pulls CRM activity and pronounces the company correctly, and cross-session DNC and wrong-number suppression. Voice backend scales out, the receptionist stops over-prompting on quiet lines, and Manual Dial returns for completed or paused reviewed sessions. <span class="cl-links">[Dialer →](https://docs.graph8.com/engage/dialer/) · <a data-parent-nav="/dialer">Open Dialer →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Tasks quick filters and subtasks</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> Tasks gain HubSpot-style quick filter tabs with URL-synced state, full-field subtasks with a record picker drawer, and a stable grid that holds its height through pagination. MCP exposes subtask args on g8_create_task and g8_update_task for headless workflows. <span class="cl-links">[Tasks API →](https://docs.graph8.com/developers/tasks/)</span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Quote send-and-sign polish</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> Quotes now ship a progressive billing intake, billing-contact signed-copy email routing, declined-status UI, fixed per-recipient sends, a draft preview that honors date terms, and clean signer contact resolution with stale recipient cleanup on resend. <span class="cl-links">[Quotes →](https://docs.graph8.com/deals/quotes/) · <a data-parent-nav="/deals">Open Deals →</a></span> </div> </div> </div> <div class="cl-entry"> <div class="cl-date-col"> <div class="cl-date-sticky"> <span class="cl-date-range">May 16-22</span> <span class="cl-date-year">2026</span> </div> </div> <div class="cl-content"> <div class="cl-item"> <div class="cl-item-header"> <h3>Sales Manager Cockpit</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> Every deal now carries a complete audit trail of stage moves, owner changes, and amount edits, with bulk actions across the pipeline and configurable Deal Intelligence custom fields. Sales managers can review what changed, who changed it, and when, without leaving the deal. <span class="cl-links">[Deals overview →](https://docs.graph8.com/deals/overview/) · <a data-parent-nav="/deals">Open Deals →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Universal Activity in the inbox</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> A new Activity dashboard inside the inbox surfaces hot-scored queues, throughput metrics, and inline previews of every conversation. AI-generated thread summaries collapse long email chains into the headline takeaways, and Preview, Thread, and Contact tabs sit on every message. <span class="cl-links">[Inbox →](https://docs.graph8.com/engage/inbox/) · <a data-parent-nav="/inbox">Open Inbox →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Quote send-and-sign redesign</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> Quotes now ship a deal-first creation flow, billing-contact CC routing, rich-text notes, hand-drawn signature capture, and custom email recipients. Row-level Edit, manual Invoice actions, and reliable signed-copy delivery round out the end-to-end send-and-sign path. <span class="cl-links">[Quotes →](https://docs.graph8.com/deals/quotes/) · <a data-parent-nav="/deals">Open Deals →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Sales Reports v2</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> A new Sales Reports surface adds cohort analytics with a custom date-range picker, progressive-disclosure filters, and a daily aggregator that rolls lead, meeting, and revenue events into rep-level reports. <span class="cl-links">[SDR analytics →](https://docs.graph8.com/analytics/sdr-analytics/) · <a data-parent-nav="/analytics">Open Analytics →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>SDR team floor leaderboards</h3> <span class="cl-tag cl-tag-new">New</span> </div> The team floor adds per-role leaderboards with KPI tiles, a meetings-booked bar chart, and a recent-activity feed tuned for SDR, GTM, and CSM cohorts. <span class="cl-links">[SDR analytics →](https://docs.graph8.com/analytics/sdr-analytics/)</span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Studio Canvas onboarding wow moments</h3> <span class="cl-tag cl-tag-new">New</span> </div> Studio Canvas generates onboarding wow moments for competitors, intent, and ICP fit on first launch. The canvas can now edit any studio document and render newsletters inline alongside campaign briefs. <span class="cl-links">[Studio →](https://docs.graph8.com/studio/global/) · <a data-parent-nav="/studio">Open Studio →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Newsletter double opt-in</h3> <span class="cl-tag cl-tag-new">New</span> </div> Newsletters now ship a confirmed double opt-in flow with short-link confirm URLs, intent-and-active subscriber toggles, and a redesigned Overview tab so list health is visible at a glance. <span class="cl-links">[Newsletter →](https://docs.graph8.com/engage/newsletter/) · <a data-parent-nav="/studio">Open Studio →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Entity merge: manual and bulk</h3> <span class="cl-tag cl-tag-new">New</span> </div> Combine duplicate contacts or companies from a single redesigned dialog, or run bulk merges across lifecycle Revenue views with a flagged automation pipeline that the kill switch can pause at any time. <span class="cl-links">[Contacts →](https://docs.graph8.com/data/contacts/) · <a data-parent-nav="/contacts">Open Contacts →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>My Desk signal completion</h3> <span class="cl-tag cl-tag-new">New</span> </div> My Desk now surfaces four new signal generators: no-reply follow-ups, intent spikes, champion changes, and enrichment wins, so reps always see the next move when they open the workspace. <span class="cl-links">[Intent tracking →](https://docs.graph8.com/signals/intent-tracking/)</span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Sequence and Nurture workflow nodes</h3> <span class="cl-tag cl-tag-new">New</span> </div> Workflows can now add or remove a contact from a sequence or nurture campaign as a single node, with a picker that scopes to nurture-only runs. <span class="cl-links">[Workflows →](https://docs.graph8.com/ai-features/workflows/) · <a data-parent-nav="/agents/workflows/visual">Open Workflows →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>MCP toolkit 0.8.0</h3> <span class="cl-tag cl-tag-new">New</span> </div> The MCP server gains a 12-tool quote-to-cash surface, full-cycle campaign builder tools for context, editor, warmup, intel, and research, an always-on task CRUD suite, and a meetings widget with an internal-meeting toggle. <span class="cl-links">[MCP tools →](https://docs.graph8.com/developers/mcp-tools/)</span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Meta audience sync wizard</h3> <span class="cl-tag cl-tag-new">New</span> </div> Audience push to Meta Ads now supports the special-ad-categories compliance toggle, chunked uploads for large audiences, and a guided wizard for first-time setup. <span class="cl-links">[Audience sync →](https://docs.graph8.com/settings/audience-sync/) · <a data-parent-nav="/integrations">Open Integrations →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Hiring signals on saved campaigns</h3> <span class="cl-tag cl-tag-new">New</span> </div> Saved hiring-signal campaigns gain a Zap badge plus a sync-config panel so refreshes happen on a schedule without manual exports. <span class="cl-links">[Intent tracking →](https://docs.graph8.com/signals/intent-tracking/) · <a data-parent-nav="/enrichment">Open Enrichment →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Lifecycle pagination and Move-Stage</h3> <span class="cl-tag cl-tag-improved">Improved</span> <span class="cl-tag cl-tag-major">Major</span> </div> Companies and Contacts lifecycle grids now use server-side pagination with debounced search, drop the 10k-row cap, and Move-Stage works correctly for Trials, Customers, and Churned cohorts. Inline owner picker and stage-change audit trail land alongside the pagination upgrade. <span class="cl-links">[Contacts →](https://docs.graph8.com/data/contacts/) · <a data-parent-nav="/contacts">Open Contacts →</a></span> </div> </div> </div> <div class="cl-entry"> <div class="cl-date-col"> <div class="cl-date-sticky"> <span class="cl-date-range">Apr 11-17</span> <span class="cl-date-year">2026</span> </div> </div> <div class="cl-content"> <div class="cl-item"> <div class="cl-item-header"> <h3>Revenue lifecycle lists</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> Every revenue lifecycle stage now maintains two auto-synced lists, one for contacts and one for companies. Stage changes propagate instantly so trial, customer, and churn cohorts stay current without manual upkeep. <span class="cl-links">[Managing lists →](https://docs.graph8.com/data/managing-lists/) · <a data-parent-nav="/lists">Open Lists →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Subscription and behavior analytics</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> New Revenue and Usage dashboards surface MRR, subscription state changes, and per-account product usage over time. Drill into any cohort to see the contacts and companies driving the number. <span class="cl-links"><a data-parent-nav="/analytics">Open Analytics →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>ABM platform</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> The account-based marketing workspace ships with a companies-first detail panel, a multi-committee library, and keyword-level assignment so the same company can belong to multiple buying committees at once. <span class="cl-links"><a data-parent-nav="/companies">Open Companies →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Workflow scheduled triggers and new action nodes</h3> <span class="cl-tag cl-tag-new">New</span> </div> Workflows can now start on a recurring schedule, and the node library gains Create Contact, loop-path branching, and three default templates with inline action configuration. <span class="cl-links">[Learn about workflows →](https://docs.graph8.com/ai-features/workflows/) · <a data-parent-nav="/agents/workflows/visual">Open Workflow Editor →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Streaming CSV import</h3> <span class="cl-tag cl-tag-new">New</span> </div> Large CSV uploads now stream through the backend with a real-time progress bar, heartbeat keepalive, and resilient enrichment so 100k-row files finish without timing out. <span class="cl-links"><a data-parent-nav="/contacts">Open Contacts →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Clone-compose landing pages and newsletter composer</h3> <span class="cl-tag cl-tag-new">New</span> </div> Studio can now compose a new landing page or newsletter directly from your cloned homepage sections, carrying brand tokens and imagery across every generated surface. <span class="cl-links"><a data-parent-nav="/studio">Open Studio →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Parallel dialing with voicemail detection</h3> <span class="cl-tag cl-tag-new">New</span> </div> The dialer now places parallel outbound calls with voicemail detection, so reps only hear live humans while machine picks drop to the answering-machine flow automatically. <span class="cl-links"><a data-parent-nav="/dialer">Open Dialer →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>LLM voicemail generation</h3> <span class="cl-tag cl-tag-new">New</span> </div> Voicemails are now generated by the latest language models with a rolling pre-generation pipeline, so drops play instantly when the beep hits. <span class="cl-links"><a data-parent-nav="/dialer">Open Dialer →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>After-hours inbound calls</h3> <span class="cl-tag cl-tag-new">New</span> </div> Inbound calls outside business hours route to a dedicated receptionist flow with voicemail capture and SMS follow-up. <span class="cl-links"><a data-parent-nav="/dialer">Open Dialer →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Create deal and editable deal sidebar</h3> <span class="cl-tag cl-tag-new">New</span> </div> Open any deal to edit stage, value, close date, and owner inline, and create new deals directly from the pipeline without leaving the grid. <span class="cl-links"><a data-parent-nav="/deals">Open Deals →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Intent search overhaul</h3> <span class="cl-tag cl-tag-improved">Improved</span> <span class="cl-tag cl-tag-major">Major</span> </div> The intent and visitor search stack moves to server-side pagination with instant first paint, a redesigned drill-down drawer, and a materialized view that drops visitor counts from 8.5 seconds to 50 milliseconds. <span class="cl-links"><a data-parent-nav="/search">Open Search →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Copilot chat history</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> Copilot sessions now persist to the database, so conversations survive page reloads and stay threaded across devices. <span class="cl-links"><a data-parent-nav="/studio">Open Copilot →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Trial attribution and lifecycle-aware detail views</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> Contact and company detail pages adapt to the revenue lifecycle stage, exposing trial attribution, first-touch source, and usage-based health scores inline. <span class="cl-links"><a data-parent-nav="/contacts">Open Contacts →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Inbox channel list performance</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> The universal inbox channel list now paginates and filters at the SQL level with denormalized thread status, so even mailboxes with hundreds of thousands of threads stay snappy. <span class="cl-links"><a data-parent-nav="/inbox">Open Inbox →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Enrichment pulls from the graph8 data index</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> CSV import and CRM pulls now enrich from the graph8 data index before hitting external providers, so matched contacts land with full coverage at zero extra credit cost. <span class="cl-links">[Learn about enrichment →](https://docs.graph8.com/data/enrichment/) · <a data-parent-nav="/enrichment">Open Enrichment →</a></span> </div> </div> </div> <div class="cl-entry"> <div class="cl-date-col"> <div class="cl-date-sticky"> <span class="cl-date-range">Apr 5-11</span> <span class="cl-date-year">2026</span> </div> </div> <div class="cl-content"> <div class="cl-item"> <div class="cl-item-header"> <h3>SDR Marketplace</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> Hire SDRs end-to-end: browse providers, compare rate cards, message talent, hire, and pay out. Accepted bookings sync to meetings for billing, payouts run through connected accounts, and unified signup supports both client and SDR roles with RBAC. <span class="cl-links"><a data-parent-nav="/marketplace">Open Marketplace →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Ads platform end-to-end</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> Launch paid campaigns from graph8: connect ad networks via OAuth, generate images and copy with AI, build audiences, and push the campaign live through a single wizard. <span class="cl-links"><a data-parent-nav="/ads">Open Ads →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Quote-to-Cash</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> Create and send quotes with e-signature, manage a product catalog, configure revenue settings, and see a Quotes tab inside each deal room. Deal values roll up automatically when a quote is accepted. <span class="cl-links"><a data-parent-nav="/deals">Open Deals →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Appointments end-to-end</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> Calendar integration completes with Google Calendar sync, public cancel and reschedule pages, booking management URLs, a webchat booking flow, and filterable insights with CSV export. <span class="cl-links"><a data-parent-nav="/appointments">Open Appointments →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>New keyword resolver</h3> <span class="cl-tag cl-tag-new">New</span> </div> Keyword resolution moves to an LLM plus MAID chain with hybrid search, giving better targeting suggestions and faster matching across the analytics engine. <span class="cl-links"><a data-parent-nav="/keywords">Open Keywords →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Workflow JSON export and import</h3> <span class="cl-tag cl-tag-new">New</span> </div> Export any workflow to JSON from the canvas toolbar and import it into another org. The JSON format captures triggers, nodes, and edges cleanly. <span class="cl-links">[Learn about workflows →](https://docs.graph8.com/ai-features/workflows/) · <a data-parent-nav="/agents/workflows/visual">Open Workflow Editor →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Enrich contact and verify email nodes</h3> <span class="cl-tag cl-tag-new">New</span> </div> Two new workflow action nodes handle inline enrichment and email verification without leaving the automation canvas. <span class="cl-links"><a data-parent-nav="/agents/workflows/visual">Open Workflow Editor →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Inbox tag applied trigger</h3> <span class="cl-tag cl-tag-new">New</span> </div> Start a workflow the moment a tag is applied to an inbox message. Useful for routing replies to the right team or kicking off follow-up sequences. <span class="cl-links"><a data-parent-nav="/inbox">Open Inbox →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Clickable lead names and grid AI actions</h3> <span class="cl-tag cl-tag-new">New</span> </div> Lead names in the leads grid link straight to the contact detail page. Bulk AI actions are now available directly from the grid header. <span class="cl-links"><a data-parent-nav="/contacts">Open Contacts →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Workbench AG Grid listing</h3> <span class="cl-tag cl-tag-new">New</span> </div> Workbench tables render in AG Grid with a JSON cell viewer for deeply nested data and a one-click export to a saved list. <span class="cl-links"><a data-parent-nav="/studio">Open Studio →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Inbound call routing</h3> <span class="cl-tag cl-tag-new">New</span> </div> Inbound calls route through the receptionist agent with phone directory gating, availability windows, and organizer default apps for Roam-based conferencing. <span class="cl-links"><a data-parent-nav="/dialer">Open Dialer →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>SugarCRM enhancements</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> SugarCRM push sync adds polish across field mappings, edge-case handling, and revenue lifecycle fields, bringing it close to parity with HubSpot and Salesforce. <span class="cl-links">[Integration settings →](https://docs.graph8.com/settings/integrations/) · <a data-parent-nav="/integrations">Open Integrations →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Sequencer outflow reliability</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> Sequencer max outflow now runs a 2-step lookahead with an execution-time guard, and the SequenceCreate Contacts tab paginates server-side for very large lists. <span class="cl-links">[Creating sequences →](https://docs.graph8.com/engage/sequencer/) · <a data-parent-nav="/sequencer">Open Sequencer →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Contact list dropdowns</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> Contact list pickers switch to infinite scroll with server-side search, so even the biggest accounts load instantly inside workflows and campaigns. <span class="cl-links"><a data-parent-nav="/lists">Open Lists →</a></span> </div> </div> </div> <div class="cl-entry"> <div class="cl-date-col"> <div class="cl-date-sticky"> <span class="cl-date-range">Mar 29 - Apr 4</span> <span class="cl-date-year">2026</span> </div> </div> <div class="cl-content"> <div class="cl-item"> <div class="cl-item-header"> <h3>graph8 developer SDK and MCP platform</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> The @graph8/sdk SDK ships with 14 modules and public endpoints, plus a CLI, docs site, and a full MCP server for Claude Desktop, Cursor, and other clients. Build directly against graph8 from code or any AI workspace. <span class="cl-links">[Developer docs →](https://docs.graph8.com/developers/)</span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Appointments and booking system</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> Native booking flows launch with public booking pages, cancel and reschedule links, host email notifications with calendar icons, and filterable insights with date ranges and CSV export. <span class="cl-links"><a data-parent-nav="/appointments">Open Appointments →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Unified Global and Studio copilot</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> Global copilot and Studio copilot now share a single engine and UI. Staging and workbench tools are registered once and available wherever you chat with the copilot. <span class="cl-links">[Learn about Studio →](https://docs.graph8.com/studio/global/) · <a data-parent-nav="/studio">Open Studio →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Web research v3</h3> <span class="cl-tag cl-tag-new">New</span> </div> The research agent moves to a planner-as-brain architecture with skill-driven execution, JSON-formatted search results, and fixed per-run credit costs. <span class="cl-links"><a data-parent-nav="/studio">Open Studio →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Data trial and PAYG signup</h3> <span class="cl-tag cl-tag-new">New</span> </div> New self-serve flows for the 10K data credits trial and the PAYG tier with credit packs. Free and trial accounts now see an Upgrade to Pro card on the billing page. <span class="cl-links"><a data-parent-nav="/studio/settings?tab=billing">Open Billing →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Multi-path workflow node</h3> <span class="cl-tag cl-tag-new">New</span> </div> The if/else condition node becomes a named-path node, so a single decision step can branch into any number of outcomes and keep the canvas readable. <span class="cl-links">[Learn about workflows →](https://docs.graph8.com/ai-features/workflows/) · <a data-parent-nav="/agents/workflows/visual">Open Workflow Editor →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Workflow node library expansion</h3> <span class="cl-tag cl-tag-new">New</span> </div> New nodes cover Document Lookup, Contact Activity Lookup, Workbench Lookup, and additional communication channels. Sequence filters land on trigger nodes, and template variables can now be inlined directly in message fields. <span class="cl-links"><a data-parent-nav="/agents/workflows/visual">Open Workflow Editor →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Campaign-workflow association</h3> <span class="cl-tag cl-tag-new">New</span> </div> Campaigns can now be linked to workflows, so orchestration and attribution stay in sync when a campaign launches complex multi-step automations. <span class="cl-links"><a data-parent-nav="/studio">Open Studio →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Staging workbench upgrades</h3> <span class="cl-tag cl-tag-new">New</span> </div> Find People lands inside the workbench alongside copilot-driven staging, LinkedIn lookup, email-attached PDF ingest, and scraper-bot enrichment. <span class="cl-links"><a data-parent-nav="/studio">Open Studio →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Studio tables in AG Grid</h3> <span class="cl-tag cl-tag-new">New</span> </div> Studio content mode tables migrate to AG Grid for faster filtering, sorting, and bulk actions across large document sets. <span class="cl-links"><a data-parent-nav="/studio">Open Studio →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Global webchat widget</h3> <span class="cl-tag cl-tag-new">New</span> </div> A reusable webchat widget ships for any graph8-powered site with starter question bubbles, a chat-with-human button, and automatic transfer detection in the copilot. <span class="cl-links"><a data-parent-nav="/webchat">Open Web Chat →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Receptionist agent and phone directory</h3> <span class="cl-tag cl-tag-new">New</span> </div> Configure a receptionist agent and phone directory to route inbound calls. Directory tools are gated to receptionists only, with availability windows honored automatically. <span class="cl-links"><a data-parent-nav="/dialer">Open Dialer →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Dialer and sequence controls</h3> <span class="cl-tag cl-tag-new">New</span> </div> Re-dial by disposition adds a power-mode filter that cycles through specific outcomes. Sequences can now be archived via soft-delete without losing history. <span class="cl-links"><a data-parent-nav="/dialer">Open Dialer →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Inbox sync reliability</h3> <span class="cl-tag cl-tag-improved">Improved</span> <span class="cl-tag cl-tag-major">Major</span> </div> The inbox sync path was simplified with event-driven reply detection, date-scoped SMTP checkpoints, a 5-minute cooldown between scans, and a skip-on-sent-folder optimization. <span class="cl-links"><a data-parent-nav="/inbox">Open Inbox →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Roam integration hardening</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> Multi-select for DM notifications, OAuth auto-refresh across voice and workflow endpoints, and a dedicated Groups API key make Roam-based workflows production-ready. <span class="cl-links">[Integration settings →](https://docs.graph8.com/settings/integrations/) · <a data-parent-nav="/integrations">Open Integrations →</a></span> </div> </div> </div> <div class="cl-entry"> <div class="cl-date-col"> <div class="cl-date-sticky"> <span class="cl-date-range">Mar 22-28</span> <span class="cl-date-year">2026</span> </div> </div> <div class="cl-content"> <div class="cl-item"> <div class="cl-item-header"> <h3>SugarCRM integration</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> SugarCRM is now a fully supported CRM alongside HubSpot, Salesforce, and Zoho. Contacts, companies, deals, and appointments sync bidirectionally with field mapping, type coercion, and phone sanitization built in. <span class="cl-links">[Integration settings →](https://docs.graph8.com/settings/integrations/) · <a data-parent-nav="/integrations">Open Integrations →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Web chat redesign</h3> <span class="cl-tag cl-tag-new">New</span> </div> The web chat widget has been completely redesigned with an updated admin interface, improved design settings, question filtering, and a refreshed visitor-facing chat experience. <span class="cl-links"><a data-parent-nav="/webchat">Open Web Chat →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Copilot web search and research mode</h3> <span class="cl-tag cl-tag-new">New</span> </div> The copilot can now search the web in real time during conversations. Toggle between research mode and thinking mode to control how the copilot approaches complex questions. <span class="cl-links">[Learn about Studio →](https://docs.graph8.com/studio/global/) · <a data-parent-nav="/studio">Open Studio →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Workflow trigger expansion</h3> <span class="cl-tag cl-tag-new">New</span> </div> Eight new workflow triggers are available: LinkedIn connection accepted, LinkedIn message received, email replied, email bounced, call completed, call no-answer, sequence started, and sequence completed. Build automations that react to engagement across every channel. <span class="cl-links">[Learn about workflows →](https://docs.graph8.com/ai-features/workflows/) · <a data-parent-nav="/agents/workflows/visual">Open Workflow Editor →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Meeting sensitivity controls</h3> <span class="cl-tag cl-tag-new">New</span> </div> Meetings marked as private or confidential in your calendar are now filtered based on participant visibility. Only attendees and their managers can see sensitive meeting details in the inbox. <span class="cl-links"><a data-parent-nav="/inbox">Open Inbox →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Google OAuth mailboxes</h3> <span class="cl-tag cl-tag-new">New</span> </div> Connect Gmail mailboxes using Google OAuth directly from the mailbox settings. No app passwords required - authenticate with one click and start sending. <span class="cl-links"><a data-parent-nav="/studio/settings?tab=mailboxes">Open Mailboxes →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Enrichment data column in list views</h3> <span class="cl-tag cl-tag-new">New</span> </div> List grids now display a global enrichment_data column, showing AI-enriched fields inline alongside standard contact data. Confidence scores render as numeric values with colored tier indicators. <span class="cl-links"><a data-parent-nav="/lists">Open Lists →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Scraper bot for staging workbench</h3> <span class="cl-tag cl-tag-new">New</span> </div> A new scraper bot agent in the staging workbench extracts structured data from web pages and feeds it directly into your enrichment pipeline. <span class="cl-links"><a data-parent-nav="/enrichment">Open Enrichment →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Dialer reliability and number management</h3> <span class="cl-tag cl-tag-improved">Improved</span> <span class="cl-tag cl-tag-major">Major</span> </div> Resolved infinite retry loops on duplicate phone numbers, fixed voicemail misclassification on live calls, added a phone number selector dropdown, and improved outbound call authentication. Dialer analytics now support score-based filtering. <span class="cl-links"><a data-parent-nav="/dialer">Open Dialer →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Sequencer stats accuracy</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> Sequence statistics now use the highest executed step for terminal attribution and no longer double-count bounced or replied contacts across multiple steps. Step templates also display correctly in the insert picker. <span class="cl-links">[Creating sequences →](https://docs.graph8.com/engage/sequencer/) · <a data-parent-nav="/sequencer">Open Sequencer →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Zoho and CRM data quality</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> Zoho CRM push sync now includes type coercion for company fields and sanitization for mobile, assistant, and fax phone numbers. Field mapping improvements apply across all connected CRMs. <span class="cl-links">[Integration settings →](https://docs.graph8.com/settings/integrations/)</span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Warmup email detection</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> The inbox now uses warmup key IDs to identify warmup emails more accurately, preventing them from appearing in your active conversations. <span class="cl-links"><a data-parent-nav="/inbox">Open Inbox →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Email editor formatting stability</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> Switching between HTML and plain text in the email editor no longer breaks your formatting or variables. Line breaks and personalization tags are now preserved when you change modes. <span class="cl-links"><a data-parent-nav="/sequencer">Open Sequencer →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Caller ID validation</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> Outbound calls now check that your selected phone number is still active before dialing. If the number was removed, you'll see a clear error instead of a silent failure. <span class="cl-links"><a data-parent-nav="/dialer">Open Dialer →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Staging field extraction checkboxes</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> Checkboxes in the staging field extraction card now respond reliably on every click. Previously, some clicks appeared to do nothing. <span class="cl-links"><a data-parent-nav="/enrichment">Open Enrichment →</a></span> </div> </div> </div> <div class="cl-entry"> <div class="cl-date-col"> <div class="cl-date-sticky"> <span class="cl-date-range">Mar 15-21</span> <span class="cl-date-year">2026</span> </div> </div> <div class="cl-content"> <div class="cl-item"> <div class="cl-item-header"> <h3>Global copilot sidebar</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> The copilot is now accessible from every page via a global sidebar. It includes a dedicated tools engine, contact and list lookups, dialer controls, and navigation commands, all available without leaving your current workflow. <span class="cl-links">[Learn about Studio →](https://docs.graph8.com/studio/global/) · <a data-parent-nav="/studio">Open Studio →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Lead qualification engine</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> Incoming leads are now scored and qualified automatically based on configurable criteria. The leads grid shows a "Qualified By" column and supports backfilling scores for existing records. Configure qualification rules in Settings. <span class="cl-links"><a data-parent-nav="/studio/settings?tab=revenue&subtab=lead-qualification">Open Settings →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Multi-platform copilot notifications</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> The copilot now sends notifications and accepts messages across Slack, WhatsApp, Roam, and iMessage. Connect your preferred platforms from the Profile page and choose per-channel notification preferences. Messages include rolling conversation history for context. <span class="cl-links"><a data-parent-nav="/profile">Open Profile →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>@Mention notifications</h3> <span class="cl-tag cl-tag-new">New</span> </div> Tag teammates with @mentions in copilot sessions. Mentioned users receive a notification in the bell icon and can jump directly into the conversation thread. <span class="cl-links"><a data-parent-nav="/studio">Open Studio →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Staging export with dynamic column mapping</h3> <span class="cl-tag cl-tag-new">New</span> </div> When exporting staged enrichment data to a list, a mapping wizard lets you match staging columns to graph8 contact fields. Unmapped columns are preserved as custom fields automatically. <span class="cl-links"><a data-parent-nav="/enrichment">Open Enrichment →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Phone number assignee</h3> <span class="cl-tag cl-tag-new">New</span> </div> Phone numbers can now be assigned to specific team members. The assignee is visible in the phone numbers table, and an edit dialog allows reassignment at any time. <span class="cl-links"><a data-parent-nav="/studio/settings?tab=phone">Phone Settings →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Server-side mailbox and domain filters</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> The mailboxes and domains settings tables now filter server-side, making them significantly faster for large mailbox fleets. Drill-down navigation links domains directly to their associated mailboxes. <span class="cl-links"><a data-parent-nav="/studio/settings?tab=mailboxes">Open Mailboxes →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Contact data quality</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> CSV-uploaded contacts are now automatically linked to their companies so the Company Name column populates correctly. List grid text preserves original casing, and missing company fields appear in form-mapping dropdowns. <span class="cl-links"><a data-parent-nav="/contacts">Open Contacts →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Dynamic domain pricing</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> Domain purchase pricing is now calculated dynamically based on TLD, with hold and charge amounts displayed in the billing UI before you confirm. <span class="cl-links"><a data-parent-nav="/studio/settings?tab=domains">Open Settings →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Copilot sequence tools</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> The copilot can now add, edit, and reorder sequence steps directly from the chat. Ask it to insert a new email step or adjust timing without opening the sequence editor. <span class="cl-links">[Creating sequences →](https://docs.graph8.com/engage/sequencer/) · <a data-parent-nav="/sequencer">Open Sequencer →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Inbox timestamp formatting</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> Message timestamps in the inbox now display consistently across all conversation types, with relative formatting for recent messages and full dates for older ones. <span class="cl-links"><a data-parent-nav="/inbox">Open Inbox →</a></span> </div> </div> </div> <div class="cl-entry"> <div class="cl-date-col"> <div class="cl-date-sticky"> <span class="cl-date-range">Mar 8-14</span> <span class="cl-date-year">2026</span> </div> </div> <div class="cl-content"> <div class="cl-item"> <div class="cl-item-header"> <h3>Studio copilot tools and home dashboard</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> The Studio home dashboard now shows smart cards with calendar events, deal summaries, and activity insights. The copilot gains new tools for accounts, inbox queries, calendar lookups, and deal management, plus a persistent memory system across sessions. <span class="cl-links">[Learn about Studio →](https://docs.graph8.com/studio/global/) · <a data-parent-nav="/studio">Open Studio →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Manual dialing</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> The dialer now supports manual dialing with session management. Start a manual call session, dial contacts one by one, log dispositions, and track call outcomes, all from the dialer interface. <span class="cl-links"><a data-parent-nav="/dialer">Open Dialer →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Intent search</h3> <span class="cl-tag cl-tag-new">New</span> </div> A new intent search surface lets you find enriched web pages that match your prospecting criteria. Search by keyword or topic and see which pages signal buyer intent, with text and semantic search modes. <span class="cl-links"><a data-parent-nav="/intent-search">Open Intent Search →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Copilot notification bridge</h3> <span class="cl-tag cl-tag-new">New</span> </div> The copilot now surfaces real-time notifications in the bell icon when events occur across your workspace, including deal stage changes, sequence completions, and workflow triggers. <span class="cl-links"><a data-parent-nav="/studio">Open Studio →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>LinkedIn on-demand content generation</h3> <span class="cl-tag cl-tag-new">New</span> </div> Generate LinkedIn message drafts directly from the campaign builder. The AI uses your campaign context, persona, and ICP to craft personalized connection requests and follow-up messages. <span class="cl-links"><a data-parent-nav="/studio">Open Studio →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Domain placement testing</h3> <span class="cl-tag cl-tag-new">New</span> </div> Run inbox placement tests for your sending domains directly from the domain settings. See which email providers land in inbox vs. spam before launching a campaign. <span class="cl-links"><a data-parent-nav="/studio/settings?tab=mailboxes">Open Mailboxes →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Zoho CRM parity</h3> <span class="cl-tag cl-tag-improved">Improved</span> <span class="cl-tag cl-tag-major">Major</span> </div> Zoho CRM now supports deal field mappings, push sync helpers, mandatory field handling, custom views, and OAuth domain extension. Integration is now on par with HubSpot and Salesforce. <span class="cl-links">[Integration settings →](https://docs.graph8.com/settings/integrations/) · <a data-parent-nav="/integrations">Open Integrations →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Studio slash commands</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> Slash commands in the copilot now auto-execute on selection from the command palette. The /edit command works reliably for structured documents, and the search palette correctly filters and highlights results. <span class="cl-links"><a data-parent-nav="/studio">Open Studio →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Sequence dialer reliability</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> Fixed auto-pause behavior, contact skipping, and session state issues in the sequence dialer. Calls now resume correctly after pausing, and contacts are no longer skipped unexpectedly. <span class="cl-links"><a data-parent-nav="/dialer">Open Dialer →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>List performance metrics</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> The list performance tab now includes call metrics in the CSV export and calculates summary statistics more efficiently. Performance queries filter by organization for accurate results. <span class="cl-links"><a data-parent-nav="/lists">Open Lists →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Inbox message formatting</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> HTML email content now converts to plain text more accurately in conversation previews, with proper marker formatting and cleaner thread display. <span class="cl-links"><a data-parent-nav="/inbox">Open Inbox →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Meeting permissions</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> Meeting access controls are now streamlined with role-based permissions. Meeting-related UI components use consistent layouts across the inbox and settings. <span class="cl-links"><a data-parent-nav="/inbox">Open Inbox →</a></span> </div> </div> </div> <div class="cl-entry"> <div class="cl-date-col"> <div class="cl-date-sticky"> <span class="cl-date-range">Mar 1-7</span> <span class="cl-date-year">2026</span> </div> </div> <div class="cl-content"> <div class="cl-item"> <div class="cl-item-header"> <h3>Company, contact, and deal detail views</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> The detail pages for companies, contacts, and deals have been completely redesigned. Each view now shows a unified layout with intelligence tabs, linked records, activity timelines, and inline editing. <span class="cl-links"><a data-parent-nav="/contacts">Open Contacts →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Meetings inbox</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> Calendar meetings now appear as a dedicated channel in the inbox. Meeting threads include extraction support for notes and action items, making post-call follow-up faster. <span class="cl-links">[Connect your calendar →](https://docs.graph8.com/getting-started/connect-calendar/) · <a data-parent-nav="/inbox">Open Inbox →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Individual record creation</h3> <span class="cl-tag cl-tag-new">New</span> </div> Create individual contacts and companies directly from the list view using the new "Add Record" dialog. Contacts are automatically linked to companies by domain, and emailless contacts are supported. <span class="cl-links"><a data-parent-nav="/contacts">Open Contacts →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Webhook node for workflows</h3> <span class="cl-tag cl-tag-new">New</span> </div> A new webhook action node is available in the workflow canvas. Send HTTP requests to external services as part of your automation, with accurate field pickers for mapping data from previous workflow steps. <span class="cl-links">[Learn about workflows →](https://docs.graph8.com/ai-features/workflows/) · <a data-parent-nav="/agents/workflows/visual">Open Workflow Editor →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Developer API tools</h3> <span class="cl-tag cl-tag-new">New</span> </div> A new Developers section in the help center documents MCP profile tools and automation platform integrations. Manage your API profile directly from the profile page. <span class="cl-links">[Developers →](https://docs.graph8.com/developers/automation-platforms/)</span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Team member detail workspace</h3> <span class="cl-tag cl-tag-new">New</span> </div> Click on any team member to see a dedicated workspace showing their activity, assigned sequences, call stats, and task queue. </div> <div class="cl-item"> <div class="cl-item-header"> <h3>A2P 10DLC messaging compliance</h3> <span class="cl-tag cl-tag-new">New</span> </div> SMS sending now routes through registered messaging services for A2P 10DLC compliance, ensuring higher deliverability and avoiding carrier filtering for US phone numbers. <span class="cl-links"><a data-parent-nav="/studio/settings?tab=phone">Phone Settings →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Deals - priority and email fields</h3> <span class="cl-tag cl-tag-new">New</span> </div> Deals now include a Priority column visible in the grid view. CRM sync also maps the email field for deal records, keeping contact context connected. <span class="cl-links"><a data-parent-nav="/lists">Open Lists →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Studio document settings</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> Expanded controls for Studio documents including library defaults, compliance settings, and scope-based tabs. Agent company knowledge now connects directly to the global context. <span class="cl-links">[Studio settings guide →](https://docs.graph8.com/studio/global/) · <a data-parent-nav="/studio/settings">Open Studio Settings →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Multi-dialing improvements</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> The parallel dialer UI now displays contact status more clearly, with improved grid rendering and guards to prevent processing contacts outside the current batch. <span class="cl-links"><a data-parent-nav="/dialer">Open Dialer →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Analytics UI polish</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> The analytics section has been refined with consistent layouts across acquisition, behavior, and engagement views. Navigation and visual hierarchy are more intuitive. <span class="cl-links"><a data-parent-nav="/analytics">Open Analytics →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Enrichment and search fixes</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> Job Title Exclude filters now work correctly in enrichment searches. Contact Type, SIC/NAICS codes, and name display issues in search results have been resolved. AI enrichment credit consumption is also optimized. <span class="cl-links">[Learn about enrichment →](https://docs.graph8.com/data/enrichment/) · <a data-parent-nav="/enrichment">Open Find People →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>List export fixes</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> CSV exports from list data tabs now respect column visibility settings and produce files that open correctly on Windows. <span class="cl-links"><a data-parent-nav="/lists">Open Lists →</a></span> </div> </div> </div> <div class="cl-entry"> <div class="cl-date-col"> <div class="cl-date-sticky"> <span class="cl-date-range">Feb 21–28</span> <span class="cl-date-year">2026</span> </div> </div> <div class="cl-content"> <div class="cl-item"> <div class="cl-item-header"> <h3>Sequencer + Campaign Builder integration</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> Sequences can now be linked directly to Campaign Builder campaigns. When launched from a campaign, the sequence inherits the campaign's personas, ICP, and content context automatically. <span class="cl-links">[Learn more →](https://docs.graph8.com/studio/campaigns/) · <a data-parent-nav="/sequencer">Open Sequencer →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Landing page and idea builder</h3> <span class="cl-tag cl-tag-new">New</span> </div> A new content surface for creating and publishing landing pages. The idea builder generates page structure from your campaign context using AI, with editable sections and a preview mode. <span class="cl-links"><a data-parent-nav="/studio?mode=content">Open Studio →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>CRM leads integration</h3> <span class="cl-tag cl-tag-new">New</span> </div> Lead records from connected CRMs now sync bidirectionally. Leads appear in the Contacts view with a "Lead" tag and sync status indicator. Supported CRMs: HubSpot, Salesforce. <span class="cl-links">[Integration settings →](https://docs.graph8.com/settings/integrations/) · <a data-parent-nav="/contacts">Open Contacts →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Send email node for workflows</h3> <span class="cl-tag cl-tag-new">New</span> </div> Workflows now support a Send Email action node. Configure the sender mailbox, subject, and body directly in the workflow canvas. <span class="cl-links"><a data-parent-nav="/agents/workflows/visual">Open Workflow Editor →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Pending approvals page</h3> <span class="cl-tag cl-tag-new">New</span> </div> A dedicated approvals inbox for workflow steps that require human review before continuing. Approvers can accept, reject, or request edits with a comment. <span class="cl-links"><a data-parent-nav="/agents/workflows/approvals">Open Approvals →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Inboxkit — external domain support</h3> <span class="cl-tag cl-tag-new">New</span> </div> Inboxkit can now manage mailboxes on your own domains, not just graph8-provisioned ones. Configure external domain mailboxes from Settings > Email Infrastructure. <span class="cl-links"><a data-parent-nav="/studio/settings?tab=mailboxes">Open Mailboxes →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Calendar service</h3> <span class="cl-tag cl-tag-new">New</span> </div> Calendar-aware briefings and copilot prompts now include scheduling context, making pre-call preparation more relevant. <span class="cl-links">[Connect your calendar →](https://docs.graph8.com/getting-started/connect-calendar/) · <a data-parent-nav="/appointments/availability">Calendar Settings →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Voice and dialer reliability</h3> <span class="cl-tag cl-tag-improved">Improved</span> <span class="cl-tag cl-tag-major">Major</span> </div> 10 coordinated improvements for call stability, covering silence detection, auto-mute behavior, call state handling, and race conditions. Calls are now significantly more reliable across all connection types. <span class="cl-links"><a data-parent-nav="/dialer">Open Dialer →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>SMS inbox enhancements</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> The SMS inbox now has filter tabs (All, Unread, Drafts). Draft messages can be deleted. Bounced and warmed-up addresses are automatically excluded from outbound SMS. <span class="cl-links"><a data-parent-nav="/inbox/sms">Open SMS Inbox →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Settings UI overhaul</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> The settings area has been redesigned with a consistent layout — left navigation, section headers, inline editing, and billing integrated directly into the settings experience. <span class="cl-links">[Settings guide →](https://docs.graph8.com/settings/team/) · <a data-parent-nav="/studio/settings">Open Settings →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Copilot improvements</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> The copilot now supports calendar-aware prompts for scheduling context. Response reliability improved in high-latency connections. <span class="cl-links">[Learn about Studio →](https://docs.graph8.com/studio/global/)</span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Agent node — dropdown selector</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> The agent node in the workflow canvas now uses a dropdown to select the target agent, replacing the manual ID input field. Reduces configuration errors significantly. <span class="cl-links"><a data-parent-nav="/agents/workflows/visual">Open Workflow Editor →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Security questionnaire</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> Improved edit mode that preserves field state on save. Best-guess answers now populate correctly when uploading a questionnaire. </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Dialer-campaign linkage</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> Dialer sessions can now be linked to a specific campaign, so call outcomes and activities roll up to the campaign's analytics. <span class="cl-links"><a data-parent-nav="/dialer">Open Dialer →</a></span> </div> </div> </div> <div class="cl-entry"> <div class="cl-date-col"> <div class="cl-date-sticky"> <span class="cl-date-range">Feb 14–20</span> <span class="cl-date-year">2026</span> </div> </div> <div class="cl-content"> <div class="cl-item"> <div class="cl-item-header"> <h3>SMS channel in AI inbox</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> SMS is now a full channel in the AI inbox. Inbound and outbound SMS threads appear alongside email conversations, with AI draft replies available for SMS. Filter tabs separate email and SMS. <span class="cl-links"><a data-parent-nav="/inbox/sms">Open SMS Inbox →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Performance analysis view</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> A new analytics surface showing per-sequence, per-campaign, and per-rep performance metrics. Includes date range filtering and CSV export. <span class="cl-links"><a data-parent-nav="/analytics">Open Analytics →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>3 new workflow triggers</h3> <span class="cl-tag cl-tag-new">New</span> </div> Three new triggers for workflow automation: contact enters a list, deal stage changes, and calendar event created. Find them in the trigger selector when creating a new workflow. <span class="cl-links"><a data-parent-nav="/agents/workflows/visual">Open Workflow Editor →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Activities feed</h3> <span class="cl-tag cl-tag-new">New</span> </div> A chronological activity timeline on contact and company detail pages. Shows calls, emails sent, meetings booked, and notes — filterable by activity type. <span class="cl-links"><a data-parent-nav="/contacts">Open Contacts →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Deals</h3> <span class="cl-tag cl-tag-new">New</span> </div> Pipeline deals can now be created, edited, and tracked natively in graph8. Deals include stage, value, close date, and linked contacts. Full CRM sync is configurable. <span class="cl-links">[Learn about deals →](https://docs.graph8.com/studio/accounts/)</span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Mailboxes revamp</h3> <span class="cl-tag cl-tag-improved">Improved</span> <span class="cl-tag cl-tag-major">Major</span> </div> The email infrastructure settings page has been completely redesigned. Mailboxes now display connection health, warmup status, bounce rates, and per-mailbox sending limits in a single view. <span class="cl-links"><a data-parent-nav="/studio/settings?tab=mailboxes">Open Mailboxes →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Non-US phone number support</h3> <span class="cl-tag cl-tag-new">New</span> </div> International phone numbers can now be added to contacts and used in sequences and the dialer. Country code selection is available in the phone number input. <span class="cl-links"><a data-parent-nav="/studio/settings?tab=phone">Phone Settings →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>AI inbox — email draft improvements</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> On-demand drafts now respect thread context more precisely. Drafts created on-demand no longer overwrite in-progress manual edits. <span class="cl-links"><a data-parent-nav="/inbox">Open Inbox →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Sequencer — phone priority</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> Sequence steps with a phone channel now auto-surface the highest-priority phone number for each contact, reducing manual selection. <span class="cl-links">[Creating sequences →](https://docs.graph8.com/engage/sequencer/)</span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Call logs</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> Call logs now show inbound vs. outbound indicators, duration, recording download links, and disposition labels. Accessible from the contact detail view and the dialer history panel. <span class="cl-links"><a data-parent-nav="/sales-coach/all-calls">View Call Logs →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Copy-to-clipboard buttons</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> Copy buttons added to key data fields: email address, phone number, and LinkedIn URL on contact cards. </div> </div> </div> <div class="cl-entry cl-entry-last"> <div class="cl-date-col"> <div class="cl-date-sticky"> <span class="cl-date-range">Feb 7–13</span> <span class="cl-date-year">2026</span> </div> </div> <div class="cl-content"> <div class="cl-item"> <div class="cl-item-header"> <h3>Find People enrichment</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> A new prospecting surface for finding contacts at target accounts without uploading a list. Enter a company name or domain, define filters (role, seniority, department), and graph8 returns matching contacts with enriched data. <span class="cl-links">[Learn about enrichment →](https://docs.graph8.com/data/enrichment/) · <a data-parent-nav="/enrichment">Open Find People →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>On-demand AI draft</h3> <span class="cl-tag cl-tag-new">New</span> <span class="cl-tag cl-tag-major">Major</span> </div> Click "Draft Reply" on any inbox thread and the AI generates a contextual reply draft. Drafts now persist across sessions, support manual editing before sending, and show generation status clearly. <span class="cl-links"><a data-parent-nav="/inbox">Open Inbox →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>CRM deal integration</h3> <span class="cl-tag cl-tag-new">New</span> </div> Deals from connected CRMs now sync into graph8 with stage, value, and owner. Bidirectional sync is configurable per CRM. <span class="cl-links">[Learn about deals →](https://docs.graph8.com/studio/accounts/) · [Integration settings →](https://docs.graph8.com/settings/integrations/)</span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Agents UI revamp</h3> <span class="cl-tag cl-tag-new">New</span> </div> The agents management screen has been rebuilt. Agent cards show status, current call load, and quick-action buttons. Creating and editing agents uses a new step-by-step flow. <span class="cl-links"><a data-parent-nav="/agents">Open Agents →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Performance view for lists</h3> <span class="cl-tag cl-tag-new">New</span> </div> List detail pages now include a Performance tab showing engagement rates, delivery rates, and activity trends for contacts in that list. <span class="cl-links">[Managing lists →](https://docs.graph8.com/data/managing-lists/) · <a data-parent-nav="/lists">Open Lists →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Call logs</h3> <span class="cl-tag cl-tag-new">New</span> </div> Inbound and outbound call history is now surfaced with separate tabs. Each entry shows the number, duration, outcome, recording link, and the originating sequence step. <span class="cl-links"><a data-parent-nav="/sales-coach/all-calls">View Call Logs →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>SDR email attribution</h3> <span class="cl-tag cl-tag-new">New</span> </div> Emails sent by SDRs are now attributed to the originating SDR in reporting, enabling team performance tracking at the individual level. <span class="cl-links"><a data-parent-nav="/analytics">Open Analytics →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Studio settings</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> Knowledge base, persona defaults, and campaign-level email settings are now consolidated into a single settings drawer for faster configuration. <span class="cl-links">[Studio settings guide →](https://docs.graph8.com/studio/global/) · <a data-parent-nav="/studio/settings">Open Studio Settings →</a></span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Webhook dry run</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> When testing a webhook step in the sequence builder, the response payload and status code are now displayed inline — making it easy to validate configurations before launch. <span class="cl-links">[Creating sequences →](https://docs.graph8.com/engage/sequencer/)</span> </div> <div class="cl-item"> <div class="cl-item-header"> <h3>Voice and dialer improvements</h3> <span class="cl-tag cl-tag-improved">Improved</span> </div> Improved auto-redial stability, auto-mute behavior, ringtone playback for inbound calls, and sales coach call scoring reliability. <span class="cl-links"><a data-parent-nav="/dialer">Open Dialer →</a></span> </div> </div> </div> </div> --- # graph8 Desktop > Find help for every graph8 Desktop feature Source: https://docs.graph8.com/desktop/ <div id="home-page" /> **Get Started** Install graph8 Desktop, run the setup wizard, and connect your accounts. [Browse articles →](https://docs.graph8.com/desktop/installation/) **LinkedIn** Organize your LinkedIn network, track engagement, and take action from one place. [Browse articles →](https://docs.graph8.com/desktop/linkedin/overview/) **X (Twitter)** Manage your X presence, organize data, and post from Desktop. [Browse articles →](https://docs.graph8.com/desktop/x/overview/) **Workbench** Local data tables with AI enrichment, formulas, triggers, and cloud sync. [Browse articles →](https://docs.graph8.com/desktop/workbench/overview/) **Radar** Screen intelligence that captures and surfaces sales signals from your daily work. [Browse articles →](https://docs.graph8.com/desktop/radar/overview/) **Brain** Auto-generated knowledge graph connecting your contacts, companies, and signals. [Browse articles →](https://docs.graph8.com/desktop/brain/overview/) **Copilot** AI assistant with 40+ tools for browser control, research, and productivity. [Browse articles →](https://docs.graph8.com/desktop/copilot/overview/) **Tasks & Autopilot** Autonomous multi-step tasks and pattern detection that suggests actions. [Browse articles →](https://docs.graph8.com/desktop/tasks/overview/) ## Popular Articles - [What is graph8 Desktop?](https://docs.graph8.com/desktop/) - [Installation](https://docs.graph8.com/desktop/installation/) - [LinkedIn Overview](https://docs.graph8.com/desktop/linkedin/overview/) - [Copilot Tools & Capabilities](https://docs.graph8.com/desktop/copilot/tools/) - [Radar Overview](https://docs.graph8.com/desktop/radar/overview/) --- # REST API Overview > Programmatic access to graph8 CRM, enrichment, and prospecting data Source: https://docs.graph8.com/developers/ > **Tip: New to graph8?** > Start with the [Getting Started](https://docs.graph8.com/developers/getting-started/) guide to choose the right integration path (SDK, MCP, CLI, or REST API). > **Tip:** > Try the developer tier free - 1,000 credits, no card required. [Get started](https://app.graph8.com/signup). > > AI agents: fetch the full doc index at [https://docs.graph8.com/llms.txt](https://docs.graph8.com/llms.txt). The REST API gives you direct HTTP access to contacts, companies, lists, enrichment, search, sequences, webhooks, deals, tasks, notes, fields, quotes, pipelines, workflows, skills, voice, intent, studio, and more. Any language, any platform. ## Quick Reference - **Base URL:** `https://be.graph8.com/api/v1` - **Auth:** `Authorization: Bearer YOUR_API_KEY` ([details](https://docs.graph8.com/developers/authentication/)) - **Format:** JSON request/response - **Rate limit:** 50 req/s + 1,000 req/min per org ([details](https://docs.graph8.com/developers/rate-limits/)) - **Docs:** [Interactive Swagger →](https://be.graph8.com/api/v1/docs) ## 30-second start If you can paste a curl, you can start integrating: **cURL** ```bash # Will return your stored contacts (or [] if none yet) curl 'https://be.graph8.com/api/v1/contacts?limit=3' \ -H "Authorization: Bearer $G8_API_KEY" ``` **TypeScript (SDK)** ```typescript import { g8 } from '@graph8/sdk'; g8.init({ apiKey: process.env.G8_API_KEY! }); const { data } = await g8.contacts.list({ limit: 3 }); ``` **Python** ```python import httpx, os r = httpx.get( "https://be.graph8.com/api/v1/contacts", params={"limit": 3}, headers={"Authorization": f"Bearer {os.environ['G8_API_KEY']}"}, ) print(r.json()) ``` **CLI** ```bash g8 login g8 search-contacts --limit 3 ``` **MCP** Configure your AI client per [MCP setup](https://docs.graph8.com/developers/mcp/), then ask: *"Show me my last 3 contacts."* → calls `g8_search_contacts`. [Don't have an API key? Get one in 30 seconds →](https://docs.graph8.com/developers/authentication/) ## API Surfaces graph8 exposes two distinct HTTP surfaces. **Only `/api/v1/*` is for developers** — everything else is internal. | Surface | Mount | Auth | Use case | |---|---|---|---| | **Developer API** | `https://be.graph8.com/api/v1/*` | `Authorization: Bearer` (API key) | What every doc on this site refers to — SDK, CLI, and MCP server all proxy this surface | | **Internal Studio** | `/v1/*` and `/campaign-builder/*` | PropelAuth session cookie | First-party UI in `app.graph8.com` — not for integrations | | **Public (write-key)** | `/api/v1/public/*` | Write key (browser-safe) | Visitor tracking, public-form enrich, copilot chat ([details](https://docs.graph8.com/developers/public-endpoints/)) | | **Intent-search** | `/intent-search/*` (no `/api/v1` prefix) | `Authorization: Bearer` (API key) | The one outlier — see [Intent](https://docs.graph8.com/developers/intent-signals/#account-level-intent-search) | Stick to `/api/v1/*` unless a doc explicitly tells you otherwise. **Contacts** List, create, update, and delete contacts in your workspace. [View reference →](https://docs.graph8.com/developers/contacts/) **Companies** List, update, and delete companies and their associated contacts. [View reference →](https://docs.graph8.com/developers/companies/) **Search** Find contacts and companies in graph8's 700M+ / 100M+ global database. [View reference →](https://docs.graph8.com/developers/search/) **Enrichment** Look up people and companies, run waterfall enrichment, and verify emails. [View reference →](https://docs.graph8.com/developers/enrichment/) **Lists** Create lists, manage membership, and organize contacts for campaigns. [View reference →](https://docs.graph8.com/developers/lists/) **Sequences** List, run, and manage automated email sequences. [View reference →](https://docs.graph8.com/developers/sequences/) **CRM:** [Notes](https://docs.graph8.com/developers/notes/) | [Tasks](https://docs.graph8.com/developers/tasks/) | [Fields](https://docs.graph8.com/developers/fields/) | [Deals](https://docs.graph8.com/developers/deals/) | [Assert/Upsert](https://docs.graph8.com/developers/assert/) **Engage:** [Sequence Lifecycle](https://docs.graph8.com/developers/sequences-lifecycle/) | [Inbox](https://docs.graph8.com/developers/inbox/) | [Meetings](https://docs.graph8.com/developers/meetings/) | [Voice & Dialer](https://docs.graph8.com/developers/voice/) | [Webhooks](https://docs.graph8.com/developers/webhooks/) **Revenue:** [Quotes](https://docs.graph8.com/developers/quotes/) | [Stage Checklist Pipelines](https://docs.graph8.com/developers/pipelines/) **Automation:** [Workflows](https://docs.graph8.com/developers/workflows/) | [Skills](https://docs.graph8.com/developers/skills/) | [Landing Pages](https://docs.graph8.com/developers/landing-pages/) **Intelligence:** [Intent & Signals](https://docs.graph8.com/developers/intent-signals/) | [GTM Campaigns](https://docs.graph8.com/developers/gtm-campaigns/) | [GTM Context](https://docs.graph8.com/developers/gtm-context/) **Sync:** [Audience Syncs](https://docs.graph8.com/developers/audience-syncs/) | [CRM Syncs](https://docs.graph8.com/developers/crm-syncs/) **Public (browser, write key):** [Public Endpoints](https://docs.graph8.com/developers/public-endpoints/) **Reference:** [Pricing](https://docs.graph8.com/developers/pricing/) | [Pagination](https://docs.graph8.com/developers/pagination/) | [Errors](https://docs.graph8.com/developers/errors/) | [Rate Limits](https://docs.graph8.com/developers/rate-limits/) | [REST API FAQ](https://docs.graph8.com/developers/api-faq/) --- The graph8 Developer API lets you programmatically discover new prospects, manage your CRM data, and enrich contact information - all through a single REST API. ## Events, webhooks & real-time Subscribe to **40+ event types** and graph8 will `POST` a signed JSON payload to your endpoint as things happen — no polling required. | Category | Example events | |---|---| | Campaigns | `campaign.launched`, `campaign.completed`, `campaign.status_changed` | | Documents & intelligence | `document.generated`, `intelligence.completed`, `research.completed` | | Enrichment | `company.enriched`, `person.enriched`, `company_intelligence.completed` | | Sequences & engagement | `sequence.draft_created`, `sequence.started`, `engagement.email_replied`, … | | Meetings | `meeting.booked`, `meeting.cancelled`, `meeting.rescheduled` | - **Signed** — every delivery carries an `X-Studio-Signature` (HMAC-SHA256 over `{X-Studio-Timestamp}.{raw_body}`); verify before processing. - **Reliable** — failed deliveries retry up to 3× with backoff (10s → 60s → 300s); inspect attempts via `GET /webhooks/{id}/deliveries`. - **Delivery model** — graph8 uses **webhook push + REST poll** (no SSE/WebSocket today). Full [Webhooks reference →](https://docs.graph8.com/developers/webhooks/). ## Rate limits & errors at a glance **Rate limits** — `50 req/s` + `1,000 req/min` per org. Every response (success *and* `429`) carries your quota so you can self-pace: | Header | Meaning | |---|---| | `X-RateLimit-Limit-Second` / `-Minute` | The ceilings (`50` / `1000`) | | `X-RateLimit-Remaining` | Requests left in the current minute window | | `X-RateLimit-Reset` | Unix epoch when the window frees a slot | | `Retry-After` | Seconds to wait (sent on `429`) | Full details + backoff code: [Rate Limits →](https://docs.graph8.com/developers/rate-limits/). **Errors** — standard HTTP status codes with a JSON `{ "detail": "..." }` body (validation errors return a field-level `detail` array): | Code | Meaning | |---|---| | `400` / `422` | Bad request / validation failure | | `401` | Missing or invalid API key (JWTs are rejected) | | `404` | Resource not found | | `429` | Rate limit exceeded | | `5xx` | Server error — retry with backoff | Full catalog + handling examples: [Errors →](https://docs.graph8.com/developers/errors/). ## How Data Works in graph8 graph8 gives you access to **two distinct pools of data**. Understanding the difference is the key to using the API effectively. ### Your data (first-party) This is data **you own** — contacts and companies that live in your graph8 workspace. They got there through one of these paths: - You **saved search results** from the open data index (via [`/search/contacts/save`](https://docs.graph8.com/developers/search/#save-contacts-to-list)) - You **imported** contacts from a CSV or CRM integration - You **created** them manually via the API (via [`POST /contacts`](https://docs.graph8.com/developers/contacts/#create-contact)) - They were **auto-created** during enrichment or campaign workflows You have full read/write access to your data. Query it, update it, delete it, organize it into lists — no credit cost. **Endpoints**: [`/contacts`](https://docs.graph8.com/developers/contacts/), [`/companies`](https://docs.graph8.com/developers/companies/), [`/lists`](https://docs.graph8.com/developers/lists/) ### Open data index This is graph8's **global B2B database** — millions of contacts and companies aggregated from multiple data providers. Think of it as a phonebook for the business world: names, titles, emails, phone numbers, company details, and more. You can **search** this index with filters (job title, industry, company size, location, etc.) and **save** matching results into your workspace. Once saved, they become your data. You can also **look up** a single person or company by email, LinkedIn URL, or domain. Open data queries consume credits. **Endpoints**: [`/search/contacts`](https://docs.graph8.com/developers/search/), [`/search/companies`](https://docs.graph8.com/developers/search/#search-companies), [`/enrichment/lookup`](https://docs.graph8.com/developers/enrichment/) ### The data flow The typical workflow moves data from open → owned: ``` ┌─────────────────────────────────────────────────────────────────┐ │ │ │ 1. DISCOVER 2. SAVE 3. MANAGE │ │ │ │ /search/contacts ──► /search/.../save ──► /contacts │ │ /search/companies (creates a list) /companies │ │ /enrichment/lookup /lists │ │ │ │ Open data index Moves into your Full CRUD on │ │ (read-only, credits) workspace as a list your data (free)│ │ │ │ 4. ENRICH │ │ /enrichment/enrich │ │ Fill missing fields │ │ on your saved contacts │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` ## Choosing the Right Endpoint | I want to... | Use | PAYG | Platform | |--------------|-----|------|----------| | Find new contacts matching my ICP | [`POST /search/contacts`](https://docs.graph8.com/developers/search/) | 1 per record returned | **Free** (within 50 rps) | | Find new companies by industry, size, etc. | [`POST /search/companies`](https://docs.graph8.com/developers/search/#search-companies) | 1 per record returned | **Free** (within 50 rps) | | Save search results to my workspace | [`POST /search/contacts/save`](https://docs.graph8.com/developers/search/#save-contacts-to-list) | 1 per record saved | **Free** (within 50 rps) | | Look up one person by email or LinkedIn | [`POST /enrichment/lookup/person`](https://docs.graph8.com/developers/enrichment/) | 2 per lookup | **Free** (within 50 rps) | | Look up one company by domain | [`POST /enrichment/lookup/company`](https://docs.graph8.com/developers/enrichment/#company-lookup) | 2 per lookup | **Free** (within 50 rps) | | List contacts I've already saved | [`GET /contacts`](https://docs.graph8.com/developers/contacts/) | Free | Free | | List companies in my workspace | [`GET /companies`](https://docs.graph8.com/developers/companies/) | Free | Free | | Manage my lists | [`GET /lists`](https://docs.graph8.com/developers/lists/) | Free | Free | | Fill missing emails/phones on my contacts (waterfall) | [`POST /enrichment/enrich`](https://docs.graph8.com/developers/enrichment/#start-enrichment-job) | Variable per contact | Variable per contact | | Verify an email address (internal validator) | [`POST /enrichment/verify-email`](https://docs.graph8.com/developers/enrichment/#verify-email) | 1 per email | **Free** (within 50 rps) | See [Pricing](https://docs.graph8.com/developers/pricing/) for the full bucket map and plan comparison. ## Base URL ``` https://be.graph8.com/api/v1 ``` All endpoints are relative to this base URL. For example, to list contacts: ``` GET https://be.graph8.com/api/v1/contacts ``` ## Quick Start **cURL** ```bash export API_KEY="YOUR_API_KEY" # 1. Search the open data index for CTOs at tech companies curl -X POST "https://be.graph8.com/api/v1/search/contacts" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "filters": [ {"field": "job_title", "operator": "any_of", "value": ["CTO", "VP Engineering"]}, {"field": "company_employee_count", "operator": "between", "value": [50, 500]} ], "limit": 5 }' # 2. Save matching contacts to a list in your workspace curl -X POST "https://be.graph8.com/api/v1/search/contacts/save" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "filters": [ {"field": "job_title", "operator": "any_of", "value": ["CTO", "VP Engineering"]}, {"field": "company_employee_count", "operator": "between", "value": [50, 500]} ], "list_title": "Tech CTOs" }' # 3. Query your saved contacts (free, no credits) curl "https://be.graph8.com/api/v1/contacts?list_id=42&limit=10" \ -H "Authorization: Bearer $API_KEY" ``` **Python** ```python import requests API_KEY = "YOUR_API_KEY" BASE_URL = "https://be.graph8.com/api/v1" HEADERS = {"Authorization": f"Bearer {API_KEY}"} # 1. Search the open data index for CTOs at tech companies results = requests.post( f"{BASE_URL}/search/contacts", headers=HEADERS, json={ "filters": [ {"field": "job_title", "operator": "any_of", "value": ["CTO", "VP Engineering"]}, {"field": "company_employee_count", "operator": "between", "value": [50, 500]} ], "limit": 5 } ).json() print(f"Found {results['pagination']['total']} matches") # 2. Save matching contacts to a list in your workspace save = requests.post( f"{BASE_URL}/search/contacts/save", headers=HEADERS, json={ "filters": [ {"field": "job_title", "operator": "any_of", "value": ["CTO", "VP Engineering"]}, {"field": "company_employee_count", "operator": "between", "value": [50, 500]} ], "list_title": "Tech CTOs" } ).json() list_id = save["data"]["list_id"] # 3. Query your saved contacts (free, no credits) contacts = requests.get( f"{BASE_URL}/contacts", headers=HEADERS, params={"list_id": list_id, "limit": 10} ).json() for c in contacts["data"]: print(f"{c['first_name']} {c['last_name']} - {c['work_email']}") ``` **TypeScript** ```typescript const API_KEY = "YOUR_API_KEY"; const BASE_URL = "https://be.graph8.com/api/v1"; const headers = { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json" }; // 1. Search the open data index for CTOs at tech companies const results = await fetch(`${BASE_URL}/search/contacts`, { method: "POST", headers, body: JSON.stringify({ filters: [ { field: "job_title", operator: "any_of", value: ["CTO", "VP Engineering"] }, { field: "company_employee_count", operator: "between", value: [50, 500] } ], limit: 5 }), }).then(r => r.json()); console.log(`Found ${results.pagination.total} matches`); // 2. Save matching contacts to a list in your workspace const save = await fetch(`${BASE_URL}/search/contacts/save`, { method: "POST", headers, body: JSON.stringify({ filters: [ { field: "job_title", operator: "any_of", value: ["CTO", "VP Engineering"] }, { field: "company_employee_count", operator: "between", value: [50, 500] } ], list_title: "Tech CTOs" }), }).then(r => r.json()); // 3. Query your saved contacts (free, no credits) const contacts = await fetch( `${BASE_URL}/contacts?list_id=${save.data.list_id}&limit=10`, { headers: { Authorization: `Bearer ${API_KEY}` } } ).then(r => r.json()); contacts.data.forEach(c => console.log(`${c.first_name} ${c.last_name} - ${c.work_email}`) ); ``` ## Endpoint Reference ### Discover (open data — credits) **Search** Find contacts and companies in graph8's global B2B database using filters like job title, industry, company size, and location. [View reference](https://docs.graph8.com/developers/search/) **Enrichment** Look up a single person or company, run waterfall enrichment across providers, and verify email deliverability. [View reference](https://docs.graph8.com/developers/enrichment/) ### Manage (your data — free) **Contacts** List, create, update, and delete contacts in your workspace. [View reference](https://docs.graph8.com/developers/contacts/) **Companies** List, update, and delete companies and their associated contacts. [View reference](https://docs.graph8.com/developers/companies/) **Lists** Create lists, manage list membership, and organize contacts for campaigns. [View reference](https://docs.graph8.com/developers/lists/) ### Reference **Pricing** Plans, credit costs, the unlimited-API bundle, and auto-CRM-capture behavior. [View pricing](https://docs.graph8.com/developers/pricing/) **Authentication** Create API keys and authenticate your requests. [Get started](https://docs.graph8.com/developers/authentication/) **Pagination** Response envelope and pagination patterns. [Learn more](https://docs.graph8.com/developers/pagination/) **Errors** Error codes and handling strategies. [Learn more](https://docs.graph8.com/developers/errors/) **Rate Limits** Request limits and backoff strategies. [Learn more](https://docs.graph8.com/developers/rate-limits/) ## Interactive API Reference For a live, interactive view of all endpoints with request/response schemas, visit the OpenAPI docs: **[Open Interactive API Docs →](https://be.graph8.com/api/v1/docs)** You can test endpoints directly from the browser — authenticate with your API key and execute requests in real time. ## Response Format All responses use a standard envelope: ```json { "data": { ... }, "pagination": { "page": 1, "limit": 50, "total": 243, "has_next": true } } ``` Single-resource responses include `data` without `pagination`. See [Pagination](https://docs.graph8.com/developers/pagination/) for details. --- # Getting Started > Set up your account, invite your team, and learn the basics of graph8. Source: https://docs.graph8.com/getting-started/ New to graph8? Start here to create your account, configure your workspace, and get familiar with the platform. ## In This Section **Quick Start Guide** Get up and running with graph8 in minutes. **Account Setup** Configure your organization, invite team members, and set preferences. **Training Programs** Role-based training paths for SDRs, AEs, and managers. **Troubleshooting** Common issues and how to resolve them. --- # graph8 Help Center > Find help for every graph8 feature Source: https://docs.graph8.com/ <div id="home-page" /> **Get Started** Set up your account, invite your team, and learn the basics. [Browse articles →](https://docs.graph8.com/getting-started/) **Data** Search prospects, manage lists, and import or export contacts. [Browse articles →](https://docs.graph8.com/data/prospecting/) **Signals** Detect buying intent from website visitors, form submissions, and keyword tracking. [Browse articles →](https://docs.graph8.com/signals/overview/) **Studio** AI-powered campaign ideation with intelligence, personas, and content generation. [Browse articles →](https://docs.graph8.com/studio/global/) **Engage** Build automated outbound sequences across email, phone, and LinkedIn. [Browse articles →](https://docs.graph8.com/engage/sequencer/) **Agents** AI capabilities across enrichment, inbox, campaigns, and sales. [Browse articles →](https://docs.graph8.com/ai-features/) **Analytics** Website analytics, marketing intelligence, acquisition, behavior, and conversion tracking. [Browse articles →](https://docs.graph8.com/analytics/overview/) **Settings** Organization settings, users, billing, and API configuration. [Browse articles →](https://docs.graph8.com/settings/team/) ## Find a feature [Browse the complete feature guide](https://docs.graph8.com/getting-started/feature-guide/) for task-oriented documentation across graph8. - [Work: conversations and agent work](https://docs.graph8.com/engage/work/) - [Custom objects and attributes](https://docs.graph8.com/data/custom-objects/) - [Organization knowledge](https://docs.graph8.com/studio/knowledge/) - [All MCP tools and input schemas](https://docs.graph8.com/developers/mcp-tools/) ## Popular Articles - [What is graph8?](https://docs.graph8.com/getting-started/what-is-graph8/) - [Initial Setup](https://docs.graph8.com/getting-started/initial-setup/) - [Prospecting](https://docs.graph8.com/data/prospecting/) - [Building Your First Sequence](https://docs.graph8.com/engage/sequencer/) - [Signals Overview](https://docs.graph8.com/signals/overview/) --- # graph8 Marketplace > Hire vetted GTM talent or join as a sales professional — the graph8 Marketplace connects companies with freelance SDRs, AEs, and GTM Engineers. Source: https://docs.graph8.com/marketplace/ <div id="home-page" /> ## A two-sided talent marketplace The graph8 Marketplace is a two-sided talent marketplace. Companies hire vetted, AI-scored sales professionals — SDRs, Account Executives, and GTM Engineers — to run real outbound, while talent earn by booking meetings and closing deals. **For Clients** Browse and filter vetted talent, read match scores, and send hire offers. [Browse articles →](https://docs.graph8.com/marketplace/clients/overview/) **For Talent** Join the Marketplace, build your profile, get certified, and start earning. [Browse articles →](https://docs.graph8.com/marketplace/talent/join/) **How It Works** Vetting, certification, rates, and the commission model — explained. [Browse articles →](https://docs.graph8.com/marketplace/how-it-works/) ## Popular Articles - [How It Works](https://docs.graph8.com/marketplace/how-it-works/) - [Browsing & Filtering Talent](https://docs.graph8.com/marketplace/clients/browse/) - [Joining the Marketplace](https://docs.graph8.com/marketplace/talent/join/) - [Your Listing & Getting Scored](https://docs.graph8.com/marketplace/talent/listing/) --- # graph8 by role > Pick your role to see the exact surfaces, URLs, and screenshots you'll touch every day. 22 field guides. Source: https://docs.graph8.com/roles/ graph8 covers the full revenue motion - prospecting, sequences, deals, quotes, billing, retention, signals, attribution, and the AI agents that run on top of all of it. **Each role uses a different subset.** Pick yours below to see exactly which surfaces matter, with live URLs and real screenshots. Every guide is grounded in shipped product. No PRD-only or in-progress features. ## Sales - [Account Executive](https://docs.graph8.com/roles/ae/): Pipeline, deal intelligence, quote-to-cash, AI inbox, copilot. - [SDR](https://docs.graph8.com/roles/sdr/): Contacts, lists, sequences, dialer, intent signals, inbox sentiment. - [Sales Engineer](https://docs.graph8.com/roles/sales-engineer/): Demo prep, copilot answers, deal intelligence, ROI docs. - [Sales Manager](https://docs.graph8.com/roles/sales-manager/): Team pipeline rollups, forecast, rep performance, coaching. - [SDR Manager](https://docs.graph8.com/roles/sdr-manager/): Sequence ROI, dialer activity, rep capacity, intent backlog. - [VP Sales / Head of Sales](https://docs.graph8.com/roles/vp-sales/): Forecast, attribution, multi-pipeline rollups, board-ready views. - [CRO](https://docs.graph8.com/roles/cro/): Revenue analytics, attribution, marketing-to-pipeline funnel, MRR. - [Account Manager](https://docs.graph8.com/roles/account-manager/): Customer book, MRR, renewals, expansion signals, CSM handoff. ## Marketing - [Marketing / Demand Gen](https://docs.graph8.com/roles/marketing/): Campaigns, landing pages, ads, attribution, intent + visitor identity. - [Campaign Manager](https://docs.graph8.com/roles/campaign-manager/): Multi-channel campaigns, sequences, ads, audience sync. - [Product Marketing](https://docs.graph8.com/roles/pmm/): Brand snapshot, ICPs, messaging house, competitive teardowns. - [Content Marketer](https://docs.graph8.com/roles/content/): Content studio, landing pages, blog distribution, SEO signals. - [Marketing Operations](https://docs.graph8.com/roles/mops/): Attribution pipelines, Jitsu events, ClickHouse, integrations. ## Operations & Customer - [RevOps](https://docs.graph8.com/roles/revops/): Pipeline configuration, stage gates, multi-pipeline, reporting layer. - [CSM](https://docs.graph8.com/roles/csm/): Customer health, MRR overlay, renewal risk, churn signals. - [Onboarding / Implementation](https://docs.graph8.com/roles/onboarding/): Workspace setup, integrations, brand snapshot, first-week playbook. - [Trial Specialist](https://docs.graph8.com/roles/trial/): Trial setup, free-trial credits, activation milestones, conversion. - [Finance](https://docs.graph8.com/roles/finance/): MRR, ARR, billing, quote-to-cash, payment links, churn. ## Builders & Leadership - [GTM Engineer](https://docs.graph8.com/roles/gtm-engineer/): Workflows, MCP server, copilot tools, automation. - [AI Agent Operator](https://docs.graph8.com/roles/agent-op/): Agents, prompts, tools, skills, copilot loop tuning. - [Recruiter](https://docs.graph8.com/roles/recruiter/): Hiring signals, job-change tracking, talent pipeline, sequences. - [Founder / CEO](https://docs.graph8.com/roles/founder/): Top-of-funnel, pipeline, MRR, attribution, weekly metrics. --- *Each guide is generated from the [`graph8-{role}-start-here.pages.dev`](https://graph8-ae-start-here.pages.dev/) scratchpads via the `role-doc-from-scratchpad` skill. Screenshots are captured from a showcase tenant (public-company data only, never real client data) so these pages are safe to share externally.* --- # Role-Based Training > Interactive, mission-based training to get you productive with graph8 fast Source: https://docs.graph8.com/training/ graph8 serves different roles across your go-to-market team. Each training path has hands-on missions that guide you through the real features — so training IS the work. ## Choose Your Role **SDR** Outbound execution — sequences, dialer, enrichment, and account intelligence. [Start SDR Training](https://docs.graph8.com/training/sdr/) **Account Executive** Account research, pipeline management, and AI-powered selling. [Start AE Training](https://docs.graph8.com/training/ae/) **Growth Manager** Campaign creation, global context, sequencer, and optimization. [Start Growth Manager Training](https://docs.graph8.com/training/growth-manager/) ## How Training Works 1. **Pick your role** — each path has 4-5 missions tailored to your daily work 2. **Follow the missions** — step-by-step guides that walk you through real features 3. **graph8 detects your progress** — import contacts, create a sequence, or launch a campaign and the system automatically checks it off 4. **Managers see team progress** — track who's completed what from the Training tab ## Platform Value by Role | Role | Primary Value | Time Saved | |------|---------------|------------| | **SDR** | Prioritization and research at scale | 2+ hrs/segment | | **Account Executive** | Account prep and deal tracking | 45+ min/account | | **Growth Manager** | Campaign delivery automation | 20+ hrs/week/client | ## Reference Guides Looking for the full reference docs instead of missions? See the complete role guides: - [SDR Reference Guide](https://docs.graph8.com/training/sdrs) - [Account Executive Reference Guide](https://docs.graph8.com/training/account-executives) - [Growth Manager Reference Guide](https://docs.graph8.com/training/growth-managers) - [Sales Manager Reference Guide](https://docs.graph8.com/training/sales-managers) - [SDR Manager Reference Guide](https://docs.graph8.com/training/sdr-managers) --- # Copilot Bridge > Access graph8 Copilot from WhatsApp, Slack, Roam, and iMessage - ask questions and run actions from your messaging apps. Source: https://docs.graph8.com/ai-features/bridge/ Copilot Bridge lets you interact with graph8's AI Copilot from your everyday messaging apps — ask questions, look up contacts, run enrichment, and get insights without opening the graph8 dashboard. Bridge gives you the full Copilot tool set directly inside your existing workflow. ## Bridge vs. Workspace Integrations Don't confuse Copilot Bridge with workspace-level Slack/Roam integrations. They use the same platforms but do different jobs: | Feature | Where | What It Does | |---------|-------|-------------| | **Copilot Bridge** | Profile → Apps | Routes your DMs to Copilot for AI lookups, deals, and enrichment | | **Settings → Integrations → Slack** | Settings | Sends graph8 notifications and alerts to channels | | **Settings → Integrations → Roam** | Settings | Syncs meeting transcripts and recordings into the inbox | Both can be active at the same time. Bridge is per-user and personal; the Settings integrations are workspace-wide. ## Supported Platforms | Platform | How It Works | Identifier | |----------|-------------|-----------| | **WhatsApp** | Message your connected phone number — replies route to Copilot | Phone number (digits only) | | **Slack** | DM the graph8 bot in your workspace | Slack user ID | | **Roam** | Message in a connected group or DM | Roam user/group ID | | **iMessage** | Text your connected phone number or email | Phone number or email | ## Setting Up a Connection 1. Go to **Profile → Apps** 2. Open the **Setup** sub-tab 3. Find the platform you want to connect under **Messaging** 4. Follow the platform-specific flow below After connecting, send a test message — Copilot replies within a few seconds. ### WhatsApp 1. Click the WhatsApp card and enter your phone number (digits only, with country code) 2. Click **Connect** 3. Send a message to graph8's WhatsApp number to verify the connection WhatsApp uses graph8's self-hosted GOWA server for delivery. Your phone number is added to a routing index so messages from that number always land in your org's bridge. > **Note:** > A single phone number can only be bridged to **one** organization. If you switch orgs, disconnect from the old one before connecting to the new one. ### Slack 1. Click **Connect Slack** 2. Authorize the graph8 bot in your Slack workspace (OAuth flow) 3. After authorization, pick your Slack user from the workspace user list 4. DM the graph8 bot to start chatting The Slack OAuth flow grants the bot access only to direct-message channels — not to public channels or other DMs. The bot identifies you by Slack user ID, so swapping workspaces requires reconnecting. ### Roam 1. Click **Connect Roam** 2. Sign in to Roam and authorize graph8 3. Select either a Roam group or a personal DM as your routing target 4. Messages in the selected group/DM route to Copilot If you connected Roam before March 2026, disconnect and reconnect to pick up the latest scopes. ### iMessage 1. Click **Connect iMessage** 2. Enter your phone number or email address 3. Send a test iMessage to graph8's iMessage contact iMessage delivery uses a self-hosted BlueBubbles server. The connection is identified by the phone number or email you registered. ## What You Can Do Bridge gives you the same capabilities as the in-app Copilot — same tools, same access, same context. ### Lookups & Research - **"What do we know about Acme Corp?"** — pulls company enrichment, recent signals, deal status - **"Show me my top 5 leads from this week"** — filters your CDP by recency + score - **"Has anyone at Stripe replied to my outreach?"** — searches inbox by company ### Actions - **"Enrich this email: jane@example.com"** — runs waterfall enrichment - **"Add Jane Smith to the Q2 Outbound list"** — modifies a graph8 list - **"Summarize the Acme deal"** — pulls deal context, notes, recent meetings - **"Draft a follow-up to my last email with Acme"** — uses inbox history + context ### Status Updates - **"How's the Q2 campaign performing?"** — pulls campaign analytics - **"What's my pipeline this quarter?"** — deal forecast summary - **"Show me unread inbox threads"** — surfaces inbox items needing attention > **Note:** > Bridge actions consume credits the same way as in-app Copilot. Each AI exchange typically costs 1–10 credits depending on tools invoked. ## Managing Connections View all active connections in **Profile → Apps → Connections**. ### Connections Tab The Connections sub-tab lists every active connection across all surfaces: | Column | Description | |--------|-------------| | **Platform** | WhatsApp, Slack, Roam, iMessage, MCP, or API key | | **Identifier** | Phone number, user ID, group, or email | | **Connected** | Date connected | | **Last Active** | Last time a message routed through this connection | | **Status** | Active or expired | ### Disconnect Click **Disconnect** next to any connection to remove it. Disconnect is immediate: - Your messages stop routing to Copilot - Chat history is preserved - The connection record is deleted ### Reconnect If a platform's OAuth token expires, the connection shows as **Expired**. Click **Reconnect** to refresh credentials without losing chat history. ## Chat History Every bridge conversation is saved as a persistent chat session in graph8. ### Where to Find It 1. Open the **Copilot** sidebar (any page in graph8) 2. Switch to the **History** tab 3. Filter by **Bridge mode** to see only bridge sessions 4. Click any session to see the full message history Sessions are organized per-platform — your WhatsApp history is separate from your Slack history. ### Privacy Bridge sessions are scoped to your user — only you can see your bridge chat history. Admins cannot access individual users' bridge conversations without explicit elevation. ## Webhook Security Each platform uses its own signature verification. graph8 rejects webhook payloads that fail verification. | Platform | Verification | |----------|-------------| | **WhatsApp** | HMAC-SHA256 signature on `X-Hub-Signature-256` header (GOWA secret) | | **Slack** | HMAC-SHA256 signature with timestamp (Slack signing secret) | | **Roam** | Svix HMAC (`x-svix-signature`, `x-svix-timestamp`, `x-svix-id`) | | **iMessage** | Internal password validation (BlueBubbles server) | Webhook secrets are configured at the org level. If you self-host any of these (GOWA for WhatsApp, BlueBubbles for iMessage), keep secrets rotated and stored in a secret manager. ## Tool Availability Bridge exposes the full Copilot tool set: | Tool Category | Examples | |---------------|----------| | **Search** | Contact search, company search, deal search, inbox search | | **CRUD** | Create contact, update deal stage, add to list, add to sequence | | **Enrichment** | Single-contact enrichment, find people | | **Analytics** | Pipeline forecast, campaign performance, sequence stats | | **Web research** | Company web search, competitive lookups (Deep model tier only) | | **Actions** | Run a Skill, send an email, draft a message | Tools that require explicit confirmation (like sending an email or modifying a deal) prompt you to approve in the chat thread before running. ## Privacy and Audit | Aspect | Behavior | |--------|----------| | **Per-user scoping** | Each bridge connection is tied to one user — your queries see only data you have permission to access | | **Org isolation** | Phone numbers map to exactly one org; cross-tenant message routing is impossible | | **Audit log** | Every bridge action is logged at **Settings → Activity** with platform, message, tool calls, and credit cost | | **Message retention** | Chat sessions retained 90 days by default; configurable in **Settings → Compliance** | | **Data deletion** | Disconnecting a platform removes the connection record but preserves chat history (deletable separately from Copilot history) | ## Auto-Match (Coming Soon) A future enhancement will auto-match users to bridge connections by comparing their platform email (e.g. Slack workspace email) with their graph8 PropelAuth email. This removes the manual user-picker step. Until it ships, you'll continue to select your Slack user / Roam group manually during setup. ## Best Practices - **One platform per use case** — don't bridge all four if you only message in one. Each connection costs credits when it routes a message - **Test after setup** — send a "hello" message to confirm routing works before relying on it - **Reconnect quarterly** — Slack and Roam OAuth tokens can expire; refresh proactively to avoid mid-conversation failures - **Use Slack for team contexts** — Slack DMs preserve conversational threading better than WhatsApp/SMS - **Use WhatsApp/iMessage for mobile-first workflows** — when you're away from a desktop, these are faster than opening the dashboard ## Troubleshooting | Issue | Likely Cause | Fix | |-------|-------------|-----| | Messages don't route to Copilot | Connection expired or webhook secret wrong | Check **Profile → Apps → Connections** for **Expired** status; reconnect | | Got connected to the wrong org | Phone number routing maps to one org only | Disconnect from the wrong org first, then reconnect from the right org | | Slack picker doesn't show my user | Slack workspace email differs from graph8 email | Reconnect Slack and use the manual picker | | WhatsApp messages echo back | GOWA webhook loop misconfigured | Contact admin — server-side webhook URL needs adjustment | | Roam connection failed | Connected before March 2026 (old scopes) | Disconnect and reconnect to pick up new scopes | | iMessage delivery delayed | BlueBubbles server lag | Self-hosted iMessage relies on Apple's APNs; delivery can be slow during APNs outages | | Slack OAuth fails | Bot was uninstalled from workspace | Have a workspace admin reauthorize the graph8 Slack app | ## Related - [Copilot →](https://docs.graph8.com/studio/copilot/) — In-app AI assistant (same tools, in the dashboard) - [Voice AI Agents →](https://docs.graph8.com/ai-features/voice-agents/) — AI-powered phone agents - [Skills →](https://docs.graph8.com/ai-features/skills/) — Reusable AI capabilities Copilot uses - [Integrations →](https://docs.graph8.com/settings/integrations/) — Workspace-level Slack notifications and Roam meeting sync --- # AI Content Generation > Create personalized content with AI assistance Source: https://docs.graph8.com/ai-features/content-generation/ Let AI help you write compelling, personalized outreach. ## Email Generation ### Quick Compose 1. Select a prospect 2. Click **AI Compose** 3. Choose email type: - Cold outreach - Follow-up - Meeting request - Re-engagement 4. Review and edit 5. Send ### AI Inputs The AI uses: - Prospect's name and title - Company information - Industry context - Previous interactions - Your product/service ### Example Output ```text Subject: Quick question about {company}'s growth plans Hi {first_name}, I noticed {company} recently expanded into the European market – congrats on the growth! Many companies at your stage struggle with [relevant challenge]. We've helped similar companies like [reference] achieve [result]. Would you be open to a 15-minute call to explore if we might help? Best, {your_name} ``` ## Subject Line Generator ### Generate Options AI creates multiple variations: 1. "Quick question about \{company\}" 2. "\{first_name\}, saw your recent announcement" 3. "Idea for \{company\}'s Q2 goals" 4. "[Mutual connection] suggested I reach out" ### A/B Test - Select top 2-3 options - AI tracks performance - Learn what works ## Personalization Suggestions ### AI Observations AI finds personalization hooks: - Recent company news - Job changes - Funding announcements - Product launches - Social media activity ### Using Suggestions ```text Personalization found: • {company} raised Series B last month • {first_name} was promoted to VP in January • Company blog mentions scaling challenges Suggested opening: "Congrats on the Series B! Scaling quickly brings exciting challenges..." ``` ## Call Scripts ### Generate Scripts AI creates talk tracks for: - Discovery calls - Demo calls - Follow-up calls - Objection handling ### Script Components - Opening hook - Qualification questions - Value proposition - Objection responses - Call-to-action ## Content Settings ### Tone - Professional - Casual - Formal - Friendly ### Length - Short (2-3 sentences) - Medium (1 paragraph) - Long (multiple paragraphs) ### Focus - Problem-focused - Solution-focused - Benefit-focused - Question-focused ## Best Practices ### Always Review - AI drafts, you edit - Add personal touches - Check for accuracy - Verify personalization ### Train the AI - Rate generated content - Provide feedback - Save best examples - AI learns your style ## Frequently Asked Questions ### Can I regenerate content if I don't like the first draft? Yes, click "Regenerate" to get a new version. You can also adjust settings (tone, length, focus) before regenerating to guide the AI in a different direction. ### How do I maintain my personal voice in AI-generated content? Edit AI drafts to add your personal touches. Save good examples as templates. Over time, rate outputs to help AI learn your style. Always review before sending. ### What information does AI use to personalize content? AI uses prospect name, title, company, industry, recent interactions, company news, and any custom fields. More data = better personalization. ### Can I generate content in languages other than English? Currently, AI content generation is optimized for English. Other languages may work but with varying quality. Multi-language support is on the roadmap. ### How long does content generation take? Most content generates in 2-5 seconds. Complex requests or high-volume batch generation may take longer. Generation runs in the background if you navigate away. --- *Think of AI as your first draft writer. The best emails combine AI efficiency with human authenticity.* --- # Copilot > AI assistant built into graph8 - ask questions, look up contacts, manage deals, run web searches, and take actions across your CRM using natural language. Source: https://docs.graph8.com/ai-features/copilot/ Copilot is graph8's built-in AI assistant. Ask questions in natural language and Copilot searches your CRM, looks up contacts, checks deal status, manages sequences, runs web searches, and navigates the app - all from a chat sidebar. ## Opening Copilot Click the **Copilot** icon in the top navigation bar to open the chat sidebar. Copilot is available from any page in graph8. ## Chat Modes Copilot operates in five modes, each optimized for different tasks: | Mode | What It Does | When to Use | |------|-------------|-------------| | **Global** | General questions, strategy guidance, CRM lookups | Day-to-day questions about your pipeline and data | | **Document** | Specialized editing with skill-matched AI | When working on campaign documents in Studio | | **Web Search** | Pull live data from the internet | When you need current market data or company news | | **Thinking** | Extended reasoning with step-by-step logic | Complex analysis or multi-factor decisions | | **Research** | Dual-provider search (company + general) | Deep research on prospects, competitors, or markets | Toggle **Web Search** on to include live internet results in Copilot responses. Enable **Thinking Mode** for complex, multi-step reasoning. ## What You Can Do ### Look Up Contacts and Companies - "What do we know about Acme Corp?" - "Find the VP of Sales at Datadog" - "Show me contacts at companies in the fintech space" - "What signals do we have on this account?" ### Manage Deals - "What deals are at risk?" - "Show me all deals in the negotiation stage" - "What's the status of the Acme deal?" - "Create a deal for this company" - "Move the Acme deal to proposal stage" ### Work with Sequences - "List my active sequences" - "How is the Q2 outbound sequence performing?" - "Pause the cold outreach sequence" - "Show me contacts in the enterprise sequence" ### Check Your Inbox - "Any pending emails I need to respond to?" - "Show me unread emails" - "List my recent chat conversations" ### Navigate the App - "Take me to the dialer" - "Open the analytics dashboard" - "Go to settings" ### Get Insights - "What meetings do I have today?" - "Give me an activity summary" - "What campaigns are running?" - "How is the ABM campaign performing?" ### Search the Web - "What's the latest news about Acme Corp?" - "Research trends in the cybersecurity market" - "Find recent funding announcements for companies in our pipeline" ### Enrich Contacts - "Look up this person's email and phone" - "Enrich Acme Corp's company data" ## Context Modes Copilot runs in one of five context modes. It switches automatically based on the page you're on, or you can toggle manually: | Mode | Optimized For | Example | |------|-------------|---------| | **General** | Cross-functional questions, campaigns, analytics | "How are our campaigns performing?" | | **Contacts** | Contact lookup, company intelligence, ICP fit | "Find decision makers at Acme" | | **Deals** | Pipeline management, at-risk deals, stage updates | "What deals are stalling?" | | **Inbox** | Email management, mailbox health, chat history | "Show me unread emails" | | **Sequencer** | Sequence stats, pause/resume, step editing | "How is the Q2 sequence doing?" | Each mode unlocks different tools. For example, sequence step editing is only available in Sequencer mode, while deal creation is available in Deals and General modes. When you're viewing a campaign document in Studio, Copilot switches to a specialized skill automatically. See [Studio Copilot →](https://docs.graph8.com/studio/copilot/) for details. ## Available Tools Copilot uses built-in tools to take actions on your behalf: | Category | What Copilot Can Do | |----------|-------------------| | **Contacts** | Search, view details, find contact's deals and company | | **Companies** | Look up signals, engagement, ICP fit scores | | **Deals** | List deals, check risk, create deals, update stages | | **Lists** | Browse and create contact lists | | **Campaigns** | List campaigns, view metrics, create and publish campaigns | | **Sequences** | List, pause, resume, duplicate, view reports | | **Inbox** | List emails, chats, mailboxes, manage tags | | **Calendar** | Check today's meetings and schedule | | **Phone** | Look up phone numbers and assignments | | **Enrichment** | Look up person or company data on demand | | **Navigation** | Navigate to any page in graph8 | | **Knowledge Base** | Search your org's knowledge base documents | | **Documents** | Create, update, and manage campaign documents | | **Landing Pages** | Edit landing page content via chat | | **Analytics** | Call analytics, visitor activity, dialer sessions | | **Voice Agents** | List agents and view capabilities | ### Approval Gates Some actions require your confirmation before Copilot executes them: | Action | Why Approval Is Needed | |--------|----------------------| | **Create a contact list** | Organizational change | | **Publish a campaign** | Sends real outreach | | **Delete a document** | Destructive action | When approval is needed, Copilot shows a confirmation dialog. Click **Approve** to proceed or **Cancel** to stop. ## Copilot Memory Copilot remembers context across your conversations. As you interact, it builds memory at four levels: | Scope | What It Stores | Example | |-------|---------------|---------| | **Org** | Shared knowledge across your team | Company positioning, brand voice preferences | | **User** | Your personal preferences and patterns | Preferred tone, common requests | | **Campaign** | Campaign-specific context | Target audience notes, messaging decisions | | **Company** | Company-specific intelligence | Key stakeholders, deal history | You don't need to repeat instructions - Copilot uses past context to provide more relevant responses over time. ## @Mentions and Collaboration Type `@` followed by a team member's name to mention them in a Copilot conversation. Mentioned users receive a notification and can join the session. Share chat sessions with your team for collaborative planning and strategy discussions. ## Credits Each Copilot interaction consumes credits. Credit cost depends on the complexity of the action: - **Simple lookup** - 1 credit - **Web search** - additional credits per search query - **Multi-step actions** - credits scale with the number of tools used Check your credit usage in **Settings → Billing → Usage**. ## Chat History and Sessions All conversations are saved automatically. Manage your sessions: - **Pin** important conversations so they stay at the top of your history - **Rename** sessions for easy identification later - **Delete** sessions you no longer need - **Restore** previous sessions to continue where you left off ### Response Controls For each Copilot response, you can: - **Thumbs up / down** - rate the quality (helps improve future responses) - **Regenerate** - retry the last response for a different angle - **Copy** - copy the response text to your clipboard ## Copilot Bridge Access Copilot from your everyday messaging apps - WhatsApp, Slack, Roam, and iMessage. See [Copilot Bridge →](https://docs.graph8.com/ai-features/bridge/) for setup instructions. > **Tip:** > Be specific in your questions. "Show me at-risk deals over $50K" gives better results than "How are my deals?" The more context you provide, the more targeted Copilot's response. Use web search mode when you need live data that isn't in your CRM. ## Related - [Studio Copilot →](https://docs.graph8.com/studio/copilot/) - Document editing, specialist skills, and slash commands - [Copilot Bridge →](https://docs.graph8.com/ai-features/bridge/) - Use Copilot from WhatsApp, Slack, Roam, and iMessage - [AI Overview →](https://docs.graph8.com/ai-features/overview/) - All AI features in graph8 - [Deals →](https://docs.graph8.com/deals/overview/) - Pipeline management - [Sequencer →](https://docs.graph8.com/engage/sequencer/) - Automated outreach sequences --- # Enrichment > Fill missing contact data with waterfall enrichment - emails, phones, job titles, and company info from multiple data providers. Source: https://docs.graph8.com/ai-features/enrichment/ Enrichment fills in missing contact data - emails, phone numbers, job titles, company info - by querying multiple data providers in sequence until the data is found. This waterfall approach maximizes match rates while minimizing credit spend. ## How Enrichment Works graph8 uses a **waterfall** strategy: when you enrich a contact, the system queries Provider A first. If it doesn't find the data, it tries Provider B, then Provider C, and so on. You only pay for the provider that returns a match. ## Enriching Contacts ### Single Contact 1. Open any contact record 2. Click **Enrich** 3. graph8 runs the waterfall and fills in missing fields 4. Review the enriched data ### Bulk Enrichment 1. Go to **Data → Contacts** or any contact list 2. Select contacts (or select all) 3. Click **Enrich** from the bulk actions menu 4. Confirm the credit cost estimate 5. Enrichment runs in the background - results appear as they complete ### From the Staging Table 1. Import contacts into a **Staging Table** (from search, file upload, or CRM sync) 2. Click **Enrich** on the staging table toolbar 3. Select which fields to enrich (email, phone, company, etc.) 4. Review enriched data before committing to your CRM ## Staging Tables Staging tables are the enrichment workbench - a spreadsheet-like workspace where you import, enrich, transform, and review data before saving it to your CRM. ### Creating a Staging Table 1. Go to **Data → Workbench** 2. Click **Create Table** 3. Name your table and choose a source: - **Search results** - import from a people or company search - **File upload** - CSV, Excel, or paste from clipboard - **CRM sync** - pull existing contacts for re-enrichment - **Find People** - discover contacts at target companies ### Working with Staging Data | Action | What It Does | |--------|-------------| | **Add columns** | Add custom columns for any data point | | **Enrich** | Run waterfall enrichment on selected rows | | **AI columns** | Use AI formulas to generate computed columns (see below) | | **Export to list** | Save enriched contacts to a graph8 list | | **Send to sequence** | Push contacts directly into an outreach sequence | ### Auto-Enrich Toggle auto-enrichment on a staging table so new rows are enriched automatically as they arrive. Configure which fields to enrich and set daily credit limits. ## AI Formulas AI formulas let you create computed columns powered by AI. Instead of static formulas, you describe what you want in plain language. ### Creating an AI Formula 1. Open a staging table 2. Click **Add Column → AI Formula** 3. Describe what you want: "Summarize this person's role in one sentence" or "Score this lead 1-10 based on ICP fit" 4. Select which input columns the AI should use 5. Click **Generate** - the formula runs across all rows ### Use Cases - **ICP scoring** - score contacts against your ideal customer profile - **Persona classification** - categorize contacts by buyer persona - **Summary generation** - create one-line summaries of each contact - **Data cleanup** - standardize job titles, fix formatting, deduplicate ### Confidence Scoring AI formula outputs include a confidence score (0-100). Use this to filter low-confidence results before acting on the data - for example, only export rows with confidence above 70. ### AI Presets graph8 includes built-in AI presets for common enrichment tasks like intent classification, lead scoring, and company analysis. Browse presets in the AI column configuration panel, or save your own formulas as templates for reuse. ## Waterfall Provider Configuration Configure which data providers the waterfall uses and in what order: 1. Go to **Settings → Enrichment** 2. View available providers (Apollo, Hunter, Clearbit, Prospeo, Dropcontact, and more) 3. Set the provider order - graph8 tries providers top-to-bottom 4. Configure authentication credentials per provider 5. Set rate limits and daily usage caps The waterfall stops as soon as a provider returns a match, so order matters - put your highest-quality providers first. ## Find People Find People discovers contacts at specific companies based on your criteria. 1. Open a staging table 2. Click **Find People** 3. Configure filters: - **Job titles** - target specific roles - **Seniority levels** - VP, Director, Manager, etc. - **Departments** - Sales, Marketing, Engineering, etc. - **Company** - which companies to search 4. Click **Search** - results populate the staging table 5. Enrich the results to fill in emails and phone numbers ## Routing Rules Routing rules automate what happens after enrichment completes. Define conditions and actions to route enriched contacts automatically. | Action | Example | |--------|---------| | **Add to list** | Enriched contacts with valid email go to "Outbound Ready" list | | **Add to sequence** | High-ICP contacts auto-enroll in a sales sequence | | **Tag** | Contacts from target industries get tagged automatically | ## Credit Costs Enrichment consumes credits. Different providers and data types have different costs: - **Email lookup** - 1 credit per match - **Phone lookup** - 1 credit per match - **Company enrichment** - 1 credit per match - **Find People** - 1 credit per contact discovered Before bulk enrichment runs, graph8 shows a credit cost estimate. You can validate available credits before committing. > **Tip:** > Start with email enrichment on your highest-priority lists. Enrich phone numbers only for contacts you plan to call - this saves credits. Use AI formulas to score contacts before enriching, so you spend credits on the best prospects first. ## Dynamic Columns Create computed columns that derive values from existing fields without AI: - **Concatenation** - combine first name + last name into a full name column - **Extraction** - pull domain from email address - **Formatting** - standardize phone numbers, capitalize names Dynamic columns update automatically when source data changes. ## Job Management Track enrichment progress in real time: - **Progress bar** - see how many contacts have been processed - **Per-record status** - which contacts succeeded, failed, or are pending - **Pause / Resume / Cancel** - control running enrichment jobs - **Retry failed** - re-run enrichment on contacts that failed Large batches process in chunks. You can navigate away while enrichment runs in the background. ## Connections Connect external data sources to your staging tables: - **CRM connections** - sync contacts from Salesforce, HubSpot, etc. - **Scheduled syncs** - set up recurring imports on a schedule - **Two-way sync** - push enriched data back to your CRM ## Troubleshooting | Issue | Fix | |-------|-----| | Low match rates | Try enriching with multiple field types (name + company works better than email alone) | | Credits not deducting | Check your credit balance in **Settings → Billing** | | Enrichment stuck | Check the job status in the staging table toolbar - large batches process in chunks | | AI formula errors | Ensure input columns have data - AI formulas skip empty rows | ## Related - [Customer Data Platform →](https://docs.graph8.com/data/customer-data-platform/) - Managing contacts and companies - [Managing Lists →](https://docs.graph8.com/data/managing-lists/) - Organize enriched contacts into lists - [Sequencer →](https://docs.graph8.com/engage/sequencer/) - Automate outreach to enriched contacts - [Lead Scoring →](https://docs.graph8.com/ai-features/lead-scoring/) - Score contacts using enriched data --- # AI Lead Scoring > Prioritize leads with AI-powered scoring Source: https://docs.graph8.com/ai-features/lead-scoring/ Focus on the leads most likely to convert with AI-powered scoring. ## How Lead Scoring Works ### Data Inputs AI analyzes: - **Firmographics** - Company size, industry, location - **Demographics** - Title, seniority, department - **Behavior** - Email opens, clicks, website visits - **Intent** - Research activity, competitor visits - **Fit** - Match to ideal customer profile ### Score Calculation Each lead receives a score from 0-100: - **80-100** - Hot lead, prioritize immediately - **60-79** - Warm lead, good potential - **40-59** - Neutral, needs nurturing - **20-39** - Cool lead, low priority - **0-19** - Poor fit, likely not a prospect ## Score Components ### Fit Score (0-50 points) How well they match your ICP: - Company size match: +10 - Industry match: +10 - Title/seniority match: +15 - Location match: +5 - Technology match: +10 ### Engagement Score (0-50 points) How engaged they are: - Email opens: +5 each - Link clicks: +10 each - Website visits: +5 each - Content downloads: +15 each - Meeting booked: +20 ## Using Lead Scores ### Prioritize Outreach Sort prospects by score: 1. Call hot leads (80+) immediately 2. Email warm leads (60-79) today 3. Nurture neutral leads (40-59) 4. Deprioritize low scores ### Route to Reps Auto-assign based on score: - Hot leads → Senior reps - Warm leads → All reps - Cool leads → SDR team ### Trigger Automation Based on score changes: - Score reaches 80 → Alert sales - Score drops below 40 → Add to nurture - Score increases 20+ → Create task ## Customizing Scoring ### Adjust Weights Change importance of factors: ``` Company Size: 2x weight (very important) Industry: 1.5x weight (important) Location: 0.5x weight (less important) ``` ### Add Custom Signals Include your own criteria: - Specific technologies used - Certain job titles - Custom field values - List membership ### Define ICP Tell AI your ideal customer: - Target company size - Target industries - Target titles - Target locations ## Score Analytics ### Score Distribution See how leads are distributed: - Hot (80+): 5% - Warm (60-79): 15% - Neutral (40-59): 30% - Cool (20-39): 35% - Poor (0-19): 15% ### Score Accuracy Track prediction quality: - Do high-score leads convert more? - Conversion rate by score band - Adjust model based on results ### Score Trends Monitor changes over time: - Rising scores (increasing engagement) - Falling scores (cooling off) - Sudden changes (new signals) ## Frequently Asked Questions ### How often are lead scores updated? Scores update in real-time when new engagement data arrives (email opens, clicks). Fit scores recalculate daily based on profile data changes. ### Can I override AI lead scores manually? You can't directly edit scores, but you can prioritize or deprioritize leads regardless of score. Use tags or custom fields to mark leads for special handling. ### Why did a lead's score drop suddenly? Score drops typically indicate negative signals: email bounced, unsubscribed, marked as spam, or became a poor ICP fit. Check the prospect timeline for recent events. ### How do I know if my scoring model is accurate? Review conversion rates by score band. If high-scoring leads don't convert better than low-scoring ones, adjust your scoring weights or ICP definition. ### Can I create multiple scoring models for different segments? Currently, one scoring model applies globally. Use custom fields and filters to apply different prioritization logic to different segments manually. --- *Lead scoring is a guide, not gospel. Use it to prioritize, but trust your instincts too.* --- # AI Features Overview > AI capabilities built into every part of graph8 - enrichment, content generation, copilot, voice agents, lead scoring, and sales coaching. Source: https://docs.graph8.com/ai-features/overview/ AI is built into every layer of graph8 - from enriching contact data to generating outreach content to coaching your sales team on live deals. Every AI feature uses your org's global context (brand brief, value props, personas, competitive intel) to stay on-brand and on-message. ## AI Capabilities | Feature | What It Does | Where to Find It | |---------|-------------|-----------------| | [**Copilot**](https://docs.graph8.com/ai-features/copilot/) | Chat assistant for lookups, deal management, and navigation | Top nav sidebar | | [**Enrichment**](https://docs.graph8.com/ai-features/enrichment/) | Waterfall data enrichment with AI formulas and staging tables | Data → Workbench | | [**Content Generation**](https://docs.graph8.com/ai-features/content-generation/) | AI-written emails, subject lines, and call scripts | Sequencer, Inbox, Studio | | [**Voice Agents**](https://docs.graph8.com/ai-features/voice-agents/) | AI phone agents for outbound and inbound calls | Engage → Voice Agents | | [**Lead Scoring**](https://docs.graph8.com/ai-features/lead-scoring/) | AI-powered scoring based on signals, fit, and engagement | Data → Contacts | | [**Sales Coach**](https://docs.graph8.com/ai-features/sales-coach/) | Deal coaching, objection handling, and next-step recommendations | Agents → Sales Coach | | [**Skills**](https://docs.graph8.com/ai-features/skills/) | Reusable AI capabilities that agents use to complete tasks | Agents → Skills | | [**Workflows**](https://docs.graph8.com/ai-features/workflows/) | Chain skills together in automated AI workflows | Agents → Workflows | | [**Research**](https://docs.graph8.com/ai-features/research/) | AI company research and competitive intelligence | Studio → Companies | | [**Copilot Bridge**](https://docs.graph8.com/ai-features/bridge/) | Use Copilot from WhatsApp, Slack, Roam, and iMessage | Profile → Apps | ## How AI Uses Your Data AI features pull from your org's **global context** - 44 documents that graph8 generates about your business: - **Brand brief** - your positioning, voice, and messaging - **Value propositions** - key benefits mapped to personas - **ICPs and personas** - ideal customer profiles and buyer personas - **Competitive intel** - competitor analysis and differentiation - **Campaign Intelligence Brief** - a pre-extracted summary of all context This means AI-generated content sounds like your brand, not generic boilerplate. Set up your global context in **Studio → Global** before using AI features. ## Credits AI features consume credits. Different actions have different costs: | Action | Credit Cost | |--------|-----------| | Contact enrichment | 1 credit per match | | AI email generation | 1 credit per generation | | Copilot interaction | 1 credit per exchange | | Voice agent call | Credits based on call duration | | AI formula (staging) | 1 credit per row processed | Monitor usage in **Settings → Billing → Usage**. Credits refresh monthly based on your plan. ## Getting Started 1. **Set up global context** - go to **Studio → Global** and generate your brand documents 2. **Enrich your data** - use [Enrichment](https://docs.graph8.com/ai-features/enrichment/) to fill in missing contact info 3. **Try Copilot** - open the Copilot sidebar and ask a question about your pipeline 4. **Generate content** - create an AI-written email in the [Sequencer](https://docs.graph8.com/engage/sequencer/) 5. **Score leads** - enable [Lead Scoring](https://docs.graph8.com/ai-features/lead-scoring/) to prioritize your outreach > **Tip:** > The quality of AI output depends on the quality of your global context. Spend time reviewing and refining your brand brief, personas, and value propositions - every AI feature uses these documents. --- # AI Research Assistant > Research prospects and companies with AI Source: https://docs.graph8.com/ai-features/research/ Get instant insights on any prospect or company. ## Company Research ### Instant Company Profile AI compiles: - Company overview - Products/services - Recent news - Key executives - Technology stack - Funding history - Competitors ### How to Use 1. Open a company profile 2. Click **AI Research** 3. View compiled insights 4. Save relevant findings ### Example Output ``` ## Acme Corp **Overview:** B2B SaaS company providing project management solutions for enterprise teams. Founded 2018, HQ in San Francisco. **Recent News:** - Jan 2026: Raised $50M Series C - Dec 2025: Launched enterprise tier - Nov 2025: Expanded to EMEA **Key Executives:** - CEO: Jane Smith (ex-Salesforce) - CTO: John Doe (ex-Google) - VP Sales: Sarah Johnson **Technology Stack:** AWS, React, PostgreSQL, Stripe, Intercom **Competitors:** Monday.com, Asana, Notion ``` ## Contact Research ### Individual Insights AI finds: - Career history - Recent activity - Mutual connections - Interests/topics - Communication style ### Personalization Hooks AI suggests conversation starters: - Recent job change - Shared connections - Common interests - Relevant content they've shared ## News Monitoring ### Company Alerts Get notified about: - Funding announcements - Leadership changes - Product launches - Partnerships - Acquisitions ### Industry News Stay informed on: - Market trends - Competitor moves - Regulatory changes - Technology shifts ## Competitive Intelligence ### Competitor Tracking Monitor competitors: - Product updates - Pricing changes - Customer wins/losses - Marketing campaigns ### Battle Cards AI-generated competitive positioning: - Strengths vs competitor - Common objections - Win themes - Proof points ## Research in Workflow ### Before Outreach Research before you reach out: 1. Open prospect 2. Run AI research 3. Find personalization hooks 4. Craft personalized message ### Before Meetings Prepare with research: 1. Review company profile 2. Check recent news 3. Understand their challenges 4. Prepare relevant questions ### During Deals Stay informed throughout: - Monitor news for triggers - Track stakeholder changes - Watch for buying signals ## Research Settings ### Sources Configure where AI looks: - ☑️ Company website - ☑️ LinkedIn - ☑️ News articles - ☑️ Press releases - ☑️ Social media - ☐ Paid databases ### Refresh Frequency How often to update: - Real-time (hot leads) - Daily (active deals) - Weekly (general database) ## Frequently Asked Questions ### How current is the AI research data? Research pulls from recent web data, news articles, and public sources. Most data is within 30 days, though some company information may be older. Check dates on news items. ### Can I save research findings to prospect records? Yes, save relevant insights directly to prospect or company profiles. Saved research becomes part of the record and persists across team members. ### What sources does AI research use? AI researches company websites, LinkedIn, news articles, press releases, and social media. It doesn't access paid databases unless you've connected them in settings. ### How do I research a company not in my database? Enter any company domain in the research tool to run a lookup. You can then save the company as a new account if relevant. ### Is there a limit on how many research queries I can run? Research queries count toward your AI usage limits. Check Settings → Usage for your current consumption. Enterprise plans have higher or unlimited limits. --- *Great salespeople are great researchers. Let AI do the heavy lifting.* --- # Sales Coach > AI-powered sales guidance - get real-time coaching on deals, objections, and next steps based on your pipeline data and competitive intel. Source: https://docs.graph8.com/ai-features/sales-coach/ Sales Coach provides AI-powered guidance for your sales team. Get coaching on active deals, objection handling, meeting preparation, and recommended next steps — all grounded in your org's competitive intelligence and buyer psychology, not generic sales tips. ## How It Works 1. Go to **Agents → Sales Coach** 2. Select a deal or account for coaching 3. Review AI-generated recommendations 4. Apply suggestions to your outreach and deal strategy Sales Coach pulls from your global context (competitor analysis, buyer personas, value propositions, battle cards) to give advice specific to your business. ## Coaching Areas | Area | What It Provides | |------|------------------| | **Deal strategy** | Recommended next steps based on deal stage, signals, and buying committee | | **Objection handling** | Responses to common objections using your competitive positioning | | **Meeting prep** | Talking points, agenda suggestions, and research briefs before calls | | **Competitive positioning** | How to differentiate against specific competitors in the deal | | **Risk assessment** | Early warning signals that a deal may be stalling or at risk | | **Email coaching** | Suggestions to improve outreach messaging and follow-up timing | | **Discovery framework** | BANT, MEDDIC, SPICED — adapted to your deal context | ## Where Sales Coach Surfaces Sales Coach insights appear in multiple places — not just the dedicated tab. ### In the AE Cockpit When working your decision queue, Sales Coach annotates relevant decision cards: - **Meeting Prep cards** include coaching notes with talking points and agenda - **At-risk deal alerts** include recommended recovery actions - **Post-meeting follow-up cards** include suggested next steps based on meeting context - **Coaching Insight cards** are first-class decisions: AI-generated coaching tips based on your patterns and performance ### On Deal Detail Pages Open any deal in **Revenue → Prospects** and the **Intelligence** tab shows Sales Coach analysis specific to that deal: - Competitive positioning vs. competitors detected in the deal - ROI analysis tailored to the prospect's company size and industry - Recommended next steps based on the current pipeline stage - Risk indicators (engagement drops, stalled progression, missing buying roles) ### Pre-Meeting Briefings Before any calendar meeting, Sales Coach generates a briefing. This briefing is: - Pushed into the AE Cockpit as a Meeting Prep decision - Available in the meeting detail page in the inbox - Optionally emailed to you 30 minutes before the meeting ## What Goes Into Coaching Sales Coach is only as good as the context it has. It pulls from: ### Global Context Documents | Document | What It Contributes | |----------|---------------------| | **Brand brief** | Your positioning, voice, messaging | | **Value propositions** | Top differentiators mapped to personas | | **ICPs and personas** | Who you sell to and how each persona buys | | **Competitor teardowns** | Per-competitor positioning, weaknesses, win themes | | **Battle cards** | Quick-reference cards for handling competitive objections | | **Proof catalog** | Case studies, customer logos, social proof | | **Buyer psychology** | Decision frameworks, objection patterns, triggers | | **Industry analyst reports** | Market context for the deal's vertical | ### Per-Deal Data | Data | What It Contributes | |------|---------------------| | **Deal stage** | What stage-appropriate guidance to give | | **Deal value** | Whether this is a strategic deal needing exec attention | | **Buying committee** | Who's been identified, who's missing, role coverage | | **Activity timeline** | What's happened, what's been promised | | **Engagement signals** | Whether deal momentum is up or down | | **Competitor mentions** | Which competitors have come up in the deal | The richer your global context and the more complete your deal records, the better Sales Coach performs. ## Objection Handling When a prospect raises an objection, paste it into Sales Coach (or it auto-detects from inbox/call transcripts). Sales Coach returns: - **Why this objection comes up** — the underlying concern - **Your response framework** — based on your battle cards - **Specific proof points** — case studies, quotes, data from your proof catalog - **Follow-up questions** — to validate the response landed For competitive objections specifically, Sales Coach references the relevant Competitor Teardown automatically. ## Meeting Prep Before any sales meeting: 1. Open the meeting in the inbox or AE Cockpit 2. Click **Generate Prep** 3. Sales Coach produces: - Attendee research (role, recent activity, LinkedIn highlights) - Account context (recent signals, deal stage, history) - Suggested agenda based on stage - Discovery questions tailored to the persona - Talking points aligned to your value props - Risk areas and probable objections to expect Prep is generated in 10–30 seconds and pushed to the meeting detail page. ### Post-Meeting Coaching After the meeting (assuming it was recorded with Roam, Gong, or another integrated provider): 1. Sales Coach analyzes the transcript 2. Generates a structured summary (BANT/MEDDIC/SPICED — pick your framework in settings) 3. Identifies action items, commitments, and gaps 4. Surfaces follow-up recommendations as AE Cockpit decisions 5. Flags coaching moments for the rep (missed discovery, unanswered objections) The same analysis surfaces in the **Coaching** view for managers reviewing their team's calls. ## Competitive Positioning When a competitor is mentioned in the deal: 1. Sales Coach detects the mention from inbox, call transcripts, or notes 2. Pulls the matching Competitor Teardown from global context 3. Generates positioning guidance: - **Strengths to lead with** — your advantages over this competitor - **Weaknesses to highlight** — credible attacks on the competitor - **Trap questions to ask** — questions that expose the competitor's gaps - **Proof points** — case studies of customers who switched from this competitor Update your Competitor Teardowns regularly — Sales Coach is only as accurate as the underlying intel. ## Risk Assessment Sales Coach watches for early warning signs and flags deals at risk: | Signal | Risk Pattern | |--------|--------------| | **Engagement drops** | Prospect emails / opens drop sharply for 7+ days | | **Champion silent** | Identified champion goes 14+ days without activity | | **Stage stalled** | Deal hasn't progressed in 21+ days | | **Missing decision maker** | Late-stage deal without an identified Decision Maker contact | | **Single-thread risk** | Only one buying-committee contact engaged | | **Competitor momentum** | Competitor mentions increasing across recent activity | Each at-risk flag becomes an AE Cockpit decision card with recommended recovery actions. ## Coaching for Managers Sales managers get a consolidated view at **Agents → Sales Coach → Team**: - Per-rep coaching scorecard (calls reviewed, gaps identified, improvements over time) - Top objection types raised across the team - Common discovery gaps (which qualification dimensions reps consistently skip) - Wins to highlight in 1:1s - Risk concentrations (which reps have the most at-risk pipeline) Use this to focus coaching conversations on the highest-leverage gaps. ## Settings Configure Sales Coach behavior at **Agents → Sales Coach → Settings**: | Setting | What It Controls | |---------|------------------| | **Discovery framework** | BANT, MEDDIC, SPICED, or custom | | **Risk thresholds** | Days-without-activity that trigger at-risk flags | | **Pre-meeting timing** | When to send the briefing (30 min before meeting, etc.) | | **Coaching aggressiveness** | Conservative (only flag certainties) → Balanced → Aggressive (flag possibilities) | | **Auto-generate** | Whether prep / summaries generate automatically or on-demand | ## Credit Costs Sales Coach actions consume credits: | Action | Cost | |--------|------| | Pre-meeting briefing | 5–10 credits | | Post-meeting summary | 5–10 credits | | Objection handling | 1–3 credits | | Deal risk analysis (per deal, daily) | 2–5 credits | | Competitive positioning | 1–3 credits | Daily auto-generated coaching across your pipeline is included in standard usage; manual on-demand coaching counts incrementally. ## Best Practices - **Keep global context current** — Sales Coach references it directly. Stale battle cards = stale advice - **Add Competitor Teardowns for every real competitor** — generic competitor advice is much weaker than specific teardown-backed guidance - **Adopt one discovery framework** — pick BANT, MEDDIC, or SPICED and stick with it; mixing dilutes the coaching - **Review flagged at-risk deals weekly** — the value of risk detection is acting on it before deals die - **Use post-meeting summaries for retrospectives** — managers should review summaries with reps, not just check that they exist > **Tip:** > The more complete your global context, the better Sales Coach performs. Make sure your battle cards and personas are up to date — Sales Coach references them directly when advising on specific deals. ## Related - [AE Cockpit →](https://docs.graph8.com/deals/ae-cockpit/) — AI-prioritized deal actions with coaching insights woven in - [Prospects →](https://docs.graph8.com/deals/prospects/) — Deal detail pages where Sales Coach Intelligence tab lives - [Studio Global →](https://docs.graph8.com/studio/global/) — Manage the intelligence documents Sales Coach uses - [Studio Intelligence →](https://docs.graph8.com/studio/intelligence/) — Generate competitor teardowns and battle cards - [Copilot →](https://docs.graph8.com/ai-features/copilot/) — General AI assistant (use this for ad-hoc queries; Sales Coach is for deal-specific work) --- # Agents & Actions > Build AI agents with custom personas, attach reusable actions, connect knowledge bases, and deploy across phone, email, and chat. Source: https://docs.graph8.com/ai-features/skills/ Agents are AI-powered assistants you build and deploy. Each agent has a persona, knowledge base, and set of actions it can perform. Deploy agents on phone calls, email responses, web chat, or internal workflows. ## Agent Types | Type | What It Is | Use Case | |------|-----------|----------| | **Agent** | AI assistant with custom persona and capabilities | Customer-facing calls, email handling, chat support | | **Twin** | Agent cloned from a real person's LinkedIn profile and writing style | Outbound outreach that sounds like a specific team member | ## Creating an Agent 1. Go to **Agents → Create Agent** 2. Configure the agent: - **Name** - display name for the agent - **Role** - what the agent does (SDR, support, researcher, etc.) - **Persona** - personality settings: - **Formality** - casual to formal tone - **Conciseness** - detailed to direct responses - **Assertiveness** - gentle to assertive approach - **Instructions** - custom behavior guidelines 3. Set up identity: - **Phone number** - assign a number for calls - **Voice** - choose from preset voices or clone a voice - **Calendar** - connect for meeting scheduling 4. Attach actions and knowledge collections 5. Click **Save** ### Creating a Twin Twins replicate a real person's communication style: 1. Click **Create Twin** 2. Enter the person's LinkedIn URL 3. graph8 extracts their profile, writing style, and background 4. Upload additional writing samples (emails, posts) for better style matching 5. The twin generates outreach that sounds like that person ## Actions Actions are the capabilities you give your agents. Each action defines one thing an agent can do. ### Action Types | Type | How It Runs | Example | |------|------------|---------| | **LLM** | AI generates a response using a prompt template | Research a company, draft an email, score a lead | | **API** | Calls an external API endpoint | Look up data in your CRM, check inventory, send a notification | | **Script** | Runs custom code | Transform data, calculate scores, format output | | **Workflow** | Executes a multi-step workflow (see below) | Lead qualification pipeline, content distribution | ### Built-In Action Templates graph8 includes 15+ pre-built action templates: - **Company Research** - deep dive on a target company - **Contact Dossier** - compile everything known about a person - **Competitor Analysis** - analyze competitive positioning - **Keywords Research** - discover relevant keywords - **FAQ Generation** - create FAQ from knowledge base - **Content creation** - generate social posts, emails, summaries Attach actions to agents so they can use them during conversations and workflows. ### Approval Gates Actions can require human approval before executing. Enable **Requires Approval** on any action, and the agent pauses to ask for confirmation before running it. Approve or reject from the execution history. ### Action Execution Input When you run an action manually (or test one before attaching it to an agent), the **Action Execution Input** dialog walks you field-by-field through the action's expected inputs. Required fields are marked, and the dialog inlines hints from the action's input schema so you can see what each variable expects before you submit. The same dialog is reused inside agent chat — when an agent kicks off an action that needs input, the prompt to the user uses this consistent form. ## Workflows Workflows chain multiple actions into automated multi-step processes using a visual builder. ### Building a Workflow 1. Go to **Agents → Workflows** 2. Click **Create Workflow** or start from a template 3. Build your workflow using three modes: - **Visual** - drag-and-drop node canvas (ReactFlow) - **Simple** - linear step-by-step list - **Code** - JSON editor for advanced configuration 4. Connect nodes to define the flow 5. Test with sample data 6. Save and attach to an agent ### Node Types | Node | What It Does | |------|-------------| | **Start** | Entry point - defines trigger and input data | | **Action** | Executes an LLM, API, or script action | | **Agent** | Hands off to another agent | | **Tool** | Calls an MCP server tool | | **Condition** | Branches based on data conditions | | **Loop** | Repeats over a list of items | | **Transform** | Transforms data between steps | | **State** | Stores intermediate state | | **Human** | Pauses for human approval | | **Handoff** | Transfers to a human or another system | | **End** | Completes the workflow | ### Workflow Templates Start from pre-built templates: - **Lead Qualification** - score and route incoming leads - **Content to Social Media** - repurpose content across platforms - Custom templates you save from your own workflows ## Knowledge Collections Knowledge collections give your agents access to your company's documents and data. 1. Go to **Agents → Collections** 2. Create a collection (e.g., "Product Docs", "Case Studies") 3. Upload files - PDFs, documents, spreadsheets 4. Organize with folders 5. Attach collections to agents Agents search their attached collections when answering questions, ensuring responses are grounded in your actual content. ## MCP Servers Connect external tool servers using the Model Context Protocol (MCP): 1. Go to **Agents → MCP Servers** 2. Click **Create Server** 3. Configure connection: - **SSE** - connect to a remote server via URL - **Stdio** - connect to a local process 4. Test the connection 5. graph8 caches available tools for offline reference MCP tools appear as **Tool nodes** in the workflow builder and are available to agents during conversations. ## Agent Chat Test and interact with agents in real time: 1. Open an agent's detail page 2. Click the **Chat** tab 3. Type a message - the agent responds using its persona, actions, and knowledge 4. Watch tool executions in the side panel as the agent works Chat sessions are saved with full history and can be resumed later. ## Agent Analytics Track agent performance: - **Call analytics** - inbound call results, duration, outcomes - **Activity feed** - chronological log of all agent actions - **Execution history** - action-by-action results with inputs and outputs > **Tip:** > Start with a focused agent that does one thing well (e.g., "Meeting Scheduler" or "Lead Qualifier"). Add capabilities gradually as you refine its persona and knowledge base. Use the approval gate on high-stakes actions until you trust the agent's judgment. ## Related - [Voice Agents →](https://docs.graph8.com/ai-features/voice-agents/) - Phone-specific agent deployment - [Workflows →](https://docs.graph8.com/ai-features/workflows/) - Detailed workflow builder guide - [Web Chat →](https://docs.graph8.com/engage/web-chat/) - Deploy agents on your website - [Copilot →](https://docs.graph8.com/ai-features/copilot/) - Built-in AI assistant --- # Voice Agents > Create AI agents that handle phone calls, generate personalized outreach, and automate voice interactions. Source: https://docs.graph8.com/ai-features/voice-agents/ Voice agents are AI personas that make and receive phone calls, generate personalized outreach copy, and respond to emails — all using your org's context and knowledge base. Each agent has a configurable personality, voice, phone number, and set of skills. ## How It Works 1. Go to **Agents → Agents** 2. Click **New** and choose **Agent** or **Twin** 3. Configure the agent's persona, identity, and knowledge 4. Attach skills and activate ## Agents vs. Twins | Type | Purpose | Extra Features | |------|---------|----------------| | **Agent** | General-purpose AI persona for calls, emails, and chat | Preset voice selection | | **Twin** | Digital replica of a real person | Custom voice cloning, LinkedIn profile extraction, writing style upload | Twins inherit their source agent's persona, role, and voice — then layer on personal context from LinkedIn data and writing samples to match the real person's tone. ## Persona The **Persona** tab controls how your agent communicates. | Setting | What It Controls | |---------|-----------------| | **Role** | SDR, AE, Receptionist, CSM, GTM Engineer, Intake, or Custom | | **Tone / Formality** | Casual (0.1) → Moderate (0.5) → Formal (0.8) | | **Conciseness** | Detailed (0.1) → Balanced (0.5) → Direct (0.8) | | **Assertiveness** | Gentle (0.1) → Balanced (0.5) → Assertive (0.8) | | **Persona Prompt** | Full free-text instructions for the agent's behavior | | **Outbound Call Instructions** | Override behavior specifically for outbound calls | | **Inbound Call Instructions** | Override behavior specifically for inbound calls | | **Voicemail Message** | Per-agent voicemail script (falls back to org-level if empty) | Voicemail messages support template variables: `{first_name}`, `{bot_name}`, `{company_name}`. ## Identity The **Identity** tab assigns communication channels to your agent. - **Phone Number** — select from available Twilio numbers. Required to make or receive calls - **Voice** — choose a preset Cartesia voice (Michael, Sarah, David, Emma, and others) or, for twins, clone a custom voice - **Calendar** — link an appointment type for booking meetings during calls - **Company Knowledge** — toggle access to the org-wide knowledge base ### Voice Cloning (Twins Only) 1. In the agent form, switch to **Custom** voice mode 2. Click **Clone New Voice** 3. Select a sample script (Professional, Conversational, or Friendly) 4. Record up to 10 seconds of audio or upload a WAV/MP3 file 5. Click **Clone Voice** Cloned voices are saved to your org and can be reused across multiple twins. ## Knowledge Agents can access two types of knowledge: - **Company Knowledge** — org-wide context including company description, services, products, pain points, value proposition, pitch, FAQs, target audience, and competitive advantage - **Collections** — up to 4 document collections per agent. Documents are chunked and vectorized for retrieval during conversations ## Skills Attach reusable AI skills to give your agent capabilities beyond conversation. Go to the **Skills** tab on the agent detail page and click **Attach Skills**. Skills have three runtime types: | Type | What It Does | |------|-------------| | **LLM** | Runs a prompt template against an AI model with configurable temperature and token limits | | **API** | Calls an external HTTP endpoint with method, headers, body template, and auth | | **Script** | Executes Python, JavaScript, or TypeScript code with configurable timeout (up to 300s) | Skills can require human approval before execution. Execution history — including input, output, duration, token count, and cost — is tracked on the agent's **Activity** tab. ## On-Demand Content Generation Voice agents power personalized content generation at send time across channels: | Channel | What Happens | |---------|-------------| | **Email** | Per-contact personalized email generated using CDP data, campaign context, and conversation history | | **LinkedIn** | Per-contact LinkedIn message generated at send time — no static templates needed | | **Call Script** | Per-contact call script generated with live CDP data before dialing | | **Voicemail** | Dynamic voicemail message personalized per contact | On-demand generation pulls from your Global Context, campaign documents, and the contact's enrichment data. Twin tone profiles (formality, conciseness, assertiveness) are applied to match the sender's style. ## Phone Calls ### Outbound Calls Agents can place outbound calls via Twilio or Telnyx. Each call links to a campaign and sequence step for attribution. After the call, a callback delivers the transcript, summary, disposition, sentiment, recording, and duration. ### Inbound Calls Assign a phone number to an agent to handle inbound calls automatically. The agent uses your inbound instructions, company knowledge, and attached collections to respond. If the agent can't resolve the call, it falls back to voicemail or queues for an SDR to claim. ## Outbound Playbooks A playbook is a structured outbound script attached to an agent. Playbooks make calls consistent across prospects and let you A/B test scripts. ### Playbook Structure | Section | Purpose | |---------|---------| | **Opening** | First 15 seconds — pattern interrupt, name drop, permission to continue | | **Discovery** | 2–4 qualifying questions tied to ICP fit | | **Value pitch** | Positioning aligned with the prospect's role and pain | | **Objection handling** | Responses to the 5 most common objections | | **Close** | CTA (book meeting, email resource, callback) and next-step confirmation | ### Attaching a Playbook 1. Open the agent's **Persona** tab 2. Paste the playbook into **Outbound Call Instructions** 3. Save — the agent reads the playbook top-to-bottom, branching on prospect responses > **Tip:** > Create one playbook per persona. A VP Sales playbook should sound different from a Marketing Director playbook — pain points, proof points, and CTAs all shift. ## Receptionist Routing Rules Receptionist agents qualify and route inbound calls. Rules run in order — the first match wins. ### Rule Types | Rule Type | When to Use | |-----------|-------------| | **Keyword match** | Caller mentions a word or phrase (e.g., "billing", "support", "demo") | | **Intent classification** | Agent classifies caller intent (e.g., sales vs. support vs. existing customer) | | **Business hours** | Route differently based on time of day and weekday | | **Caller identity** | Route known contacts (from CRM) differently from unknown callers | | **Voicemail fallback** | Default action when no other rule matches | ### Routing Actions | Action | Behavior | |--------|----------| | **Transfer to human** | Warm or cold transfer to a phone number or user | | **Transfer to another agent** | Hand off to a specialized AI agent | | **Take a message** | Record a voicemail and email it to a recipient | | **Book a meeting** | Pull up calendar and schedule on the call | | **Hang up** | For known-bad numbers (sales lists, spam) | ### Warm vs. Cold Transfer | Transfer Type | Behavior | |---------------|----------| | **Warm transfer** | Agent briefs the human before connecting — explains who's calling and why | | **Cold transfer** | Agent connects the caller directly with no briefing | Warm transfers take ~10 extra seconds but reduce friction for the human picking up. ## Post-Call Summary After every call, graph8 generates a structured summary for CRM and coaching. ### Generated Fields | Field | Description | |-------|-------------| | **Disposition** | Call outcome (Booked, Callback, Not Interested, etc.) | | **Summary** | 2–3 sentence recap of what was discussed | | **Key points** | Bulleted list of topics, objections, and commitments | | **Next steps** | Action items for the rep (send case study, schedule follow-up) | | **Sentiment** | Positive, Neutral, or Negative based on tone analysis | | **Qualification score** | 0–100 based on discovery answers vs. ICP criteria | | **Intent signals** | Buying signals surfaced during the call | Summaries push to the connected CRM as activity notes and appear in the contact's Inbox timeline. ## Coaching Loop Turn calls into training material without manual review. ### Auto-Flagged Calls graph8 auto-flags calls that match coaching criteria: - Calls with low qualification scores (missed discovery) - Calls with unresolved objections - Calls where transfer was triggered by frustration - Calls with long silences or interruptions Flagged calls appear in the agent's **Activity** tab with timestamps on the problem moments. ### Review Workflow 1. Open the **Activity** tab 2. Filter by flag type (e.g., "Missed Objections") 3. Click a call to open the transcript 4. Click any line to jump to that timestamp in the recording 5. Update the agent's call instructions based on what you learned > **Tip:** > Batch coaching weekly. Review 10 flagged calls, extract the 3 most common gaps, and update the playbook or objection-handling section. Redeploy and measure conversion delta. ## Webhook Events Subscribe to voice events to build custom integrations. | Event | When It Fires | |-------|---------------| | `call.started` | Agent picks up or dials out | | `call.answered` | Far side answers | | `call.ended` | Call completes (any disposition) | | `call.transferred` | Call handed off to another party | | `call.voicemail` | Agent leaves voicemail | | `call.summary.generated` | Post-call summary is available | | `agent.flagged` | Call auto-flagged for coaching | Configure webhook URLs at **Settings → Developer → Webhooks**. Events are signed with HMAC-SHA256. ## Privacy and Recording Retention | Setting | Default | Notes | |---------|---------|-------| | **Recording retention** | 90 days | Extend in **Settings → Compliance** | | **Transcription retention** | Indefinite | Used for search, coaching, and analytics | | **Two-party consent states** | Auto-notice | Agent plays a recording notice at call start in states requiring consent | | **Do Not Call list** | Auto-scrubbed | Numbers on the DNC list are excluded from outbound campaigns | > **Caution:** > Check local call recording laws. graph8 enables the auto-notice by default, but you are responsible for compliance with state and country-level wiretap rules. ## Testing Click **Test Digital Agent** on the agent detail page to run a test before going live. ### Phone Call Test - **Outbound** — enter a phone number and click **Start Test Call**. A live status card shows call state (Dialing → Ringing → Active → Completed) with a real-time transcript - **Inbound** — call the agent's assigned number from your phone. The modal shows the expected greeting and instructions ### Email Test - **Respond** — select a pre-built scenario (Billing Inquiry, Technical Support, Sales Inquiry, Feature Request, Service Complaint), review the incoming email, and generate a response - **Compose** — choose a scenario or write custom context, then generate a fresh email with recipient info and context ## Activity & Analytics The **Activity** tab on the agent detail page shows a filterable feed of all interactions: | Filter | What It Shows | |--------|-------------| | **Calls** | Inbound/outbound calls with transcript, audio player, summary, disposition, sentiment, and duration | | **Skills** | Skill executions with status, token count, cost, input/output JSON, and errors | | **Workflows** | Workflow runs with status, duration, input/output data | The **Analytics** tab shows aggregate metrics: total calls, success rate, average duration, and total duration. > **Tip:** > Test both inbound and outbound calls before activating an agent. The real-time transcript in the test modal lets you verify persona behavior, knowledge retrieval, and call instructions without affecting live contacts. ## Next Steps - [Skills →](https://docs.graph8.com/ai-features/skills/) — Create reusable AI actions for agents - [Workflows →](https://docs.graph8.com/ai-features/workflows/) — Chain skills and actions into automated multi-step processes - [Dialer →](https://docs.graph8.com/engage/dialer/) — Use agents with the parallel dialer for outbound calling - [Sequencer →](https://docs.graph8.com/engage/sequencer/) — Enroll contacts in sequences that use agent-generated content --- # Workflows > Automate multi-step AI workflows that chain skills, triggers, and actions together. Source: https://docs.graph8.com/ai-features/workflows/ Workflows let you automate multi-step processes by chaining triggers, skills, and actions together. Build once, run automatically. ## How It Works 1. Go to **Agents → Workflows** 2. Click **Create Workflow** 3. Define a trigger (e.g., new contact added, form submitted, deal stage changed) 4. Add steps — each step uses a skill or performs an action 5. Activate the workflow ## Workflow Structure Every workflow follows the same pattern: a **trigger** starts it, **steps** process the data, and **actions** produce an output. | Component | Role | Examples | |-----------|------|----------| | **Trigger** | Starts the workflow when an event occurs | New website visitor, form submitted, new intent signal, webhook, manual run | | **Steps** | Process, transform, or decide on data | Run an AI action, check a condition, loop over a list, wait for human approval | | **Actions** | Produce an output or side effect | Send email, add to list, add to sequence, send Slack DM, hand off to agent | The visual builder lets you connect these as nodes on a canvas. Drag nodes from the palette, connect them with edges, and configure each one in the side panel. ### Builder Modes You can build workflows in three ways: - **Visual** — drag-and-drop node canvas (recommended for complex logic) - **Simple** — linear step list (best for straightforward chains) - **Code** — raw JSON editor (for advanced users) Switching between modes converts the workflow automatically. ### Available Nodes | Category | Nodes | |----------|-------| | **Core** | Start, End, Trigger, Webhook | | **Actions** | Action (LLM / API / Script), Agent, Built-in Tool (web search, knowledge search) | | **Control Flow** | If/Else (conditional branching), Loop (for-each, while, times), Human Approval | | **Data** | Transform (map, filter, extract, format), Set Variable | | **Multi-Agent** | Handoff (transfer to another agent with context) | | **Integrations** | Add to List, Add to Sequence, Send Email, Send Slack Notification, Send Roam Notification | ## Workflow Actions Actions are the nodes that do work. Each action has an **input mapping** that pulls data from previous steps using variables. ### Variables Use `${input.field_name}` to reference workflow inputs and `${step1.result}` to reference output from earlier steps. Variables are available in any text field inside a node configuration. For example, a "Draft Outreach Email" step might map: - `company_info` → `${step1.result}` - `recipient_name` → `${input.contact_name}` ### Integration Actions | Action | What It Does | |--------|-------------| | **Send Email** | Send or draft an email through a connected mailbox. Configure to, subject, content, CC/BCC | | **Add to List** | Add a contact to a CDP list by mapping input fields | | **Add to Sequence** | Enroll a contact in a sequence with optional list association | | **Send Slack Notification** | Send a message to a Slack channel or DM a user. Choose **channel** or **dm** mode, select the target, and write the message using variables | | **Send Roam Notification** | Send a notification to a team member via ro.am | | **Round-Robin Assignment** | Distribute the workflow's current contact across a pool of reps (sales managers, AEs, SDRs). Configure the pool, weighting, and skip-rules in the node panel | | **Visitor Enrichment** | Enrich a visitor or contact with multiple fields in a single call. Pick the fields you want filled (work email, phone, job title, company size, etc.) — the node returns them all together so downstream steps can branch on any of them | ### Error Handling Each action node has an error strategy: - **Stop** — halt the workflow on failure - **Continue** — skip the failed step and move on - **Retry** — retry with configurable attempts, delay, and backoff (fixed or exponential) - **Fallback** — route to a different node on failure ## Using Skills in Workflows Skills are reusable AI action definitions with a fixed prompt, input schema, and output format. Inside a workflow, skills appear as **Action** nodes with `runtime_type: llm`. To use a skill in a workflow: 1. Add an **Action** node to the canvas 2. Select an existing skill from the action picker — or create one from a template 3. Map the skill's inputs to data from the trigger or previous steps 4. The skill's output becomes available to downstream nodes as `${stepN.result}` graph8 includes pre-built skill templates across categories: Sales (lead qualification, company research), Marketing (content summarization, social post generation), Support (ticket analysis, knowledge search), and Research (competitive analysis, meeting prep). ## Templates Click **Use Template** when creating a workflow to start from a pre-configured example: | Template | Category | What It Does | |----------|----------|-------------| | **Lead Qualification** | Sales | Research company → qualify lead → draft outreach email | | **Content to Social Media** | Marketing | Summarize content → generate LinkedIn post → generate Twitter thread | | **Contact Data Enrichment** | Data Processing | Enrich company data → validate email → generate contact summary | | **Customer Support** | Support | Analyze ticket → search knowledge base → draft response | | **Meeting Preparation** | Productivity | Research attendees → generate agenda → prepare discussion points | | **Competitive Intelligence** | Research | Research competitor → analyze features → generate comparison report | Templates are starting points — customize triggers, add steps, or swap actions to fit your process. ## Execution & Monitoring After activating a workflow, you can monitor runs from the workflow detail page: - Each execution shows status (**running**, **completed**, **failed**, **paused**) - Click into a run to see per-node results, duration, and any errors - Workflows with a **Human Approval** node pause until someone approves or rejects ## Best Practices - Start with a template and modify it rather than building from scratch - Use **If/Else** nodes to handle edge cases — e.g., skip outreach if the lead score is too low - Set **stop on failure** for critical workflows where partial execution causes problems - Add a **Human Approval** node before irreversible actions like sending emails or enrolling in sequences - Keep workflows focused — one workflow per outcome. Chain workflows via webhooks for complex pipelines > **Tip:** > Test workflows with the **Test Execution** dialog before activating. It runs the workflow with sample data and shows results for each step without triggering real actions. ## Related - [Skills →](https://docs.graph8.com/ai-features/skills/) — The building blocks workflows use - [Agents →](https://docs.graph8.com/ai-features/overview/) — AI agents that execute workflow steps --- # Acquisition > Understand where your visitors come from — traffic channels, campaigns, referrals, and AI-sourced traffic. Source: https://docs.graph8.com/analytics/acquisition/ The Acquisition dashboard shows where your visitors come from and how they find your website. Break down traffic by channel, campaign, referral source, and AI-driven traffic. ## Acquisition Overview ### Summary Cards Four cards summarize your acquisition performance: | Card | What It Shows | |------|--------------| | **Total Users** | All visitors in the selected period | | **Total Sessions** | Total browsing sessions | | **Engagement Rate** | Percentage of sessions with meaningful engagement (inverse of bounce rate) | | **Top Channel** | Your highest-traffic channel and its share of total sessions | ### Channel Breakdown A chart showing how traffic is distributed across channels: | Channel | What It Includes | |---------|-----------------| | **Direct** | Typed URLs, bookmarks, untagged traffic | | **Organic Search** | Google, Bing, and other search engines | | **Paid Search** | Search ads (Google Ads, Bing Ads) | | **Social** | Social media platforms | | **Email** | Email campaigns and newsletters | | **Referral** | Links from other websites | | **Display** | Display advertising | | **AI/LLM** | Traffic from AI assistants and language models | AI/LLM traffic is automatically separated from standard referral traffic, so you can see how much of your inbound is coming from AI sources. ### Source / Medium Table A detailed table breaking down traffic by individual source and medium: | Column | What It Shows | |--------|--------------| | **Source** | Where the visitor came from (google, linkedin, direct) | | **Medium** | How they arrived (organic, cpc, referral, email) | | **Visitors** | Unique visitor count | | **Sessions** | Session count | | **Page Views** | Total pages viewed | | **Bounce Rate** | Single-page session percentage | ## Campaigns Track the performance of your marketing campaigns using UTM parameters. | Column | What It Shows | |--------|--------------| | **Campaign** | UTM campaign name | | **Source** | UTM source | | **Medium** | UTM medium | | **Visitors** | Unique visitors from this campaign | | **Sessions** | Sessions attributed to this campaign | | **Page Views** | Pages viewed by campaign visitors | Sorted by visitors descending by default. Use this to compare campaign performance and identify which campaigns drive the most engaged traffic. ## Referrals See which external websites are sending visitors to you: | Column | What It Shows | |--------|--------------| | **Referring Domain** | The website linking to you | | **Visitors** | Unique visitors from this referrer | | **Sessions** | Sessions from this referrer | | **Page Views** | Pages viewed by referred visitors | | **Bounce Rate** | Bounce rate from this referrer | ## AI Channels A dedicated view for traffic coming from AI assistants and language models. ### Summary Cards | Card | What It Shows | |------|--------------| | **Total AI Visitors** | Visitors arriving from AI sources | | **AI Sessions** | Sessions from AI-sourced traffic | | **AI Sources** | Number of distinct AI sources detected | ### AI Channel Chart A chart showing the distribution of AI-sourced traffic across different AI platforms and assistants. ### AI Referrals Table Detailed breakdown of individual AI referral entries showing source, medium, visitor count, and session details. > **Tip:** > Check the AI Channels tab regularly — AI-sourced traffic is growing across B2B websites. Understanding which AI assistants reference your content helps you optimize for AI discoverability alongside traditional SEO. ## Next Steps - [Behavior →](https://docs.graph8.com/analytics/behavior/) — See what visitors do on your site - [Conversions →](https://docs.graph8.com/analytics/conversions/) — Track goals and form submissions - [Marketing Intelligence →](https://docs.graph8.com/analytics/marketing-intelligence/) — Connect acquisition to pipeline --- # Behavior > Analyze visitor behavior on your website — page performance, landing pages, exit pages, and custom event tracking. Source: https://docs.graph8.com/analytics/behavior/ The Behavior dashboard shows what visitors do on your website — which pages they view, how long they stay, and what events they trigger. Use this to identify your best-performing content and spot pages that need improvement. ## Behavior Overview The overview shows your top pages and top events side by side. ### Top Pages A table of your most-viewed pages: | Column | What It Shows | |--------|--------------| | **Page Title** | The page's HTML title | | **Page Path** | The URL path | | **Views** | Total page views | | **Unique Visitors** | Distinct visitors who viewed this page | | **Avg Time on Page** | Average time spent on the page | | **Bounce Rate** | Percentage of visitors who left after viewing only this page | ### Top Events A table of your most-fired custom events: | Column | What It Shows | |--------|--------------| | **Event Category** | Category grouping for the event | | **Event Action** | Specific action that was tracked | | **Event Label** | Additional context or label | | **Total Events** | Total times this event fired | | **Unique Events** | Distinct users who triggered this event | ## Pages Report The full Pages report expands the top pages table with complete data for all tracked pages. | Column | What It Shows | |--------|--------------| | **Page Title** | Page's HTML title | | **Page Path** | URL path | | **Views** | Total page views | | **Unique Visitors** | Distinct visitors | | **Avg Time on Page** | Average time spent (formatted as minutes and seconds) | | **Bounce Rate** | Single-page session rate | ### What to Look For - **High views, high bounce rate** — the page attracts traffic but doesn't engage. Review the content or calls to action. - **High time on page** — visitors are reading deeply. This content resonates. - **Low unique visitors, high views** — a small audience is revisiting this page frequently. Could indicate a reference page or FAQ. ## Events Report The full Events report shows all custom events tracked on your website. | Column | What It Shows | |--------|--------------| | **Event Category** | Category grouping | | **Event Action** | The specific action tracked | | **Event Label** | Additional label or context | | **Total Events** | Total event occurrences | | **Unique Events** | Distinct users who triggered the event | Custom events are tracked using the graph8 tracking script. Common examples: - Button clicks (CTA clicks, navigation) - Video plays - Scroll depth milestones - Feature usage in web applications - Download actions > **Tip:** > Combine the Pages and Events reports to understand your conversion paths. If your pricing page has high views but low form submissions, check if visitors are engaging with CTAs (via events) or bouncing before they reach the form. ## Next Steps - [Conversions →](https://docs.graph8.com/analytics/conversions/) — Track goals, forms, and funnels - [Acquisition →](https://docs.graph8.com/analytics/acquisition/) — Understand where visitors come from - [Realtime →](https://docs.graph8.com/analytics/realtime/) — Monitor live activity --- # Conversions > Track goals, form submissions, and conversion funnels — measure what matters with completion rates, form performance, and step-by-step funnel analysis. Source: https://docs.graph8.com/analytics/conversions/ The Conversions dashboard tracks the actions that matter most — goal completions, form submissions, and multi-step funnels. See what's converting, what's not, and where visitors drop off. ## Conversions Overview ### Summary Cards | Card | What It Shows | |------|--------------| | **Goal Completions** | Total goals achieved in the period with trend | | **Conversion Rate** | Overall conversion rate with trend | | **Form Submissions** | Total form submissions with trend | | **Top Form** | Form with the highest conversion rate | ### Goals Table A table tracking each conversion goal: | Column | What It Shows | |--------|--------------| | **Goal** | Goal name with a type icon (form, meeting, signup, purchase) | | **Completions** | Total completions | | **Unique Completions** | Distinct users who completed the goal | | **Conversion Rate** | Completion rate with a color-coded progress bar | | **Value** | Revenue value attributed to this goal (if configured) | ### Forms Table A table showing performance across all tracked forms: | Column | What It Shows | |--------|--------------| | **Form** | Form name | | **Form ID** | Technical identifier | | **Viewers** | Visitors who saw the form (if form analysis is enabled) | | **Submissions** | Total form submissions | | **Unique Submitters** | Distinct users who submitted | | **Conversion Rate** | Viewer-to-submission rate (with color-coded progress bar) | | **Last Submission** | When the most recent submission occurred | Conversion rate progress bars are color-coded: - **Green** — above 10% - **Blue** — 5–10% - **Yellow** — below 5% ## Goals The full Goals view provides the complete goals table without row limits, plus summary metrics: - Total completions across all goals - Total attributed value (if goals have revenue tracking) Goal types are identified by icon: | Type | What It Tracks | |------|---------------| | **Form** | Form submission goals | | **Meeting** | Meeting or demo bookings | | **Signup** | Account creation or registration | | **Purchase** | Purchase or checkout completions | ## Funnels Funnels let you visualize a multi-step conversion path and see where visitors drop off. ### Default Funnel The default funnel tracks a standard marketing conversion path: | Step | What It Measures | |------|-----------------| | **Site Visitors** | All visitors entering the funnel | | **Viewed Form** | Visitors who saw a form (form impression) | | **Submitted Form** | Visitors who submitted the form | | **Booked Meeting** | Visitors who booked a meeting | ### Funnel Metrics Each step in the funnel shows: | Metric | What It Means | |--------|--------------| | **Users** | Number of visitors at this step | | **Share of Total** | Percentage of the first step's users who reached this step | | **Conversion Rate** | Percentage of the previous step's users who reached this step | | **Dropoff Rate** | Percentage lost between this step and the previous step | ### Reading the Funnel The biggest drops indicate your highest-leverage optimization opportunities: - **Big drop at "Viewed Form"** — visitors aren't finding or seeing your forms. Check page layout and CTA placement. - **Big drop at "Submitted Form"** — visitors see the form but don't complete it. Simplify the form or improve the value proposition. - **Big drop at "Booked Meeting"** — form submitters aren't booking. Review your follow-up timing and meeting scheduling flow. ## Forms The full Forms view provides detailed performance data for every tracked form on your site. ### Form Analysis When form analysis is enabled, additional metrics become available: - **Viewers** — how many visitors saw the form on the page - **Conversion rate** — the percentage of viewers who submitted This helps you distinguish between forms that aren't seen (traffic problem) and forms that are seen but not submitted (conversion problem). ### Performance Indicators Forms are flagged with performance badges when their conversion rate exceeds 15%, helping you quickly identify your highest-performing forms. > **Tip:** > Start with the Funnel view to find your biggest conversion bottleneck. Then use the Forms table to identify which specific forms underperform. A form with many viewers but few submissions is a higher-leverage optimization target than one that simply gets less traffic. ## Next Steps - [Behavior →](https://docs.graph8.com/analytics/behavior/) — Understand page-level performance - [Marketing Intelligence →](https://docs.graph8.com/analytics/marketing-intelligence/) — Connect conversions to pipeline - [Overview →](https://docs.graph8.com/analytics/overview/) — Return to the traffic summary --- # Dialer Analytics > Call volume, connection rate, talk time, dispositions, redial performance, and team leaderboard — built into the Dialer. Source: https://docs.graph8.com/analytics/dialer-analytics/ Dialer Analytics is built directly into the Dialer surface — there's no separate analytics page. Open **Engage → Dialer** and the **Analytics** panel shows real-time metrics on calls, connections, dispositions, and redial performance. ## Accessing Dialer Analytics Go to **Engage → Dialer** and open the **Analytics** panel from the dialer toolbar. ## SDR Performance Dashboard | Metric | What It Shows | |--------|--------------| | **Dials** | Total calls placed | | **Connections** | Calls answered by a human (excludes voicemails) | | **Connection rate** | Human-answered calls as a percentage of total dials | | **Voicemail rate** | Percentage of calls that reached voicemail | | **Talk time** | Total minutes on connected (human-answered) calls | | **Average call duration** | Mean length of connected calls — voicemails excluded | | **Disposition breakdown** | Distribution across all dispositions (booked, callback, not interested, DNC, gatekeeper, etc.) | | **Success rate** | Meetings booked per connected call | | **Redial metrics** | Redial attempts and success rates per disposition | | **Peak calling hours** | Best times of day for connections | All timestamps display in your local timezone. ## Filtering Filter the analytics view by: - **Date range** — any custom window - **Team member** — individual SDR performance - **Dialing session** — scope to a specific session - **Score range** — show only graded calls within a `Min Score` / `Max Score` window (calls under 30 seconds and non-conversation dispositions are excluded from scoring) ## Redial Analytics The redial-by-disposition feature surfaces dedicated metrics for re-engagement passes: - **Redial attempts** per disposition (voicemail, callback, gatekeeper, etc.) - **Conversion rate** on redials — how often a previously-dispositioned contact converts on a second pass - **Cursor state** — where the dialer was when the session was paused, so resumed sessions don't double-dial See [Engage → Dialer → Redial by Disposition](https://docs.graph8.com/engage/dialer/#redial-by-disposition) for how to configure the filter. ## Team Leaderboard Compare SDR performance across your team with daily, weekly, and monthly views. Rankings by total dials, connections, and meetings booked. ## Call Targets Set targets at the org level or per SDR for daily call and meeting goals. Progress against targets appears alongside the metrics above. ## Campaign Linkage Dialer sessions linked to a campaign roll their analytics into campaign-level reporting. From a campaign-linked session, all calls placed during that session are attributed to the campaign and surface alongside email and LinkedIn metrics. ## Using Dialer Analytics - **Low connection rate** — try different calling times or local-presence numbers - **Short call durations** — review scripts for stronger openers - **High voicemail rate** — adjust call timing, or use [voicemail drops](https://docs.graph8.com/engage/dialer/#voicemail) so unanswered calls still leave a message - **High redial attempts with low conversion** — reconsider whether those dispositions are worth redialing > **Tip:** > Pair Dialer Analytics with [SDR Analytics](https://docs.graph8.com/analytics/sdr-analytics/) for a complete picture of outbound performance — the SDR dashboard surfaces what to do next, while the Dialer analytics show how the calls themselves are performing. ## Related - [Dialer →](https://docs.graph8.com/engage/dialer/) — Full dialer documentation including dispositions and redial filters - [SDR Analytics →](https://docs.graph8.com/analytics/sdr-analytics/) — Personal SDR performance dashboard - [Voice Agents →](https://docs.graph8.com/engage/voice-agents/) — AI-powered calling agents --- # Marketing Intelligence > Measure the web-to-outbound conversion funnel — qualified traffic, campaign attribution, content performance, and pipeline impact. Source: https://docs.graph8.com/analytics/marketing-intelligence/ Marketing Intelligence connects your website analytics to your outbound pipeline. It tracks the full journey from anonymous visitor to booked meeting — showing where your marketing efforts are driving real pipeline, not just traffic. ## Overview Cards Four summary cards give you a quick read on marketing performance: | Card | What It Shows | |------|--------------| | **Qualified Traffic** | Identified visitors and the identification rate | | **Engagement Quality** | Average time on site and engagement trend | | **Campaign Influence** | Top campaign by influenced contacts | | **Pipeline Impact** | Meetings booked and total replies | ## Outbound Bridge Funnel The centerpiece of Marketing Intelligence is the Outbound Bridge — a visual funnel that tracks how website visitors flow into your outbound pipeline. The funnel stages: | Stage | What It Measures | |-------|-----------------| | **Web Visitors** | Total visitors to your site in the selected period | | **Identified Visitors** | Visitors resolved to known contacts (via identity resolution) | | **Added to Outbound** | Identified contacts enrolled in outbound sequences | | **Replied** | Contacts who replied to outbound messages | | **Meetings Booked** | Contacts who booked a meeting | Below the funnel, summary stats show: - **Identification Rate** — percentage of visitors resolved to contacts - **Outbound Conversion** — percentage of identified visitors added to sequences - **Reply Rate** — percentage of outbound contacts who replied ## Campaign Attribution A sortable table breaks down marketing performance by campaign: | Column | What It Shows | |--------|--------------| | **Campaign** | Campaign name (from UTM parameters) | | **Visitors** | Total visitors attributed to this campaign | | **Engaged** | Percentage of visitors who viewed more than one page and spent over 30 seconds | | **Identified** | Visitors resolved to known contacts | | **Added to Outbound** | Contacts enrolled in sequences | | **Reply Rate** | Percentage who replied | | **Meetings Booked** | Meetings attributed to this campaign | Sort by any column to find your highest-performing campaigns by traffic, engagement, replies, or meetings. ## Engagement Trend A daily line chart showing engagement metrics over time. Use this to spot trends in how deeply visitors interact with your content and whether engagement is improving or declining. ## Content Performance A table showing which pages on your site drive the most pipeline: | Column | What It Shows | |--------|--------------| | **Page** | Page title | | **Views** | Total page views | | **Unique Visitors** | Distinct visitors to the page | | **Identified** | Visitors resolved to contacts | | **Added to Outbound** | Contacts entered into sequences | | **Reply Rate** | Outbound reply rate for contacts who visited this page | | **Meetings Booked** | Meetings attributed to visitors of this page | Sort by meetings booked or reply rate to identify which content generates the most pipeline. > **Tip:** > Use the Campaign Attribution table to evaluate which campaigns drive meetings — not just traffic. A campaign with fewer visitors but higher reply rates and more meetings booked is outperforming one with lots of traffic and no conversions. ## Next Steps - [Realtime →](https://docs.graph8.com/analytics/realtime/) — Monitor live website activity - [Acquisition →](https://docs.graph8.com/analytics/acquisition/) — Deep-dive into traffic sources - [Conversions →](https://docs.graph8.com/analytics/conversions/) — Track goals and form submissions --- # Overview > High-level snapshot of website traffic and engagement — visitors, sessions, page views, bounce rate, and top traffic sources. Source: https://docs.graph8.com/analytics/overview/ The Analytics Overview gives you a high-level snapshot of your website's performance. See total visitors, sessions, page views, and engagement metrics at a glance — with trend indicators showing how things are changing over time. ## Dashboard Metrics The overview displays key metrics with period-over-period trend comparisons: | Metric | What It Measures | |--------|-----------------| | **Unique Visitors** | Total distinct users visiting your site | | **New Visitors** | First-time visitors in the selected period | | **Sessions** | Total browsing sessions | | **Page Views** | Total pages viewed across all sessions | | **Bounce Rate** | Percentage of single-page sessions (lower is better) | | **Avg Session Duration** | Average time visitors spend on your site | Each metric includes a trend indicator showing whether it's trending up, down, or neutral compared to the previous period. ## Top Traffic Sources A bar chart shows your top traffic channels ranked by session volume: | Channel | Source | |---------|--------| | **Direct** | Visitors who typed your URL or used bookmarks | | **Organic Search** | Visitors from search engines (Google, Bing, etc.) | | **Paid Search** | Visitors from paid search ads | | **Social** | Visitors from social media platforms | | **Email** | Visitors from email campaigns | | **Referral** | Visitors from other websites linking to you | | **AI/LLM** | Visitors from AI assistants and language models | Each channel shows its session count and percentage of total traffic. ## Audience Trend An interactive line chart tracks four metrics over time: - Users - New users - Sessions - Page views Use this to spot traffic patterns, identify spikes from campaigns, or detect drops that need attention. ## Controls | Control | What It Does | |---------|-------------| | **Date range** | Select the time period to analyze (1–365 days) | | **Stream selector** | Filter to a specific data stream (if you have multiple websites) | | **Browser key** | Filter by specific tracking key | ## How Trends Are Calculated Trends compare the second half of your selected period to the first half. For example, if you select a 30-day range, the trend compares days 16–30 against days 1–15. This gives you a built-in comparison without needing to manually set date ranges. > **Tip:** > Check the Overview daily to stay on top of traffic trends. A sudden drop in new visitors or a spike in bounce rate usually signals something worth investigating in the Acquisition or Behavior dashboards. ## Next Steps - [Marketing Intelligence →](https://docs.graph8.com/analytics/marketing-intelligence/) — Measure web-to-outbound conversion - [Realtime →](https://docs.graph8.com/analytics/realtime/) — Monitor live website activity - [Acquisition →](https://docs.graph8.com/analytics/acquisition/) — Understand where visitors come from --- # Realtime > Monitor live website activity — active visitors, page views, traffic sources, visitor locations, and company identification in real time. Source: https://docs.graph8.com/analytics/realtime/ The Realtime dashboard shows what's happening on your website right now. Active visitors, page views, traffic sources, and identified companies — all updating automatically every 10 seconds. ## Realtime Overview The top section shows current activity across two time windows: | Metric | 5-Minute Window | 30-Minute Window | |--------|----------------|-----------------| | **Active Users** | Visitors in the last 5 minutes | Visitors in the last 30 minutes | | **Page Views** | Pages viewed in the last 5 minutes | Pages viewed in the last 30 minutes | ### GTM Metrics Three additional real-time indicators help you spot high-value activity: | Metric | What It Means | |--------|--------------| | **Identified Visitors** | Visitors resolved to known contacts right now | | **High Intent Visitors** | Visitors showing buying signals (repeated visits, high engagement) | | **Campaign Visitors** | Visitors who arrived via a campaign link | ### Active Locations Map An interactive map showing where your current visitors are located geographically. ## Active Pages A live table showing which pages visitors are on right now: | Column | What It Shows | |--------|--------------| | **Page** | URL path of the page | | **Visitors** | Current visitor count on this page | | **Page Views** | Views in the last 5 minutes | ## Traffic Sources A live table showing where current visitors are coming from: | Column | What It Shows | |--------|--------------| | **Source / Medium** | Traffic source and medium | | **Visitors** | Active visitor count from this source | | **Sessions** | Active sessions from this source | ## Live Visitors The Live Visitors view gives a detailed, row-level look at every visitor currently on your site. ### Visitors Mode A data grid showing individual visitors: | Column | What It Shows | |--------|--------------| | **Visitor** | Visitor identifier (name if known, anonymous ID if not) | | **Company** | Company name (if identified via IP or contact match) | | **Current Page** | The page they're currently viewing | | **Time on Page** | How long they've been on the current page | | **Session Start** | When their session began | | **Type** | Known or Anonymous | | **Status** | Active or Idle | ### Companies Mode Switch to Companies mode to see visitor activity aggregated by company: | Column | What It Shows | |--------|--------------| | **Company** | Company name | | **Active Visitors** | Number of people from this company on your site | | **Recent Activity** | Latest page viewed | ### Controls | Control | What It Does | |---------|-------------| | **View toggle** | Switch between Visitors and Companies mode | | **Time window** | Filter to 5 minutes, 30 minutes, or 1 hour | | **Visitor type** | Show all, known only, or anonymous only | | **Stream** | Filter by data stream | > **Tip:** > Keep the Live Visitors tab open during campaign launches to watch prospects arrive in real time. Switch to Companies mode to see which target accounts are engaging with your content right now. ## Next Steps - [Acquisition →](https://docs.graph8.com/analytics/acquisition/) — Understand traffic sources in detail - [Behavior →](https://docs.graph8.com/analytics/behavior/) — Analyze page performance and events - [Marketing Intelligence →](https://docs.graph8.com/analytics/marketing-intelligence/) — Connect visitors to pipeline --- # SDR Analytics > Personal SDR dashboard — execution health, outcomes, next-best actions, and your reply rate vs the team. Source: https://docs.graph8.com/analytics/sdr-analytics/ The SDR dashboard is your personal command center. It surfaces what's due today, how your outreach is landing, what to do next, and how you compare to the team — all on one page tuned for daily use. ## Accessing the Dashboard Go to **Outbound → SDR** to view your dashboard. The page is scoped to you (the signed-in user) and updates automatically as you complete tasks. ## Layout The dashboard is laid out as four panels — execution and outcomes on top, next-best actions and personal stats below. ``` ┌──────────────────────┬──────────────────────┐ │ Execution Health │ Outcome Feedback │ ├──────────────────────┼──────────────────────┤ │ Action Guidance │ Personal Stats │ └──────────────────────┴──────────────────────┘ ``` A date-range picker in the top-right controls every panel. Presets: **Today**, **7d**, **14d**, **30d**, plus a custom range. ## Execution Health How well are you executing on your queue today? | Field | Meaning | |-------|---------| | **Tasks due** | Number of outreach tasks scheduled for the active range | | **Tasks completed** | Of those, how many you've completed | | **Completion rate** | Tasks completed ÷ tasks due, shown as a progress bar | | **SLA adherence** | Percentage of tasks completed within their target window | Use this panel as your "am I keeping up?" check. A low SLA adherence with a high completion rate means you're getting tasks done — but late. ## Outcome Feedback What's the outreach actually producing? | Field | Meaning | |-------|---------| | **Replies (positive)** | Replies classified as interested, asking questions, or moving forward | | **Replies (negative)** | Replies classified as no-thanks, not-now, or unsubscribe | | **Conversations** | Multi-message exchanges (a real back-and-forth, not just an auto-reply) | | **Meetings booked** | Meetings scheduled from outbound activity in the date range | Reply classification runs automatically — graph8 reads each reply and tags it positive or negative so the panel doesn't require manual triage. ## Action Guidance The next-best-actions queue tells you what to do right now. Each card has: | Field | Meaning | |-------|---------| | **Action type** | Call, Email, or Follow-up | | **Contact** | Who to reach | | **Why now** | The signal that triggered this action (e.g., "replied 2 days ago", "opened email twice") | | **CTA** | Click-through to start the action | Actions are ordered by priority — the system picks the best next move based on contact engagement, sequence stage, and time-since-last-touch. When the queue is empty, you'll see an empty-state card. That means you're caught up — pick a list and start a new sequence or dialer session. ## Personal Stats How are you doing relative to your team? | Field | Meaning | |-------|---------| | **Your reply rate** | Your reply rate over the date range | | **Team average** | The team's reply rate over the same range | | **Difference** | Your rate minus the team average — green if above, red if below | | **Trend** | Whether your reply rate is going up or down vs the prior period | Use this as a quick coaching signal. Above team average and trending up — keep doing what you're doing. Below average and trending down — review the next set of replies for tone or timing patterns. ## Manager View The dashboard above is the SDR's personal view. Sales managers and agency admins also have access to: - **Roll-up SDR list** at **Agency → Manage Orgs** (when you manage multiple orgs) - **Org-restricted access** — when an SDR is scoped to a specific org, the dashboard automatically filters to that org's data only For team-level reporting (rep-by-rep comparison, pipeline rollups, leaderboard), use the campaign and sequence dashboards in **Engage**. > **Tip:** > Treat Execution Health as the "do" panel and Outcome Feedback as the "look" panel. Most SDRs check Execution Health every morning, then re-check Outcome Feedback after lunch to see if their changes from the morning are landing. ## Related - [Dialer →](https://docs.graph8.com/engage/dialer/) — Power dialer with built-in call analytics - [Sequencer →](https://docs.graph8.com/engage/sequencer/) — Multi-channel outreach automation - [Inbox →](https://docs.graph8.com/engage/inbox/) — Where replies land for review and response - [Overview →](https://docs.graph8.com/analytics/overview/) — Org-wide analytics dashboard --- # Companies > View and organize company records with firmographic data, technographics, and account-level signals. Source: https://docs.graph8.com/data/companies/ Companies are the account-level records in your database. Every contact is linked to a company, giving you a unified view of organizational data, technology stack, and engagement signals. ## How It Works 1. Go to **Data → Companies** 2. Browse your company database in the grid view 3. Click any company to see its full profile — contacts, signals, enrichment data, and deals 4. Use filters and saved views to segment by industry, size, technology, or signal activity ## Company Data | Field Category | Examples | |---------------|----------| | **Firmographic** | Industry, employee count, revenue, founding year, location | | **Technographic** | Technology stack, tools used, platforms | | **Signals** | Website visits, intent keywords, engagement score | | **Relationships** | Associated contacts, deals, and lists | ### Create a Single Company You can also manage and enrich these contacts later from the Contacts page. You can also create contacts without importing a file: 1. Go to **Data → Contacts** 2. Click the **+Add Company** button above the grid 3. Enter Company details manually ## Detail Page Each company has a dedicated page with an **account-first view** — showing the organization as a whole, not just individual contacts. - **Overview panel** — company summary, firmographic data, and key metrics - **Contacts** — all people associated with the company - **Deals** — active and historical deals linked to the account - **Signals** — intent data and engagement signals - **Intelligence panel** — AI-generated insights and enrichment data This layout helps you understand both the company and everyone connected to it in one place. ## Activities The **Activities** tab shows a timeline of all interactions related to the company: - CRM activities (calls, emails, meetings) - Engagement events across contacts - Calendar events and updates You can also sync the latest activities from your CRM on demand. ## Enrichment Enrich company records with waterfall enrichment to fill in missing firmographic and technographic data. See [Enrichment →](https://docs.graph8.com/data/enrichment/) for details. ## Related - [Contacts →](https://docs.graph8.com/data/customer-data-platform/) — People associated with companies - [Enrichment →](https://docs.graph8.com/data/enrichment/) — Fill in missing company data - [Studio Companies →](https://docs.graph8.com/studio/accounts/) — Run AI intelligence on target companies --- # Contacts > View, manage, and enrich individual contact records with activities, intelligence documents, and AI actions. Source: https://docs.graph8.com/data/contacts/ Contacts are the people in your database. Each contact is linked to a company, giving you a complete view of the person, their role, and the organization they belong to. Browse, filter, and act on contacts from a single grid — or open any contact to see the full picture. ## Browsing Contacts 1. Go to **Data → Contacts** 2. Use column filters and saved views to segment by role, company, engagement, or signals 3. Click any row to open a slide-over with quick details 4. Click **Open full view** to go to the full detail page You can also create a contact directly — click the **+** button above the grid to add a single record. Email is used for deduplication. ## Detail Page Each contact has a dedicated page with tabs across the top and an **Intelligence Panel** on the left. | Tab | What It Shows | |-----|---------------| | **Overview** | Engagement summary, about text, and contact dashboard | | **Activity** | CRM activities, engagement events, and calendar entries | | **Notes** | Free-form notes attached to the contact | | **Tasks** | Tasks assigned to or about this contact | | **Deal Room** | Active deals linked through the contact's company | | **Meetings** | Scheduled and past meetings | | **Signals** | Intent signals and website activity | | **Next Steps** | AI-suggested next actions for the account | | **Inbox** | Email conversations with the contact | The sidebar shows key fields at a glance: email, phone, title, department, seniority, company, industry, and employee count. ### Create a Single Contact You can also manage and enrich these contacts later from the Contacts page. You can also create contacts without importing a file: 1. Go to **Data → Contacts** 2. Click the **+Add Contact** button above the grid 3. Enter contact details manually Email is used for deduplication — if a contact with the same email already exists, the record will be updated. ## Activities Tab The **Activity** tab is a filterable timeline of all interactions: - **Type filters** — All, Emails, Calls, Meetings, Notes, Tasks - **Source filters** — All Sources, Manual Only, CRM Only - **Create Activity** — add a manual entry (note, call, email, meeting, or task) with subject, description, and date Click the sync icon to pull the latest activities from your connected CRM on demand. ## Intelligence Documents The **Intelligence Panel** (toggle with the panel icon in the header) shows AI-generated documents organized into folders: | Folder | Documents | |--------|-----------| | **Outreach** | Outreach Personalization, Conversation Prep | | **Intelligence** | Stakeholder Intel Brief, Career & Trigger Timeline, Relationship Map | | **Strategy** | Persona-Mapped Value Prop | | **Engagement** | Engagement History Digest | Click **Generate** on the root folder to create all 7 documents at once, or click any individual document to generate it on its own. Generation runs in the background — you can close the modal and come back when it finishes. Documents are built from your **Global Context** and the contact's data, including enrichment fields and engagement history. ## Running Actions Click the **⚡ action icon** in the contact header to open a searchable list of your org's AI actions. Select one to run it against this contact — the contact's data is automatically injected as context. Results appear in a dialog showing the output, execution time, and token usage. Click **Save to Intelligence** to persist the result to the contact's intelligence panel for future reference. > **Tip:** > Generate intelligence documents before running outreach actions — actions that reference a contact's intelligence panel will produce better results when documents are already populated. ## Next Steps - [Companies →](https://docs.graph8.com/data/companies/) — Company-level view of the organizations behind your contacts - [Enrichment & Formulas →](https://docs.graph8.com/data/enrichment/) — Fill in missing emails, phones, and firmographic data - [Import & Export →](https://docs.graph8.com/data/import-export/) — Add contacts via CSV, CRM sync, or manual creation - [Managing Lists →](https://docs.graph8.com/data/managing-lists/) — Organize contacts into lists for sequencing and enrichment --- # Custom Fields > Create custom fields (dynamic columns) on contacts and companies to track data specific to your business. Source: https://docs.graph8.com/data/custom-fields/ Custom Fields let you add your own data columns to contacts and companies - track any information specific to your business beyond graph8's built-in fields. ## How Custom Fields Work Custom fields appear as additional columns in your contact and company grids. They work like built-in fields - you can filter, sort, export, and enrich them. There are two scopes: | Scope | Visible In | Use Case | |-------|-----------|----------| | **Global** | All lists across your organization | Fields every team needs (e.g., "Customer Tier", "Lead Source") | | **List-specific** | Only the list where it was created | Fields for a specific campaign or project | ## Creating a Custom Field 1. Open any contact or company list 2. Click the **+** icon in the grid header (or **Add Column**) 3. Enter a field name 4. Select a data type: - **Text** - free-form text (names, URLs, notes) - **Numeric** - numbers (scores, revenue, headcount) 5. Choose the scope: **Global** (all lists) or **This list only** 6. Click **Create** The new column appears in your grid immediately. ## Using Custom Fields ### In the Grid - Click any cell in the custom column to edit its value - Sort by clicking the column header - Filter using the column filter (text search for text fields, range for numeric) ### In Exports Custom fields are included when you export to CSV or XLSX. The column header uses your display name (not the internal `udo_*` name). ### With Enrichment Custom fields can be enrichment targets - use AI formulas or waterfall enrichment to automatically populate custom columns: 1. Go to **Data → Lists** and open the list 2. Click the **+** column header to add an enrichment column, with the custom field as the target 3. Configure the enrichment source (AI formula, provider chain, etc.) 4. Run enrichment > **Tip:** > Use global custom fields for data you want consistent across all lists (e.g., "ICP Score"). Use list-specific fields for one-off analysis or temporary campaigns. ## Deleting a Custom Field 1. Click the column header in the grid 2. Select **Delete Column** 3. Confirm deletion > **Caution:** > Deleting a custom field removes the column and all its data from all records. This cannot be undone. ## Related - [Contacts →](https://docs.graph8.com/data/contacts/) - Contact management and detail views - [Companies →](https://docs.graph8.com/data/companies/) - Company management - [Enrichment →](https://docs.graph8.com/data/enrichment/) - Auto-populate fields with AI and data providers ## More help - [Define a custom object and its attributes](https://docs.graph8.com/data/custom-objects/) --- # Custom objects and attributes > Define record types and typed fields for information that does not fit standard CRM records. Source: https://docs.graph8.com/data/custom-objects/ Custom objects let administrators define record types such as Projects, with labels and attributes appropriate to the information your organization stores. Open **Data → Custom objects**. ## Create a record type 1. Choose **Create object**. 2. Enter the **Singular label** and **Plural label** your team will see. 3. Choose an **API name** that can remain stable. The edit form keeps this name read-only after creation. 4. Save, then find the object in the list and open its attributes. ## Add attributes 1. Open an active object from the list. 2. Choose **Create attribute**. 3. Enter the **Field label**, **Attribute API name**, and **Description**. 4. Select a **Field type** and complete its configuration. The form changes to match the selected type. 5. Set **Required**, **Unique values**, or **Allow multiple values** as appropriate, then save. Choose field types and uniqueness requirements before importing or creating records. They determine which values the object accepts. ## Edit, archive, and restore The objects list shows the API name and whether the object is **Active** or **Archived**. Users with management access can edit labels or use **Archive** and **Restore**. Review the confirmation before archiving a record type used by other processes. ## Access and errors Creation and management controls depend on administrator access. If a save fails, read the validation message and correct the field rather than repeatedly submitting the same values. A failed load offers a retry; check your access if the error persists. ## Related - [Custom fields on standard records](https://docs.graph8.com/data/custom-fields/) - [Contacts](https://docs.graph8.com/data/contacts/) - [Companies](https://docs.graph8.com/data/companies/) - [API reference](https://docs.graph8.com/developers/) --- # Customer Data Platform > How graph8 stores, unifies, and activates customer data — contacts, companies, events, audiences, enrichment, and identity resolution. Source: https://docs.graph8.com/data/customer-data-platform/ graph8's Customer Data Platform (CDP) is the foundation of the entire system. Every contact, company, website event, and behavioral signal flows into the CDP — where it's unified, enriched, and made available for activation across all channels. ## How It Works Data enters the CDP from multiple sources: | Source | What It Captures | |--------|-----------------| | **Website tracking** | Page views, form submissions, custom events, session data | | **CRM imports** | Contacts, companies, deals, and activities from HubSpot, Salesforce, or Pipedrive | | **CSV uploads** | Bulk contact and company imports | | **Enrichment providers** | Work emails, phone numbers, company data, technographics | | **Sequences** | Email opens, clicks, replies, bounces, call outcomes | | **Form submissions** | Captured from any form on your website via the tracking script | | **Intent signals** | Keyword searches and topic-level buying intent | All data is stored per-organization with full isolation — your data is never shared or mixed with other accounts. ## Contacts Every person in your database is a unified contact record with fields across multiple categories. ### Personal Information | Field | Example | |-------|---------| | **Name** | First, middle, last | | **Gender** | Male, Female | | **Age** | Derived from date of birth | | **About** | Short bio or summary | ### Contact Details | Field | Example | |-------|---------| | **Work email** | Primary business email | | **Personal emails** | Additional email addresses | | **Mobile phone** | Cell number | | **Direct phone** | Direct dial at work | | **Additional phones** | Other numbers on file | ### Job Information | Field | Example | |-------|---------| | **Job title** | VP of Marketing | | **Department** | Marketing | | **Seniority level** | VP, Director, Manager, Individual Contributor | | **Job category** | Functional category | | **Start date** | When they started the role | | **Job change** | Flag for recent role changes | ### Location City, state, country, zip code, and full address — both personal and work locations. ### Social Profiles LinkedIn URL, LinkedIn headline, Facebook URL, Twitter URL, and social connection count. ### Confidence Scoring Each contact has a **confidence score** (0–100) indicating data quality, plus a separate **email confidence score** for deliverability. ### Record Sources Contacts are tagged by how they entered the system: | Source | Meaning | |--------|---------| | **graph8** | Enriched from graph8's data network | | **Custom** | Uploaded by your team (CSV, manual) | | **HubSpot** | Synced from HubSpot CRM | | **Salesforce** | Synced from Salesforce CRM | | **Pipedrive** | Synced from Pipedrive CRM | ## Companies Every company in your database has a unified record with firmographic data. | Category | Fields | |----------|--------| | **Basics** | Name, description, domain, website, logo | | **Location** | Address, city, state, country, zip code | | **Business** | Founded year, employee count, revenue range, industry, NAICS/SIC codes | | **Social** | LinkedIn URL, LinkedIn followers, Facebook, Twitter, Crunchbase | | **Stock** | Exchange, ticker symbol | | **Scoring** | Company fit score | ### Company Size Categories | Category | Employee Count | |----------|---------------| | **Startup** | Under 10 | | **Small** | 10–50 | | **Medium** | 50–200 | | **Large** | 200–1,000 | | **Enterprise** | Over 1,000 | ## Identity Resolution graph8 resolves anonymous website visitors to known contacts and companies using a multi-layer identification system. ### Layer 1 — Email Matching When a visitor's hashed email is available (from form submissions, login events, or email clicks), graph8 matches it against its B2B and B2C data network to return a full contact profile. ### Layer 2 — B2B and B2C Data Network graph8 maintains a large-scale contact database with: - **B2B contacts** — business emails, direct phone numbers, company information, job history - **B2C contacts** — personal emails, demographics (age range, income, homeowner status) Visitors matched through Layer 1 are enriched with this data automatically. ### Layer 3 — IP-to-Company Resolution When a visitor can't be matched by email, graph8 falls back to IP-based company identification. The visitor's IP is matched against known company IP ranges, revealing which company they belong to — even if the individual remains anonymous. ### Layer 4 — Anonymous ID Persistence Every visitor gets a persistent anonymous ID stored in their browser. This ID links all their sessions together over time, so when they eventually identify themselves (via a form submission or email click), graph8 retroactively connects all their prior visits to their contact record. ### Resolution Output The result is a unified profile that ties together: - The visitor's contact record (if identified) - Their company - All website sessions and page views - Form submissions - Email engagement history - Intent signals ## Event Tracking graph8's event store captures every interaction from your website and applications. ### Event Types | Event | What It Captures | |-------|-----------------| | **Page views** | URL, title, referrer, time on page | | **Custom events** | Any event you define (button clicks, signups, feature usage) | | **Form submissions** | All form fields, form identifier, submission timestamp | | **Identify** | User identification with traits (email, name, etc.) | | **Group** | Account or company grouping | ### Event Context Every event includes rich contextual data: | Context | Fields | |---------|--------| | **Page** | Path, title, URL, referrer, host, search params | | **User** | Email (if identified), anonymous ID, user agent, locale | | **Device** | Screen dimensions, screen density | | **Geo** | Country, city, region, latitude/longitude, timezone, ISP | | **UTM** | Campaign, source, medium, term, content | Events are stored in graph8's analytics engine — optimized for fast aggregation across millions of rows. Each organization's events are fully isolated. ## Audiences Audiences let you segment your contacts and companies for targeting. ### Audience Types | Type | What It Contains | |------|-----------------| | **Contacts** | People lists | | **Companies** | Company lists | | **Suppressions** | Exclusion lists (do-not-contact) | | **Deals** | Deal records from CRM | | **Leads** | Lead records from CRM | ### Static vs. Dynamic - **Static audiences** — a fixed list of contacts. Built from CSV uploads, CRM imports, or manual selection. The list only changes when you add or remove contacts. - **Dynamic audiences** — rules-based segments that auto-update. Define criteria (industry, seniority, location, etc.) and graph8 continuously re-evaluates contacts against those rules. ### Audience Sources Audiences can be populated from: - graph8 prospecting and enrichment - CSV uploads - Form submissions - Website visitors - Keyword and intent signals - CRM sync (HubSpot, Salesforce, Pipedrive) ## Enrichment graph8's enrichment engine fills in missing contact and company data using a waterfall approach. ### How Waterfall Enrichment Works 1. You select a field to enrich (work email, phone number, company domain, etc.) 2. You configure a provider sequence — an ordered list of data providers to try 3. graph8 calls the first provider. If it returns data, enrichment stops. If not, it tries the next provider. 4. This continues until data is found or all providers are exhausted. This approach maximizes coverage while controlling cost — cheaper providers can be tried first, with premium providers as fallback. ### Enrichment Providers graph8 integrates with multiple data providers: | Provider | Capabilities | |----------|-------------| | **Apollo** | Email, phone, job title, company data | | **Hunter** | Email discovery, domain verification | | **Clearbit** | Email, company firmographics | | **ZeroBounce** | Email validation, bounce detection | | **Lusha** | Contact info, company data | | **Prospeo** | Email finder, company insights | | **Leadmagic** | Verified B2B contact data | | **RocketReach** | Professional profiles, contact info | | **DropContact** | Contact validation | | **BuiltWith** | Technology stack detection | ### Auto-Enrichment Audiences can be configured with auto-enrichment. When new contacts are added — via form submission, CRM sync, or import — graph8 automatically triggers enrichment jobs in the background. Events are batched and processed every 30 seconds with up to 500 records per job. ## Data Activation The CDP isn't just storage — it powers every outbound action in graph8. | Activation | How CDP Data Is Used | |------------|---------------------| | **Sequences** | Contact data personalizes emails, selects channels, and determines timing | | **Dialer** | Phone numbers and company context are available during calls | | **Inbox** | Full contact and company profiles appear alongside conversations | | **Signals** | Intent data and website behavior trigger alerts and workflows | | **Studio** | Contact and company attributes inform AI campaign generation | | **Analytics** | Event data powers all dashboards and reports | ### Real-Time Triggers The CDP fires events in real time when contacts are created or updated. These triggers can: - Start enrichment jobs automatically - Add contacts to sequences based on criteria - Update CRM records via sync - Generate audit trail entries > **Tip:** > Build dynamic audiences early — they keep your targeting criteria fresh as new contacts enter the system. Combine enrichment with auto-enrichment to ensure every new contact has complete data before they enter a sequence. ## Next Steps - [Data →](https://docs.graph8.com/data/prospecting/) — Search and build prospect lists - [Signals →](https://docs.graph8.com/signals/overview/) — Detect buying intent from website visitors - [Analytics →](https://docs.graph8.com/analytics/overview/) — Track website and campaign performance --- # Enrichment & Formulas > Fill in missing data with waterfall enrichment, AI research, and computed formula columns — like a spreadsheet that enriches itself. Source: https://docs.graph8.com/data/enrichment/ graph8's enrichment engine lets you fill in missing contact and company data automatically. Chain multiple data providers, run AI research prompts, and build formula columns — all from the list grid, with results streaming in live. ## Enrichment Types | Type | What It Does | Credits | |------|-------------|---------| | **Waterfall** | Queries data providers in sequence until a value is found | Per provider per record | | **AI Enrichment** | Uses an LLM to research, analyze, or generate data | Per record (token-based) | | **Formula** | Computes a value from existing fields using expressions | Free | | **Find People** | Searches for contacts at specific companies | Per search | ## Waterfall Enrichment Waterfall enrichment queries multiple data providers in order. If the first provider doesn't return a result, the next one is tried — like a fallback chain. ### How It Works 1. Open a list and click the **+** column header to add an enrichment column 2. Select **Waterfall** as the enrichment type 3. Choose what you want to find (work email, phone, company data, etc.) 4. Select and order your providers — graph8 tries them top to bottom 5. Click **Run** — results stream into the grid in real time ### Available Providers graph8 integrates with 15+ data providers: | Category | Providers | |----------|----------| | **Email & Phone** | Apollo, Hunter, Prospeo, RocketReach, LeadMagic, Lusha, IcyPeas | | **Company Data** | Clearbit, BuiltWith | | **Verification** | ZeroBounce, DropContact, EmailListVerify | Each provider has different strengths. For example, Apollo is strong for B2B work emails, while BuiltWith reveals a company's technology stack. ### Provider Ordering The order matters. graph8 stops at the first provider that returns a result, so put your most reliable (or cheapest) provider first. You can drag providers to reorder them. ### Optional Parameters Some providers offer extra options: - **Reveal phone numbers** — enables direct dial discovery (additional credits) - **Reveal personal emails** — includes personal email addresses - **Email verification** — automatically verify found emails as a post-enrichment step ### Running Enrichment When you click **Run**: 1. graph8 validates you have enough credits 2. Records are queued and processed in batches 3. Cells update in real time as results arrive via live streaming 4. Click any enriched cell to see which provider returned the value ### Updating Waterfall Configuration When you update and save a waterfall configuration, it replaces the existing provider chain for that column. It does not create duplicate columns. > **Tip:** > Set up **auto-enrichment** on a list so new contacts added via import or prospecting are automatically enriched with your saved configuration. ## AI Enrichment AI enrichment uses a large language model to research, analyze, or generate data for each record in your list. Think of it as having a research assistant process every row. ### Column Behavior - Each AI column runs independently — adding a new AI column does not overwrite or remove existing columns - Custom columns remain unchanged when running additional enrichment This allows you to layer multiple enrichment strategies on the same dataset. ### Use Cases | Use Case | What It Does | Credits | |----------|-------------|---------| | **Web Research** | Searches the web to find information about a contact or company | 7 per record | | **Content Generation** | Generates personalized copy based on contact/company data | 1 per record | | **Create / Modify Content** | Transforms or reformats existing field values | 1 per record | ### Setting Up AI Enrichment 1. Open a list and click **+** to add a column 2. Select **AI Enrichment** 3. Write your prompt using **field variables** — type `@` to insert any column as a variable **Example prompts:** - `Research COMPANY_NAME (COMPANY_DOMAIN) and write a one-sentence summary of what they do and who they sell to.` - `Based on FIRST_NAME's role as JOB_TITLE at COMPANY_NAME, write a personalized opening line for a cold email.` - `Look up recent news about COMPANY_NAME from the past 30 days and summarize the most relevant item.` Field variables appear as interactive pills in the prompt editor — type `@` to insert any column from your list. 4. Define the output columns — one AI prompt can populate multiple columns 5. Click **Run** ### Viewing AI Results Click any AI-enriched cell to open the results panel, which shows: - The full AI response - Sources and citations (for web research) - The reasoning behind the output - Token usage ## Formula Columns Formula columns compute values from existing fields using JavaScript expressions. They're free to run and update instantly — no credits required. ### Using Formula Templates graph8 includes built-in templates for common calculations: **Contact formulas:** | Template | Output | |----------|--------| | **Lead Score** | 0–10 score based on company size, industry, and title | | **Seniority Level** | Categorizes job titles into Executive, Senior, Mid, Junior | | **Contact Quality Score** | 0–100 completeness rating based on filled fields | | **Email Validation** | Classifies emails as Personal, Professional, or Missing | | **Full Name Formatter** | Combines first and last name | **Company formulas:** | Template | Output | |----------|--------| | **Company Size Category** | Startup, Small, Medium, Large, or Enterprise | | **Revenue Category** | Early Stage, Growth, Scale, or Enterprise | | **Industry Tech Score** | 0–10 rating of technology focus | | **Growth Stage Analyzer** | Seed, Early, or Growth based on size and founding year | To use a template: 1. Click **+** to add a column → select **Formula** 2. Browse the template library 3. Select a template — the formula is pre-filled 4. Click **Run** ### Writing Custom Formulas Write your own JavaScript expressions using field name placeholders: ``` // Score leads based on employee count const size = parseInt(COMPANY_EMPLOYEE_COUNT) || 0; let score = 0; if (size >= 50 && size <= 500) score += 3; if (size > 500) score += 5; return score; ``` Field references like `COMPANY_EMPLOYEE_COUNT` are replaced with actual values at runtime. The formula editor highlights available fields as you type. Formula outputs can be **text**, **number**, or **boolean**. ### AI-Generated Formulas Don't want to write JavaScript? Describe what you want in plain English and graph8's AI will generate the formula for you. Example: *"Score contacts 1-10 based on how senior their job title is, giving C-suite a 10 and individual contributors a 2"* graph8 generates the JavaScript, lets you preview results on sample rows, then saves and runs it. > **Tip:** > Formula columns are free and recalculate instantly. Use them to score, categorize, or flag records before running paid enrichment on the best matches. ## Find People Find People searches for contacts at specific companies in your list. 1. Open a list that contains company data 2. Click **+** → select **Find People** 3. Set filters for the contacts you want (job title, seniority, department) 4. Choose which company column to search against 5. Select a destination list for found contacts 6. Click **Run** graph8 searches each company and returns matching contacts. Review the results, select the ones you want, and import them to your destination list. ## List Routing List routing lets you automatically move or copy contacts between lists based on rules. ### How It Works 1. Define filter conditions (e.g. job title, industry, enrichment results) 2. Select a destination list 3. Apply routing manually or via automation You can also use AI to suggest routing rules based on your data. ### Use Cases - Route high-quality leads into a sales-ready list - Separate contacts by persona, industry, or score - Automatically organize enriched data into segmented lists ## Credits Enrichment runs on a credit system: | Enrichment Type | Credit Model | |----------------|-------------| | **Waterfall** | Fixed credits per provider per record (varies by provider) | | **AI — Web Research** | 7 credits per record | | **AI — Content Generation** | 1 credit per record | | **Formula** | Free | | **Find People** | Credits per search | Before any enrichment runs, graph8 shows you the estimated credit cost and validates your balance. Credits are held during processing and charged as results come in. Unused holds are released when the job completes. Check your credit balance in **Settings → Credits & Billing**. ## Real-Time Updates All enrichment types stream results live into the grid using server-sent events. You don't need to refresh the page — cells update as each record is processed. This means you can watch results flow in and stop a job early if the data quality isn't what you expected. ## Next Steps - [Prospecting →](https://docs.graph8.com/data/prospecting/) — Search the database to find new prospects - [Managing Lists →](https://docs.graph8.com/data/managing-lists/) — Organize and filter your lists - [Import & Export →](https://docs.graph8.com/data/import-export/) — Upload contacts or download lists --- # Import & Export > Upload contacts from CSV or CRM, and export prospect lists from graph8. Source: https://docs.graph8.com/data/import-export/ Bring your existing contacts into graph8 via CSV upload or CRM sync, and export lists when you need data outside the platform. ## Importing Contacts ### CSV Upload 1. Go to **Data → Lists** 2. Click **Import** in the top toolbar 3. Drag and drop your `.csv` or `.xlsx` file, or click to browse 4. **Map columns** — graph8 auto-detects common headers (Email, First Name, Last Name, Company, Title). Review and adjust any columns that weren't matched. 5. **Choose a destination list** — create a new list or add to an existing one 6. Click **Start Import** The import runs in the background. You'll see a notification when it completes. **Requirements:** - File must have a header row - Email column is required (used for deduplication) - Maximum file size: 50 MB - Supported formats: `.csv`, `.xlsx` ### Create a Single Contact You can also manage and enrich these contacts later from the Contacts page. You can also create contacts without importing a file: 1. Go to **Data → Contacts** 2. Click the **+Add Contact** button above the grid 3. Enter contact details manually Email is used for deduplication — if a contact with the same email already exists, the record will be updated. > **Tip:** > graph8 deduplicates by email address. If an imported contact already exists in your workspace, the existing record is updated with the new data rather than creating a duplicate. ### Column Mapping graph8 automatically maps these common headers: | Your CSV Header | Maps To | |----------------|---------| | Email, Email Address, E-mail | **Email** | | First Name, FirstName, Given Name | **First Name** | | Last Name, LastName, Surname | **Last Name** | | Company, Company Name, Organization | **Company** | | Title, Job Title, Position | **Job Title** | | Phone, Phone Number, Direct | **Phone** | | LinkedIn, LinkedIn URL | **LinkedIn** | For non-standard headers, manually assign the correct field during the mapping step. ### CRM Sync Connect your CRM to automatically sync contacts: 1. Go to **Settings → Integrations** 2. Select your CRM (Salesforce, HubSpot, Pipedrive, Zoho) 3. Authorize the connection 4. Configure sync settings — choose which objects and fields to sync 5. Set the sync direction (one-way or bidirectional) CRM sync runs automatically on a schedule. Changes in either system are reflected in the other based on your sync rules. ## Exporting Lists ### Export to CSV 1. Go to **Data → Lists** and open the list you want to export 2. Optionally apply filters to export a subset 3. Click **Export** in the toolbar 4. Choose which columns to include 5. Click **Download** The CSV is generated and downloaded to your browser. ### What's Included in Exports Exports include all visible columns plus any enriched data: - Contact fields (name, email, phone, title, LinkedIn) - Company fields (name, size, industry, revenue, domain) - Custom fields you've configured - List metadata (date added, source) > **Tip:** > Use column filters before exporting to create targeted extracts. For example, filter a list to only contacts with verified emails before exporting for an external email tool. ## Troubleshooting **Import fails with a column mapping error** - Ensure your CSV has a header row in the first row - Rename ambiguous headers (e.g., change "Name" to "First Name" and "Last Name") **Duplicates appearing after import** - graph8 deduplicates by email. If duplicates appear, check that the email column was mapped correctly. Contacts without an email can't be deduplicated. **CRM sync not updating** - Check that the integration is still authorized in **Settings → Integrations** - Verify your sync rules include the fields you expect - CRM syncs run on a schedule — changes may take up to 15 minutes to appear ## Next Steps - [Prospecting →](https://docs.graph8.com/data/prospecting/) — Search the database to find new prospects - [Managing Lists →](https://docs.graph8.com/data/managing-lists/) — Organize and filter your lists - [Enrichment & Formulas →](https://docs.graph8.com/data/enrichment/) — Waterfall providers, AI research, and formula columns --- # Managing Lists > Create prospect lists, import from CSV, filter and segment, and run bulk actions. Source: https://docs.graph8.com/data/managing-lists/ Lists are how you organize prospects in graph8. Every search result you save, every CSV you upload, and every audience you build lives as a list. You can also create an empty list and add records to it later. ## Viewing Your Lists Go to **Data → Lists** to see all your lists. ![The Lists page: every list with its type, status, dynamic-list state, source, total records, tags, and created date, plus the Create List button](https://docs.graph8.com/images/data/lists-overview.png) Each row shows: - **Name**: the label you assigned - **Type**: whether the list holds Contacts or Companies - **Status**: processing state (e.g. Completed) - **Dynamic List**: whether the list auto-updates from a saved filter - **Source**: where the records came from (graph8, Visitor, an import, etc.) - **Total Records**: number of records in the list - **Tags** and **Date Created** ## Creating a List Click **+ Create List** in the top-right. Pick whether the list holds **Contacts** or **Companies**, give it a name, then choose how to populate it. ![The Create list dialog: pick Contact or Company, name the list, then Create & add contacts or Import CSV instead](https://docs.graph8.com/images/data/create-list.png) ### Create an empty list Name the list and click **Create & add contacts**. This creates the list immediately and drops you into it so you can add records by hand (**Add Contact**), from Search, or from a CSV. Use this when you want the list to exist first and fill it later. ### Import from a CSV Name the list, then click **Import CSV instead** to open the upload wizard. It walks through four steps: 1. **Upload**: drag and drop your file or click **Choose File** (supports `.csv`, `.xlsx`, and `.xlsm`) 2. **Preview File**: confirm the rows were read correctly 3. **Map Columns**: match your spreadsheet columns to graph8 fields 4. **Preview Result**: review before the records are created ![The Upload File wizard: Upload, Preview File, Map Columns, Preview Result; accepts .csv, .xlsx, and .xlsm](https://docs.graph8.com/images/data/list-import-csv.png) > **Note:** > You must name the list before the **Import CSV instead** option becomes available. ### Create a list from Search From **Data → Search**, run a search and save the results to a new (or existing) list. See [Prospecting](https://docs.graph8.com/data/prospecting/) for the full search-to-list flow. ## Inside a List Open a list to see its records in a grid. The toolbar gives you **List / Enrichment** views, **Filter**, **Views** (saved filter views), **Download**, **Add Contact**, and a three-dot menu of bulk actions. ![A list opened to its grid: Name, Job Title, Company, Website, Owner, Source columns with the List/Enrichment, Filter, Views, Download, and Add Contact controls](https://docs.graph8.com/images/data/list-detail.png) ### Filtering Contacts Within a List | Filter Type | How It Works | |-------------|-------------| | **Text search** | Search by name, email, company, or title | | **Column filters** | Filter any column: title contains "VP", company size > 200, etc. | | **Sort** | Sort by any column ascending or descending | | **Saved views** | Save a filter combination as a reusable view from the **Views** menu | Filters are additive; each one narrows the results further. ## Bulk Actions Select records (or use the header checkbox to select the page, then **Select all N in this list**), then open the three-dot (⋮) menu: ![The bulk actions menu: Merge Lists, Clone List, Verify Emails, Unlock Selected, Add to Suppressions, and Delete](https://docs.graph8.com/images/data/list-bulk-actions.png) | Action | Description | |--------|-------------| | **Merge Lists** | Combine this list with another | | **Clone List** | Duplicate the list | | **Verify Emails** | Run a fresh email-verification pass on the selected records | | **Unlock Selected** | Reveal locked contact info for the selected records | | **Add to Suppressions** | Exclude the selected contacts from outreach (see [Suppressions](https://docs.graph8.com/data/suppressions/)) | | **Delete** | Remove the selected records from the list | To export, use the **Download** button in the toolbar; it downloads the visible columns (including custom enrichment and AI columns) as a CSV. Hide any columns you don't need before exporting to control the output. ### Apply to the Full List When you select the page, a banner offers **Select all N contacts in this list**, so a bulk action can target every matching record, not just the rows currently loaded. Combine with filters first (for example, filter to VPs at 200+ employee companies, then select all and act) to bulk-act on large lists safely. ## List Types | Type | Description | |------|-------------| | **Contact lists** | Hold individual people (the most common list) | | **Company lists** | Hold companies/accounts | | **Dynamic lists** | Auto-updating lists driven by a saved filter; records that match are added automatically | > **Note:** > Suppression is no longer a list type. To stop contacting people, add them to **Suppressions** instead (see [Suppressions](https://docs.graph8.com/data/suppressions/)). ## Picking Lists from Dropdowns When adding contacts to a list (from prospecting, sequencer, or workflows), the list-picker dropdown supports infinite scroll and type-to-search, so orgs with hundreds of lists no longer hit a hard cap when picking the destination. ## Deleting Lists When you delete a list, it is removed from all selection dropdowns across the platform, so it will no longer appear when choosing lists for routing, sequencing, or filtering. ## Next Steps - [Suppressions →](https://docs.graph8.com/data/suppressions/): Exclude contacts from outreach across channels - [Prospecting →](https://docs.graph8.com/data/prospecting/): Search the database to build new lists - [Enrichment & Formulas →](https://docs.graph8.com/data/enrichment/): Waterfall providers, AI research, and formula columns - [Import & Export →](https://docs.graph8.com/data/import-export/): Upload contacts or download lists --- # Prospecting > Search graph8's 200M+ contact database to find and save prospects to targeted lists. Source: https://docs.graph8.com/data/prospecting/ graph8 gives you access to a database of over 200 million B2B contacts. Search by job title, industry, company size, location, and more to build targeted prospect lists for outreach. ## Searching the Database 1. Go to **Data → Search** 2. Choose the **People** or **Companies** tab 3. Apply filters to narrow your results, then click **Run Search** 4. Review the matching contacts 5. Click **Save List** to save all results, or select rows and click **Save Selected** to save just those contacts ![The Data menu open, showing Search alongside Companies, Contacts, Data Pipelines, Lists, Suppressions, and Workbench](https://docs.graph8.com/images/data/data-menu.png) ## Available Filters ![The Search page: People and Companies tabs, the Run Search button, and the ICP Criteria panel with Contact Search, Company Search, Location, Intent Topics, and Intent Search sections](https://docs.graph8.com/images/data/search-filters.png) Filters are grouped into sections in the panel on the left: | Section | Filters | Examples | |---------|---------|---------| | **Contact Search** | Persona (Seniority Level, Job Title, Job Department), Social Profiles, Education, Background, Scoring and Confidence, Personal Information | C-Suite, VP Marketing, Engineering | | **Company Search** | Company, Industry, Number of Employees, Company Revenue, Year Founded, Company Social Media, Stock Ticker, Stock Exchange | SaaS, 51-200 employees, $10M+ revenue | | **Location** | Contact or company geography | Country, state, city, or metro area | | **Intent Topics** | Companies showing buying intent on selected topics | Researching topics relevant to your product | | **Intent Search** | Free-text intent keyword search | Product or category keywords | | **Lists** | Include or exclude contacts from your existing lists | Exclude current customers | You can also set **Max results** above the filters to cap how many records the search returns (up to 50,000). ## Combining Filters Stack multiple filters to build precise targeting. For example: - **VP or Director** of **Marketing** at **SaaS companies** with **51-200 employees** in the **United States** - **CTO or Head of Engineering** at companies with **$10M+ revenue** founded after **2015** - **SDR Managers** at companies showing **buying intent** on topics related to your product, excluding contacts already on your customer **lists** Adjust filters and click **Run Search** again to update the results. ### Active Filters Collapsed filter sections show a badge with the number of active filters applied. This helps you quickly see how many filters are active, even when sections are closed. ## Saving to a List ![Search results in the grid with matching contacts and the Save Selected button in the top-right corner](https://docs.graph8.com/images/data/search-results.png) Once you have a set of matching prospects: 1. Click **Save List** to save the full result set, or check individual rows and click **Save Selected** to save only those contacts 2. Give the list a descriptive title (e.g., "Q1 SaaS VP Marketing - West Coast") 3. Save the list; it appears under **Data → Lists** Prospects saved to a list are now in your graph8 workspace and can be enrolled in sequences, enriched, or exported. ## Saved Filters Save your most-used filter combinations from the filter panel so you can re-run the same search later. As the database updates, re-running a saved search finds new prospects matching your ICP. ## Per-Company Caps When a search returns multiple matching contacts at the same company, you can cap how many are saved per company: - When saving a contact list, check **Limit number of employees per company** in the save dialog and set the cap (e.g., 3 contacts max per company) - graph8 picks the highest-seniority matches first - Useful for account-based targeting where you don't want to over-saturate one company ## Enrichment graph8 enriches prospect data automatically when you save contacts to a list. Enrichment fills in missing fields like: - **Email addresses**: verified work emails - **Phone numbers**: direct dials and mobile - **LinkedIn profiles**: profile URLs - **Company data**: size, revenue, industry, tech stack You can also run enrichment manually on any list: open the list from **Data → Lists** and click the **+** column header to add an enrichment column. For advanced enrichment (waterfall providers, AI research, and formula columns), see [Enrichment & Formulas](https://docs.graph8.com/data/enrichment/). ## Next Steps - [Managing Lists →](https://docs.graph8.com/data/managing-lists/): Organize, filter, and work with your saved lists - [Enrichment & Formulas →](https://docs.graph8.com/data/enrichment/): Waterfall providers, AI research, and formula columns - [Import & Export →](https://docs.graph8.com/data/import-export/): Upload your own contacts or export lists --- # Workbench Pipelines > Automate multi-step data processing in the staging workbench — chain AI extraction, waterfall enrichment, formulas, and branching into reusable pipelines. Source: https://docs.graph8.com/data/staging-pipelines/ Workbench Pipelines let you chain enrichment steps into automated workflows that run whenever new data arrives. Instead of manually triggering AI extraction, waterfall enrichment, and formula columns one at a time, define a pipeline once and let it process every incoming row automatically. Pipelines are configured per workbench table and trigger on specific ingest events — email PDF uploads, webhook payloads, scraper bot results, or manual imports. ## How It Works 1. Open a workbench table and click the **Pipeline** tab 2. Click **New Pipeline** or choose a pre-built template 3. Configure the **start event** — what triggers this pipeline (email PDF, webhook, scraper bot, or import) 4. Add steps to the pipeline — AI extraction, waterfall enrichment, formula evaluation, branching, or export to list 5. Drag steps to reorder them, or add branch conditions to route data through different paths 6. Click **Save** — the pipeline runs automatically whenever matching data arrives ## Pipeline Builder The visual pipeline builder shows your workflow as a connected chain of steps. Each step card displays its type, name, and a summary of its configuration. ### Start Events Every pipeline begins with a start event that determines when it triggers: | Start Type | When It Triggers | Use Case | |------------|-----------------|----------| | **Email PDF** | A PDF is received via email ingest | Processing resumes, invoices, or inbound documents | | **Webhook** | Data arrives via the table's webhook endpoint | Integrating with external tools or APIs | | **Scraper Bot** | A scraper bot delivers results | Processing web-scraped company or contact data | | **Import** | Rows are added via CSV, paste, CRM import, or search | Enriching newly imported batches automatically | For email PDF and webhook start events, you can configure expected source types and fields so the pipeline knows what data to extract. ### Step Types Add steps in any order. Each step runs after the previous one completes. | Step | What It Does | Configuration | |------|-------------|---------------| | **AI Group** | Runs AI extraction using a saved column group | Select an existing AI column group or create one inline | | **Waterfall** | Runs waterfall enrichment on a column | Select a waterfall-configured column | | **Formula** | Evaluates a formula expression and writes the result | Uses the G8X expression engine — see [formula syntax](#formula-expressions) | | **Branch** | Routes rows through different paths based on a condition | Configure field, operator, and value — rows go to "if true" or "else" path | | **Export to List** | Sends the row to a contact or company list | Choose destination list and map fields | ### Branching Branch steps split the pipeline into two paths based on a condition. Configure the condition using a field name, an operator, and a value: | Operator | Example | |----------|---------| | Equals | `status` equals `qualified` | | Not equals | `tier` not equals `disqualified` | | Contains | `title` contains `VP` | | Greater than | `score` greater than `70` | | Less than | `score` less than `30` | | Is empty | `email` is empty | | Is not empty | `phone` is not empty | Rows matching the condition follow the "if true" path. All others follow the "else" path. Each path can have its own steps. > **Caution:** > Only one branch step is allowed per pipeline. Nested branches are not supported. ## Templates The pipeline builder includes pre-built templates to get started quickly: | Template | Steps | Use Case | |----------|-------|----------| | **Email PDF Extraction** | AI extraction → formula scoring → export | Processing inbound resumes or documents | | **Webhook Routing** | AI classification → branch → export | Routing webhook data based on content | | **Import Classification** | AI analysis → formula tagging → export | Categorizing newly imported contacts | | **SDR Candidate Grading** | Resume extraction → location detection → scoring → tier mapping → narrative → export | Full candidate evaluation pipeline | Click a template to scaffold the pipeline with pre-configured steps. You can then customize each step's settings. ## Formula Expressions Pipeline formula steps use the G8X expression engine — a safe, deterministic formula language. Formulas can reference any field in the row using dot notation. ### Common Functions | Category | Functions | |----------|----------| | **String** | `UPPER`, `LOWER`, `TRIM`, `CONCAT`, `SPLIT`, `JOIN`, `REPLACE`, `SUBSTRING`, `LEFT`, `RIGHT` | | **Math** | `ADD`, `SUB`, `MUL`, `DIV`, `MOD`, `MIN`, `MAX`, `FLOOR`, `CEIL`, `ROUND`, `ABS` | | **Logic** | `IF`, `AND`, `OR`, `NOT`, `EQ`, `GT`, `GTE`, `LT`, `LTE` | | **Type** | `TO_NUMBER`, `TO_STRING`, `IS_EMPTY`, `COALESCE`, `DEFAULT` | | **JSON** | `GET`, `KEYS`, `VALUES`, `LENGTH`, `INCLUDES`, `FIND` | ### Examples Score a contact based on title and location: ``` IF(INCLUDES(LOWER(title), "vp"), 90, IF(INCLUDES(LOWER(title), "director"), 70, 50)) ``` Classify a row by source: ``` IF(IS_EMPTY(email), "no-email", IF(INCLUDES(email, "@company.com"), "internal", "external")) ``` Combine first and last name: ``` CONCAT(TRIM(first_name), " ", TRIM(last_name)) ``` > **Tip:** > Formulas run in a sandboxed environment with no access to external data or APIs. They can only read fields from the current row and call whitelisted functions. ## Async Processing Pipelines process data asynchronously. When rows arrive, graph8 evaluates which pipelines match the start event and enqueues each step for background processing. This means: - **No timeouts** — large batches process reliably without blocking the UI - **Retry on failure** — failed steps retry automatically with exponential backoff - **Progress tracking** — the grid updates in real time as each step completes AI and waterfall steps are distributed across multiple workers for throughput. Formula steps execute immediately since they require no external calls. ## Related - [Staging Workbench →](https://docs.graph8.com/data/staging-workbench/) — Import and prepare data before it enters your lists - [Enrichment & Formulas →](https://docs.graph8.com/data/enrichment/) — Enrich contacts already in a list - [Managing Lists →](https://docs.graph8.com/data/managing-lists/) — Organize and filter your lists --- # Staging Workbench > Import, structure, enrich, and prepare raw data before adding it to your contact lists. Source: https://docs.graph8.com/data/staging-workbench/ The Staging Workbench is a dedicated workspace for preparing data before it enters your main lists. Import raw data from any source (CSV files, CRM exports, webhooks, or clipboard paste), then structure, enrich, and transform it in an isolated environment. When the data is ready, export it to a list with field mapping and optional auto-enrichment. This is different from [list enrichment](https://docs.graph8.com/data/enrichment/), which fills in missing data on contacts that are already in a list. The workbench handles the step before that: getting raw, messy data into shape so it's ready for your lists and campaigns. ## How It Works 1. Go to **Data → Workbench** 2. Click **Create Workbench** and choose the entity type (contact or company) 3. Import data using any supported source: CSV upload, CRM import, paste from clipboard, webhook, or manual entry 4. Add columns to structure, enrich, and transform your data, including waterfall enrichment, AI columns, and formula columns 5. Review the results in the grid, then click **Map & Export** to send the prepared data to a list ![The Workbench page listing workbench tables with their rows, columns, and created date, plus the New Workbench button](https://docs.graph8.com/images/data/workbench-overview.png) The workbench guides you through each stage. When you first create a workbench, you'll see import options. Once data arrives, graph8 helps you extract and structure fields. From there, the full grid and enrichment tools become available. ## Why Use Staging Workbench **Use the workbench when your data isn't list-ready yet.** If you already have clean, structured contacts in a list and just need to fill in missing fields, use [Enrichment & Formulas](https://docs.graph8.com/data/enrichment/) directly. Use the workbench when: - You received a CSV or spreadsheet and need to clean it up before importing - You're pulling data from a CRM and want to enrich it before creating a list - You have webhook or API data in raw JSON format that needs field extraction - You want to combine, score, or transform data from multiple sources before outreach - You need to preview enrichment results before committing data to your main lists | Scenario | Use | |----------|-----| | Fill in missing emails on an existing contact list | [Enrichment](https://docs.graph8.com/data/enrichment/) | | Clean up a raw CSV and enrich it before creating a list | **Staging Workbench** | | Add AI-generated columns to contacts already in a list | [Enrichment](https://docs.graph8.com/data/enrichment/) | | Import webhook data, extract fields, enrich, then export | **Staging Workbench** | ## Import & Prepare Data The workbench supports multiple ways to bring data in: | Source | How It Works | |--------|-------------| | **CSV Upload** | Drag and drop a file or browse to upload. Map columns on import. | | **Paste Data** | Paste rows from a spreadsheet or CSV. graph8 auto-detects delimiters. | | **CRM Import** | Connect Salesforce, HubSpot, Pipedrive, or Zoho and pull records directly. | | **Search Import** | Pull contacts from graph8's prospecting database into the workbench. | | **Webhook / JSON** | Create a raw JSON column to receive payloads from external systems. Use column discovery to extract nested fields automatically. | | **Manual Entry** | Add rows and fill in field columns by hand. | Once data is in the grid, you can add enrichment columns (waterfall, AI, formula) using the same tools documented in [Enrichment & Formulas](https://docs.graph8.com/data/enrichment/). The difference is that everything happens in an isolated workspace; nothing touches your main lists until you explicitly export. ## Export to a List When your data is ready, click **Map & Export** to start the export wizard: 1. **Map fields**: Match workbench columns to standard contact or company fields. graph8 suggests mappings automatically. 2. **Choose destination**: Create a new list or add to an existing one. 3. **Configure enrichment**: Optionally set up waterfall enrichment (email, phone, company data) to run automatically after export. ## Related - [Workbench Pipelines →](https://docs.graph8.com/data/staging-pipelines/): Automate multi-step enrichment with visual pipelines - [Enrichment & Formulas →](https://docs.graph8.com/data/enrichment/): Enrich contacts already in a list - [Import & Export →](https://docs.graph8.com/data/import-export/): Upload contacts or download lists - [Managing Lists →](https://docs.graph8.com/data/managing-lists/): Organize and filter your lists --- # Suppressions > Exclude contacts from outreach across email, phone, and LinkedIn, and reinstate them when needed. Source: https://docs.graph8.com/data/suppressions/ Suppressions are contacts you never want to reach. A suppressed contact is excluded from outreach across sequences, the dialer, and LinkedIn, so you don't accidentally contact competitors, existing customers, or anyone who has opted out. Go to **Data → Suppressions** to see who's suppressed, from where, and why. ## How to suppress contacts Contacts are added to suppressions from a **list**. Open any contact list, select the contacts you want to exclude, open the three-dot (⋮) menu, and choose **Add to Suppressions**. ![The list three-dot menu open, showing bulk actions including Add to Suppressions for the selected contacts](https://docs.graph8.com/images/data/list-bulk-actions.png) Pick which channels the suppression applies to: ![The Add to suppressions dialog: suppress the selected contacts on All channels, Email, Phone, or LinkedIn](https://docs.graph8.com/images/data/add-to-suppressions.png) | Option | Effect | |--------|--------| | **All channels** | Excludes the contacts from every outreach channel | | **Email** | Excludes them from email only | | **Phone** | Excludes them from dialer/SMS only | | **LinkedIn** | Excludes them from LinkedIn outreach only | Confirm with **Add to suppressions**. The contacts appear on the Suppressions page immediately. > **Note:** > Uploading a suppression CSV directly is no longer supported. To suppress people in bulk, import or build a list first (see [Managing Lists](https://docs.graph8.com/data/managing-lists/)), then use **Add to Suppressions** on that list. ## The Suppressions page ![The Suppressions page: Contact, Email/Phone, Company, Channel, and Category columns with a Reinstate action per row](https://docs.graph8.com/images/data/suppressions-list.png) Each row shows: - **Contact** and **Company** - **Email / Phone**: the identifiers being suppressed - **Channel**: which outreach the suppression covers (e.g. All outreach) - **Category** and **Source / Origin**: why the contact was suppressed and where it came from (for example, a manual bulk add by your team) - **Reinstate**: removes the contact from suppressions so they can be contacted again ### Account-wide vs channel-specific The **Channel-specific opt-outs** toggle at the top of the page controls how suppressions are scoped: - **Off (account-wide)**: a suppression blocks the contact everywhere - **On (channel-specific)**: suppressions are tracked per channel, so a contact can be suppressed on one channel but reachable on another Use **View details** to manage this behavior under **Settings → Channels → Preferences** (open **Settings** from the profile menu in the top-right). ## Reinstating a contact Click **Reinstate** on any row to remove that contact from suppressions. They become eligible for outreach again on the channels that were suppressed. ## Related compliance tools Suppressions sit alongside the broader compliance controls: - The **global suppression list** and **Do Not Contact (DNC)** list also block outreach and are managed under **Settings → Compliance**. - Unsubscribes, hard bounces (when enabled), and spam complaints feed those lists automatically. See [Compliance](https://docs.graph8.com/settings/compliance/) for the global suppression list, DNC, and unsubscribe handling. ## Next Steps - [Managing Lists →](https://docs.graph8.com/data/managing-lists/): Build the lists you suppress from - [Compliance →](https://docs.graph8.com/settings/compliance/): Global suppression list, DNC, and unsubscribe handling - [Sequencer →](https://docs.graph8.com/engage/sequencer/): Outreach that respects your suppressions --- # Tasks > Create, assign, and track follow-ups with links to the exact records and source context. Source: https://docs.graph8.com/data/tasks/ Tasks let your team track follow-ups, to-dos, and action items across any record in graph8. Link contacts, companies, deals, leads, recruiting applications, or team members, assign an owner, and track completion. A task can have several related records. ## Viewing Tasks Click the **task icon** in the top navigation bar to open the Tasks page. Choose a scope beside **Tasks**: - **My tasks**: tasks assigned to you. - **Assigned by me**: tasks you assigned to other people, so you can track their status. Only verified assignments appear here. Older tasks without a recorded assigner remain available in **All tasks**; creating a task alone does not count as assigning it. - **All tasks**: every task you have permission to view. - **Agent tasks**: tasks assigned to an agent executor, with a person accountable for the work. Use **List** or **Board** for the same scope and filters. List supports grouping by executor, status, record or meeting. Board groups cards into status columns. Under Agent tasks, **Execution activity** opens the separate view of agent runs; select List or Board to return to assigned tasks. Open **Filter** to choose completion status, due date, task type, priority, person or a specific agent. The default is **Incomplete**. Choose **Completed** to inspect finished work, or remove the Incomplete chip to show all statuses. Date options include Today, Overdue, Upcoming, No due date and Custom range. These filters work in every task scope: for example, combine **Assigned by me** with **Overdue** to check delegated follow-ups. Active filters appear as removable chips below the toolbar. Remove one chip to broaden that condition, or use **Clear filters** to remove all conditions, including completion. Search is separate and has its own clear button. Filters, search and sorting stay selected when you switch between List and Board or reload the page. Use **Sort** to order by due date, priority, task name, newest or recently updated. **Default order** restores the view's usual ordering and lets you use column sorting in the grid. Open a task to inspect its details; click a related record name to open that exact record. Use **Add task** to create work from the Tasks page. ## Opening related records Record names are links in task rows, board cards, compact lists, drawers, and subtasks. If a task shows an additional-record count, open it to see every linked record. You can open links in a new tab or use browser Back to return to your task view. A lead link opens that lead's context and its associated contact. An application link opens the specific submission, including its candidate and role. Two applications from the same candidate remain separate records. **Record unavailable** means that record cannot be opened with your current access. **No related records** means none are attached; use the task's edit action to add records if you can edit the task. Access to a task does not automatically grant access to its linked records. Source links, when present, are separate from related records and open the captured meeting, transcript, conversation, or page. ## Creating Tasks ### From a Record 1. Open any contact, company, or deal 2. Click **Add Task** (or use the task icon) 3. Fill in task details: - **Title** - what needs to be done - **Assignee** - team member responsible - **Due date** - when it's due - **Priority** - no priority, low, medium, or high - **Description** - additional context or instructions 4. Click **Create Task** The task starts with the source record selected. In **Records**, search for additional records and select the exact result. Review the selected records before saving. The **Assignee** controls who owns the work; it is separate from the records the task is about. For an application, check the candidate and role before selecting it. You can also create a task from an exact lead or application detail page. Available record types depend on your organization's modules and your access. ### From the AE Cockpit The AE Cockpit surfaces tasks automatically as decision cards. When you approve an overdue task decision, it creates or highlights the relevant task. See [AE Cockpit →](https://docs.graph8.com/deals/ae-cockpit/) for details. ## Task Properties | Property | Description | |----------|-------------| | **Title** | What needs to be done | | **Assignee** | Team member responsible | | **Due date** | When it's due | | **Priority** | No priority, low, medium, or high | | **Related records** | Linked contacts, companies, deals, leads, applications, or team members | | **Status** | Open, in progress, or completed | | **Description** | Additional notes or instructions | ## Managing Tasks - **Complete** - click the checkbox to mark a task done - **Reassign** - change the assignee if someone else should handle it - **Reschedule** - update the due date to push it forward - **Delete** - remove tasks that are no longer relevant Changing the title, owner, or due date preserves the existing record links. To change the associations, edit **Records** and remove or add the specific record. Subtasks can carry their own related records and retain inherited source context. ## Working with an agent Open a task and choose an eligible Work agent under **Assigned executor**. Keep an active person as the **Accountable human**. Selecting an agent saves the assignment; it does not start work. Select **Start** when you want the agent to begin. The task shows its execution state, progress and next action when available. Open **Execution and results** to inspect the work and handle any required approvals. Execution details remain private to the person who started the run; other task readers can see its state. Queued work has not begun. Running, waiting for information, waiting for approval and blocked work are different states. Failed or cancelled runs remain in history. Retry an uncertain Start to check the same request; do not create a duplicate task. Finish or cancel an active run before reassigning the task. After the run finishes, inspect its result before completing the task. ## Approval tasks Use the dedicated **Approve** or **Reject** action on an approval request. Completing a task, moving a board card, completing a child task or applying a bulk status change does not approve work. If a decision fails, the request remains available to retry after resolving the error. ## Faster daily work Use quick creation to add a title and related record, or **Add task** for the full form. Within a record, the record is already selected. The quick form keeps your draft if saving fails. Search task text and related records, and group by status, assignee, record or meeting. Inline title edits save only after confirmation; failed saves retain the draft. Bulk actions report successes and failures separately and leave failed tasks selected for retry. Read-only users can inspect tasks and switch views without gaining editing access. ## Overdue Tasks Tasks past their due date appear highlighted in your queue. The AE Cockpit also surfaces overdue tasks as decision cards, so at-risk follow-ups don't fall through the cracks. > **Tip:** > Create tasks immediately after meetings and calls while context is fresh. Link them to the relevant contact or deal so your team has full context when they pick up the task. ## Related - [Contacts →](https://docs.graph8.com/data/customer-data-platform/) - Create tasks from contact records - [Deals →](https://docs.graph8.com/deals/overview/) - Track deal-related follow-ups - [AE Cockpit →](https://docs.graph8.com/deals/ae-cockpit/) - AI surfaces overdue tasks as decisions --- # AE Cockpit > AI-powered decision queue for account executives - prioritized actions, meeting prep, deal risk alerts, and coaching insights. Source: https://docs.graph8.com/deals/ae-cockpit/ The AE Cockpit is your daily command center. It surfaces prioritized decisions - meeting prep, at-risk deals, new leads, follow-ups - so you spend time on the actions that move pipeline, not hunting for what to do next. ## How It Works graph8 monitors your pipeline, signals, and calendar to generate **decision cards**. Each card represents one action you should take, ranked by urgency and deal value. Work through your queue from top to bottom. 1. Go to **Deals → AE Cockpit** 2. Review your prioritized decision queue 3. For each card: **Approve**, **Edit**, **Skip**, or **Defer** 4. Approved actions execute automatically ## Decision Types | Type | What It Surfaces | |------|-----------------| | **Meeting Prep** | Upcoming meeting with briefing, agenda suggestions, and talking points | | **Post-Meeting Follow-Up** | Action items from a recent meeting that need execution | | **At-Risk Deal** | Deal showing warning signs - stalled pipeline, engagement drops, competitor mentions | | **New Lead** | Qualified lead entering your pipeline that needs initial outreach | | **Re-engagement** | Cold contact or deal that should be revived based on new signals | | **Overdue Task** | Task past its due date that needs attention | | **Deal Stage Suggestion** | AI recommends moving a deal to a new stage based on activity | | **Competitive Alert** | Competitor activity detected on one of your accounts | | **Playbook Deviation** | Your deal is not following the expected sales playbook | | **Coaching Insight** | AI-generated coaching tip based on your patterns and performance | | **No Reply** | Outbound-origin email, SMS, or LinkedIn thread that hasn't received a reply past your threshold (default 7 days) | | **Intent Signal** | An account or contact you own just hit a tracked intent keyword, topic, or high-intent page | | **Champion Change** | A contact you own - or a contact on one of your deals - was reassigned to a new owner | | **Enrichment Win** | A high-value field just got populated on a target contact or account (direct phone, executive email, headcount, funding, etc.) | ## Signal Sources Decision cards are driven by three upstream signal streams: | Source | What It Feeds | Examples | |--------|--------------|----------| | **LeadSignals stream** | Signal-driven cards | Intent keyword hits, page visits, no-reply detection | | **LeadEvents stream** | Event-driven cards | Meeting reviewed, contact owner changed, deal stage moved | | **Polled scans** | Recurring sweeps (every 30 sec) | Meeting prep windows, stalled deals, overdue tasks, no-reply | Enrichment wins are bridged from the enrichment service via a dedicated subscriber - when a high-value field (direct phone, executive email, headcount, funding, etc.) lands on a contact or account you own, the cockpit picks it up within seconds. Each signal type has an org-level kill switch (Settings → AE Cockpit → Signals) so you can mute a noisy source without disabling the cockpit entirely. Per-org daily caps (default 50 cards per generator) protect against runaway emission. ## Priority Scoring Each decision card carries a priority score (0-100) based on five factors: | Factor | Weight | Example | |--------|--------|---------| | **Time urgency** | High | Meeting in 2 hours scores higher than meeting tomorrow | | **Deal value** | Medium | $100K deal outranks $10K deal | | **Risk level** | Medium | Deal stalled 14+ days gets priority boost | | **Signal recency** | Low | Signal from 1 hour ago ranks above signal from yesterday | | **Engagement momentum** | Low | Sudden 50% drop in engagement triggers alert | Cards are sorted by score, so the most urgent and valuable actions appear first. ## Working the Queue ### Approve Click **Approve** to accept the AI's recommended action. The action executes automatically - an email sends, a task creates, a follow-up schedules. ### Edit Click **Edit** to modify the recommended action before executing. Change the email content, adjust the timing, or update the approach. ### Skip Click **Skip** to dismiss a card without acting. Use this when the recommendation doesn't apply (e.g., you already handled it outside graph8). ### Defer Click **Defer** to push a card to later. It drops in priority and resurfaces at a more appropriate time. ## Decision Statuses | Status | Meaning | |--------|---------| | **Pending** | Waiting for your action | | **Approved** | You approved - action executed | | **Edited** | You modified and executed | | **Skipped** | Dismissed without action | | **Deferred** | Pushed to later | | **Auto-Executed** | Executed automatically by autopilot (see below) | | **Undone** | Action was reversed within the undo window | ## Undo System Every approved or auto-executed action can be undone within a **30-minute window**. If you approve a follow-up email and immediately realize the timing is wrong, click **Undo** to reverse it before it sends. After the 30-minute window closes, the action is final. ## Autonomy Lanes Control how much the AI does on its own for each decision type: | Lane | Behavior | |------|----------| | **Autopilot** | AI executes automatically - you review after the fact | | **Approve** | AI recommends, you approve before execution | | **Off** | AI does not generate this decision type | Configure autonomy per decision type in **Settings → AE Cockpit**. Start with everything on **Approve** and move high-confidence, low-risk actions to **Autopilot** as you build trust. > **Tip:** > Start by reviewing every decision manually. After 8+ consecutive approvals of a decision type without edits, graph8 suggests promoting it to autopilot. This gradual handoff ensures the AI earns your trust before acting independently. ## Real-Time Updates The cockpit refreshes automatically every 30 seconds. New decisions appear as signals arrive - meeting reminders, risk alerts, and lead notifications surface without manual refresh. ## Pulse Sidebar The Pulse sidebar provides a real-time snapshot of your pipeline health: - **Active deals** - count and total value - **Meetings today** - upcoming calendar events with prep status - **Signals** - recent intent signals across your accounts - **Pending decisions** - how many cards need your attention - **Completed today** - decisions you've already resolved ## Related - [Deals Overview →](https://docs.graph8.com/deals/overview/) - Pipeline views and deal management - [Prospects →](https://docs.graph8.com/deals/prospects/) - Active deals in progress - [Deal Contacts →](https://docs.graph8.com/deals/deal-contacts/) - Buying committee mapping - [Pipeline Settings →](https://docs.graph8.com/deals/pipeline-settings/) - Customize stages and probabilities --- # Customers > Manage closed-won accounts - track post-sale activity, spot upsell opportunities, and monitor customer health. Source: https://docs.graph8.com/deals/customers/ Customers are accounts with closed-won deals. Track post-sale activity, identify upsell and expansion opportunities, and keep relationships strong. ## Viewing Customers 1. Go to **Deals → Customers** 2. Browse all closed-won accounts in list view 3. Filter by close date, deal value, assigned rep, or product Each customer record shows: - Company name and primary contact - Original deal value and close date - Active subscriptions and products - Last activity date - Signal indicators for upsell intent ## Customer Signals graph8 continues tracking signals after a deal closes: | Signal | What It Means | |--------|--------------| | **Website visits** | Customer is browsing your site (especially pricing or new product pages) | | **Keyword research** | Customer is researching topics related to your other products | | **Engagement spikes** | Sudden increase in email opens or content downloads | | **Usage changes** | Changes in product usage patterns | These signals surface upsell and expansion opportunities automatically. The AE Cockpit includes customer signals in its decision queue. ## Creating Upsell Deals When a customer shows expansion intent: 1. Open the customer record 2. Click **Create Deal** to start a new opportunity 3. The deal links to the existing customer account 4. Track the upsell through your pipeline like any other deal ## Customer Health Monitor relationship health across your customer base: - **Active customers** with recent engagement - **At-risk customers** with declining signals or no activity - **Expansion candidates** showing upsell signals > **Tip:** > Set up a recurring pipeline review for your customer base. Customers who go silent for 30+ days without any signal activity are at churn risk - reach out before it's too late. ## Related - [Prospects →](https://docs.graph8.com/deals/prospects/) - Active deals not yet closed - [Products & Services →](https://docs.graph8.com/deals/products/) - Revenue tied to customer subscriptions - [Deal Contacts →](https://docs.graph8.com/deals/deal-contacts/) - Buying committee contacts on each deal - [AE Cockpit →](https://docs.graph8.com/deals/ae-cockpit/) - AI surfaces re-engagement and upsell decisions --- # Deal Contacts & Notes > Manage buying committee contacts on deals - assign roles like champion, decision maker, and blocker. Add notes with @mentions for team collaboration. Source: https://docs.graph8.com/deals/deal-contacts/ Deal Contacts let you map the buying committee on each deal - assign roles to contacts so your team knows who the champion is, who makes the final decision, and who might block the deal. ## Adding Contacts to a Deal 1. Open a deal from the **Deals** board or list view 2. Click the **Contacts** tab 3. Click **Add Contact** 4. Search for an existing contact or create a new one 5. Assign a **buying role** (see below) 6. Click **Save** ## Buying Roles Each contact on a deal can be assigned one role: | Role | Description | Why It Matters | |------|-------------|----------------| | **Champion** | Internal advocate pushing for your solution | Your primary ally - keep them armed with ammunition | | **Decision Maker** | Has final budget and sign-off authority | Must be engaged before proposal stage | | **Influencer** | Shapes the decision without final authority | Can accelerate or slow the process | | **Blocker** | Actively resists or creates obstacles | Identify early and build a strategy to address concerns | | **Coach** | Provides insider information and guidance | Valuable source of intel on the buying process | | **End User** | Will use the product day-to-day | Their input drives adoption and renewal | > **Tip:** > A healthy deal has at least a Champion and Decision Maker identified. If you only have end users, the deal is at risk of stalling. ## Managing Deal Contacts ### Changing a Role Click the role badge next to any contact to update their role. Role changes are logged in deal activity. ### Removing a Contact Click the remove icon next to a contact to unlink them from the deal. This does not delete the contact - it only removes the deal association. ## Deal Notes Add internal notes to deals for team collaboration. ### Creating a Note 1. Open a deal and go to the **Notes** tab 2. Type your note in the text field 3. Use **@** to mention team members (they receive a notification) 4. Click **Save** ### @Mentions Type `@` followed by a team member's name to mention them. Mentioned users receive a notification so they can review the note. ### Editing and Deleting - Click any note to edit it - Click the delete icon to remove a note ## Related - [Deals Overview →](https://docs.graph8.com/deals/overview/) - Pipeline views and deal management - [Prospects →](https://docs.graph8.com/deals/prospects/) - Managing active deals - [Pipeline Settings →](https://docs.graph8.com/deals/pipeline-settings/) - Customize stages and probabilities --- # Leads > Unqualified to qualifying records — work the lead lifecycle with status, lifecycle stage, scoring, and bulk actions. Source: https://docs.graph8.com/deals/leads/ The Leads view is where unqualified-to-qualifying records live before they convert into deals. It surfaces every contact and company moving through the early lifecycle stages — Subscriber, Lead, MQL, SQL — with status, score, source, and owner all in one grid. ## Opening the Leads View 1. Click **Revenue** in the top nav 2. Click **Leads** 3. The full-width AG Grid loads with every lead in your org The grid uses server-side pagination, so it scales to millions of records without slowing the browser. ## Lead Statuses A lead's **status** captures where it is in the qualification process — independent of its lifecycle stage: | Status | When to Use | |--------|-------------| | **New** | Just arrived, not yet reviewed | | **Open** | Reviewed and assigned, ready to work | | **In Progress** | Currently being worked by a rep | | **Contacted** | Initial outreach attempted | | **Qualified** | Met qualification criteria — ready to convert | | **Unqualified** | Doesn't meet criteria — disqualified | Status changes are tracked in the lead's activity timeline. ## Lifecycle Stages The **lifecycle stage** is the broader marketing-to-sales journey — separate from status but evolving alongside it: | Lifecycle Stage | Definition | |-----------------|------------| | **Subscriber** | Opted into communications (newsletter, content) but no deeper qualification | | **Lead** | Demonstrated interest beyond subscription (form fill, demo request) | | **MQL** | Marketing-qualified — meets ICP criteria and engagement thresholds | | **SQL** | Sales-qualified — confirmed fit and intent through sales conversation | | **Opportunity** | Active deal in pipeline (appears in Deals view too) | | **Customer** | Closed-won (appears in Customers view too) | | **Evangelist** | Customer who refers, advocates, or amplifies | A single record progresses through both axes — status tracks short-term work, lifecycle tracks long-term journey. A "Contacted" status with "Lead" lifecycle is normal; an "Unqualified" status with "MQL" lifecycle means the lifecycle should likely be pushed back. ## Grid Columns The leads grid shows these fields by default: | Column | What It Shows | |--------|---------------| | **Name** | Contact full name (link to contact detail) | | **Status** | One of the 6 lead statuses above | | **Pipeline** | Which pipeline this lead belongs to (if multi-pipeline org) | | **Pipeline Stage** | Current stage within the pipeline | | **Lifecycle Stage** | Subscriber → Evangelist scale | | **Score** | Lead score (intent + fit + engagement) | | **Source** | How the lead arrived (form, intent signal, sequence reply, etc.) | | **Qualified By** | Last signal type that triggered qualification | | **Type** | Lead type classification | | **Converted** | Whether the lead has been converted to a deal | | **Converted Date** | When conversion happened | | **Owner** | Assigned rep | | **CRM Source** | If imported from a CRM, which one | | **Email** | Contact email | Use the **Columns** panel on the right edge of the grid to add, remove, or reorder columns. ## Bulk Actions Select one or more rows to enable bulk action buttons in the toolbar: | Action | What It Does | |--------|--------------| | **Status** | Change status for all selected leads | | **Lifecycle** | Change lifecycle stage for all selected leads | | **Owner** | Reassign all selected leads to a specific user | | **Generate Content** | Run AI content generation across the selection (uses your global context) | | **Batch Generate** | Generate content for many leads in one batched job | | **Delete** | Remove selected leads from the org (cannot be undone) | Bulk actions update the grid in place — no page refresh needed. ## Lead Sources Every lead has a `lead_source` field indicating how it arrived: | Source | How It Arrived | |--------|----------------| | **Form submissions** | Filled out a website form (tracked via the graph8 snippet) | | **Intent signals** | Researching your keywords or visiting your site | | **Sequence replies** | Positive replies from outbound sequences | | **Manual** | Added by a rep directly from a contact record | | **Find People** | Discovered via the Find People tool with ICP filters | | **CRM sync** | Imported from HubSpot, Salesforce, Pipedrive, or Zoho | | **API / webhook** | Pushed in via the public API or a custom integration | ## Lead Scoring Leads are scored automatically. The score combines four factors: | Factor | What It Measures | |--------|------------------| | **Engagement signals** | Website visits, email opens, link clicks | | **Intent data** | Keyword research activity, competitor comparisons | | **Profile fit** | How well the contact matches your ICP | | **Recency** | Newer signals score higher than stale ones | Configure scoring rules in **Settings → Lead Qualification** — toggle which event types trigger qualification, define page patterns that count as high-intent, and set thresholds. > **Tip:** > Set up lead scoring early. Without scores, your team reviews every lead equally. With scores, they focus on the hottest opportunities first — the ones most likely to convert. ## Converting a Lead When a lead is ready to become an active deal: 1. Click the lead's name to open its detail view 2. Click **Convert to Deal** 3. graph8 creates a deal in **Prospects** linked to this contact + company 4. The lead's `is_converted` flag flips to true and `converted_date` is recorded 5. The lifecycle stage advances to **Opportunity** Converted leads stay visible in the Leads grid (filtered by `converted=true`) for historical tracking. ## Generate Content The **Generate Content** action runs AI content generation across selected leads. It uses your global context (brand brief, value props, personas) to draft personalized messages for each contact: - **Single lead** — open the lead, click **Generate Content**, pick a content type (email, LinkedIn message, call script) - **Bulk** — select multiple leads, click **Batch Generate**, configure the job and let it run in the background Each generation costs credits per lead. Track progress and review results in the **Activity** tab. ## CSV Export Click the **Download** icon in the grid toolbar to export the current view as CSV. The export respects your active filters — so if you've filtered to "MQL + Status = Qualified", only those rows export. ## Lead Qualification Settings Lead qualification rules are configured at **Settings → Lead Qualification**: - Toggle which signal events count toward qualification (email replies, calls, LinkedIn responses, form fills, web visits, meetings) - Define **page patterns** — URL patterns that score as high-intent (e.g., pricing pages, demo pages) - Trigger a **backfill** to re-evaluate historical signals against current rules See [Settings → Lead Qualification →](https://docs.graph8.com/settings/roles/) for the full qualification configuration. ## Related - [Deals Overview →](https://docs.graph8.com/deals/overview/) — How leads flow into the broader pipeline - [Prospects →](https://docs.graph8.com/deals/prospects/) — Where converted leads live as active deals - [AE Cockpit →](https://docs.graph8.com/deals/ae-cockpit/) — AI surfaces hot leads as decision cards - [Signals →](https://docs.graph8.com/signals/overview/) — Where intent signals come from - [Enrichment →](https://docs.graph8.com/ai-features/enrichment/) — Fill in missing lead data before outreach --- # Deals Overview > Manage your sales pipeline from lead to close with deals, forecasting, revenue tracking, and AI-powered deal management. Source: https://docs.graph8.com/deals/overview/ The Deals section is your pipeline command center. Track every opportunity from first signal to closed revenue - leads, prospects, customers, and products all live here, with AI-powered coaching and decision support. ## Pipeline Stages graph8 ships with a configurable pipeline. Each stage carries a win probability for forecasting: | Stage | Probability | |-------|------------| | Lead | 10% | | Qualified | 20% | | Meeting | 40% | | Proposal | 60% | | Negotiation | 80% | | Closed Won | 100% | | Closed Lost | 0% | Customize stages in [Pipeline Settings →](https://docs.graph8.com/deals/pipeline-settings/) - add, rename, remove stages, adjust probabilities, or create multiple pipelines for different sales motions. ## Views | View | Best For | |------|----------| | **Board** | Drag-and-drop deals across stages - visual pipeline management | | **List** | Sortable, filterable table for bulk actions and data analysis | | **Forecast** | Revenue projections by stage, rep, or time period | ## Pipeline Workflow ``` Signals → Leads → Prospects → Customers ↓ ↓ ↓ Qualify Work Deal Upsell ``` 1. **Signals** surface qualified interest (intent data, form fills, sequence replies) 2. **Leads** collect and score incoming opportunities 3. **Prospects** track active deals through your pipeline stages 4. **Customers** manage post-sale relationships and expansion ## AI-Powered Deal Management The [AE Cockpit →](https://docs.graph8.com/deals/ae-cockpit/) adds AI intelligence to your pipeline: - **Decision queue** - prioritized actions based on deal value, urgency, and risk - **Meeting prep** - briefings generated before every call - **At-risk alerts** - early warnings when deals show danger signs - **Coaching insights** - AI recommendations for deal strategy - **Autonomy lanes** - let AI handle routine decisions automatically ## CRM Integration Deals sync with external CRMs: - **HubSpot** - two-way deal sync - **Salesforce** - two-way deal sync - **Pipedrive** - two-way deal sync - **Zoho CRM** - two-way deal sync Deal stage changes, contact associations, and activity log in both systems. ## Next Steps - [Leads →](https://docs.graph8.com/deals/leads/) - Qualified interest entering your pipeline - [Prospects →](https://docs.graph8.com/deals/prospects/) - Active deals in progress - [Customers →](https://docs.graph8.com/deals/customers/) - Closed business and upsell - [Products & Services →](https://docs.graph8.com/deals/products/) - Revenue and subscriptions - [AE Cockpit →](https://docs.graph8.com/deals/ae-cockpit/) - AI-powered decision queue - [Deal Contacts →](https://docs.graph8.com/deals/deal-contacts/) - Buying committee mapping - [Pipeline Settings →](https://docs.graph8.com/deals/pipeline-settings/) - Customize stages and probabilities --- # Pipeline Settings > Create and customize deal pipelines - add stages, set probabilities, and configure multiple pipelines for different sales processes. Source: https://docs.graph8.com/deals/pipeline-settings/ Pipeline Settings let you define how deals move through your sales process - create custom stages, set win probabilities, and run multiple pipelines for different deal types. ## Accessing Pipeline Settings Go to **Settings → Deals → Pipelines** to manage your pipelines and stages. ## Default Pipeline Every organization starts with a default pipeline containing seven stages: | Stage | Probability | Purpose | |-------|------------|---------| | **Lead** | 10% | Initial contact, not yet qualified | | **Qualified** | 20% | Meets basic criteria, worth pursuing | | **Discovery** | 30% | Active discovery, understanding needs | | **Proposal** | 50% | Proposal or demo delivered | | **Negotiation** | 70% | Terms being discussed | | **Closed Won** | 100% | Deal won | | **Closed Lost** | 0% | Deal lost | ## Customizing Stages ### Adding a Stage 1. Go to **Settings → Deals → Pipelines** 2. Select your pipeline 3. Click **Add Stage** 4. Enter the stage name, probability (0-100%), and color 5. Click **Save** ### Editing a Stage 1. Click the stage name in the pipeline view 2. Update the name, probability, or color 3. Click **Save** ### Reordering Stages Drag and drop stages to reorder them. The order defines how deals progress from left to right in the board view. ### Deleting a Stage 1. Click the delete icon next to the stage 2. If deals exist in that stage, you will be prompted to move them to another stage first 3. Confirm deletion > **Caution:** > Deleting a stage is permanent. Move any active deals to a different stage before deleting. ## Multiple Pipelines Create separate pipelines for different sales processes (e.g., "Enterprise Sales", "SMB Self-Serve", "Partner Deals"). ### Creating a New Pipeline 1. Go to **Settings → Deals → Pipelines** 2. Click **Create Pipeline** 3. Name the pipeline 4. Add stages with probabilities 5. Click **Save** ### Setting a Default Pipeline One pipeline is always the default (used when creating deals without specifying a pipeline): 1. Go to **Settings → Deals → Pipelines** 2. Click the three-dot menu on the pipeline you want as default 3. Select **Set as Default** ### Switching Pipelines in the Board View In **Deals → Board**, use the pipeline dropdown at the top to switch between pipelines. Each pipeline shows its own stages and deals. ## Stage Probabilities and Forecasting Stage probabilities drive your revenue forecast: - A deal worth $10,000 at the "Proposal" stage (50%) shows as $5,000 in weighted pipeline - Moving a deal to "Negotiation" (70%) updates the forecast to $7,000 - "Closed Won" (100%) counts as actual revenue > **Tip:** > Set probabilities that match your actual historical conversion rates. Review and adjust quarterly based on your team's win rates at each stage. ## Related - [Deals Overview →](https://docs.graph8.com/deals/overview/) - How the deal pipeline works - [Prospects →](https://docs.graph8.com/deals/prospects/) - Managing active deals - [Roles →](https://docs.graph8.com/settings/roles/) - Control who can edit pipeline settings --- # Products & Services > Manage your product catalog - connect Stripe, track subscriptions, and attach products to deals for accurate revenue tracking. Source: https://docs.graph8.com/deals/products/ Products & Services connects your Stripe product catalog to your pipeline. View products, track subscriptions, attach line items to deals, and monitor recurring revenue. ## Connecting Stripe 1. Go to **Settings → Integrations** 2. Find **Stripe** and click **Connect** 3. Authorize graph8 to access your Stripe account 4. Your products, prices, and subscriptions sync automatically Once connected, graph8 pulls your full product catalog and keeps it updated. ## Product Catalog View all your Stripe products in **Deals → Products & Services**: | Column | What It Shows | |--------|-------------| | **Product name** | Name from your Stripe catalog | | **Pricing** | Price tiers and billing intervals (monthly, annual, one-time) | | **Active subscriptions** | How many customers are on this product | | **MRR contribution** | Monthly recurring revenue from this product | ## Attaching Products to Deals When creating or editing a deal, attach products to track what the prospect is buying: 1. Open a deal 2. Go to the **Products** section 3. Click **Add Product** 4. Select from your Stripe catalog 5. Set quantity and any custom pricing 6. The deal value updates automatically based on attached products This ensures your pipeline value reflects actual product pricing, not estimates. ## Subscription Tracking For customers with active subscriptions: - **Renewal dates** - see when subscriptions renew - **MRR** - monthly recurring revenue per customer - **Plan changes** - upgrades, downgrades, and cancellations ## Revenue Reporting Products data feeds into pipeline forecasting: - **Revenue by product** - which products drive the most pipeline - **Average deal size** - by product and plan tier - **Win rates** - by product (some products close faster than others) > **Tip:** > Attach products to deals early in the pipeline. This makes your forecast more accurate and helps identify which products have the strongest demand signal. ## Related - [Customers →](https://docs.graph8.com/deals/customers/) - Companies with active subscriptions - [Prospects →](https://docs.graph8.com/deals/prospects/) - Active deals with attached products - [Pipeline Settings →](https://docs.graph8.com/deals/pipeline-settings/) - Customize stages and probabilities - [Billing →](https://docs.graph8.com/settings/billing/) - Manage your own graph8 subscription --- # Prospects > Companies with active deals in the pipeline - track progress from qualified to closed. Source: https://docs.graph8.com/deals/prospects/ Prospects are companies with active deals moving through your pipeline. This is where your team manages day-to-day deal progression - from qualified opportunity to closed revenue. ## Views ### Board View Drag-and-drop deals across pipeline stages. Each card shows: - Deal name and company - Deal value - Days in current stage - Assigned rep - Signal indicators Drag a card from one stage to another to update its progress. ### List View A sortable, filterable table of all active deals. Columns include deal name, company, stage, value, probability, close date, assigned rep, and last activity. Use list view for: - **Bulk actions** - select multiple deals and update stage, assign rep, or add tags - **Sorting** - sort by value, close date, or days in stage - **Filtering** - filter by stage, rep, value range, or custom fields ### Forecast View Revenue projections broken down by stage, rep, or time period. Each stage's forecast = deal value multiplied by stage probability. ## Deal Detail Click any deal to open the full detail view: | Tab | What It Shows | |-----|-------------| | **Overview** | Deal value, stage, close date, assigned rep, key dates | | **Contacts** | Buying committee with roles (champion, decision maker, etc.) | | **Activity** | Timeline of all emails, calls, meetings, and stage changes | | **Signals** | Aggregated intent signals from all deal contacts | | **Notes** | Internal notes with @mentions for team collaboration | | **Documents** | Attached proposals, contracts, and files | | **Intelligence** | AI-generated deal insights - competitive analysis, ROI analysis, and recommended next steps | ## Deal Signals Each deal shows aggregated signals from all associated contacts: - **Signal score** - combined activity across all deal contacts - **Intent activity** - are contacts visiting your site or researching keywords? - **Email engagement** - are they responding to sequences? - **Meeting activity** - bookings, cancellations, and no-shows > **Tip:** > Use deal signals during pipeline reviews to spot at-risk opportunities early. A deal with declining signals and no recent activity is likely stalling - the AE Cockpit will surface these as at-risk decisions automatically. ## Creating a Deal 1. Click **Create Deal** from the board or list view 2. Fill in: - **Deal name** - a descriptive name (e.g., "Acme Corp - Enterprise License") - **Company** - link to an existing company record - **Value** - expected deal value - **Stage** - starting pipeline stage - **Close date** - expected close date - **Assigned to** - the rep owning this deal 3. Click **Save** ## Moving Deals Move deals through stages by: - **Dragging** on the board view - **Editing** the stage field in the deal detail - **Bulk updating** from list view - **AI suggestion** - the AE Cockpit recommends stage changes based on activity Stage changes are logged in the deal activity timeline with timestamps and the user who made the change. ## Related - [Leads →](https://docs.graph8.com/deals/leads/) - Where new opportunities enter the pipeline - [Customers →](https://docs.graph8.com/deals/customers/) - Where closed deals land - [Deal Contacts →](https://docs.graph8.com/deals/deal-contacts/) - Map the buying committee - [AE Cockpit →](https://docs.graph8.com/deals/ae-cockpit/) - AI-prioritized deal actions - [Pipeline Settings →](https://docs.graph8.com/deals/pipeline-settings/) - Customize stages and probabilities --- # Quotes > Create, send and track quotes: smart defaults, installments, discount approval, e-signature, forwarding to another signer, and paying right after signing. Source: https://docs.graph8.com/deals/quotes/ Quotes lets your team build, send, and track sales quotes without leaving graph8. Pull line items from your Stripe catalog, attach contract terms, send for signature, and watch the quote progress from draft to accepted in real time. ## Quote Lifecycle Every quote moves through a sequence of statuses: | Status | Meaning | |--------|---------| | **Draft** | Being authored, not yet sent to the prospect | | **Sent** | Delivered to the signer's email | | **Viewed** | Signer has opened the quote at least once | | **Accepted** | Signer has signed and accepted the quote | | **Declined** | Signer rejected the quote | | **Expired** | Quote passed its `valid_until` date without being accepted | Status transitions are automatic: graph8 updates them as the prospect interacts with the quote. Two extra badges show where a quote is waiting on the buyer: | Badge | Meaning | |-------|---------| | **Forwarded** | The buyer handed the signature to a colleague, who has not signed yet | | **Signed, awaiting payment** | The quote is signed and the first invoice is not paid yet | ## Creating a Quote The quote builder walks you through five steps: **Deal & buyer**, **Products & pricing**, **Payment & terms**, **Signature** and **Review & send**. A live preview on the right updates as you type, and you can **Save as draft** at any step. 1. Go to **Revenue → Quotes** and click **New quote** (or open a deal and click **New Quote** on its Quotes tab) 2. **Deal & buyer**: pick the deal. Recent deals show first, and if only one deal is open it is picked for you. The buyer (signer), title and company details fill in from the deal 3. **Products & pricing**: add line items (see below) 4. **Payment & terms**: check payment terms, contract dates, payment methods and installments 5. **Signature**: confirm who signs and who gets a copy 6. **Review & send**: check exactly what the buyer receives, then **Save & send** or **Save & generate link** graph8 fills in as much as it can for you: | Field | Default | |-------|---------| | **Signer** | The deal's contact | | **Title** | Generated from the company and month | | **Valid until** | Your quote settings, else 30 days | | **Payment terms** | The template, else your settings, else Net 30 | | **Contract term** | 12 months as soon as any line is recurring | | **Billing start** | The contract start date | | **Billing contact** | The signer (see below) | The **Needs your input** box at the top only lists what is still missing for this quote. ### Separate billing contact By default the person who signs is also the person who gets the invoice, so you do not need to fill in any billing contact or billing email. If invoices should go to someone else (for example finance or accounts payable), tick **Separate billing contact** in the **Deal & buyer** step. The billing contact picker and billing email appear so you can choose that person. - The box starts ticked when the deal already has a contact with the **billing** role, or when you edit a quote whose billing contact is not the signer - Unticking it never deletes what you typed. It just goes back to using the signer - Company legal name and address stay visible when your quote settings require them > **Tip:** > The buyer can also send the payment step to their finance team after signing, so you do not need to know the billing contact up front. ## Line Items Line items are the products and services on the quote. Each line pulls from your Stripe catalog or can be a custom one-off. | Field | Description | |-------|-------------| | **Product** | Pulled from your Stripe catalog (or custom name for one-offs) | | **Description** | Optional free-text description shown to the signer | | **Quantity** | Number of units | | **Unit amount** | Price per unit (in cents internally; entered as currency) | | **Discount %** | Per-line discount applied before tax | | **Billing frequency** | Monthly, annual, one-time, or custom | | **Line total** | Auto-calculated: `quantity × unit_amount × (1 - discount_pct)` | The quote preview and PDF show your configured company logo and, when available, the linked customer company’s stored logo under **Prepared for**. If no customer logo is stored, the company name still appears. ### Choose when billing starts For each recurring service, use **Billing starts** to choose **At contract start**, **After 1 month**, **After 2 months**, or a later month. To choose a calendar date instead, select **On a specific date** and fill in the date picker. The contract start date should match the agreed kickoff date. For example, **After 2 months** on a January 31 contract starts March 31. A specific March 15 start renews on that service's own anniversary. Dates must fall within the contract. One-time charges remain at contract start. Save and reopen the draft to review each choice. Preview and PDF show a payment schedule, including intervening renewals through the last service start. Usage or performance fees remain additional where specified. The collection-mode notice identifies Stripe versus manual billing; saving a draft starts neither. Stripe-backed quotes cannot be issued with dates outside Stripe's supported 730-day scheduling window. ### Adding a Line Item from Stripe 1. Click **Add Line Item → From Stripe** 2. Search by product name 3. Pick a price tier (Stripe products can have multiple price points) 4. Set quantity and any per-line discount 5. Click **Add** The Stripe product name, price, and billing frequency are pre-filled. You can override description and discount per line. ### Custom Line Items For services, one-off implementations, or anything not in Stripe: 1. Click **Add Line Item → Custom** 2. Enter product name, description, unit amount, and quantity 3. Set billing frequency manually Custom line items don't sync back to Stripe. They're local to the quote. ## Quote Totals The quote totals row shows: | Total | Calculation | |-------|-------------| | **Subtotal** | Sum of all line totals | | **Discount** | Cumulative discounts applied | | **Tax** | Tax amount (per your org tax settings) | | **Total** | Subtotal − Discount + Tax | All amounts respect the quote's selected currency. ## Payment Installments Split the quote total into several payments in **Payment & terms → Payment installments**. 1. Click **Add installment** 2. Enter a percentage or an amount, plus a milestone label (for example "On signature") 3. Watch the balance line: it shows **Remaining $X** or **Over by $X** 4. Click **Auto-balance** to put the remainder on the last row so the schedule adds up exactly The schedule must add up to the quote total to the cent before you can send. ## Discount Approval Quotes with a discount above your limit (default 15%) or a margin below your minimum (default 5%) need approval before the buyer receives them. - The **Review & send** step tells you why before you send, for example "Discount 22% is above your 15% limit. This goes to Owner/Admin for approval." - The send button reads **Request approval**. The buyer gets nothing until the quote is approved - The quote shows "Waiting on an approver for 2 hours" while it waits - Once approved, click **Send now** on the quote to send it in one step - If it is rejected, the reason shows on the quote. Edit it and click **Resubmit for approval** Changing price, quantity or discount after approval clears the approval, so the new numbers are checked again. ## Contract Terms Beyond line items, quotes capture contract terms that govern the sale. ### Standard Terms | Field | Purpose | |-------|---------| | **Payment terms** | When payment is due (e.g., "Net 30", "Upon receipt") | | **Contract months** | Length of the contract in months | | **Contract start date** | When the contract begins | | **Contract end date** | When the contract ends | | **Accepted payment methods** | Which payment methods the signer can use | | **Marketing rights** | Whether you can use the customer in case studies, logos, press | ### Terms Templates Save reusable term sets in **Settings → Quotes → Terms** so you don't re-type them on every quote. Pick a template when creating a quote and the standard fields auto-populate. ### Custom Terms and Privacy Content | Field | What It Holds | |-------|---------------| | **Terms content** | Full contract terms, typically a long-form legal block | | **Privacy content** | Privacy and data-handling clauses | | **Notes** | Internal notes (not visible to the signer) | | **Public notes** | Notes that appear on the signer-facing quote page | Use a rich text or HTML format. The quote renderer escapes appropriately for safe display. ## Quote Templates Reusable quote skeletons let you spin up consistent quotes quickly. 1. Go to **Settings → Quotes → Templates** 2. Click **New Template** 3. Build it like a regular quote: terms, line items, payment terms 4. Save the template When creating a quote, pick a template and all fields pre-populate. You can then customize per-prospect. ### Template Documents Templates can include attached documents (e.g., NDAs, MSAs): | Doc Type | Common Use | |----------|-----------| | **NDA** | Mutual non-disclosure | | **MSA** | Master service agreement | | **Other** | Any custom attachment | Attached documents are included in the signature envelope alongside the quote. ## Sending a Quote 1. Open a draft quote 2. Check the **Email** tab: it shows the exact email the buyer will get, marked **Preview, not sent yet** 3. Click **Send** 4. Confirm the signer's email 5. Optionally customize the email body 6. Click **Send Now** graph8 generates a public token and viewer token for the quote, then emails the signer a link. The status flips to **Sent**. When the signer opens the link, status flips to **Viewed**. When they sign, status flips to **Accepted**. ### What the Signer Sees The signer-facing page shows: - Your company name, logo, and brand colors - All line items with descriptions and totals - Contract terms (the public ones; internal notes are hidden) - Accepted payment methods - A signature pad for accepting Signers do not need a graph8 account. The public link is sufficient. ### Forwarding to another signer If the person who received the quote is not the one who should sign, they can hand it over without contacting you. 1. On the signing page, the buyer clicks **Someone else needs to sign** 2. They enter the colleague's email, and optionally a name and a note 3. If the email is not on the company's domain, an amber warning asks them to confirm. It never blocks the forward 4. A confirm step reminds them "You will no longer be able to sign this quote" 5. The colleague gets their own signing link by email What happens next: - The colleague **replaces** the original signer. The original link stops working and shows "This quote was forwarded to ..." with your contact details - The colleague is added as a contact on the company and the deal - You get a notification, and the quote timeline shows **Forwarded for signature**, with an **External domain** badge when it went outside the company domain - The quote shows the **Forwarded** badge until the colleague signs - No approval from you is needed ### Paying after signing When the quote is signed, the buyer sees: - **Pay $X due now**: opens the invoice so they can pay straight away. With installments, this is the first payment - **Send to finance/AP**: sends the payment step to someone else (see below) - **Download signed copy**: the signed PDF If your billing system does not take online payment, the page says the invoice will be emailed instead. Payment states are shown clearly: pending, didn't go through (with **Try again**), and paid. ### Sending payment to finance 1. After signing, the buyer clicks **Send to finance/AP** and enters the finance contact's email 2. The finance contact gets a pay-only link: they see the amount, the quote and **Pay**, but no signing controls or signer details 3. They become the quote's billing contact and a **billing** contact on the deal 4. The quote timeline shows **Payment sent to finance** ## E-Signature Accepted quotes flow through the e-signature workflow: | Field Captured | Source | |----------------|--------| | **Signer name** | Typed by the signer at signing | | **Signer IP** | Auto-captured from the signing session | | **User agent** | Auto-captured from the signing session | | **Accepted at** | Timestamp of signature | graph8 stores an envelope record with these audit fields. If your org uses a third-party e-sign provider (DocuSign, etc.), the envelope ID syncs back so you have a single source of truth. ### PDF Generation After acceptance, graph8 renders the signed quote as a PDF and stores it in S3. The `pdf_s3_key` is recorded on the quote record. Reps can download the signed PDF from the quote detail page. ## Tracking Quote Activity Every interaction with the quote logs to its activity timeline: | Action | Logged As | |--------|-----------| | **Quote created** | Actor: rep | | **Quote sent** | Actor: rep, with email destination | | **Quote viewed** | Actor: signer (anonymous), with IP + user agent | | **Quote accepted** | Actor: signer, with signature metadata | | **Quote declined** | Actor: signer, with optional reason | | **Quote expired** | Actor: system, automatic on `valid_until` | | **Quote duplicated** | Actor: rep, with source quote ID | | **Forwarded for signature** | Actor: signer, with the new signer's email and an external-domain flag | | **Payment sent to finance** | Actor: signer, with the finance contact's email | The quote detail **Overview** also has a **Signers and payers** panel: the current signer, who forwarded to whom and when, and anyone who was sent the payment step. The timeline is visible on every quote detail page. ## Duplicating a Quote To create a similar quote (e.g., a renewal or a tweaked version): 1. Open the source quote 2. Click **Duplicate** 3. graph8 creates a draft copy with all line items, terms, and metadata 4. Update any differences (signer, dates, line items) 5. Save and send ## Stripe Integration When connected to Stripe (see **Settings → Integrations**): - Quote line items can pull directly from your Stripe product catalog - Signing a quote sets up billing automatically: the customer, any subscriptions and the first invoice - Subscription line items create Stripe subscriptions on signing Choose the billing system for quotes in **Settings → Quotes → Settings**. ## Quote Settings Org-wide quote behavior is configured at **Settings → Quotes**: | Setting | What It Controls | |---------|------------------| | **Default currency** | Currency used for new quotes | | **Default expiration** | Days until a quote auto-expires (e.g., 30) | | **Default payment terms** | Net 30, Net 60, etc. | | **Default tax rate** | Applied to quotes unless overridden | | **Billing provider** | Which billing system sets up the customer and invoice when a quote is signed | | **Approval limits** | Maximum discount and minimum margin before a quote needs approval | | **Email branding** | From name, From email, signature block | ## Permissions | Role | Capabilities | |------|-------------| | **Admin** | Create, edit, send, delete any quote; manage templates and settings | | **Member** | Create, edit, send, delete their own quotes | | **Viewer** | Read-only access to quotes | ## Limits & gotchas - Forwarding is limited to a few forwards per quote per day, to stop abuse of the email - Only the signer can forward the signature. A finance contact can pay but cannot forward the payment link again - A forwarded link cannot be used again. If the wrong address was typed, resend the quote from the quote page - Editing a quote that was already sent takes it back to draft and sends a new link. Older links stop working - Payment after signing needs a billing integration that supports online payment. Otherwise the buyer is told the invoice will be emailed ## Troubleshooting | Issue | Fix | |-------|-----| | Signer reports the quote link doesn't work | Check that the quote isn't expired or accepted; resend if needed | | Stripe products don't show in line items | Verify Stripe is connected at **Settings → Integrations** and the product list has refreshed | | Tax amount looks wrong | Confirm the org's tax rate is set correctly in **Settings → Quotes** | | Quote stuck in "Sent", signer says they got the email | The signer may not have clicked the link yet; status flips to "Viewed" only on click | | Can't delete a quote | Accepted quotes cannot be deleted (compliance / audit). Delete only Draft or Expired quotes | | Signature didn't capture name | Older browsers may not support the signature pad; ask the signer to re-open in Chrome/Safari | | I can't see **Separate billing contact** | It is in the **Deal & buyer** step, in the **Billing contact** card under **Title**. Tick it to choose the billing person; the picker lists contacts once a deal is picked | | The buyer says their link says "forwarded" | They or a colleague handed the signature to someone else. The quote timeline shows who. Resend the quote if it went to the wrong person | | **Send** says "Request approval" | The discount or margin is outside your policy. The reason is shown next to the button. An admin or the approver role must approve it | | I can't send because of installments | The schedule must add up to the total. Click **Auto-balance** | | The buyer doesn't see **Pay now** after signing | Online payment needs a connected billing system with card payments allowed. Otherwise the invoice is emailed | | The forward email never arrived | The buyer is told on screen if delivery failed, and you get a notification. Resend from the quote page | | Preview shows an error | Click **Try again**. If it keeps failing, refresh the page | ## Related - [Products & Services →](https://docs.graph8.com/deals/products/) - Stripe catalog feeding quote line items - [Prospects →](https://docs.graph8.com/deals/prospects/) - Active deals where quotes attach - [Customers →](https://docs.graph8.com/deals/customers/) - Where accepted-quote accounts land - [Pipeline Settings →](https://docs.graph8.com/deals/pipeline-settings/) - Stage probabilities used for forecasting - [Deal Contacts →](https://docs.graph8.com/deals/deal-contacts/) - Contacts linked to a deal, including forwarded signers and billing contacts --- # Knowledge Graph > Visual network graph of Brain pages with PageRank scoring and Louvain community detection. Source: https://docs.graph8.com/desktop/brain/graph/ ## Visual graph The knowledge graph shows a visual network of all Brain pages and their connections. ## How it works - **Importance scoring** - most connected/important pages are visually larger - **Community detection** - related pages are clustered together - Connections form automatically from backlinks and shared entities ## Interaction Click nodes to navigate to pages. Zoom and pan to explore your knowledge network. --- # Brain Overview > Your second brain - auto-generated knowledge management system with full-text search and backlinks. Source: https://docs.graph8.com/desktop/brain/overview/ Brain is your second brain - a personal knowledge management system where all pages are auto-generated from Radar extractions. ## Key features - Pages are **auto-generated** from Radar - you never write them manually - Stored as markdown files at `~/.config/graph8 Desktop/brain/` - Two modes: **Work** (sales/professional) and **Life** (personal) - Full-text search across all pages - Pages linked by backlinks, creating a knowledge web --- # Pages & Types > 16 Brain page types - contacts, companies, deals, signals, meetings, journals, and more. Source: https://docs.graph8.com/desktop/brain/pages/ ## Page types Brain supports 16 page types: | Type | Description | |------|-------------| | **contact** | Person profiles synthesized from LinkedIn, X, meetings | | **company** | Company profiles with intelligence | | **deal** | Deal tracking pages | | **signal** | Individual signals (hiring, funding, etc.) | | **meeting** | Meeting notes and action items | | **journal** | Daily journals auto-generated from activity | | **weekly** | Weekly review summaries | | **workflow** | Automation workflow documentation | | project | Project documentation | | resource | Resource references | | idea | Ideas and brainstorms | | reference | Reference materials | | template | Page templates | | snippet | Code or text snippets | | archive | Archived pages | | other | Uncategorized pages | ## Browsing Filter pages by type, search, or sort by recent activity. ## Editing Pages use a rich text editor with autosave. ## Backlinks The backlinks panel shows all pages that reference the current page. --- # Auto-Synthesis > LLM-powered pipeline that creates Brain pages from Radar extractions on a 60-second processing queue. Source: https://docs.graph8.com/desktop/brain/synthesis/ ## How synthesis works An LLM-powered pipeline automatically creates Brain pages from Radar extractions. ## Processing queue - **60-second queue** - Radar extracts a signal, synthesis creates or updates a Brain page - **Synthesis status** - pending count and processing count visible in the UI - **Daily memory journals** - auto-generated summary of the day's activity and interactions ## Fully automatic No manual intervention needed. Brain grows automatically as you work - Radar captures signals, synthesis turns them into knowledge pages. --- # Browser Profile > Built-in browser with persistent sessions - stay logged in across app restarts. Source: https://docs.graph8.com/desktop/browser-profile/ ## Persistent sessions graph8 Desktop includes a built-in browser with a persistent profile stored locally. Your sessions survive app restarts so you don't need to re-login to LinkedIn or X each time you open the app. ## Profile management Clear your browser profile from Settings to start fresh. Browser status is visible in **Settings > Profile > Device** section. --- # Chat & Projects > Chat interface with model selection, file attachments, project organization, and persistent session history. Source: https://docs.graph8.com/desktop/copilot/chat/ ## Chat interface Type messages and see streaming AI responses in real-time. ## Model selector Choose which AI model to use for your conversation. ## Attachments Use the attachment menu to: - Attach images - Attach files - Select skills (e.g., /skill-creator) ## Projects Organize conversations by topic or project. Select or create projects from the project panel. ## Session memory - Last 10 messages kept verbatim - Older messages compressed into summaries - Token budgeting automatically trims context to prevent overflow ## Persistence All messages are saved locally. Sessions support full CRUD operations. ## Personal context Set your personal context in **Settings > Profile** to shape all Copilot responses to your goals and preferences. --- # Copilot Overview > AI assistant with 40+ local tools - control your browser, read files, search Brain, run skills, and manage workflows. Source: https://docs.graph8.com/desktop/copilot/overview/ Copilot is your AI assistant with access to 40+ local tools. It can control your browser, read files, search your Brain, run skills, manage workflows, and control desktop apps. ## Memory system Copilot uses a three-tier memory system: 1. **Agent memory** - past tool outcomes stored for future reference 2. **Brain search** - searches your Brain pages for relevant knowledge 3. **Session memory** - rolling conversation summaries for context continuity ## Key capabilities - Project-based conversations to organize chats by topic - Streaming responses for real-time interaction - Access to all skills and desktop controls --- # Tools & Capabilities > 40+ Copilot tools organized by category - system, browser, search, workflows, desktop control, file system, and integrations. Source: https://docs.graph8.com/desktop/copilot/tools/ Copilot has access to 40+ tools organized by category. ## System tools | Tool | What it does | |------|-------------| | system_state | Check browser, executor, session, upload queue status | | restart_browser | Restart the built-in browser | | flush_executor | Clear stuck skill executor | | read_logs | Read recent app logs | | read_db | Run SELECT queries on the local database | | check_session | Verify LinkedIn/X login, detect CAPTCHAs | ## Browser tools | Tool | What it does | |------|-------------| | get_browser_pages | List all open browser pages | | browser_navigate | Go to any URL | | browser_click | Click elements on page | | browser_type | Type text into fields | | browser_scroll | Scroll page up/down | | browser_snapshot | Get page accessibility snapshot | | browser_hover | Hover over elements | | browser_wait | Wait for elements to appear | | browser_select_option | Select dropdown values | | browser_extract | Extract text content | | browser_go_back/forward | Navigate history | ## Search & memory | Tool | What it does | |------|-------------| | search_screen | Search screen captures (OCR text, audio) | | search_brain | Search Brain pages, agent memory, playbooks | | brain_cleanup | Clean up false-positive extractions | ## Workflows | Tool | What it does | |------|-------------| | execute_skill | Run any registered skill | | manage_workflow | Create/update/delete/toggle scheduled workflows | | run_radar_skill | Run Radar extraction on captures | | schedule_radar_skill | Schedule Radar skills on cron | | manage_autopilot | Accept/dismiss Autopilot proposals | ## Desktop control | Tool | What it does | |------|-------------| | desktop_screenshot | Capture full screen | | desktop_click | Click at screen coordinates | | desktop_type | Type text anywhere | | desktop_hotkey | Press keyboard shortcuts | | desktop_launch_app | Open any application | | desktop_list_windows | List open windows | | desktop_focus_window | Bring app to front | | desktop_move_window | Move/resize windows | | desktop_read_clipboard | Read clipboard contents | | desktop_write_clipboard | Write to clipboard | | desktop_list_apps | List running applications | | desktop_script | Run system scripts | | desktop_notification | Show desktop notifications | ## File system | Tool | What it does | |------|-------------| | list_directory | List files in a directory | | read_file | Read file content | | write_file | Write content to file | | move_file | Move or rename files | | delete_file | Delete files | | search_files | Search by name or content | | create_directory | Create directories | | download_file | Download file from URL | ## Integrations | Tool | What it does | |------|-------------| | run_gws | Google Workspace: Gmail, Drive, Calendar, Sheets, Docs, Chat, Tasks | | local_llm | Run local LLM for classification, extraction, tagging, summarization | | Dynamic MCP tools | Additional tools from connected MCP servers | --- # Settings > Profile, services, schedules, connectors, skills, Radar, and LLM configuration. Source: https://docs.graph8.com/desktop/desktop-settings/ ## Profile tab - **Account info** - email, name, org ID, user ID - **Personal context** - free-text field that shapes all Copilot responses (your goals, preferences, working style) - **Theme** - Light / Dark / System - **Device info** - device ID, job client connection status, MCP server status - **About** - version, API endpoint - **Sign out** ## Services tab - **LinkedIn** - connection status, login/logout - **X (Twitter)** - multi-account management, add/remove accounts - Session display: profile info, connection date, status - Re-run setup wizard button ## Schedules tab - Table of all scheduled sync jobs: name, frequency, daily limit, lookback days, retry interval, last run - Enable/disable toggle per workflow - Add new schedule: select skill, frequency (Hourly/Daily/Weekly/Monthly), daily limit, lookback days, retry interval - Delete workflow ## Connectors tab Manage external service integrations. ## Skills tab View and edit skill source code. ## Radar tab Role configuration (SDR/AE/CSM) and Radar-specific settings. ## LLM tab - Local model status (ready/not downloaded) - Model ID and size - Download with progress bar - Test model functionality --- # Installation > System requirements, download, first launch, and authentication setup for graph8 Desktop on Mac and Windows. Source: https://docs.graph8.com/desktop/installation/ ## System requirements - macOS (Apple Silicon and Intel) or Windows 10+ - 8GB+ RAM recommended ## Download Download graph8 Desktop from [graph8.com](https://graph8.com) or use the direct download link provided in your account. ## First launch On first launch, the app may request system permissions: - **Accessibility** - required for desktop control features (macOS) - **Screen Recording** - required for Radar (macOS) ## Authentication graph8 Desktop uses PropelAuth for authentication: 1. Click **Connect** in the app 2. Your browser opens to the login/signup page 3. Log in or create your account 4. The deep link callback (`g8-desktop://auth`) automatically returns you to the app After login, the app auto-generates an API key and connects to the WebSocket job server. --- # Actions > Take action on LinkedIn from graph8 Desktop - connect, message, post, engage, and visit profiles. Source: https://docs.graph8.com/desktop/linkedin/actions/ ## Available actions | Action | What it does | |--------|-------------| | Connect | Send connection request with optional note | | Accept | Accept pending connection requests | | Message | Send a direct message to a connection | | Post | Publish a LinkedIn post | | Like | Like a post | | Comment | Add a comment to a post | | Visit Profile | View someone's profile | ## How actions work Actions can be initiated through Copilot using natural language. For example, ask Copilot to "send a connection request to [name]" or "post this update to LinkedIn." Actions are performed through the built-in browser using your authenticated LinkedIn session. --- # Company Intelligence > Track LinkedIn company pages - monitor followers, visitors, messages, and posts in one place. Source: https://docs.graph8.com/desktop/linkedin/companies/ ## Managing companies The Workbench left panel shows your tracked companies. For each company, you can choose to monitor: - **Followers** - who follows the company page - **Visitors** - who visits the company page - **Messages** - company page conversations ## Company data Each company displays: - Company name - Follower count - Visitor count - Message count ## Keeping data fresh Desktop periodically refreshes company data for all tracked companies - followers, visitors, and messages. Company posts are refreshed on a separate schedule. All company data flows into Workbench tables and Radar for intelligence extraction. --- # LinkedIn Overview > How graph8 Desktop works as your LinkedIn companion - organize contacts, track engagement, and surface intelligence. Source: https://docs.graph8.com/desktop/linkedin/overview/ ## Your LinkedIn companion graph8 Desktop helps you get more out of LinkedIn by organizing your network data, tracking engagement, and surfacing intelligence you'd otherwise miss. ## How it works Desktop uses a built-in browser where you log into LinkedIn normally. As you use LinkedIn, Desktop helps you organize and enrich the data you interact with - your connections, conversations, posts, and company pages. ## Key capabilities - **Network organization** - keep your connections, followers, and conversations organized locally - **Company intelligence** - track company pages, followers, and engagement - **Content tracking** - monitor your posts, comments, and feed activity - **Search and discovery** - find and organize people from LinkedIn search results - **Cloud sync** - push organized data to graph8 Cloud when you're ready --- # Organizing Data > Organize your LinkedIn data locally - connections, followers, messages, posts, search results, and company pages. Source: https://docs.graph8.com/desktop/linkedin/syncing/ graph8 Desktop organizes your LinkedIn data into structured local tables so you can search, filter, and enrich it. ## What you can organize | Data type | What it captures | |-----------|-----------------| | Your Profile | Your own LinkedIn profile data | | Profiles | Profiles you view or interact with | | Connections | Your connections list | | Followers | Your followers list | | Following | People you follow | | Messages | Your LinkedIn conversations | | Feed | Posts from your LinkedIn feed | | Your Posts | Posts you've published | | Your Comments | Comments you've made | | Search Results | People from LinkedIn searches | | Companies | Company page data | | Company Posts | Posts from company pages | | Company Followers | Company page follower lists | | Company Visitors | Company page visitor data | | Company Messages | Company page conversations | ## Scheduling Configure how often Desktop refreshes your data from **Settings > Schedules**: - **Frequencies**: Hourly, Daily, Weekly, Monthly - **Weekday-only**: Can be set to skip weekends - **Smart refresh**: Detects incomplete data and retries automatically --- # MCP Server > Built-in MCP server exposing 11 browser tools - compatible with Claude Desktop, Cursor IDE, and any MCP client. Source: https://docs.graph8.com/desktop/mcp-server/ graph8 Desktop includes a built-in MCP (Model Context Protocol) server that exposes 11 browser tools. ## Running the MCP server ```bash npm run mcp ``` ## Compatible clients - Claude Desktop - Cursor IDE - Any MCP client that supports stdio transport ## Available tools | Tool | What it does | |------|-------------| | browser_navigate | Go to URL | | browser_click | Click element | | browser_type | Type text | | browser_scroll | Scroll page | | browser_snapshot | Get accessibility snapshot with reference numbers | | browser_hover | Hover over element | | browser_go_back | Navigate back | | browser_go_forward | Navigate forward | | browser_wait | Wait for element | | browser_select_option | Select dropdown option | | browser_extract | Extract text from page | ## Shared browser session The MCP server shares the same browser session as the main app. Actions performed via MCP affect the same browser instance. --- # Data Grids > Radar data grids - deals, campaigns, contacts, companies, discoveries, and meetings with sortable columns and filtering. Source: https://docs.graph8.com/desktop/radar/grids/ Radar provides data grids for each entity type with sortable columns, filtering, detail sidebar, and entity detail dialogs. ## Available grids | Grid | What it shows | |------|-------------| | **Deals** | Track deals with stage, value, contacts, and signals | | **Campaigns** | Campaign performance and engagement data | | **Contacts** | Merged contact data from LinkedIn, X, and meeting captures | | **Companies** | Company intelligence from all sources | | **Discoveries** | New auto-extracted intelligence awaiting review | | **Meetings** | Meeting data with speakers and action items | --- # Meetings > Meeting capture, speaker detection, and auto-generated meeting notes from Radar audio and screen recording. Source: https://docs.graph8.com/desktop/radar/meetings/ ## Meeting capture Meetings are captured from Radar audio and screen recording. ## Speaker detection Radar identifies meeting participants automatically. ## Speaker management - **Rename speakers** - correct auto-detected names - **Merge duplicates** - combine duplicate speaker entries - **Hide speakers** - remove irrelevant speakers from view ## Meeting details Each meeting has a detail dialog showing full meeting info, participants, and extracted signals. Meeting notes are auto-generated from the transcript. --- # Radar Overview > Screen intelligence system that watches your screen and extracts sales signals using AI. Source: https://docs.graph8.com/desktop/radar/overview/ Radar is the screen intelligence system that watches your screen and extracts sales signals. ## How it works - Captures screen content via OCR - Analyzes with AI to extract structured data - Surfaces actionable sales signals ## Role-based views Radar adapts its views based on your role: - **SDR** - prospecting signals - **AE** - deal signals - **CSM** - retention signals ## Tabs Radar has 8 tabs: Dashboard, Timeline, Deals, Campaigns, Contacts, Companies, Discoveries, Meetings. ## Status indicator - **Green** - active and capturing - **Yellow** - starting up - **Gray** - off - **Red** - error --- # Productivity > Daily productivity metrics, digital twin analysis, and AI-synthesized work pattern summaries. Source: https://docs.graph8.com/desktop/radar/productivity/ ## Productivity metrics Daily productivity metrics are calculated from your screen activity. ## Digital twin analysis AI-synthesized profile of your work patterns - what you focus on, how you spend your time. ## Daily summaries Auto-generated summaries of what you worked on and who you interacted with each day. ## Trend tracking Track your productivity trends over time to identify patterns and optimize your workflow. --- # Screen Intelligence > How Radar captures your screen, cleans text, and stores it locally for search and analysis. Source: https://docs.graph8.com/desktop/radar/screen-intelligence/ ## How capture works Radar records your screen every 30 seconds and ingests the captures automatically. Text is cleaned and deduplicated before storage. ## Timeline view Browse screen captures by date. See what was on your screen at any point in time. ## Search Keyword search across all captured text to find past conversations, pages, or information. ## Privacy All data is stored locally and never sent to the cloud unless you explicitly sync it. --- # Signals & Discoveries > Sales signal types extracted from screen captures - hiring, promotion, funding, company news, and engagement signals. Source: https://docs.graph8.com/desktop/radar/signals/ ## Signal types Radar extracts these signal types from your screen captures: - **Hiring signals** - job posts, team growth - **Promotion signals** - title changes - **Funding signals** - investment rounds - **Company news** - product launches, partnerships - **Job change signals** - people moving companies - **Engagement signals** - who's interacting with your content ## Discoveries queue New signals land in the Discoveries queue awaiting your review. ## Review workflow - **Accept** - creates a Brain page from the signal - **Dismiss** - removes the signal from the queue ## Relevance scoring Signals are ranked by importance based on your role (SDR/AE/CSM). --- # Setup Wizard > Walk through the setup wizard - browser setup, LinkedIn login, X login, and Radar permissions. Source: https://docs.graph8.com/desktop/setup/ The setup wizard guides you through initial configuration. You can skip any step and re-run the wizard later from Settings. ## Wizard steps 1. **Welcome** - introduction to graph8 Desktop 2. **Browser Setup** - creates a persistent browser profile so your sessions are saved between app restarts 3. **LinkedIn Login** - opens the built-in browser to LinkedIn. Log in normally. Desktop detects your session and confirms the connection. 4. **X Login** - same flow for X/Twitter. Supports multiple X accounts via X's native account switcher. 5. **Radar Permissions** - requests Screen Recording and Accessibility permissions needed for Radar (macOS). 6. **Complete** - you're ready to go ## Re-running the wizard You can re-run the setup wizard at any time from **Settings > Services > Re-run setup wizard**. --- # Autopilot > Autonomous pattern detection with hourly and nightly scans, proposals, and an insights dashboard. Source: https://docs.graph8.com/desktop/tasks/autopilot/ Autopilot autonomously detects patterns in your activity and suggests actions. ## Detection cycles - **Hourly light scan** - quick check for obvious patterns - **Nightly full scan** - deep analysis with AI reasoning ## Proposals Autopilot suggests actions based on detected patterns. You review and either: - **Accept** - activates the automation - **Dismiss** - removes the suggestion ## Insights dashboard View pending proposals, active automations, and dismissed suggestions. Filter by status: Suggested, Active, Dismissed. ## Customization Configure Autopilot behavior in **Settings**. --- # Connectors > Integrate external services via OAuth or API key - each connector enables additional Copilot tools. Source: https://docs.graph8.com/desktop/tasks/connectors/ Connectors integrate external services with graph8 Desktop. ## Setup A multi-step wizard guides you through configuring each connector. ## Auth types - **OAuth connectors** - click to authorize (opens browser flow) - **API key connectors** - paste your API key manually ## Management - Add, update, or remove connectors from **Settings > Connectors** - Each connector enables additional tools for Copilot - Available connectors are listed in the connectors registry --- # Tasks Overview > Autonomous multi-step agent executions with streaming progress, permission system, and choice cards. Source: https://docs.graph8.com/desktop/tasks/overview/ Tasks are autonomous multi-step agent executions powered by Copilot. ## Task lifecycle **Planning** - Copilot plans the approach **Running** - executing tool calls **Awaiting Input** - needs your approval or a decision **Completed/Failed** - finished or errored ## Execution canvas Visual display of each task step with tool inputs and outputs. ## Features - **25-iteration agent loop** - Copilot can chain up to 25 tool calls per task - **Permission system** - tasks request approval before risky actions - **Choice cards** - tasks can present multi-choice decisions to you - **Streaming** - real-time progress updates in the UI - **Task ideas** - AI-suggested tasks based on your data and patterns --- # AI Enrichment > Add AI-powered columns to any workbench - LLM enriches each row based on your prompt template. Source: https://docs.graph8.com/desktop/workbench/ai-enrichment/ ## How it works Add AI-powered columns to any workbench. Each AI column uses an LLM to enrich rows based on a prompt template and existing row data. ## Configuration 1. Open the **AI Config** sidebar 2. Set a prompt template 3. Select which columns to use as input 4. Run enrichment ## Running enrichment - Processes rows in batch with real-time progress - Cancel in-progress enrichment at any time - Rerun failed rows individually - Run all AI columns at once for bulk processing --- # Cloud Sync > Push local workbench data to graph8 Cloud - map columns, export, and link cloud lists for ongoing sync. Source: https://docs.graph8.com/desktop/workbench/cloud-sync/ ## Push to Cloud Sync local workbench data to graph8 Cloud (Studio). ## Map & Export Wizard Map local columns to cloud list fields, then export. The wizard walks you through field mapping before pushing data. ## Link Cloud List Associate a local workbench with a cloud list for ongoing sync. Once linked, changes can flow between local and cloud. ## Download Cloud List Import a cloud list into a local workbench to work with cloud data locally. ## Sync status View last sync time and error states for each linked workbench. --- # Formulas & Triggers > Define computed formula columns and automation triggers that fire on row changes. Source: https://docs.graph8.com/desktop/workbench/formulas-triggers/ ## Formula columns Define computed columns using expressions over other columns. - **Evaluate formulas** - test formula output before applying - **Recompute formulas** - recalculate all formula values across the workbench ## Triggers Automation rules that fire on row changes. - **Create trigger** - define a condition and an action - **Update/delete triggers** - manage existing triggers - **Triggers tab** - view all triggers in a dedicated tab --- # Importing Data > Import data via CSV upload, clipboard paste, cloud list import, or auto-sync import. Source: https://docs.graph8.com/desktop/workbench/importing/ ## Import methods ### CSV upload Import any CSV file and map columns to workbench fields using the file picker dialog. ### Paste import Paste tabular data directly from your clipboard. ### Cloud list import Pull lists from graph8 Cloud (Studio) into a local workbench. ### Auto-sync import Import from any auto-sync data type - connections, profiles, posts, and more. ## Column mapping During CSV and cloud list imports, you can map source columns to your workbench fields before importing. --- # Workbench Overview > Local data tables stored in SQLite - auto-sync tables from LinkedIn/X and custom user workbenches. Source: https://docs.graph8.com/desktop/workbench/overview/ Workbench provides local data tables stored in SQLite on your machine. ## Two types of data 1. **Auto-sync tables** - populated from your LinkedIn and X activity (connections, profiles, posts, companies, messages, followers, following, search results) 2. **User workbenches** - custom tables you create for any purpose ## Interface - **Left sidebar** shows auto-sync data types with counts, new row badges (+N), and sync status indicators - **Main grid view** with sortable columns, row selection, and detail sidebar - **Analytics tab** for data visualization --- # Actions > Take action on X from graph8 Desktop - post tweets and reply to conversations. Source: https://docs.graph8.com/desktop/x/actions/ ## Available actions | Action | What it does | |--------|-------------| | Post Tweet | Publish a tweet | | Reply to Tweet | Reply to an existing tweet | Actions can be initiated through Copilot using natural language. For example, ask Copilot to "post this tweet" or "reply to that thread." --- # X (Twitter) Overview > graph8 Desktop as your X/Twitter companion - organize your presence, track engagement, and manage multiple accounts. Source: https://docs.graph8.com/desktop/x/overview/ ## Your X companion graph8 Desktop helps you manage your X (Twitter) presence by organizing your data and tracking engagement across accounts. ## Multi-account support Manage multiple X accounts from a single interface. X's native account switcher handles switching between accounts seamlessly. ## Getting started During setup (or from Settings), log into X through the built-in browser. Desktop detects your session and connects to your account. Add additional accounts from **Settings > Services**. --- # Organizing Data > Organize your X/Twitter data locally - profile, tweets, followers, and following. Source: https://docs.graph8.com/desktop/x/syncing/ ## What you can organize | Data type | What it captures | |-----------|-----------------| | Profile | Your X account profile data | | Your Tweets | Tweets you've published | | Followers | Your X followers | | Following | Accounts you follow | Scheduling works the same as LinkedIn - configure refresh frequency from **Settings > Schedules**. --- # Appointments > Schedule and manage appointments — automatic contact creation, CRM meeting sync, and owner assignment. Source: https://docs.graph8.com/engage/appointments/ Appointments handles scheduling and syncs booked meetings to your CRM. When a prospect books a meeting, graph8 automatically creates or updates the contact, syncs the meeting to your CRM, and assigns the correct owner. ## Appointments vs Meetings These two areas work together, but they are not the same feature: | Feature | Primary Purpose | |---------|-----------------| | **Appointments** | Booking workflow, scheduling events, and CRM sync | | **Meetings** | Reviewing calendar meetings in the Inbox with transcript and follow-up context | Use **Appointments** when you need to control how meetings are booked and synced. Use **Meetings** when you want to review what happened in the meeting and what should happen next. ## How It Works The flow: 1. A prospect books a meeting through your scheduling page 2. The scheduling system sends an event to graph8 3. graph8 processes the event — creates/updates contacts and syncs to your CRM 4. The meeting appears in your CRM with the correct contact associations This runs automatically in the background for every booking. ## Appointment Events graph8 processes four types of scheduling events: | Event | What Happens | |-------|-------------| | **Booking created** | New meeting booked — contact created/updated, meeting synced to CRM | | **Booking updated** | Meeting details changed — CRM meeting updated | | **Booking canceled** | Meeting canceled — CRM meeting marked as canceled | | **Booking rescheduled** | Meeting moved — original marked as rescheduled, new meeting created in CRM | ### Reschedule Handling Reschedules are handled differently by CRM: - **HubSpot / Pipedrive** — the original meeting is updated to "rescheduled" status and a new meeting is created - **Salesforce** — a new meeting is created (Salesforce doesn't reliably support finding and updating the original) ## Contact Creation When a booking comes in, graph8 automatically processes the attendees: - **Creates new contacts** if they don't exist in your database - **Updates existing contacts** with any new fields (phone number, name) - **Processes guests** from the booking response (additional attendees) - **Skips the host** — the meeting host is treated as the owner, not a contact - **Deduplication** — prevents creating duplicate contacts ## CRM Sync Meetings are synced to your connected CRM: | CRM | Capabilities | |-----|-------------| | **HubSpot** | Create meetings, update on reschedule/cancel, associate with contacts | | **Salesforce** | Create meetings, associate with contacts | | **Pipedrive** | Create meetings, update on reschedule/cancel, associate with contacts and attendees | Each synced meeting includes: - Title and description - Start and end times - Location and meeting URL - Contact associations - Owner assignment - Booking UID for tracking reschedules and cancellations ## Owner Assignment Control how meeting owners are assigned in your CRM: | Mode | Behavior | |------|----------| | **Use host as owner** | The meeting host becomes the CRM owner — no fallback if host isn't found | | **Use default owner** | Try the host first, fall back to a configured default owner | Configure owner assignment per CRM integration in **Settings → Integrations**. ## Settings Appointment settings are configured per CRM integration: | Setting | What It Controls | |---------|-----------------| | **Entity type** | Whether to create contacts, leads, companies, or deals from bookings | | **Field mappings** | Map booking fields to CRM fields | | **Owner assignment mode** | How meeting owners are determined | | **Default owner** | Fallback owner when host can't be matched | > **Tip:** > Connect your CRM before your first appointment goes live. When meetings sync automatically, your sales team sees booked meetings in their CRM without any manual data entry. ## Routing Forms A routing form is a public form that qualifies prospects before they book. Based on the answers they submit, graph8 routes them to the right calendar, the right rep, or an ineligible-lead page. ### What Routing Forms Are For - **Qualify inbound leads** — ask company size, role, or budget before offering a meeting slot - **Route to the right rep** — enterprise leads to AEs, SMB leads to SDRs, support requests to CS - **Block out-of-ICP traffic** — deny meetings to prospects who fail qualification criteria - **Collect context before the call** — so reps walk into meetings prepared ### Creating a Routing Form 1. Go to **Engage → Appointments → Routing Forms** 2. Click **New Form** 3. Add questions (each question becomes a form field): - **Short text** — name, title - **Email** — auto-validated - **Phone** — auto-formatted with country code - **Dropdown** — single-select with predefined options - **Multi-select** — multiple choices (e.g., tools they already use) - **Number** — company size, budget tier - **Long text** — free-form context 4. Mark required questions with **Required** 5. Click **Routing Rules** to configure how answers map to outcomes 6. Click **Publish** to get a shareable URL ### Routing Rules Rules are evaluated in order. The first match wins. If no rule matches, the fallback action applies. | Rule Condition Types | Example | |---------------------|---------| | **Field equals** | `Company size = 1000+` | | **Field contains** | `Role contains "VP" or "Director"` | | **Field in range** | `Budget between 10,000 and 100,000` | | **All of** | Multiple conditions must all match | | **Any of** | At least one condition must match | ### Routing Actions | Action | Behavior | |--------|----------| | **Show calendar** | Display a specific calendar (e.g., AE calendar vs. SDR calendar) | | **Assign rep** | Match the prospect to a specific user's calendar | | **Round-robin** | Distribute across a pool of reps | | **Redirect URL** | Send prospect to an external page (pricing, resources, ineligible page) | | **Thank-you page** | Show confirmation without offering a meeting | | **Webhook** | Send the submission to your own endpoint | ### Example: Enterprise vs. SMB Routing A typical B2B setup: 1. Ask **Company size** 2. Rule 1: If `Company size ≥ 500` → show **AE calendar** with round-robin across enterprise AEs 3. Rule 2: If `Company size 50–499` → show **Mid-market calendar** 4. Rule 3: If `Company size < 50` → redirect to self-serve signup page 5. Fallback: show **SDR calendar** (catches unanswered cases) ### Form Analytics Each form tracks: | Metric | What It Shows | |--------|---------------| | **Views** | How many prospects opened the form | | **Completions** | How many submitted | | **Completion rate** | Completions ÷ Views | | **Rule breakdown** | How many prospects hit each routing rule | | **Meeting rate** | Of those offered a calendar, how many booked | | **Disqualified** | Count of prospects blocked or redirected | Use analytics to tune qualification thresholds — if too many prospects get disqualified, loosen rules; if too many unqualified leads reach AEs, tighten them. ### Embedding on Your Site Embed a form on your site to replace a generic contact form: 1. Open the form in the editor 2. Click **Embed** 3. Choose **Inline** (replaces a `<div>` on the page) or **Popup** (opens on button click) 4. Paste the script tag into your site > **Tip:** > Connect a routing form to a web chat button instead of a contact form. Chat-to-form conversion is 2–3x higher than static contact forms because prospects qualify in-context. ## Out-of-Office and Buffer Rules Control when meetings can and can't be booked, even after a prospect qualifies. | Rule | Behavior | |------|----------| | **Business hours** | Only offer slots during the rep's configured hours | | **Minimum notice** | Require N hours of lead time before a meeting can be booked | | **Maximum lookahead** | Don't offer slots more than N days in the future | | **Buffer time** | Add N minutes before/after meetings (travel, prep, debrief) | | **Booking limits** | Cap bookings (or total booked time) per day, week, month or year — for the whole event, or for **each host** on a round-robin or collective event | | **Your own limit** | On a team event, each host can set a stricter limit for themselves; admins can give one person a custom limit or no limit | | **Out-of-office** | Block dates from the calendar (manual or auto-synced from Google/Outlook) | Configure hours and out-of-office in **Engage → Appointments → Availability**, and booking limits on each event type's **Limits** tab. On a team event, a limit that applies to **each host** only takes that person out of the rotation when they're full — the others keep getting booked. A **whole event** limit is one total shared by everyone. Only workspace admins can change a team event's limits, and, while a limit applies to each host, which hosts are fixed (a fixed host joins every booking, so their limit would apply to everyone). ## Booking Confirmation Emails When a prospect books, graph8 sends a confirmation email via SES to the prospect (and CCs the host) with: - Meeting title, time, and timezone-localized details - Conferencing link (Google Meet / Zoom / Teams / custom) - A `.ics` calendar invite as an attachment so the prospect can add the meeting to any calendar app - Reschedule and cancel links signed with secure tokens (no login required for the prospect) Reschedule and cancel emails are sent automatically when the matching booking event fires. ## Conferencing Providers graph8 auto-generates conferencing links based on the host's connected provider: | Provider | Link Type | Notes | |----------|-----------|-------| | **Google Meet** | `meet.google.com/…` | Requires Google Calendar connected | | **Zoom** | `zoom.us/j/…` | Requires Zoom OAuth connection | | **Microsoft Teams** | `teams.microsoft.com/…` | Requires Microsoft 365 connected | | **Custom** | Any URL | Use for WebEx, Whereby, in-person meetings | The conferencing link is included in the calendar event and synced to CRM. ## Next Steps - [Connections →](https://docs.graph8.com/engage/connections/) — Set up your CRM integration - [Meetings →](https://docs.graph8.com/engage/meetings/) — Review meetings and transcript context in the Inbox - [Web Chat →](https://docs.graph8.com/engage/web-chat/) — Let visitors book meetings from chat - [Sequencer →](https://docs.graph8.com/engage/sequencer/) — Link sequences to appointment outcomes - [Forms (inbound tracking) →](https://docs.graph8.com/signals/forms/) — Different feature: track form submissions on your existing website --- # Connections > Connect and sync your tools — manage data streams, destinations, transformations, and real-time event tracking. Source: https://docs.graph8.com/engage/connections/ Connections is graph8's data pipeline. It collects events from your website, transforms them with custom functions, and routes them to destinations — webhooks, data warehouses, JavaScript tags, and external connectors. ## How It Works Events flow through a simple pipeline: 1. **Streams** collect events from your website or app (page views, form submissions, custom events) 2. **Functions** transform or filter events in transit using custom JavaScript 3. **Destinations** receive the processed events (webhooks, warehouse, tags, connectors) 4. **Links** define which streams connect to which destinations, with routing rules ## Streams A stream is a data source — typically your website. Each stream has: | Field | What It Does | |-------|-------------| | **Name** | Label for the data source (e.g., "Website", "Mobile App") | | **Domains** | Allowed source domains for CORS validation | | **Browser key** | Public write key for the JavaScript SDK | | **Server key** | Private key for server-side event tracking | When you set up graph8, a default "Website" stream is created automatically with browser and server keys. ### Event Types Streams accept standard event types: | Event | What It Tracks | |-------|---------------| | **page** | Page views with URL, referrer, and page metadata | | **track** | Custom events (e.g., button clicks, signups) | | **identify** | User identification with traits | | **group** | Account/company grouping | | **form_submission** | Form fills captured from your website | ## Destinations Destinations are where events are sent after collection and transformation. ### Destination Types | Type | What It Does | |------|-------------| | **Webhook** | HTTP POST event payloads to any URL | | **Tag** | Inject JavaScript into the page for client-side integrations | | **Warehouse** | Batch events to a data warehouse for analytics | | **Connector** | External connectors for data sync | ### Default Destinations When you set up Connections, graph8 creates several destinations automatically: | Destination | Type | Purpose | |-------------|------|---------| | **Identify** | Tag | Visitor identification pixel — resolves anonymous visitors to companies | | **Chat** | Tag | Web chat widget script | | **Form Tracker** | Tag | Captures form submissions from any form on your site | | **Campaign Visitor Trigger** | Webhook | Tracks campaign link clicks and associates them with contacts | | **Forms Sync** | Webhook | Sends form submissions to graph8 for contact creation | | **Tailor** | Tag | Website personalization engine | | **Warehouse** | Warehouse | Event storage for analytics (optional) | ### Custom Destinations Add your own destinations to route events wherever you need: - **Webhook destinations** — send events to any HTTP endpoint - **Tag destinations** — inject custom JavaScript (analytics, pixels, chat widgets) - **Connector destinations** — sync data to external databases and SaaS tools ## Functions Functions are custom JavaScript that transform events in transit. Use them to: - **Filter** — drop events that don't meet criteria - **Enrich** — add data to events before they reach a destination - **Transform** — reshape event payloads to match destination expectations - **Route** — conditionally send events to different destinations graph8 includes built-in functions for common tasks like campaign visitor filtering and form submission processing. ## Links Links connect streams to destinations. Each link defines: | Setting | What It Controls | |---------|-----------------| | **Source** | Which stream sends events | | **Destination** | Where events are routed | | **Functions** | Which transformation functions to apply | | **Event filter** | Which event types to forward | | **Host filter** | Which source domains to accept | For sync-type links, you can also configure: - **Schedule** — sync frequency - **Timezone** — scheduling timezone - **Table prefix** — naming convention for destination tables ## Syncs Syncs enable bidirectional data transfer with external systems. Configure: - Which data streams to sync - Sync schedule - Destination table naming ## Live Events Monitor your event pipeline in real time: - View incoming events as they arrive - Inspect event payloads and metadata - Debug routing and transformation issues - Verify that destinations are receiving data ## Campaign Tracking Connections powers campaign attribution automatically. When a prospect clicks a campaign link: 1. The URL contains a campaign hash identifying the sequence step and contact 2. The graph8 tracking script captures the page view with the hash 3. The Campaign Visitor Trigger destination sends the event to graph8 4. graph8 links the visit to the correct contact and sequence step This happens automatically — no manual setup required beyond having Connections installed on your site. > **Tip:** > Install the graph8 tracking script on all pages of your website. This enables visitor identification, form tracking, and campaign attribution across your entire site — not just landing pages. ## Next Steps - [Web Chat →](https://docs.graph8.com/engage/web-chat/) — Chat widget delivered via Connections - [Sequencer →](https://docs.graph8.com/engage/sequencer/) — Campaign tracking powered by Connections - [Appointments →](https://docs.graph8.com/engage/appointments/) — Appointment booking and CRM sync --- # Dialer > Power dialer for outbound calls — parallel dialing, call recording, voicemail detection, AI voice agents, and SDR analytics. Source: https://docs.graph8.com/engage/dialer/ The Dialer is a power dialer built for sales teams. Dial up to 4 contacts simultaneously, record and transcribe calls, detect voicemail, and track performance with real-time analytics. ## Using the Dialer Follow these steps to go from a contact list to a live call. ### 1. Open the Dialer and start a session 1. Go to **Engage → Dialer** 2. Click **+ Start dialing a new list** (top right) 3. In **Start dialing a new list**, configure: - **Contact List** — select or upload the list you want to call. - **Studio Campaign** — link the session to a campaign - **Session Name** — enter a name for the session - **Call from** — pick the phone number to call from - **Agent (twin)** — select the Twin agent that powers the call - **Appointment** — choose the meeting type booked from this session, for example a 30-minute discovery call 4. Click **Start dialing** ![The Start dialing a new list dialog — select or upload a contact list, link a Studio campaign, name the session, pick the number to call from, the Twin agent, and the appointment type](https://docs.graph8.com/images/engage/dialer-new-session.png) > **Note:** > If the list already has earlier sessions, they appear in the dialog so you can resume one instead of starting fresh. ### 2. Configure the session The session view opens with your contact queue on the right and the settings sidebar on the left. 1. **Dialing mode** — choose how many contacts to call at once: - **1 (Power)** — one call at a time - **2, 3, or 4 (Parallel)** — dial multiple contacts simultaneously to reach more people per hour 2. **Caller ID** — pick the phone number to call from (see [Phone Number Selection](#phone-number-selection)) 3. **Agent & Campaign** — attach the Twin agent that powers the call and the Studio campaign calls are attributed to (see [Add a Twin Agent](#add-a-twin-agent) below) 4. **Priority** — set the order phone numbers are dialed in (see [Phone Priority and Multi-Number Dialing](#phone-priority-and-multi-number-dialing)) ![The dialer session view — settings sidebar (dialing mode, caller, agent, phone priority) on the left and the contact queue on the right](https://docs.graph8.com/images/engage/dialer-session-view.png) ### 3. Start dialing and handle the call 1. Click **Start Dialing** at the bottom of the sidebar 2. When a prospect answers, the call drawer opens automatically with contact info, a live transcript, and the call outcome panel 3. After the call, select a [disposition](#dispositions) (and an optional sentiment + note), then **Log & Dial Next** to advance ![The in-call drawer — contact profile on the left, the live transcript in the middle, and the call outcome panel (book meeting, dispositions, log and dial next) on the right](https://docs.graph8.com/images/engage/dialer-active-call.png) From the call drawer you can also **Book Meeting** directly, open the **Dialpad** to send DTMF tones, **Leave AI VM and Dial Next** to drop a voicemail, or **Leave & Pause**. > **Caution:** > Contacts with **No Phone** or a **DNC** status are skipped automatically and never dialed. To add a missing phone number, [edit the contact in the list](#edit-contact-data-from-the-list). ### Session controls You manage a running session from the header and the sidebar: - **Mute / unmute** — toggle the microphone icon in the session header to mute or unmute yourself - **Pause Session** — stop dialing and resume later from where you left off (sidebar) - **Next Batch** — in parallel mode, dial the next set of contacts once the current batch finishes - **Exit Session** — leave the session entirely (top right). Active and paused sessions can be resumed later from the sessions list During a live call, the call drawer adds: **End Call** to hang up, **Mute**, **Dialpad** to send DTMF tones, **Leave & Pause**, **Leave AI VM and Dial Next** to drop a voicemail, and **Book Meeting**. ### Check session analytics Click the **dials / talk-time chip** in the session header (the stats with the chart icon) to open **Session Analytics** for the current session. It has three tabs: - **Overview** — total dials, connections and connection rate, talk time, voicemails, success rate, redials, peak hour, booked, and callbacks - **Dispositions** — the breakdown across every disposition - **Daily Breakdown** — activity by day ![Session Analytics for the active session — Overview tab showing total dials, connections, talk time, voicemails, success rate, and more](https://docs.graph8.com/images/engage/dialer-session-analytics.png) ## Add a Twin Agent Every dialing session runs with a **Twin agent** — the AI voice agent attached to the call. Select one under **CALLER → Select agent** before you start dialing. 1. In the session sidebar, click **Select agent** 2. Search your **Twin Agents** and pick one, or click the gear to **Configure** an existing agent 3. If you don't have a twin yet, click **Create a new agent** at the bottom of the picker — this opens the twin creation flow (**Agents → New → Twin**) in a new tab ![The Select agent picker lists your Twin agents with a "Create a new agent" link](https://docs.graph8.com/images/engage/dialer-agent-picker.png) For the full walkthrough of building a twin — persona, voice cloning, identity, and skills — see [Voice Agents →](https://docs.graph8.com/ai-features/voice-agents/). ## Edit Contact Data from the List If a contact is missing a phone number, job title, or any other field, an SDR can edit it directly in the list without leaving the dialer. 1. In the session sidebar, under **SESSION**, click the **list link** next to **List ·**. graph8 opens that list in a **new browser tab** so your session stays intact 2. In the list, **double-click the cell** in the column you want to fill — for example **Job Title** or a phone field 3. Type the new value and press **Enter** to save (or **Esc** to cancel) ![Double-click a cell in the list to add or edit a contact's data inline](https://docs.graph8.com/images/engage/dialer-list-edit.png) > **Tip:** > This is the fastest way to fix contacts that were skipped for **No Phone**. Add a number in the list, then return to the dialer tab and re-run the session to include them. ## Parallel Dialer Parallel dialing calls several contacts at once so you spend more time talking and less time listening to ringing. This section explains **how it works** and **how to use it well**. ### How it works 1. **You pick a batch size** — 1 (Power) or 2–4 (Parallel) in the **Dialing mode** setting. 2. **graph8 dials the whole batch at once** — for a batch of 4, four contacts ring simultaneously. 3. **The first live answer wins.** The moment a real person answers, graph8 connects them to you and **instantly hangs up the other ringing calls**, so no one is left picking up to silence. The call drawer opens automatically for the connected contact. 4. **Voicemail and phone trees are handled for you.** graph8 detects when a call reaches voicemail or an IVR / auto-attendant and — based on your settings — either skips it or drops your voicemail message. Those calls never tie up your line waiting for the batch. See [Voicemail](#voicemail). 5. **No wasted or duplicate dials.** The same contact is never called twice in a session, and **DNC** / **No Phone** contacts are skipped automatically. 6. **The next batch is one click away.** When a batch finishes, click **Next Batch** to dial the next set. Contacts that didn't connect are automatically retried on their next phone number — see [Phone Priority and Multi-Number Dialing](#phone-priority-and-multi-number-dialing). ### Choosing a batch size More lines mean more people reached per hour — but also a higher chance that two prospects answer at nearly the same moment, in which case only the first is connected and the other is dropped. Match the batch size to the list: | Mode | Best for | Trade-off | |------|----------|-----------| | **1 (Power)** | Warm leads, callbacks, VIP accounts | Most personal, never drops a live prospect — but lowest volume | | **2–3 (Parallel)** | Everyday prospecting, balanced lists | Good reach with a low chance of dropping an answered call | | **4 (Parallel)** | Cold lists with low pick-up rates, peak hours | Highest volume — but the greatest chance of dropping a simultaneous answer | > **Tip:** > Start at **2**, then watch your **connection rate** and **voicemail rate** in [Session Analytics](#check-session-analytics). If most calls go to voicemail (a low-pick-up list), move up to **3** or **4** to keep your line busy. For warm or high-value contacts, drop to **1 (Power)** so you never risk dropping a live prospect. ### Get the most out of a session - **Set up voicemail drops** and enable **skip voicemails** so unanswered calls still leave your message and don't waste your time — see [Voicemail](#voicemail). - **Order your phone priority** (Direct → Mobile → Company) so the most reachable number is tried first, and multi-number contacts are automatically retried — see [Phone Priority and Multi-Number Dialing](#phone-priority-and-multi-number-dialing). - **Have your Twin agent and script ready** before you start — parallel dialing connects fast, so there's no time to fumble. See [Add a Twin Agent](#add-a-twin-agent). - **Call during peak hours** — typically 10am–12pm and 2pm–4pm local time — when pick-up rates are highest. - **Pause instead of exiting** if you need a break; a paused session resumes exactly where you left off, keeping your queue, redial filter, and cursor. > **Caution:** > Because a batch dials several people at once, occasionally two prospects answer within the same instant. graph8 connects the first and drops the rest — a normal trade-off of parallel dialing. If you can't afford to drop any live answer (for example, high-value accounts), use **1 (Power)** mode. ## Phone Priority and Multi-Number Dialing A contact can have up to four phone numbers — **Direct Phone**, **Mobile Phone**, **Company Phone**, and **Additional Phone Numbers**. The **PRIORITY** section of the sidebar controls the order they're tried. - **Reorder** — drag the phone types to set which number is dialed first - **Exclude** — uncheck a phone type to skip it (at least one must stay active) - **Automatic retry** — if a number doesn't connect, the dialer automatically retries the contact with the next number in priority order on the following batch - **No duplicate dials** — the same number is never dialed twice for the same contact within a session The default order is **Direct Phone → Mobile Phone → Company Phone**. **Additional Phone Numbers** is listed last but unchecked by default; check it to include it. ## Redial by Disposition When you re-run a list, choose which past dispositions you want to redial. The redial filter lives in the Dialer sidebar and applies to the active list. | Filter | Typical Use | |--------|-------------| | **Voicemail** | Try again — they didn't pick up last time | | **No answer / Hangup** | Same as voicemail; new attempt at a different time of day | | **Callback** | Honor scheduled callbacks | | **Gatekeeper** | Try again to get past the gatekeeper | | **All dispositioned** | Full revival pass — combine with a date filter to age the list | The dialer skips dispositions you don't select (for example **Booked**, **DNC**, **Not interested**) so you don't burn calls on contacts already resolved. The redial state is persisted with the session — pausing and resuming preserves your filter and cursor. ## Manual Dialer You can make ad-hoc calls outside of a dialing session using the manual dialer. Click **Manual Dial** at the bottom of the dialer sidebar to open the keypad, enter a phone number, and click **Start Dialing**. The number you call from is shown below the keypad. You can also expand **Add contact details** to attach a name and company to the call. ![The Manual Dial keypad — enter a number, see the number you're calling from, and start the call](https://docs.graph8.com/images/engage/dialer-dialpad.png) Manual calls: - Are not tied to a sequence - Can be used for quick outreach or follow-ups - Are still logged as activities ### Session Statuses | Status | Meaning | |--------|---------| | **Active** | Session is running, calls in progress | | **Paused** | Session paused — can resume | | **Completed** | All contacts in the session have been dialed | | **Failed** | Session encountered an error | ### Call Statuses | Status | Meaning | |--------|---------| | **Dialing** | Call is being placed | | **Ringing** | Phone is ringing | | **Active** | Call connected, conversation in progress | | **Automation** | Voice AI is handling the call | | **Hangup** | Call ended | | **Failed** | Call could not connect | ## Phone Number Selection The phone number dropdown shows the numbers assigned to you and the numbers owned by your workspaces. Workspace numbers carry the workspace's name and colour so you can tell them from your own. Use the search box to find a specific number by label or digits. Numbers purchased for the dialer and numbers provisioned for AI voice agents are managed separately. If you don't have a number yet, the dropdown includes a link to get one. For the full walkthrough of buying and assigning a number, see [Phone Numbers →](https://docs.graph8.com/settings/phone-numbers/) in Settings. ## Workspaces If your org uses Workspaces, a workspace switcher appears to the left of the Dialer tabs. - **All workspaces** (the default) shows every session and every call, the same as before - Picking a workspace limits **previous sessions** and **Call Logs** to sessions started under it, and new sessions you start are tagged with it - The **New Session** dialog has a Workspace field that defaults to the switcher; you can change it per session or leave it empty The choice is kept in the page address (`?workspace=`), so a link to the Dialer can open on a workspace. Orgs without workspaces do not see the switcher. ## Inbound Calls Handle incoming calls in real time: - **Real-time notifications** — SDRs receive instant alerts for inbound calls - **SDR online status** — the system tracks which reps are available - **Call claiming** — SDRs can claim or decline inbound calls - **Who is rung** — a call to a person's number rings that person; a call to a workspace number rings the member who last called the prospect, then every member; when nobody targeted is online it rings the whole org - **Voicemail fallback** — when no SDR is available, callers are routed to voicemail - **Pending queue** — inbound calls queue until an SDR is available ### Incoming Call Notifications When an inbound call is received, a notification appears showing the caller's phone number. If the number matches an existing contact, graph8 also displays: - Contact name - Company You can choose to answer or dismiss the call directly from the notification. ## Call Recording & Transcription Every call is automatically recorded and transcribed: - **Recording** — saved to cloud storage, accessible from call results - **Transcription** — AI-powered speech-to-text for full call transcripts - **Call summaries** — AI generates a summary of the conversation - **Call evaluation** — AI grades call quality for coaching ## Voicemail graph8 detects voicemail and handles it automatically: - **Voicemail detection** — identifies when a call reaches voicemail - **Voicemail drops** — leave a pre-recorded message automatically - **Greetings** — set voicemail greetings at the org level or per agent - **Transcription** — voicemail recordings are transcribed - **Read/unread tracking** — track which voicemails your team has reviewed ## Dispositions After each call, the SDR (or system) selects a disposition: ### SDR-Selected | Disposition | Meaning | |-------------|---------| | **Booked** | Meeting or call scheduled | | **Callback** | Contact wants a callback (sub-sentiments: has budget, interested, hesitant, not ready, interested but wrong person) | | **Not interested** | Contact explicitly declined | | **DNC** | Do Not Call (sub-sentiments: legal DNC, request DNC, harassed) | | **Not ICP** | Contact doesn't match your ideal customer profile | | **Has solution** | Company already has a competing solution | | **Gatekeeper** | Speaking with a gatekeeper (sub-sentiments: will pass through, callback scheduled, won't pass) | | **Wrong number** | Invalid or wrong phone number | | **Left org** | Contact no longer works at the company | ### System-Set | Disposition | Meaning | |-------------|---------| | **Voicemail** | Call reached voicemail | | **Hangup** | Call disconnected (by contact, by SDR, or no answer) | | **No voice** | Failed to connect | | **Not answered** | Call dropped before connecting | | **Failed** | Call failed to complete | ## Voice AI AI-powered voice features for smarter calling: - **Voice agents** — AI agents that can handle calls autonomously - **Text-to-speech** — natural-sounding AI voices for automated calls - **Voice cloning** — clone a voice for personalized outbound - **Call scripts** — guided scripts for SDRs and AI agents - **SDR training agent** — practice calls with an AI that simulates prospects ## Analytics Track dialer performance in real time: ### SDR Performance Dashboard | Metric | What It Shows | |--------|--------------| | **Dials** | Total calls made | | **Connections** | Calls answered by a human (excludes voicemails) | | **Connection rate** | Human-answered calls as a percentage of total dials (voicemails not counted as connections) | | **Voicemail rate** | Percentage of calls that reached voicemail | | **Talk time** | Total minutes on connected (human-answered) calls | | **Average call duration** | Mean length of connected calls - voicemail duration excluded | | **Disposition breakdown** | Distribution across all dispositions | | **Success rate** | Meetings booked per connected (human-answered) call | | **Redial metrics** | Redial attempts and success rates | | **Peak calling hours** | Best times of day for connections | > **Note:** > All timestamps in call logs and session history display in your local timezone. ### Score Filtering Filter graded calls by quality score using the **Min Score** and **Max Score** fields. Only calls within the specified range appear in the results. Calls under 30 seconds and non-conversation dispositions are excluded from scoring. ### Team Leaderboard Compare SDR performance across your team with daily, weekly, and monthly views. Rankings by total calls, connections, and meetings booked. ### Call Targets Set targets at the org level or per SDR for daily call and meeting goals. ## Campaign Linkage Dialer sessions can be linked to a campaign so call activity rolls up into campaign-level reporting. - **Link a session** — when creating or editing a dialer session, select a campaign to associate it with - **Attribution** — every call placed during that session is attributed to the linked campaign - **Aggregated analytics** — call metrics (dials, connections, dispositions, talk time) appear in the campaign's performance view alongside email and LinkedIn data - **Back to sessions** — while in a campaign-linked session, use the back button to return to the sessions list without losing context This gives managers a single view of all outreach activity per campaign, across channels. ## Sequencer Integration The Dialer integrates directly with the Sequencer: - **Manual Dialer steps** — add a **Manual Dialer** step to any sequence - **Call results** — linked back to the sequence step and contact - **Campaign attribution** — calls from sequences track back to the originating campaign through the session link > **Tip:** > Use the parallel dialer during peak calling hours (typically 10am–12pm and 2pm–4pm local time). Set up voicemail drops so no call goes to waste — even unanswered calls leave your message. ## Next Steps - [Voice Agents →](https://docs.graph8.com/ai-features/voice-agents/) — Create the Twin agent that powers your calls - [Inbox →](https://docs.graph8.com/engage/inbox/) — View call results and transcripts - [Meetings →](https://docs.graph8.com/engage/meetings/) — Review booked calls alongside meeting context - [Sequencer →](https://docs.graph8.com/engage/sequencer/) — Add dialer steps to sequences - [Appointments →](https://docs.graph8.com/engage/appointments/) — Manage booked meetings --- # HeyReach Campaign Overview > Monitor LinkedIn campaign performance, lead progress, and engagement metrics directly from your sequence step cards. Source: https://docs.graph8.com/engage/heyreach-campaign-overview/ HeyReach is the LinkedIn automation provider that powers graph8's LinkedIn sequence steps. Every LinkedIn step in a sequence maps to a HeyReach campaign, and the **View Campaign** link on each step card opens a dialog showing live engagement data and lead progress — no need to leave the sequencer. ## How It Works 1. Go to **Engage → Sequences** and open a sequence that contains HeyReach LinkedIn steps 2. On any HeyReach step card, click **View Campaign** 3. The campaign overview dialog opens with two tabs: **Overview** and **Leads** If a step has no campaign created yet, the card shows *Campaign not created* instead of the link. This happens if the sequence hasn't started or if the LinkedIn account isn't yet connected. ## Campaign Lifecycle Each HeyReach campaign moves through these states as the sequence runs: | State | Meaning | |-------|---------| | **Not created** | Sequence hasn't launched the step yet | | **Scheduling** | Contacts are being added to the HeyReach queue | | **Active** | HeyReach is executing LinkedIn actions for contacts | | **Paused** | Sequence paused, HeyReach stopped sending | | **Completed** | All leads have finished the step | | **Failed** | HeyReach rejected the campaign (see troubleshooting) | When a sequence is paused or terminated, the corresponding HeyReach campaigns pause within 5 minutes. ## Overview Tab The Overview tab shows campaign progress and engagement broken down by LinkedIn action type. ### Lead Counts | Stat | What It Shows | |------|---------------| | **Total leads** | Number of leads added to this campaign | | **In progress** | Leads currently active in the sequence | | **Completed** | Leads who have finished the full sequence | | **Exited** | Leads who exited early (replied, withdrawn, etc.) | ### Engagement by Action Type Each LinkedIn action type tracks its own metrics: | Action Type | Sent | Successful | Measured As | |-------------|------|-----------|-------------| | **Connection** | Connection requests sent | Accepted connections | Accept rate = Accepted ÷ Sent | | **Message** | Direct messages sent | Replies received | Reply rate = Replies ÷ Sent | | **InMail** | InMails sent | Replies received | Reply rate = Replies ÷ Sent | | **Profile View** | Profile views executed | N/A (no outcome) | View count | | **Like** | Post likes performed | N/A | Like count | | **Follow** | Follows performed | N/A | Follow count | Filter engagement stats by action type using the selector at the top of the section. This helps isolate performance per step when a campaign has multiple action types. ### AI Instructions When creating a HeyReach step, you can choose **AI Instructions** instead of writing static copy. Instead of defining a fixed message, you provide instructions and graph8 generates the message at send time for each contact. The AI uses: - Contact data (name, title, company) - Global Context from Studio - Campaign documents from Studio This results in more personalized LinkedIn outreach compared to static templates. Messages are generated fresh each send, so they reflect the latest company data and signals. ## Leads Tab The Leads tab shows a paginated list of all leads in this campaign with their current status. Use **Previous** and **Next** to navigate through pages. ### Columns | Column | Description | |--------|-------------| | **Name** | Contact's full name (link to contact detail page) | | **Title** | Job title at time of enrollment | | **Company** | Company name | | **Status** | Current HeyReach step status for this lead | | **Action History** | Which actions have been performed (connect, message, etc.) | | **Reply Status** | Whether the lead has replied | | **Last Activity** | Timestamp of most recent action | ### Per-Lead Status Values | Status | Meaning | |--------|---------| | **Queued** | Added to the campaign, waiting to be processed | | **In Progress** | Currently executing actions | | **Connection Pending** | Connection request sent, awaiting accept/decline | | **Connected** | Connection accepted, ready for next action | | **Messaged** | Message sent successfully | | **Replied** | Lead replied — sequence stops if finish-on-reply is enabled | | **Rejected** | Connection request declined or message failed | | **Skipped** | Lead did not meet step conditions | ## Rate Limits and LinkedIn Caps HeyReach enforces LinkedIn's platform limits to protect accounts from restriction: | Limit | Default | Notes | |-------|---------|-------| | **Connection requests per day** | 50 (per LinkedIn account) | LinkedIn weekly limit: ~100 total | | **Messages per day** | 50 (per LinkedIn account) | Only to existing connections | | **InMails per month** | Based on account plan | Sales Navigator: 50–150/mo | | **Profile views per day** | 300 | Higher tiers available | | **Likes per day** | 100 | Post likes only | When a cap is hit, HeyReach pauses the action and resumes the next day. The sequence step stays in progress; individual leads wait in the queue. ## Troubleshooting | Issue | Fix | |-------|-----| | Campaign stuck in "Scheduling" | HeyReach is still provisioning; normal for first 15 minutes. If it persists over 1 hour, check HeyReach account connection in **Settings → Integrations** | | "Campaign not created" shown on step | Verify a LinkedIn account is assigned to the sequence in **Channels** tab | | Low connection accept rate | Check that your LinkedIn profile has a professional photo and recent activity — HeyReach mirrors your profile to prospects | | Messages not sending | Confirm the contact accepted your connection first; messages can only be sent to connections | | InMail limit exceeded | You've used your monthly InMail quota; upgrade Sales Navigator plan or wait until the next billing cycle | | Rejected leads spike | Your targeting may be too broad or your messaging too generic — review AI Instructions quality | | LinkedIn account restricted | Pause all HeyReach campaigns for 48 hours; restriction lifts automatically. Reduce daily caps before resuming | ## Related - [Sequencer →](https://docs.graph8.com/engage/sequencer/) — Set up and manage multi-channel sequences - [Connections →](https://docs.graph8.com/engage/connections/) — Manage LinkedIn and CRM integrations - [Inbox →](https://docs.graph8.com/engage/inbox/) — Respond to LinkedIn replies in one place --- # Inbox > Manage all conversations in one place — email, LinkedIn, SMS, chat, calls, and meetings with AI-powered response and review tools. Source: https://docs.graph8.com/engage/inbox/ The Inbox aggregates every conversation channel into a single view. Email threads, LinkedIn messages, SMS, chat, call results, and meetings all appear in one place — with AI tools to help you respond faster and follow up faster. ## Channels | Channel | Source | |---------|--------| | **Email** | Gmail and SMTP mailboxes | | **LinkedIn** | HeyReach-connected LinkedIn accounts | | **SMS** | Connected phone numbers | | **Chat** | Web chat conversations | | **Calls** | Call results with transcripts and dispositions | | **Meetings** | Calendar events enriched with transcript and account context | ## Universal Activity Timeline The Activity Timeline shows all engagement events with a contact in one place. Instead of viewing each channel separately, you can see everything in a single chronological timeline: - Emails - Calls - LinkedIn messages - Meetings Each event is ordered by time, giving you a complete view of the interaction history across all channels. Use the timeline to quickly understand where a conversation stands and what has already happened before taking your next step. ## Email Management ### Mailbox Setup Connect Gmail or SMTP mailboxes from **Settings → Channels → Mailboxes** to pull conversations into the Inbox; see [Mailboxes](https://docs.graph8.com/settings/mailboxes/) for the connection steps. Once a mailbox is connected and healthy, the AI Inbox syncs it automatically. There is no per-mailbox sync or enable toggle to switch on: AI Inbox is always on for healthy mailboxes. AI behavior for connected mailboxes is configured under **Settings → Channels → Inbox**, which groups settings into dedicated panes: - **AI Settings** — agent behavior, including auto-tagging, company knowledge base access, and business-hours-only processing (with timezone and start/end times) - **Mailbox Policy** — per-mailbox **Auto-respond** toggle and a manual **Sync now** action - **HeyReach**, **SMS**, **Tags**, **Team**, and **Shared Inboxes** — channel- and team-level settings Opening settings from the Inbox now lands you in these Studio Settings panes. ### Filtering & Views Filter your inbox by: - Mailbox (specific email address) - Campaign - Tags (include or exclude) - Status (sent, AI responded, draft) - Date range - Assigned user - Starred (per-user flag) All emails are shown in threaded conversation view with full message history. The filter tab bar scrolls horizontally when you have many active filters, so the rest of the inbox UI stays put. ## LinkedIn Conversations LinkedIn conversations sync via HeyReach. From the Inbox you can: - View conversation history with any LinkedIn contact - Send messages and InMails - Create and manage drafts - Mark conversations as read - Filter by sequence, LinkedIn account, or date range - Add and remove tags ### LinkedIn Account Management Manage your connected LinkedIn accounts directly from the Inbox: - View account configuration - Activate or deactivate accounts - Update account settings ## SMS SMS is a full inbox channel with AI-powered composition and response tools. - **Filter tabs** - switch between All, Unread, and Drafts - **Phone number selection** - choose which number to send from - **AI composition** - generate and refine SMS messages using copilot - **Draft management** - save drafts, edit, or delete them - **Bounce handling** - bounced numbers are automatically excluded from future sends - **Conversation history** - view threaded message history per contact SMS conversations appear alongside email and LinkedIn in the unified inbox view. ## Chat (Web Chat & Internal Support) Web chat conversations from the public widget and internal support chats appear in the Inbox as their own channel. ### Starring Chat Conversations Star important chat conversations to keep them surfaced: - Click the star icon on any chat conversation card to flag it - Use the **Starred** filter in the conversation list to see only flagged chats - Stars are per-user — each team member has their own starred view - Combine with tags and assignee filters to build focused inbox views ### Composer The chat composer in the inbox is the same one used by the public widget — it auto-grows as you type long messages, supports file attachments and voice notes, and persists drafts. See [Web Chat](https://docs.graph8.com/engage/web-chat/) for full composer details. ## Calls The Calls channel lists the calls on your phone numbers: the calls your reps make and take in the Dialer (every inbound call, missed ones included, and the outbound calls where someone picked up), and the inbound calls your AI agents handle. Pick a workspace to see only the calls on that workspace's numbers; a workspace member sees them without any extra permission. A row leads with the contact and names the rep who handled a Dialer call. Each call opens read-only with: - **Summary** and **disposition** - **Recording** and **transcript** - **Duration** - **Tags** and **assignees**, plus internal comments Calls has three folders: **All calls**, **Archive** and **Deleted**. Archive a call from its row or the thread header to move it out of All calls; it stays under Archive and can be unarchived. Use the toolbar search to find a call by number, name, email or summary. To call a contact back, open the call and use **Call**: the from-number picker opens and the Dialer places the call. Compose is disabled on this channel; new calls to anyone else are placed from the Dialer. ## Meetings The Meetings channel gives you a meeting-first inbox view built from calendar events. When graph8 finds a transcript, each meeting can be enriched with: - Executive summary - Key topics - Action items and linked follow-up tasks - Participant and account context - Transcript status and manual transcript-linking tools Use Meetings to prep for upcoming calls, review customer and prospect conversations, and catch meetings that still need transcript coverage. ## AI Features ### Message Composition The AI can help you write and refine messages across any channel. Apply one or more operations in a single action: | Operation | What It Does | |-----------|-------------| | **Change tone** | Rewrite as professional, friendly, casual, assertive, empathetic, or urgent | | **Fix grammar** | Correct spelling and grammar | | **Change objective** | Reframe toward: book meeting, follow up, re-engage, provide value, handle objection, close deal | | **Expand** | Add more detail to a short message | | **Shorten** | Condense a long message | | **Change persona** | Rewrite as SDR, growth manager, account executive, customer success, or founder | | **Generate from scratch** | Generate a full reply based on the conversation context | Multiple operations can be combined in a single request (1 credit per transformation). ### Booking Meetings from Inbox You can book meetings directly from an active conversation. Click the calendar icon in the conversation toolbar, select an available time, and confirm. The meeting is automatically: - Linked to the contact - Added to your calendar - Visible in the conversation history ### AI-Surfaced Meeting & Booking Links When the AI drafts a reply, it can automatically include the right scheduling link without you having to copy-paste: - For contacts in an active deal cycle, the AI surfaces the AE's calendar link - For inbound interest, the AI uses your default routing form URL - For follow-ups, the AI inserts the originally-shared booking link from earlier in the thread - Links are pulled from your connected appointment configuration, not invented — so visitors always land on a working booking page You can edit or remove the AI-suggested link before sending; the AI just removes the friction of finding the right URL. ### Auto-Tagging The AI suggests tags for emails, chats, and call results based on content analysis. Tags with a confidence score above 0.7 are recommended. Only tags marked as "AI-applicable" are considered. ### Auto-Response Mailboxes can be configured to auto-respond using AI. The system tracks who responded to each message: - **User** — a team member replied - **Other** — the contact or an external party - **AI** — graph8 auto-responded ## Workspaces Organize your team's inbox with workspaces: - Create team workspaces with shared access - Add members to a workspace - Add shared mailboxes visible to all workspace members - Role-based access (admin, member) ## Tags Tags help organize and filter conversations: - Create tags with name, color, and description - Mark tags as AI-applicable to include them in auto-tagging - Apply tags to emails, LinkedIn conversations, chats, and call results - Filter any view by tag ## Blocked Senders Block unwanted senders to keep your inbox clean: - Block by email address - Block directly from a conversation - Blocked senders' messages are automatically filtered out during processing > **Tip:** > Set up auto-tagging early — it categorizes incoming conversations (interested, not interested, out of office, etc.) without manual effort. Use tags to build filtered views for your team's daily workflow. ## Next Steps - [Meetings →](https://docs.graph8.com/engage/meetings/) — Review meetings, transcripts, and follow-up work - [Sequencer →](https://docs.graph8.com/engage/sequencer/) — Build automated outreach sequences - [Dialer →](https://docs.graph8.com/engage/dialer/) — Power dialer for outbound calls - [Web Chat →](https://docs.graph8.com/engage/web-chat/) — Engage website visitors with live chat --- # LinkedIn Publishing > Compose, schedule, and publish organic LinkedIn posts from graph8, then turn the people who engage into pipeline. Source: https://docs.graph8.com/engage/linkedin-publishing/ LinkedIn Publishing lets you write a post once, preview exactly how it will look in the feed, schedule it (or publish immediately), and then watch who engages — automatically resolving likers and commenters into contacts you can sequence. It lives in **Studio → Content → LinkedIn**, alongside Landing Pages, Newsletter, and Ads. ## How It Works 1. Open **Studio → Content** and pick the **LinkedIn** tab in the content bar. 2. Connect a LinkedIn account in **Accounts** (one-click — opens the secure connection wizard; your LinkedIn login and 2FA never touch graph8). 3. In **Compose**, pick the account, write your post (or let AI draft it from your brand brief), optionally add an image, and preview it. 4. **Schedule** it for later, drop it in a **Queue slot**, or **Publish now**. 5. Once it's live, **Analytics** tracks impressions and engagement, and **Leads** shows everyone who engaged — ready to add to an audience or enroll in a sequence. ## The LinkedIn tabs | Tab | What it does | |-----|--------------| | **Compose** | Write or AI-generate a post, attach media, preview the feed appearance (including the "…see more" fold), and save / schedule / publish. | | **Drafts** | Posts you've started but not scheduled. Edit or delete. | | **Scheduled** | Posts queued to publish at a set time. Edit, reschedule, or cancel. Failed posts show why and offer a retry. | | **Live** | Posts that published successfully, with a link to view them on LinkedIn. | | **Queue slots** | Define recurring weekly time slots per account; Compose suggests the next free slot. | | **Analytics** | Impressions, reactions, comments, shares — per day and per post. | | **Leads** | Everyone who engaged with your live posts, matched to your CDP. Add them to an audience or enroll them in a sequence in one click. | | **Accounts** | Connect / reconnect LinkedIn accounts and see today's remaining post capacity. | ## Composing a post - **AI studio** — click **Generate with AI** to open the studio. Two modes: - **Write**: give instructions and pick what to ground on — brand voice, your campaign brief, the proof catalog, what prospects actually ask in your inbox, recent content, or your top-performing posts. Every source is a visible, toggleable chip. - **Mine ideas**: the system proposes ideas from your own data — recurring themes from meetings and inbound emails, trending posts on your tracked keywords, proof points you've never used, and content worth repurposing. Ideas are shared with the content calendar's Ideas feed, graded for LinkedIn fit, and carry receipts back to their sources. Generation always returns **three differently-framed variants** (e.g. story / contrarian / Q&A) with a fold check on each hook; pick one, or refine it ("punchier hook", "shorter", "add a proof point") until it lands. - **The fold** — LinkedIn truncates posts at roughly 210 characters with a "…see more". The preview shows exactly where that line falls, so you can make the hook land above it. - **Media** — add an image from your asset library or upload a new one. (Carousels and AI-generated visuals are coming.) - **Character limit** — posts are capped at 3,000 characters, matching LinkedIn. ## Scheduling and queue slots You can give any post an exact publish time, or set up **queue slots** — recurring weekly times per account (e.g. Tue & Thu at 9:00). When you schedule, graph8 suggests the next open slot so your cadence stays consistent without manual date-picking. Posts publish durably in the background; you don't need to keep graph8 open. > **Note:** > "Publish now" still goes through the scheduler, so a post may take up to ~30 seconds to go live. You'll see it move from **Scheduled → Publishing → Live**. ## The Leads loop This is what makes publishing more than a megaphone. After a post goes live, graph8 periodically reads who reacted and commented, and matches each person against your CDP by their LinkedIn URL: - **Known contact** — already in your CDP; we log the engagement as an intent signal. - **New** — not in your CDP yet; we can create a contact and add them to an audience. - **Unresolved** — no usable LinkedIn URL to match on. From the **Leads** tab you can select engagers and **add them to an audience** or **enroll them in a sequence** — turning a post's engagement directly into outreach. ## Connection options You can connect LinkedIn through more than one provider, and what you can do depends on which one you choose. The **Accounts** tab shows each option with its capabilities. | Provider | Publishing | Analytics | Engager leads | Status | |----------|-----------|-----------|---------------|--------| | **Unipile** (recommended) | Personal + company | ✅ | ✅ | Available — one-click connect | | **Share on LinkedIn** (official) | Personal only | ❌ | ❌ | Coming soon | | **Company Pages** (official) | Company only | ✅ (page) | ❌ | Requires LinkedIn approval | The UI adapts to your connected account: if you publish through a provider that doesn't expose post analytics or engager data (like the official "Share on LinkedIn" product), the **Analytics** and **Leads** tabs are hidden for that account, and the Leads view explains why. **Unipile** is the only option that powers the full feature — publishing, analytics, and the leads loop — which is why it's recommended. ## Account capacity & safety Each connected LinkedIn account has a daily publishing budget (shared with any LinkedIn outreach steps on the same account) so your activity stays within safe limits. The **Accounts** tab shows how much of today's budget remains. If an account's session expires, it shows **Reconnect needed** — one click re-authorizes it. ## What's not here (yet) - A dedicated calendar view (Scheduled posts will deep-link into the unified content calendar in a later update). - AI-generated images, quote cards, and document carousels (upload + asset library work today). - The two official LinkedIn providers are listed but not yet connectable — "Share on LinkedIn" is in setup and Company Pages is pending LinkedIn partner approval. Unipile is fully available today. --- # Meetings > Review scheduled meetings in the Inbox — filter customer, prospect, and internal calls, manage transcript links, and work from AI-generated meeting context. Source: https://docs.graph8.com/engage/meetings/ The Meetings channel turns your calendar into a working queue inside the Inbox. Instead of jumping between calendar, transcripts, and account records, you can review each meeting with participant context, transcript enrichment, and follow-up tasks in one place. ## What Appears in Meetings Meetings are built from calendar events first. If graph8 finds a linked transcript, the meeting is enriched with summary data, topics, action items, and account context. Each meeting can be classified as: - **Customer** — at least one external attendee matches an active account - **Prospect** — external attendees are present but not mapped to an active customer account - **Internal** — no external attendees are detected - **Unknown** — the meeting could not be classified confidently ## Views Use the left sidebar to move between saved meeting views: | View | What It Shows | |------|---------------| | **External** | External-facing meetings across customers and prospects | | **Customers** | Meetings classified as customer calls | | **Prospects** | Meetings classified as prospect calls | | **Internal** | Internal-only meetings | | **With Transcript** | Meetings with a linked transcript | | **Missing Transcript** | Meetings that still need a transcript link or review | At the top of the list, switch between these scope tabs: | Tab | What It Does | |-----|--------------| | **My Meetings** | Meetings owned by your calendar context | | **All Meetings** | Meetings visible across the configured team scope | | **My Upcoming** | Your future meetings, sorted soonest first | | **All Upcoming** | Upcoming meetings across the visible team scope | ## Filters and Sorting Use the filter menu to narrow the list quickly: | Filter | Options | |--------|---------| | **Timeframe** | Upcoming, Past | | **Transcript status** | All meetings, With transcript, Missing transcript | | **Audience** | All audiences, Customers, Prospects, Internal | | **Internal mode** | Include internal meetings or focus on external-facing meetings | | **Search** | Match meeting title, attendees, transcript summary, or transcript text | | **Sort order** | Newest first or oldest first | The default view shows **Past** meetings. Switch to **Upcoming** to see scheduled meetings. > **Tip:** > Use **My Upcoming** for prep and **Missing Transcript** for cleanup. Those two views give most teams the fastest daily workflow. ## Meeting Detail View Open any meeting to see the full context panel. ### Header The header shows: - Meeting title - Start and end time - Audience badge - Transcript status badge - Organizer and linked participant chips - **Open Meeting** link when the join URL is available ### Executive Summary When a transcript is linked, graph8 can show: - A concise meeting summary - **Key Topics** - **Campaign Mentions** If the transcript is linked but structured extraction is still limited, the meeting still shows transcript status and raw transcript context where available. ### Tasks graph8 surfaces follow-up work in two ways: - **Meeting tasks** — tracked tasks already created from transcript action items - **Action items** — extracted next steps that still need review or ownership Each task can show: - Current assignee - Linked company or record - Whether the task still needs assignee review ### Meeting Context The context section can include: - Linked account - Organizer details - Transcript source and link state ### Participants Participant rows can resolve to: - Internal team members - External contacts - Linked accounts When possible, graph8 shows participant titles, company linkage, and RSVP status. ### Transcript If transcript text was imported, the full transcript body appears in the detail panel. If only a summary is available, the panel explains that the transcript is linked but raw text was not imported. ## Transcript Management Meetings support both automatic and manual transcript linking. | Action | What It Does | |--------|--------------| | **Find Transcript** | Opens likely transcript candidates near the meeting time | | **Link Transcript** | Manually attach the best candidate | | **Mark No Transcript** | Mark the meeting as not expecting a transcript | | **Resume Transcript Search** | Clear the no-transcript state and look again | | **Remove Manual Link** | Remove a manually attached transcript | When you review transcript candidates, graph8 shows: - Match confidence - Match method - Time delta from meeting start - Title overlap - Attendee overlap - Domain overlap ## Best Practices - Review **Upcoming** meetings before the day starts so you have account context ready. - Check **Missing Transcript** regularly to keep meeting history complete. - Split customer and prospect reviews into separate workflows because follow-up work is usually different. - Use the Meetings channel as the handoff point between conversation review and account follow-up. ## Troubleshooting **Issue:** No meetings appear in the Meetings channel. **Solution:** Confirm your calendar is connected and that the meeting falls within the current scope and filters. **Issue:** A meeting is showing as internal when it should be external. **Solution:** Check the attendee emails and your configured internal domains in **Settings -> Docs -> Meetings**. **Issue:** A transcript is missing. **Solution:** Open the meeting, click **Find Transcript**, and review candidate matches. If no transcript should exist, mark it as **No Transcript**. ## Next Steps **Inbox** [Manage all conversation channels](https://docs.graph8.com/engage/inbox/) **Appointments** [Understand booking and CRM sync](https://docs.graph8.com/engage/appointments/) **Connect Your Calendar** [Connect calendars and availability](https://docs.graph8.com/getting-started/connect-calendar/) **Docs Settings** [Configure meeting extraction rules](https://docs.graph8.com/settings/docs/) --- # Newsletter > Send branded newsletters to your subscriber lists with full engagement tracking. Source: https://docs.graph8.com/engage/newsletter/ Newsletter lets you create, schedule, and send email newsletters directly from graph8 - no third-party tool needed. Every open, click, and unsubscribe feeds back into your lead scoring and audience targeting. ## Why It Matters Most teams use a separate newsletter tool (Mailchimp, Beehiiv, ConvertKit) alongside their sales platform. That creates data silos - newsletter engagement never reaches your pipeline. graph8 Newsletter closes that loop: a prospect who clicks a pricing link in your newsletter becomes a warm lead in your outbound sequences automatically. ## How It Works 1. Go to **Studio → Campaign** and click **Generate** 2. Select **Newsletter** as the campaign type 3. Choose a goal and describe your newsletter direction 4. graph8 generates 5 newsletter ideas with AI 5. Push your favorite idea to a campaign - 9 newsletter-specific documents are generated automatically 6. Go to the campaign dashboard and click **Create New Issue** to compose your first newsletter 7. Write your content, preview it, and send ## Newsletter Documents When you create a newsletter campaign, graph8 generates 9 strategic documents tailored to newsletter publishing: | Document | What It Does | |----------|-------------| | Newsletter Brief | Mission, positioning, and value proposition to subscribers | | Content Pillars | 4-6 recurring themes mapped to subscriber pain points | | Editorial Calendar | 12-issue plan with themes, hooks, and CTA focus per issue | | Tone & Voice Guide | Voice traits, DO/DON'T examples, newsletter vs sales email comparison | | Subject Line Bank | 30+ subject line formulas grouped by psychology type | | CTA Playbook | CTA types, placement rules, and rotation strategy | | Subscriber Persona | Ideal subscriber profile with engagement triggers | | Growth Playbook | Subscriber acquisition channels, targets, and list hygiene | | Performance Benchmarks | Industry benchmarks, health score framework, SES thresholds | These documents appear in the campaign sidebar. Click any document to view it as a rich, structured page - not raw text. ## Creating Issues Each newsletter send is called an **issue**. To create one: 1. Open your newsletter campaign in **Studio → Campaign** 2. Click **Create New Issue** in the dashboard sidebar 3. Fill in the subject line and preview text 4. Write your HTML content in the editor 5. Optionally map to an editorial calendar issue number for tracking 6. Click **Save Draft** or **Send Now** ### Issue Statuses | Status | Meaning | |--------|---------| | Draft | Not yet sent - still editable | | Scheduled | Queued for future delivery | | Sending | Currently being delivered via SES | | Sent | Delivery complete - metrics tracking active | | Failed | Send failed - check SES configuration | ### Send Validation Before an issue can be sent, graph8 runs a preflight check: - **Confirmed subscribers** — only confirmed subscribers count toward the send (double opt-in pending subscribers are excluded) - **Suppression check** — bounced and complained subscribers are skipped - **Domain status** — sending domain must be Verified - **Required fields** — subject, preview text, and HTML content must be present If any check fails, the Send dialog blocks the action and lists what to fix. The issue editor scrolls into long content automatically so you can review before sending. ## Engagement Tracking Every newsletter issue tracks these metrics automatically: | Metric | What It Measures | |--------|-----------------| | Delivered | Emails that reached the inbox | | Opened | Unique opens (pixel tracking) | | Clicked | Link clicks with URL tracking | | Bounced | Hard and soft bounces | | Unsubscribed | One-click unsubscribe actions | | Complained | Spam complaints from ISPs | **Data loop**: Opens and clicks publish lead signals to graph8's scoring engine. A subscriber who clicks your pricing link gets scored higher in your outbound campaigns - automatically. ## Subscribers Newsletter sending is **subscriber-based** — every send targets a list of confirmed subscribers, separate from your outbound audiences. Each subscriber has their own status, preference center, and engagement history. ### Subscriber Statuses | Status | Meaning | |--------|---------| | **Pending** | Confirmation email sent, waiting on double opt-in | | **Subscribed** | Confirmed and receiving newsletters | | **Unsubscribed** | Opted out via one-click unsubscribe or preference center | | **Bounced** | Hard bounce — automatically excluded from future sends | | **Complained** | Marked as spam — automatically excluded across all graph8 modules | ### Adding Subscribers You can populate a newsletter's subscriber list in three ways: - **Promote from an audience** — convert an existing graph8 audience into subscribers (a confirmation email is sent if double opt-in is enabled) - **Manual add** — paste emails or upload a CSV from the Subscribers tab - **Public signup form** — embed a subscribe form on your site; submissions trigger the double opt-in flow ### Setup Checklist When you open a newsletter campaign for the first time, the **Setup Checklist** shows what's left before you can send: 1. Verified sending domain 2. At least one confirmed subscriber 3. From name and reply-to address configured 4. Newsletter content drafted The checklist runs against your subscriber list (not audience size) and updates in real time as you complete steps. ### Compliance & Hygiene graph8 handles compliance automatically: - **Double opt-in** — new subscribers receive a confirmation email; only confirmed subscribers receive issues - **One-click unsubscribe** (RFC 8058) — included in every email; clicking unsubscribes immediately without an extra click - **Preference center** — secure HMAC-signed tokens give subscribers a per-newsletter preference page (no login required) - **Bounce management** — hard bounces automatically suppress the subscriber - **Complaint handling** — spam complaints suppress the subscriber across newsletter, outbound, and every other graph8 module - **NULL email guard** — subscribers without valid email addresses are filtered from sends ## Settings Newsletter settings live at two levels: org-wide defaults and **per-newsletter** overrides. ### Org-Level Settings 1. Go to **Settings → Newsletter** 2. Configure your sending domain (subdomain verification via DNS) 3. Set default sender values (From Name, From Email, Reply-To) 4. Enable double opt-in (recommended) 5. Set warmup daily limits for new domains ### Per-Newsletter Settings Each newsletter campaign can override the org defaults — useful when you publish multiple newsletters from the same org with different brands or sender identities. Open the newsletter campaign and go to the **Settings** tab to set per-newsletter: - From Name and Reply-To (override org default) - Sending domain (pick from verified domains via the domain picker) - Subscribe / unsubscribe redirect URLs - Footer customization (legal address, brand) ### Domain Verification To send newsletters, you need a verified sending domain: 1. In **Settings → Newsletter → Domains**, enter your subdomain (e.g., `updates.yourcompany.com`) 2. Click **Verify Domain** — graph8 generates the required DNS records 3. Add the TXT record and 3 CNAME records to your DNS provider (records are shown with copy buttons and per-record verification status) 4. Click **Re-check** to verify — most DNS providers propagate in 15–60 minutes 5. Once all records show **Verified**, the domain is available in the per-newsletter domain picker The DNS UX shows live verification state per record, so you can see exactly which entry is missing or misconfigured. > **Tip:** > Use a subdomain like `updates.yourcompany.com` or `news.yourcompany.com` instead of your root domain. This protects your main domain's email reputation if something goes wrong with newsletter deliverability. ## Campaign Dashboard Newsletter campaigns have a specialized dashboard. When you open a newsletter campaign, you'll see: - **NEWSLETTER** badge next to the campaign name - **Newsletter Content** card with deep links to issues and subscriber management - **Strategic assessment** and **readiness score** from AI analysis - **Risk assessment** specific to newsletter operations Outbound-specific features (sequencer, audience pipeline) are hidden for newsletter campaigns. ## Next Steps - [Campaigns →](https://docs.graph8.com/studio/campaigns/) - Learn about campaign creation and management - [Sequencer →](https://docs.graph8.com/engage/sequencer/) - Set up automated outbound sequences - [Inbox →](https://docs.graph8.com/engage/inbox/) - Manage replies to your outreach --- # Nurture > Engage consented audiences at scale with multi-step nurture campaigns, built for warm leads, customers, and opted-in subscribers. Source: https://docs.graph8.com/engage/nurture/ Nurture campaigns engage audiences who have already opted in: newsletter subscribers, trial signups, customers, and event attendees. Unlike outbound sequences (which prospect to cold leads), nurture is built for ongoing engagement with people who already know you. ## Nurture vs. Sequencer Both run multi-step automation, but they're built for different jobs: | Capability | Sequencer | Nurture | |------------|-----------|---------| | **Audience type** | Cold prospects (outbound) | Consented contacts (inbound, opted-in) | | **Use case** | New business prospecting | Lifecycle engagement, retention, upsell | | **Compliance posture** | Cold outreach rules apply | Marketing consent already on file | | **Pacing** | Aggressive: speed-to-lead | Considered: long-cycle drip | | **Volume** | High volume per day | Steady cadence over weeks/months | | **Channels** | All: email, LinkedIn, phone, SMS, WhatsApp | All: same step types as Sequencer | | **Mailbox strategy** | Rotation across many mailboxes | Pinned to a brand mailbox | If you're sending to people who haven't asked to hear from you, use **Sequencer**. If you're following up with people who opted in, use **Nurture**. ## Common Use Cases | Audience | Nurture Goal | |----------|-------------| | **Newsletter subscribers** | Convert to product trials | | **Trial signups** | Drive activation and conversion to paid | | **Webinar registrants** | Follow-up content and meeting CTAs | | **Closed-lost deals** | Stay top-of-mind for re-engagement | | **Customer onboarding** | Step-by-step product education | | **Customer expansion** | Cross-sell, upsell, feature adoption | | **Event attendees** | Post-event content and next-step CTAs | ## Building a Nurture Campaign 1. Go to **Engage → Nurture** 2. Click **Create Nurture Campaign** 3. Walk through the four tabs: **Meta → Contacts → Steps → Review** 4. Click **Launch** to activate ### Meta Tab The first tab captures the campaign's identity and configuration: | Field | Description | |-------|-------------| | **Name** | Internal name for the campaign | | **Description** | What this nurture is for (used in reporting) | | **Schedule** | Sending window: when steps are allowed to execute | | **Pinned mailbox** | A single mailbox to send all emails from (preserves brand consistency) | | **Pinned phone number** | A single number for SMS and calls | | **Studio campaign** | Optional link to a Studio campaign for messaging context | | **Finish on reply** | Stop the nurture for a contact when they reply | | **Send in same thread** | Keep all email steps in one thread | > **Tip:** > Nurture campaigns work best with a pinned mailbox tied to a recognizable sender: a person's name (`sarah@yourcompany.com`) or a branded address (`hello@yourcompany.com`). Rotation hurts brand recognition for warm audiences. A nurture campaign uses exactly one pinned mailbox, and that mailbox can send up to **500 emails per day**. Because nurture audiences have already opted in, the pinned mailbox is exempt from the cold outbound warmup ramp that applies to sequences. ### Contacts Tab Pick which contact lists to enroll. Nurture supports the same list selection as Sequencer: - Select one or multiple contact lists - Filter by attributes (job title, company size, custom fields) - Use signals (visited pricing page, opened last campaign) to gate enrollment Audiences should be lists of opted-in contacts. Use [Audience syncs →](https://docs.graph8.com/data/managing-lists/) to keep the audience updated automatically. ### Steps Tab Add nurture steps. The Steps tab uses the same builder as the Sequencer, so every step type, content mode, and condition available in sequences is available here. See [Sequencer → Steps](https://docs.graph8.com/engage/sequencer/) for the full step reference. Common nurture-specific patterns: | Pattern | Description | |---------|-------------| | **Long wait intervals** | 5–14 days between steps for warm audiences | | **Single-channel email cadence** | Most nurtures stay on email to preserve a consistent sender voice | | **Branch on engagement** | Send different content to openers vs. non-openers | | **AI-personalized content** | Use AI Instructions so each send references the contact's role and recent activity | ### Review Tab Final pre-launch check. Shows: - Total contacts to enroll - Step count and total nurture duration - Selected mailbox and phone number - Schedule and timezone - Any validation warnings (missing required fields, invalid templates) Click **Launch** to start the campaign. ## Personal vs. Organization The nurture list view has two tabs: | Tab | Shows | |-----|-------| | **Personal** | Nurtures you created | | **Organization** | Every nurture in your org | Filtering by tab makes it easier to find your own campaigns in larger orgs. ## Statuses Nurture campaigns share the Sequencer's status model: | Status | Meaning | |--------|---------| | **Drafted** | Created but not started | | **Scheduling** | Processing contacts | | **Live** | Actively running | | **Paused** | Stopped; can resume | | **Waiting** | Paused, will resume when new contacts arrive | | **Completed** | All contacts finished | | **Terminated** | Permanently stopped | Controls match the Sequencer: **Start**, **Pause / Resume**, **Terminate**, **Duplicate**. ## Adding Contacts to a Live Campaign Nurtures often run continuously. To add new contacts to an active nurture: 1. Open the nurture 2. Click **Add Contacts** 3. Select a list or upload contacts 4. New contacts enter at the first step automatically If you enable **Wait for new contacts** in Meta settings, the nurture stays in `Waiting` status when no contacts are queued and resumes when new ones arrive. ## Reports Each nurture tracks: | Metric | What It Shows | |--------|---------------| | **Enrolled** | Total contacts added to the nurture | | **Active** | Currently progressing through steps | | **Completed** | Finished all steps | | **Replied** | Contacts who replied (sequence stops if finish-on-reply is on) | | **Per-step performance** | Send, open, click, reply rates per step | | **Engagement trend** | Daily reply rate over time | | **Conversion funnel** | Drop-off rate at each step | ## Compliance Because nurture targets opted-in audiences, compliance posture is different from cold outbound: - **Unsubscribe links** are required on every email step - **List hygiene**: automatically suppress unsubscribes, hard bounces, and complaints - **Consent tracking**: record the source of consent on each contact (form submission, double opt-in, customer agreement) - **Regional rules**: GDPR, CASL, CAN-SPAM compliance flows differently for marketing vs. transactional content Configure unsubscribe behavior at **Settings → Compliance**. ## Best Practices - **Pin a mailbox**: warm audiences should hear from a consistent sender, not a rotating pool - **Keep cadence reasonable**: for warm audiences, 1 email every 3–7 days is typical; daily is too much - **Segment audiences**: separate nurtures for trial users, customers, and lost deals work better than one giant nurture - **Use AI Instructions** for personalization: the AI uses Studio context plus contact data, so each email feels written for the recipient - **Monitor unsubscribes**: a spike means cadence is too high or content isn't relevant ## Troubleshooting | Issue | Fix | |-------|-----| | Nurture stuck in Scheduling | Wait 5 minutes; large lists take time to enroll. If stuck >1 hour, check the audience list isn't empty | | Contacts not advancing | Check the step's wait interval and the schedule's sending window; contacts pause until both align | | Unsubscribe rate is high | Lower send frequency, segment audience more narrowly, review content relevance | | Replies not stopping the campaign | Verify **Finish on reply** is enabled in Meta tab | | Pinned mailbox at send cap | Reduce daily volume or split the audience across multiple nurtures with different pinned mailboxes | ## Related - [Sequencer →](https://docs.graph8.com/engage/sequencer/): Cold outbound prospecting (use this for non-consented audiences) - [Newsletter →](https://docs.graph8.com/engage/newsletter/): One-shot broadcast emails (no multi-step automation) - [Inbox →](https://docs.graph8.com/engage/inbox/): Manage replies from nurture campaigns - [Managing Lists →](https://docs.graph8.com/data/managing-lists/): Build audiences for nurture - [Studio Campaigns →](https://docs.graph8.com/studio/campaigns/): Generate nurture content with AI --- # Sequencer > Build automated multi-step outreach sequences across email, LinkedIn, phone, SMS, and WhatsApp. Source: https://docs.graph8.com/engage/sequencer/ The Sequencer automates multi-step outreach across every channel. Define a series of steps (emails, LinkedIn actions, phone calls, SMS, WhatsApp) and graph8 executes them on a schedule for each contact in your list. ## Channels Sequences support multiple channels, each with its own resource allocation: | Channel | What It Does | |---------|-------------| | **Email** | Send via Gmail or SMTP mailboxes | | **LinkedIn** | Connection request, direct message, InMail, follow, view profile, like post (via HeyReach) | | **Phone** | Voice AI calls or manual dialer | | **SMS** | Text messages | | **WhatsApp** | WhatsApp Business messages | Each channel requires an allocated resource: a mailbox for email, a LinkedIn account for LinkedIn actions, a phone number for calls/SMS, etc. ### Mailboxes and Sending Volume **There is no limit on how many mailboxes you can add to a sequence.** graph8 rotates sends across all assigned mailboxes automatically. Each mailbox can send up to **15 sequence emails per day** once it is fully warmed up. New mailboxes ramp up gradually to protect sender reputation: | Mailbox age | Sequence emails per day | |-------------|------------------------| | First 2 weeks | 0 (warm-up only) | | Week 3 | 5 | | Week 4 | 10 | | Week 5 and beyond | 15 (fully warmed) | The ramp is based on when the mailbox was created in graph8. It applies to cold outbound sequences only and is enabled by default; it can be adjusted for your organization. The 15/day cap is a deliverability safeguard that keeps volume well under mailbox provider limits. To increase your total daily sending volume, add more mailboxes to the sequence. For example, 4 fully warmed mailboxes can send up to 60 emails per day combined. Nurture campaigns work differently: they use a single pinned mailbox that can send up to 500 emails per day and skip the warmup ramp. See [Nurture →](https://docs.graph8.com/engage/nurture/). ## Building a Sequence 1. Go to **Engage → Sequencer** and click **Create Sequence** 2. Name your sequence and select a contact list 3. Add steps; each step has a channel, timing, and content 4. Assign resources (mailboxes, phone numbers, LinkedIn accounts) 5. Set a schedule for sending windows ### Steps Each step defines one action in the sequence: | Step Type | Channel | What It Does | |-----------|---------|-------------| | **Email** | Email | Send a personalized email | | **Phone** | Phone | Trigger a voice AI call | | **Manual Dialer** | Phone | Queue contact for manual dialing | | **LinkedIn** | LinkedIn | Execute a LinkedIn action (connect, message, InMail, follow, view, like) | | **SMS** | SMS | Send a text message | | **WhatsApp** | WhatsApp | Send a WhatsApp Business message | For each step, choose how the content is created: - **On-demand**: write the content yourself - **AI-generated template**: let AI generate the content - **Manual template**: use a saved template Set the **time interval** between steps (e.g., wait 2 days after the previous step before executing). ### LinkedIn Actions LinkedIn steps support multiple action types: - **Connection request** - send a connection request with an optional message and fallback message - **Message** - send a direct message to an existing connection - **InMail** - send an InMail with subject and body - **Follow** - follow the contact's profile - **View profile** - view the contact's profile (creates a notification) - **Like post** - like a recent post with a selectable reaction type **LinkedIn account requirement**: Sequences with HeyReach LinkedIn steps require at least one LinkedIn account to be assigned as a channel. If no LinkedIn accounts are connected, the step creation form shows a prompt to connect an account first. You can add or remove LinkedIn accounts from the **Channels** tab in Sequence Details after the sequence is created. ### AI Instructions (LinkedIn) LinkedIn steps support an **AI Instructions** mode. Instead of writing a static message, you can provide instructions and let graph8 generate a personalized message for each contact at send time. The AI uses: - Contact data (name, role, company) - Global Context - Campaign documents This allows dynamic, per-contact personalization and keeps messages relevant and up to date. ## Sequence Settings | Setting | What It Controls | |---------|-----------------| | **Finish on reply** | Stop the sequence for a contact when they reply | | **Send in same thread** | Keep all emails in the same email thread | | **Wait for new contacts** | Pause the sequence and resume when new contacts are added to the list | | **Shared** | Make the sequence visible to your entire team | ## Running a Sequence Sequences move through these statuses: | Status | Meaning | |--------|---------| | **Drafted** | Created but not yet started | | **Scheduling** | Processing and scheduling contacts | | **Live** | Actively executing steps | | **Paused** | Temporarily stopped; can resume | | **Waiting** | Paused, will resume when new contacts are added | | **Completed** | All contacts have finished the sequence | | **Terminated** | Manually stopped; cannot resume | Controls: - **Start**: begin executing the sequence - **Pause / Resume**: temporarily stop and restart - **Terminate**: permanently stop the sequence - **Duplicate**: create a copy of the sequence ### Pre-Launch Validation Before launching a sequence, graph8 checks that all required fields are available for the selected channels. If any required data is missing, you'll see a warning with details about what needs to be fixed before launch. ## Contact States Each contact in a sequence has their own state: | State | Meaning | |-------|---------| | **Queued** | Waiting to begin | | **In progress** | Currently executing steps | | **Paused** | Manually paused | | **Completed** | Finished all steps | | **Failed** | A step failed | | **Bounced** | Email or message bounced | | **Removed** | Manually removed from the sequence | | **Replied** | Contact replied (sequence stopped if finish-on-reply is enabled) | You can pause or resume individual contacts without affecting the rest of the sequence. ## Schedules Schedules define when steps can execute: - **Sending windows**: set the hours and days of the week when messages can be sent - **Timezone**: respect each contact's timezone for delivery timing Create reusable schedules in **Engage → Schedules** and assign them to any sequence. ## Templates Save time with reusable templates: - **Sequence templates**: save an entire sequence structure (steps, timing, channels) for reuse - **Step templates**: save individual step content as templates ## Reports & Analytics Every sequence tracks detailed performance: | Metric | What It Shows | |--------|--------------| | **Overview** | Total contacts, active, completed, success rate, overall progress | | **Step breakdown** | Per-step counts: queued, in progress, completed, failed, bounced, replied | | **Conversion funnel** | Contacts entered vs. completed at each step | | **Step performance** | Sent, delivered, replied, bounced counts and rates per step | | **Response rates** | Reply rates across time frames (24h, 48h, 7 days) | | **Engagement trends** | Daily reply rate and bounce rate trends | ### Skipped Contacts The step progress also tracks **Skipped** contacts. Skipped contacts are those that were not processed due to: - Missing required data (e.g., no email or phone) - Deduplication rules - Step conditions not met The skipped count appears when there are affected contacts. ## Step Conditions Attach conditions to any step so it only runs for contacts who meet specific criteria. Contacts who fail the condition are marked as **Skipped** and continue to the next step. ### Condition Types | Condition | Example | |-----------|---------| | **Field value** | `Job title contains "VP"` | | **Previous step outcome** | `Step 1 email was opened` | | **Engagement threshold** | `Opened ≥ 2 previous emails` | | **Custom field** | `ICP score > 80` | | **List membership** | `Contact is in list "Enterprise ICP"` | | **CRM field** | `HubSpot lifecycle stage = MQL` | | **Time since event** | `Website visit within last 7 days` | ### Adding a Condition 1. Open a step in the sequence editor 2. Click **Add Condition** 3. Pick the condition type and value 4. Choose what happens if the condition fails: - **Skip step**: move to the next step - **Exit sequence**: remove the contact from the sequence - **Wait and retry**: re-evaluate after N days (useful for "wait until they visit the site") > **Tip:** > Use conditions to personalize sequences without branching. One example: "If contact opened email 1, send LinkedIn message; otherwise, send email 2." Keeps the sequence simple while adapting to behavior. ## Branching Branches split a sequence into multiple paths based on contact attributes or behavior. Each path can have its own steps, content, and timing. ### When to Use Branches - Send different content to different personas (VP vs. IC) - Fork sequences by channel preference (email-first vs. LinkedIn-first) - Treat replied contacts differently from non-repliers - Escalate to phone after 2 ignored emails ### Adding a Branch 1. In the sequence editor, click **Add Branch** on any step 2. Define the split condition (same types as Step Conditions) 3. Add steps to each branch 4. Paths can re-converge into a shared follow-up step or end independently ### Branch Analytics Each branch reports independently: | Metric | Scope | |--------|-------| | **Contacts per branch** | How the split distributed | | **Per-branch conversion** | Reply rate, meeting-booked rate per path | | **Winning branch** | Side-by-side comparison for A/B test patterns | ## Provider Fallback When a primary channel resource fails, graph8 can fall back to a secondary resource automatically. ### Fallback Modes | Mode | Behavior | |------|----------| | **Strict** | If the assigned resource fails, skip the step | | **Rotate** | Try the next resource in the assigned pool | | **Cross-channel** | Fail over to a different channel (e.g., email → LinkedIn) | ### Common Use Cases - **Mailbox at send cap** → rotate to a different mailbox in the pool - **LinkedIn account rate-limited** → wait 4 hours and retry on the same account - **Phone number rejected** (do-not-call list) → skip and continue sequence - **WhatsApp template rejected** → email fallback with the same content Configure fallback per sequence in **Sequence Settings → Fallback**. ## Rate Limits and Send Caps graph8 enforces daily caps per channel to protect sender reputation: | Cap Type | Default | Purpose | |----------|---------|---------| | **Per-mailbox daily cap** | 15 emails (fully warmed) | New mailboxes ramp up over their first 5 weeks; see [Mailboxes and Sending Volume](#mailboxes-and-sending-volume) | | **Per-LinkedIn-account daily cap** | 25 actions per day | Matches LinkedIn platform safety limits | | **Phone, SMS, WhatsApp** | Set per organization | Protects number and account reputation | Defaults shown; limits are managed by graph8 and can be adjusted for your organization. When a cap is hit, the step re-queues for the next available sending window. ## Deliverability Sequence performance depends on the underlying mailbox health. Before launching a high-volume sequence: - **Warm up mailboxes** for 2–3 weeks so cold sends land in the inbox, not spam - **Verify domain DNS** (SPF, DKIM, DMARC) is configured and active - **Run placement tests** weekly to catch deliverability drops early Deliverability is configured in Settings, not Engage. See [Email Warmup →](https://docs.graph8.com/settings/email-warmup/) for the full warmup workflow, placement testing, and reputation monitoring. ## Studio Integration Campaigns created in Studio can be launched directly into the Sequencer. When launched, the sequence inherits the campaign's messaging, personas, and channel strategy. The link between Studio campaign and Engage sequence is maintained for end-to-end performance tracking. > **Tip:** > Start with 3–5 steps per sequence. Multi-channel sequences (email + LinkedIn + phone) typically outperform single-channel ones. Use the conversion funnel report to identify where contacts drop off. ## Next Steps - [Inbox →](https://docs.graph8.com/engage/inbox/): Manage replies and conversations - [Dialer →](https://docs.graph8.com/engage/dialer/): Power dialer for outbound calls - [Connections →](https://docs.graph8.com/engage/connections/): Connect your CRM for contact sync - [Email Warmup →](https://docs.graph8.com/settings/email-warmup/): Build sender reputation before launching --- # Web Chat > Engage website visitors with a customizable live chat widget — AI agents, action buttons, preset questions, and calendar booking. Source: https://docs.graph8.com/engage/web-chat/ Web Chat adds a live chat widget to your website. Assign AI agents to handle conversations, configure action buttons, suggest questions to visitors, and let prospects book meetings — all without leaving your site. ## Chat Agents Chat agents are AI-powered assistants that handle conversations on your website. You can assign different agents to different pages. | Setting | What It Controls | |---------|-----------------| | **Agent name** | Which AI agent handles conversations on matched pages | | **URL filter** | URL paths where this agent is active (e.g., `/pricing`, `/product`) | | **Active** | Enable or disable the agent | | **Default** | Used when no URL-specific agent matches | ### How Agent Routing Works 1. A visitor opens the chat widget on a page 2. graph8 checks the page URL against all configured agents' URL filters 3. The matching agent handles the conversation 4. If no URL filter matches, the default agent responds You can configure multiple agents — for example, a product-focused agent on `/product` pages and a support agent on `/help` pages. ## Chat Buttons Add action buttons to the chat widget for quick visitor actions: | Action Type | What It Does | |-------------|-------------| | **Link** | Opens a URL (e.g., documentation, pricing page) | | **Question** | Sends a preset question to the AI agent | | **Calendar** | Opens a booking page for scheduling a meeting | Each button has: - **Label** — display text - **Action and value** — what happens when clicked (URL, question text, or calendar slug) - **Icon** — optional icon - **Category** — group related buttons together - **Paths** — URL paths where the button appears (show different buttons on different pages) ## Preset Questions Suggest conversation starters to visitors so they know what to ask: - Set questions per category - Target specific pages with URL path filters (or show on all pages) - Toggle individual questions on or off Preset questions appear when the visitor first opens the chat, reducing friction and guiding the conversation toward topics your AI agent handles well. ## Widget Design Customize the widget's look and feel to match your brand: ### Colors & Layout | Setting | Default | |---------|---------| | **Primary color** | #7E2DF3 | | **Secondary color** | #f8fafc | | **Font family** | Inter | | **Border radius** | 0.5rem | | **Widget width** | 350px | | **Dark mode** | Off | ### Message Styling | Setting | What It Controls | |---------|-----------------| | **User message background** | Color behind visitor messages | | **User message text** | Text color for visitor messages | | **Assistant message background** | Color behind AI responses | | **Assistant message text** | Text color for AI responses | ### Header & Button | Setting | What It Controls | |---------|-----------------| | **Assistant name** | Name shown in the chat header | | **Show header** | Toggle the header bar | | **Header text color** | Color of the header text | | **Floating button icon** | Icon style for the chat trigger button | | **Floating button color** | Color of the trigger button | ## Calendar Integration Visitors can book meetings directly from the chat widget using the calendar action button. Prospects can pick a time without leaving the conversation. ## Attachments, Voice Notes & Emoji The chat composer supports rich media so visitors can share context that text alone can't convey. ### File Attachments Visitors can attach files directly in the chat composer: - **Images** — JPG, PNG, GIF, WebP - **Documents** — PDF and common document formats - **Size limits** — enforced server-side to prevent abuse - **SSRF protection** — uploads are validated and isolated; remote URLs are not fetched on the server Once uploaded, the attachment is rendered inline in the conversation and persists across history reloads — if a visitor returns later, their previous attachments still display correctly. ### Voice Notes Visitors (and your support team) can send voice notes from the composer: - Click the microphone icon to start recording - Click again to stop and send - Voice notes appear in the conversation with a built-in player (play, pause, scrub) - Codec-flexible: graph8 accepts codec-parameterized voice MIME types from any modern browser - Voice notes are transcribed and fed to the AI agent so it can respond in context ### Emoji Picker The composer includes an emoji picker for quick reactions and lighter conversational tone — useful for support agents handling friendly support flows. ## Support Avatars When a human support agent joins a conversation, their avatar appears in the chat header so visitors know they're talking to a real person. If no custom avatar is set, graph8 falls back to the agent's initials. This applies in both the public widget and the internal-support widget view. ## Hiding the Chat Widget Visitors can collapse the chat whenever they want. Click the **arrow** tab on the edge of the open chat panel to hide it. The floating chat button stays on the page, so visitors can reopen the conversation any time. ![Click the arrow tab to hide the chat widget — the floating button stays so visitors can reopen it](https://docs.graph8.com/images/engage/web-chat-hide-widget.png) > **Tip:** > Set up URL-specific agents for your highest-traffic pages. A pricing page agent trained on your pricing and packaging will convert better than a generic assistant. Use preset questions to guide visitors toward booking a demo. ## Next Steps - [Appointments →](https://docs.graph8.com/engage/appointments/) — Manage booked meetings and CRM sync - [Inbox →](https://docs.graph8.com/engage/inbox/) — View chat conversations alongside email and LinkedIn - [Connections →](https://docs.graph8.com/engage/connections/) — Connect your CRM for contact sync --- # Work: conversations and agent work > Use Work to coordinate conversations, review agent activity, and schedule recurring work. Source: https://docs.graph8.com/engage/work/ Work brings team conversations and agent activity into the same workspace. Open **Work** in the main navigation and start with **Inbox**. ## Find the right view | View | Use it to | |---|---| | **Inbox** | Chat with teammates and agents, and return to a conversation's history. | | **All Work** | Review units of agent work across conversations, including work still running. | | **People** | Find people in your organization and see their presence. | | **Activity** | Review the organization-wide activity log. | | **Routines** | Set up recurring work and receive results in chat. | | **Team HQ** | Review team performance and moments. | The views shown depend on the current product navigation and your access. A link or older screenshot may refer to a view that is not offered in your menu. ## Work in a conversation 1. Open **Inbox** and choose the conversation that contains the relevant people and context. 2. Write the outcome you want. Include the records, files, constraints, and intended audience the agent needs. 3. Follow the conversation for questions, progress, and results. Review any approval request before allowing the described action. 4. Open **All Work** to inspect related work across conversations. Return to the original conversation when you need its instructions or context. 5. Review the resulting message, artifact, or changed record. A running task or an approval request is not a completed result. ## Review attachments and artifacts Keep supporting files in the conversation where the work is happening. When a result includes an artifact, open it and review its contents before sharing it. If the interface offers versions or export, check that you are working with the intended version. ## Schedule recurring work Open **Routines** to manage repeated work. Describe the task, choose the schedule and destination offered by the form, and review the configuration before saving. Check the resulting chat message after a run. If a run needs access or a decision, resolve that request before expecting later runs to complete. ## Recover from a problem - If work needs approval, review the requested action and its scope in the conversation. - If an attachment or result is missing, return to the source conversation rather than starting a duplicate task immediately. - If an agent reports an access or connection error, fix that dependency before retrying a consequential action. - If a view is absent, check with your administrator; browser and desktop capabilities can differ. ## Related - [Agents and workflows](https://docs.graph8.com/ai-features/workflows/) - [Task management](https://docs.graph8.com/data/tasks/) - [Team access](https://docs.graph8.com/settings/team/) - [Browse the feature guide](https://docs.graph8.com/getting-started/feature-guide/) ## More help - [Configure a routine step by step](https://docs.graph8.com/engage/work-routines/) --- # Recurring work with Routines > Choose an agent, schedule and conversation for repeated work. Source: https://docs.graph8.com/engage/work-routines/ Routines run an instruction on selected days and deliver the result into a conversation. Open **Work → Routines**. ## Create a routine 1. Open the create form and enter a title you can recognize later. 2. Pick the **Agent** that should do the work. 3. Write the instruction with the expected result, relevant context, and any limits on actions. 4. Select at least one day of the week. 5. Enter the **Time** in 24-hour `HH:MM` format and check the **Timezone**. 6. Under **Deliver into**, pick the conversation that should receive the result. 7. Choose **Create routine**. Open the saved routine and check its settings. The form requires a title, an agent, an instruction, selected days, a valid time, a timezone, and a delivery conversation. If agent or conversation choices fail to load, resolve that before creating the routine. ## Update the schedule Open the routine, change the instruction or schedule, and choose **Save routine**. Check the timezone when moving a routine between teams or regions. ## Review the result Return to the delivery conversation after the scheduled run. Check that the result answers the instruction and inspect any requested approval or access error. A saved schedule does not prove that the agent completed its work. ## Related - [Work](https://docs.graph8.com/engage/work/) - [Agents and workflows](https://docs.graph8.com/ai-features/workflows/) --- # Browsing & Filtering Talent > Search, sort, and filter the talent catalog to find the right SDRs, AEs, and GTM Engineers — and save searches you want to reuse. Source: https://docs.graph8.com/marketplace/clients/browse/ The **Browse** tab is the talent catalog. Use search, quick filters, the filter rail, and sorting to narrow hundreds of vetted profiles down to the ones that fit. ![The Browse tab with talent cards, tier badges, rates, and the filter rail](https://docs.graph8.com/images/marketplace/hiring-browse-grid.png) ## Search and sort - **Search** by name, bio, role, tools, or industry from the search bar. - **Sort** with the dropdown. Options are **Top rated** (the default), **Most deals closed**, **Most meetings booked**, **Lowest monthly rate**, **Highest monthly rate**, **Newest**, and **Lowest rated**. - **Switch views** between **Cards** and **Table**. ## Quick filters One-click chips above the results apply common presets: **Available now** · **Top tier** · **Has video intro** · **Americas** · **EMEA** · **APAC** · **Favorites** ## The filter rail For finer control, open the filter rail on the right. Each section narrows the catalog, and applied filters show as chips you can clear individually or all at once. | Filter | Options | |--------|---------| | **Role** | SDR · Account Executive · GTM Engineer · Campaign Manager | | **Quality** | Certification score range, plus tiers (Strategic, Data-Driven, Top Performer, and more) | | **Availability** | Available · Limited · Fully booked, plus a **Favorites only** toggle | | **Geography** | Americas · EMEA · APAC, plus a country filter | | **Rates** | Monthly base and Per meeting range sliders | | **Experience** | Min meetings booked · Min deals closed · Has video intro · Has LinkedIn | | **Industries / Tools / Languages** | Multi-select by industry, tool / CRM, and language | ## Save a search Once you've dialed in a useful set of filters, save it to reuse later: 1. Apply at least one filter. 2. Open the **Saved** menu and choose **Save current view**. 3. Name the search and click **Save**. Your saved searches appear in the same menu — click one to re-apply it instantly. ## Favorites Click the **star** on any talent card (in Cards or Table view) to favorite a profile. Use the **Favorites** quick filter or the **Favorites only** toggle to see just your shortlist. ## Next Steps - [Talent Profiles & Match Scores →](https://docs.graph8.com/marketplace/clients/profiles/) - [Hiring Talent →](https://docs.graph8.com/marketplace/clients/hiring/) --- # Hiring Talent > Send a hire offer with custom rates and a start date, and follow it from pending acceptance to an active contract. Source: https://docs.graph8.com/marketplace/clients/hiring/ When you've found the right talent, click **Hire** on their card or profile to send an offer. The talent reviews it and accepts or declines. ## Sending an offer The **Hire** dialog — titled with the talent's name — lets you set the terms of the engagement: | Field | Notes | |-------|-------| | **Monthly Base Rate ($)** | The recurring monthly rate | | **Per Meeting Rate ($)** | What you pay per booked meeting | | **One-Time Hiring Fee ($)** | Optional upfront fee | | **Commission (%)** | Set by talent after meetings — 0 if not in the contract | | **Start Date** | When the engagement begins | Click **Send Offer**. graph8 generates an order form and sends it to the talent. ![The Hire dialog with monthly base, per-meeting rate, hiring fee, commission, and start date](https://docs.graph8.com/images/marketplace/hiring-offer-form.png) ## From offer to contract After you send an offer, track it in the **Contracts** tab, where contracts are grouped by status: - **Pending Acceptance** — the talent hasn't responded yet - **Active** — the talent accepted; the engagement is live - **Terminated** — the contract has ended Each contract card shows the rates, order number, and start/end dates. ![The Contracts tab listing active engagements with order numbers and dates](https://docs.graph8.com/images/marketplace/hiring-order-form.png) > **Note:** > An offer becomes an active contract only once the talent accepts the order form. Until then it stays under **Pending Acceptance**. ## Next Steps - [Managing Hired Talent →](https://docs.graph8.com/marketplace/clients/managing/) - [Talent Profiles & Match Scores →](https://docs.graph8.com/marketplace/clients/profiles/) --- # Managing Hired Talent > Work with the talent you've hired — message them, track meetings, and review and pay their statements. Source: https://docs.graph8.com/marketplace/clients/managing/ Once talent accept your offers, you manage the ongoing relationship from the **My Talent**, **Meetings**, **Messages**, and **Statements** tabs. ## My Talent The **My Talent** tab lists everyone you've actively hired. From each talent's menu you can: - **Manage list access** — control which contact lists the talent can work - **Terminate contract** — end the engagement ![The My Talent tab with active hired talent](https://docs.graph8.com/images/marketplace/hiring-my-talent.png) Terminating opens a confirmation that explains the final prorated invoice and that you can re-hire later: ![The terminate contract confirmation dialog](https://docs.graph8.com/images/marketplace/hiring-terminate.png) ## Messages & Meetings - **Messages** — real-time threads with your talent for day-to-day coordination. - **Meetings** — every meeting your talent have booked, so you can see the outbound they're generating. ![The Meetings tab with logged meetings by date, type, status, and contact](https://docs.graph8.com/images/marketplace/hiring-meetings-log.png) ![A message thread with a talent](https://docs.graph8.com/images/marketplace/hiring-messages.png) ## Statements (billing) Talent submit **service statements** for the work they've done, and you review and pay them from the **Statements** tab. Each statement moves through these statuses: | Status | Meaning | |--------|---------| | **Draft** | The talent hasn't submitted it yet | | **Submitted** | Waiting on your review | | **Approved** | You approved it for payment | | **Rejected** | You declined it (with a reason) | | **Changes Requested** | You asked the talent to revise it | | **Expired** | It lapsed without action | When a statement is **Submitted**, you can: 1. **Approve & Pay** — approve and pay it immediately. 2. **Request Changes** — send it back with a note on what needs to change. 3. **Reject** — decline it with a reason. Approved statements show a **Pay Now** action if payment is still pending. ![The Statements tab with submitted and approved service statements and Approve & Pay actions](https://docs.graph8.com/images/marketplace/hiring-invoices.png) ## Next Steps - [Hiring Talent →](https://docs.graph8.com/marketplace/clients/hiring/) - [Browsing & Filtering Talent →](https://docs.graph8.com/marketplace/clients/browse/) --- # Opening the Marketplace > Find the Marketplace in graph8 and get oriented with the six tabs clients use to hire and manage talent. Source: https://docs.graph8.com/marketplace/clients/overview/ Clients open the Marketplace from the **Marketplace** icon in the top navigation. It's where you browse vetted talent, send hire offers, and manage everyone you've hired — all in one place. ![The Marketplace browse view with vetted talent cards](https://docs.graph8.com/images/marketplace/hiring-overview-browse.png) ## The six tabs The Marketplace is organized into six tabs across the top: | Tab | What it's for | |-----|---------------| | **Browse** | Search and filter the full talent catalog, then view profiles and send offers | | **My Talent** | The talent you've actively hired — message them, manage list access, or end a contract | | **Meetings** | Meetings your hired talent have booked | | **Messages** | Your message threads with talent | | **Contracts** | Every offer you've sent, grouped by status | | **Statements** | Billing — review and pay the service statements talent submit | ## Who it's for The Marketplace is for any team that wants to add outbound capacity without recruiting and onboarding in-house. You hire pre-vetted SDRs, Account Executives, and GTM Engineers on flexible terms and pay based on the rates you agree on. ## Next Steps - [Browsing & Filtering Talent →](https://docs.graph8.com/marketplace/clients/browse/) - [Talent Profiles & Match Scores →](https://docs.graph8.com/marketplace/clients/profiles/) - [Hiring Talent →](https://docs.graph8.com/marketplace/clients/hiring/) --- # Talent Profiles & Match Scores > Read a talent card, understand certification tiers, and interpret the match-score radar before you hire. Source: https://docs.graph8.com/marketplace/clients/profiles/ Every talent has a card in **Browse** and a full profile behind **View Profile**. Both are built from graph8's AI scoring, so you can compare candidates on consistent, vetted signals. ## Reading a talent card Each card shows, at a glance: | Field | What it means | |-------|---------------| | **Name** (or role) | The talent's name, or just their role if their profile is anonymous | | **Tier badge** | A summary label such as *Strategic* or *Data-Driven* | | **Match** | How well the talent fits your business (e.g. "78 match") | | **Country & availability** | Location and current status — Available, Limited, or Fully Booked | | **$X/mo base** | Monthly base rate | | **$Y/meeting** | Per-meeting rate | | **Score: N/100** | The overall certification score | Use **View Profile** to open the full detail view, or **Hire** to send an offer. ## Certification tiers Tiers come from certification scoring and summarize where a talent is strongest. You'll see labels such as **Strategic**, **Data-Driven**, **Independent**, **Disciplined**, **Top Performer**, **Leadership-track**, **Senior**, **Solid**, and **Transition**. The **Top tier** quick filter narrows to the highest tiers. ## The match-score radar Open a profile and the match view shows a radar chart scoring the talent across several dimensions, grouped into: - **Fit to your business** — Industry Fit, Tool Fit, Persona Fit, Deal Size Fit, Motion Fit - **Role-specific strengths** — Prospecting Style, Meeting Quality, Cycle Length, Negotiation Complexity, Quota Band - **Edge** — Competitive Displacement Higher scores mean a stronger fit on that dimension. The match number on the card is a roll-up of these dimensions against your business. ![A talent profile with the dimension radar, standout strengths, and rate card](https://docs.graph8.com/images/marketplace/hiring-candidate-match.png) > **Tip:** > The certification **Score** rates the talent overall; the **match** rates how well they fit *your* business. A strong all-rounder may have a high Score but a lower match for a niche motion — check both. ## Next Steps - [Hiring Talent →](https://docs.graph8.com/marketplace/clients/hiring/) - [Managing Hired Talent →](https://docs.graph8.com/marketplace/clients/managing/) --- # How It Works > How the graph8 Marketplace connects clients and talent — vetting, certification, rates, and the commission model. Source: https://docs.graph8.com/marketplace/how-it-works/ The Marketplace has two sides: **clients** who hire sales talent, and **talent** (SDRs, Account Executives, and GTM Engineers) who join to earn. graph8 vets and scores every profile so clients can hire with confidence. ## The two sides | Side | Who they are | What they do | |------|--------------|--------------| | **Clients** | Companies that need outbound | Browse vetted talent, compare match scores, and send hire offers with custom rates | | **Talent** | Freelance SDRs, AEs, and GTM Engineers | Build a profile, get certified and scored, then accept offers and run real outbound | ## Vetting and certification Talent don't list themselves manually — they get **scored by AI**. When a profile uploads a resume, graph8 analyzes it to produce: - **Dimension scores** — a breakdown across competencies like industry fit, prospecting style, meeting quality, and negotiation - **A certification score** — an overall rating from 0–100 - **A tier** — a label such as *Strategic*, *Data-Driven*, or *Top Performer* that summarizes a talent's strengths New talent complete a **30-day certification** to get listed as vetted GTM talent. Scoring is what powers the **Score: N/100**, tier badges, and match scores that clients see when browsing. ## How talent get paid | Stage | Earnings | |-------|----------| | **During certification** | Commission-only — talent earn from day one when meetings are booked | | **After being hired** | A monthly base rate plus a per-meeting rate, set on each offer (an optional one-time hiring fee and commission can also apply) | Payouts are sent via Stripe Connect, Stripe Cross-Border, or Wise, depending on the talent's country. ## Rates and availability Every talent profile carries a **rate card** — a monthly base rate, a per-meeting rate, and an availability status (**Available**, **Limited**, or **Fully Booked**). Clients filter and sort on exactly these fields when they browse. > **Tip:** > Clients set the rates on each offer, and talent accept or decline. The rate card on a profile is the talent's asking rate — the starting point for a hire. ## Next Steps - [For Clients: Opening the Marketplace →](https://docs.graph8.com/marketplace/clients/overview/) - [For Talent: Joining the Marketplace →](https://docs.graph8.com/marketplace/talent/join/) --- # Setting Up Your AI Twin > Complete your AI Twin so graph8 can write and act on your behalf — career details, voice, and a LinkedIn import. Source: https://docs.graph8.com/marketplace/talent/ai-twin/ The **AI Twin** tab is where you teach graph8 about you, so the AI can write and act on your behalf in outreach. The more complete it is, the better your outbound performs. It has four sub-tabs: **Profile Completion**, **Career**, **About Me**, and **Voice**. ## Profile Completion The **Profile Completion** sub-tab tracks your setup as a weighted checklist. Each item adds to your score, and your profile is considered **AI Ready** at 80% or more. | Item | Why it matters | |------|----------------| | Add your job title | Helps the AI position you correctly in outreach | | Add your company | Context for who you represent | | Import your LinkedIn profile | Auto-fills title, bio, expertise, and job history | | Write a short bio | 1–2 sentences the AI uses to introduce you | | Set your voice & tone | How the AI should sound when writing as you | | Add expertise areas | At least 3 areas so the AI knows your domain | | Add writing samples | Examples of your writing style to learn from | | Add a profile photo | Used in campaign content and team views | | Write a full bio | Detailed background for thought-leadership content | | Connect Twitter / X | Enables X-specific content and writing samples | > **Tip:** > The fastest way to fill most of this in is to **import your LinkedIn profile** using the desktop app or Chrome extension — it auto-fills your title, bio, expertise, and job history. ## Career, About Me & Voice - **Career** — your current role (job title, company, department, responsibilities), job history, expertise and skills, tools and platforms, and key achievements. - **About Me** — personal and social details, including your Twitter / X profile. - **Voice** — your short bio, voice and tone, and writing samples the AI learns your style from. ## Next Steps - [Your Listing & Getting Scored →](https://docs.graph8.com/marketplace/talent/listing/) - [Receiving & Accepting Offers →](https://docs.graph8.com/marketplace/talent/offers/) --- # Joining the Marketplace > Sign up as graph8 Marketplace talent, accept your profile, and start the certification that gets you listed. Source: https://docs.graph8.com/marketplace/talent/join/ Anyone can join the graph8 Marketplace as talent — SDRs, Account Executives, and GTM Engineers. You earn commission by running real outbound campaigns, and complete a 30-day certification to get listed as vetted GTM talent. ## Sign up Go to [app.graph8.com/marketplace/join](https://app.graph8.com/marketplace/join) and fill in: | Field | Notes | |-------|-------| | **First name** / **Last name** | Your name | | **Email** | Any email — personal or work | | **Your role** | SDR, Account Executive, or GTM Engineer | | **Country** | Determines how you get paid | Click **Sign Up & Join**. ![The marketplace application form: name, email, role, and country](https://docs.graph8.com/images/marketplace/get-hired-apply-form.png) > **Note:** > Certification is commission-only — you earn from day one when meetings are booked. ## Accept your profile and get certified After signing up, you'll confirm your marketplace profile and start your **4-week certification**. From there you build out your profile and upload your resume so graph8 can score and activate your listing. ## The Commission-Only Track (optional) You may be offered the **Commission-Only Track** — sell on commission during certification with a dedicated target list and weekly payouts. You can opt in to receive a target list, or decline and stay on the Marketplace waiting for client matches. Declining keeps your profile active and visible to clients. ## Next Steps - [Setting Up Your AI Twin →](https://docs.graph8.com/marketplace/talent/ai-twin/) - [Your Listing & Getting Scored →](https://docs.graph8.com/marketplace/talent/listing/) --- # Your Listing & Getting Scored > Set up your public Marketplace Profile — upload your resume to get scored, set your rate card, and control your availability. Source: https://docs.graph8.com/marketplace/talent/listing/ The **Marketplace** tab in your profile is your **Marketplace Profile** — your public listing. It's what hiring clients see: your role, scores, rates, and availability. Setting it up and uploading your resume is what activates your listing. ## Get scored The single most important step is uploading your resume: 1. Open the **Marketplace** tab. 2. In the resume section, click **Upload Resume (PDF)**. > Your resume is analyzed by AI to generate dimension scores and a certification score. This activates your profile on the marketplace so hiring orgs can find you. The certification score and dimension scores produced here are exactly what clients see as your **Score: N/100**, tier badge, and match score. ![Your Marketplace Profile with rate card, certification score, dimension scores, and strengths](https://docs.graph8.com/images/marketplace/get-hired-profile-editor.png) ## Your rate card Set your asking rates and availability in the **Rate Card**: | Field | Notes | |-------|-------| | **Monthly Base** | Your monthly base rate | | **Per Meeting** | Your per-meeting rate | | **Availability** | Available, Limited, Fully Booked, or Hidden | > **Note:** > Set **Availability** to **Hidden** to temporarily remove your profile from client search without deleting it. ## Edit your profile Use **Edit Profile** to round out your listing — add a **headshot**, a short **tagline** (up to 140 characters), a longer **summary** (up to 1,200 characters), a **video intro** (record one directly in your browser, upload a file, or paste a URL), and the **industries** you've worked. A video intro also unlocks the **Has video intro** filter clients use. ## Delete your profile **Delete Profile** removes your listing. It requires typing **DELETE** to confirm. ## Next Steps - [Receiving & Accepting Offers →](https://docs.graph8.com/marketplace/talent/offers/) - [Getting Paid →](https://docs.graph8.com/marketplace/talent/payouts/) --- # Messages & Meetings > Stay in touch with hiring clients and track the meetings you book from the Messages and Meetings tabs. Source: https://docs.graph8.com/marketplace/talent/messages-meetings/ Once you're working with clients, the **Messages** and **Meetings** tabs are where you coordinate day to day and track the outbound you generate. ## Messages The **Messages** tab holds your conversation threads with hiring organizations. When a hiring org reaches out, the thread appears here so you can reply directly. Until then it shows *"No messages — when a hiring org reaches out, the thread will appear here."* ![Your inbox with the conversation list, the hiring company's org brief, and the message thread](https://docs.graph8.com/images/marketplace/get-hired-inbox.png) ## Meetings The **Meetings** tab lists the meetings you've booked for clients. You can filter by client, status, date range, and search, and each meeting shows the client org, order number, and date. Meetings move through these statuses: | Status | Meaning | |--------|---------| | **Scheduled** | Booked and upcoming | | **Taken** | The meeting happened | | **Rescheduled** | Moved to a new time | | **Missed** | The prospect didn't attend | | **Cancelled** | Called off | > **Tip:** > Booked meetings are how you earn during certification, so keep your meeting statuses accurate — they feed your earnings and the statements you submit. ## Next Steps - [Getting Paid →](https://docs.graph8.com/marketplace/talent/payouts/) - [Receiving & Accepting Offers →](https://docs.graph8.com/marketplace/talent/offers/) --- # Receiving & Accepting Offers > Review hire offers from clients, check the order form, and accept or decline. Source: https://docs.graph8.com/marketplace/talent/offers/ When a client sends you a hire offer, it lands in your **Offers** tab. Until you have one, the tab shows *"No offers yet — when a client sends you a hire offer, it will appear here."* ![Your SDR dashboard, where Offers, Meetings, Statements, and Payouts live](https://docs.graph8.com/images/marketplace/get-hired-dashboard.png) ## Reviewing an offer Each offer shows the key terms: | Field | What it shows | |-------|---------------| | **Client** | The hiring organization | | **Order number** | The order reference (e.g. `#123`) | | **Status** | Pending acceptance, active, rejected, or terminated | | **Monthly rate** | The monthly base rate offered | | **Per-meeting rate** | The per-meeting rate offered | | **Start date** | When the engagement would begin | ## Accepting or declining For a pending offer you can: 1. **View Order Form** — open the full order form to review the terms. 2. **Accept** — accept the offer and start the engagement. 3. **Decline** — turn the offer down. Accepting confirms your marketplace profile for that engagement and the contract becomes active on the client's side. ## Next Steps - [Messages & Meetings →](https://docs.graph8.com/marketplace/talent/messages-meetings/) - [Getting Paid →](https://docs.graph8.com/marketplace/talent/payouts/) --- # Getting Paid > Submit statements for your work, set up a payout method, and track your earnings from the Invoices and Payouts tabs. Source: https://docs.graph8.com/marketplace/talent/payouts/ You bill for your work from the **Invoices** tab and receive your earnings through the **Payouts** tab. ## Submitting statements For each engagement you submit a **service statement** covering a period of work. A statement includes the engagement, the period start and end, an amount, and a summary, and moves through these statuses: | Status | Meaning | |--------|---------| | **Draft** | Not submitted yet | | **Submitted** | Sent to the client for review | | **Approved** | The client approved it for payment | | **Rejected** | The client declined it (with a reason) | | **Changes Requested** | The client asked you to revise it | | **Expired** | It lapsed without action | ![The Statements tab with a submitted service statement](https://docs.graph8.com/images/marketplace/get-hired-statements.png) ## Setting up payouts The **Payouts** tab is where you configure how you get paid and track what you've earned. Set up a payout account, then watch your payout history and net balance. Your payout method depends on your country: - **Stripe Connect Express** — most major markets - **Stripe Cross-Border** — select additional countries - **Wise** — broad coverage for everywhere else Your payout history lists each payout or chargeback with its date, type, amount, and status (pending, processing, paid, or failed). ![The Payouts tab with Set Up Payouts and payout history](https://docs.graph8.com/images/marketplace/get-hired-payout-setup.png) > **Note:** > During certification you're commission-only and earn when meetings are booked. Once a client hires you, you also earn the monthly base and per-meeting rates set on your offer. ## Next Steps - [Switching Between Accounts →](https://docs.graph8.com/marketplace/talent/switching-accounts/) - [Your Listing & Getting Scored →](https://docs.graph8.com/marketplace/talent/listing/) --- # Switching Between Accounts > Move between your talent profile and a client organization using the org switcher. Source: https://docs.graph8.com/marketplace/talent/switching-accounts/ If you both sell on the Marketplace and hire through it, your account can belong to more than one organization — your **talent** org and one or more **client** orgs. The org switcher lets you move between them. ## Switch organizations 1. Open the **user menu** in the top-right corner. 2. Choose **Switch organization**. 3. Pick the organization you want to work in. graph8 switches your active organization and reloads so you land in the right context — your talent profile (Profile, AI Twin, Marketplace, Offers, Payouts) or a client workspace (Browse, My Talent, Contracts, Statements). Your current organization is marked in the list. > **Tip:** > If the Marketplace looks different than you expect — talent tabs instead of client tabs, or vice versa — check which organization is active in the user menu and switch if needed. ## Next Steps - [Joining the Marketplace →](https://docs.graph8.com/marketplace/talent/join/) - [Opening the Marketplace (clients) →](https://docs.graph8.com/marketplace/clients/overview/) --- # graph8 for Account Manager > graph8 field guide for Account Manager. Live URLs, real surfaces, screenshots from a public-data showcase tenant. Source: https://docs.graph8.com/roles/account-manager/ *A field guide for Account Manager. Live URLs, real surfaces. Screenshots captured from a showcase tenant with public-company data only.* ## The five things to use today ### 01. Your book + expansion levers per customer *Open `/deals/l/customers`, sort by MRR desc, open the top accounts one at a time. The right-rail Customer Health card shows **MRR · ARR · Health · Expansion Levers · Risk Signals · Role Assignments** — that's your QBR prep.* **Open it at** - `app.graph8.com/deals/l/customers` — your paying customers - Click any customer → Overview tab with Health/Expansion right rail ![Customers list showing your customers with MRR column ($2.5K-$8.9K), Status, Plan (GTM POD / New price for platform), Subscription, Owner columns](../../../../assets/roles/account-manager/1-customers-mrr.webp) ***Fig 1.1** — `/deals/l/customers`. your paying customers. Columns: MRR · Plan · Subscription · Customer Since · Owner · Stakeholders · Deals · Deal Value. Sort by MRR desc — top accounts come first. Filter Owner = you for your book.* live AM brief · a paying customer · captured 2026-05-20 MRR · ARR$8,800 · $105,600 HealthCritical (Score 0) Engagement (30d)0 / 0 / 0 / 0 / 0 Risk signalNo meeting on record Expansion leverCredits exhausted: 0 Owner assignmentsSDR · AE · CSM — all Unassigned > **Tip: Try this now** > **AM move.** Sort book by MRR desc. Open the top 5. Each "Critical" health badge with "No meeting on record" = book a QBR this week. Each high-credit-usage customer = expansion conversation. Each unassigned-Owner row = ask the manager to assign before someone else picks up the renewal email. ### 02. Conversion pipeline + win-back — Trials and Churned *Trials about to convert (or expire). Churned customers ranked by recency — your win-back funnel.* **Open these** - `app.graph8.com/deals/l/trials` — Active + Expired sub-tabs, with countdown timers - `/deals/l/churned` — ex-customers with Churn Reason column ![Trials view with Active/Expired tabs, Trial Ends countdown column (67d left, 66d left), Signup, Source, Referrer, First Touch columns](../../../../assets/roles/account-manager/3-trials-conversion.webp) ***Fig 2.1** — `/deals/l/trials`. Active trials with **Trial Ends countdown** + Source + Referrer + First Touch. Anyone < 14d left = immediate call. The Expired sub-tab is your "missed conversion, send a discount email" list.* ![Churned customers showing Gmail, riseup-asia-llc, x-hydration, a sample deal, Lombard Metals with Churn Reason column (mostly blank), MRR column, Plan, Update date](../../../../assets/roles/account-manager/4-churned-winback.webp) ***Fig 2.2** — `/deals/l/churned`. Ex-customers: *Gmail · riseup-asia-llc · x-hydration · a sample deal · Lombard Metals · Quantum Global Airways*. *Churn Reason* column is mostly empty today — fix that by logging reason during cancellation (workflow improvement).* > **Tip: Try this now** > **AM move.** Friday afternoon ritual: open Trials, list everyone < 21 days from expiry. Schedule a 15-min "trial check-in" with each. Open Churned, pick 2 highest-MRR customers from the last 90 days, draft a personalized win-back email with a specific product update they didn't see. ### 03. Renewal & expansion deals — Deal Intelligence for AMs *Every expansion deal gets the same DEAL INTELLIGENCE sidebar as new-biz: auto-generated Deal Risks, Next Steps, Close Plan, ROI Analysis, Competitive Analysis. Use it for renewal prep the same way an AE uses it for new-logo close.* **Open it at** - From customer detail → Active Deals → click the expansion deal - Or `/deals/pipeline` filtered to your owner ![Synapze GmbH deal showing DEAL INTELLIGENCE sidebar (Strategy: Deal Risks/Next Steps/Close Plan; Analyze: ROI/Competitive), Deal Progress 10%, Stage Readiness 100%, Suggest next step button, Deal Information (Value $1003, Stage New Meeting)](../../../../assets/roles/account-manager/5-deal-intelligence-renewal.webp) ***Fig 3.1** — *Synapze GmbH* deal. Left sidebar: **Strategy** (Deal Risks · Next Steps · Close Plan) + **Analyze** (ROI · Competitive). All 5 generated. Top: *"Suggest next step"* button + Stage Readiness gauge. This works just as well for renewal/expansion deals as it does for new biz.* > **Tip: Try this now** > **AM move.** When a customer renewal is 60 days out, create a renewal deal in the pipeline + let graph8 auto-generate the Strategy stack. Read the Deal Risks before the renewal call. The system is honest about *why* the renewal might be at risk so you can address it head-on. ### 04. Send renewal quotes + triage replies *Quote-to-cash works for renewals + expansions: build a quote, send for signature, Stripe auto-provisions the new subscription on sign. Then triage replies in Inbox with sequence-attribution chips.* **Open these** - `app.graph8.com/deals/quotes` — All / Draft / Sent / Viewed / Accepted / Expired / Declined - `/inbox/email` — sentiment chips + sequence attribution ![Quotes table with status filter tabs and a sample quote, a sample quote quotes showing your data status, totals $50-$8498, Net 30 terms, Valid Until](../../../../assets/roles/account-manager/6-quotes-renewals.webp) ***Fig 4.1** — `/deals/quotes`. Renewal + expansion quotes flow through the same surface as new-biz quotes. *Accepted* tab is auto-provisioned in Stripe; *Viewed* but not Signed = your follow-up call list.* ![Inbox /inbox/email with Focused/Other tabs, threads attributed to sequences (ROOFS_BANS_SEQUENCE, SALEONOMICS_QA_CO, ACCOUNTEXECUTIVES_SAR)](../../../../assets/roles/account-manager/7-inbox-customer-replies.webp) ***Fig 4.2** — Inbox at `/inbox/email`. Replies tagged with the campaign+channel that drove them. Filter by your customer's email domain to find their threads.* > **Tip: Try this now** > **AM move.** Before sending a renewal quote: open the customer's threads in the Inbox, read the last 10 emails. Reference one specific value moment in your renewal email — beats a generic "your renewal is coming up" by 5×. ### 05. Site-visit signals + Copilot for expansion candidates *the identified companies hit graph8 properties today. Cross-reference customer domains = expansion or churn signal. Then ask the copilot to surface candidates with a single prompt.* **Open these** - `/visitors?tab=companies` — the identified companies on the site today - `/reports?report=a14` — Account Health Comparison (cross-account drift) - Copilot — sparkle icon or Cmd+Shift+K > **Tip: Try this now** > **AM move.** Try these AM-specific copilot queries: *"Which of my customers haven't been emailed in 30 days?"* · *"Show me past_due customers + total revenue at risk."* · *"Which of my customers visited /pricing in the last 7 days?"* · *"List my customers approaching annual renewal in the next 90 days."* ## URL cheat-sheet | To do this | Go here | | --- | --- | | Your book of business + MRR | `/deals/l/customers` | | Customer detail with your data | Click any customer row | | Past_due customers ($ at risk) | `/deals/l/customers` · Subscription filter | | Trial accounts (conversion pipeline) | `/deals/l/trials` | | Churned customers (win-back candidates) | `/deals/l/churned` | | Open expansion/renewal deal | `/deals/pipeline` filtered to Owner | | Send a renewal/expansion quote | `/deals/quotes` | | Inbox with sequence attribution | `/inbox/email` | | Site visitors (existing customers?) | `/visitors?tab=companies` | | Account Health Comparison report | `/reports?report=a14` | | Open Copilot anywhere | Cmd+Shift+K · or sparkle top-right | ## Heads-up — known AM gaps + workarounds > **Caution:** > **1. Owner column on /deals/l/customers is empty for graph8 today.** All 41 customer rows have no Owner. **Workaround:** assign yourself as Owner on your accounts immediately — otherwise filtering "my book" doesn't work. > > **2. Churn Reason column is mostly empty.** When a customer churns, log the reason. Otherwise the Churned list is just a list of names, not a win-back analysis surface. > > **3. No customer-status filter on /visitors.** You manually scan for customer domains in the 42-row list. **Workaround:** ask the copilot *"Which of my customers visited the site this week?"* > > **4. No customer-filter on /inbox/email.** Search by domain to find threads. Sentiment chips help. > > **5. Customer detail's "Active Deals" panel shows empty if there's no open deal.** Create a renewal deal 60-90 days ahead of contract date — that activates the DEAL INTELLIGENCE sidebar for prep. > > **6. Copilot has no native MRR/subscription tool.** Use the past_due filter directly + the Customer Health right-rail for revenue-at-risk truth. --- <small>Generated 2026-05-20 against `app.graph8.com` (org `your org`). Surface state verified live before publication</small> --- # graph8 for Account Executive > graph8 field guide for Account Executive. Live URLs, real surfaces, screenshots from a public-data showcase tenant. Source: https://docs.graph8.com/roles/ae/ *A field guide for AE. Live URLs, real surfaces. Screenshots captured from a showcase tenant with public-company data only.* ## The six things to use today ### 01. Run your pipeline — Deals, Companies, Contacts *A real pipeline with auto-generated deal intelligence on every deal. This is the strongest shipped surface in graph8 today.* The Deals shell is the AE's home. Top sub-nav: **My Desk · Companies · Contacts · Deals**. The Sales Pipeline selector top-right lets you switch pipelines once multi-pipeline ships; for now you're working in *Sales Pipeline*. **Open it at** - `app.graph8.com/deals/pipeline` — deal list with columns: *Deal · Company · Docs (e.g. 6/16) · Stage · Value · Owner · Close Date · Last Activity* - `app.graph8.com/deals/{deal-id}` — full deal detail panel ![Deals pipeline list with 4 deals: test, a sample deal, a sample deal, stuff](../../../../assets/roles/ae/1-hero.webp) ***Fig 1.1** — Deal list view at `/deals/pipeline`. The *Docs* column (6/16, 17/16, 10/16) tells you which deals have full discovery / ROI / competitive coverage. Top sub-nav: My Desk · Companies · Contacts · Deals.* **The killer feature — Deal Intelligence sidebar** Open any deal. The left rail shows two stacks, both auto-generated by graph8 from the deal's data + conversation history: - **Strategy:** Deal Risks · Next Steps · Close Plan - **Analysis:** ROI Analysis · Competitive Analysis Verified on the *a sample deal* deal — all 5 documents generated (5/5 indicator). Center tabs: **Overview · Data · Activity · Memory · Notes · Tasks · Quotes · Contacts · Signals**. Right panel: Value · Stage · Pipeline · Close Date · Next Step · Closed Won Reason. Top-right: *"Suggest next step"* button + Stage Readiness % gauge (100% on a sample deal). ![Deal Intelligence sidebar showing Strategy + Analysis stacks, 5/5 generated](../../../../assets/roles/ae/2-deal-intel.webp) ***Fig 1.2** — Deal Intelligence sidebar. The "Intelligence 5/5" indicator means graph8 has auto-generated every strategy + analysis doc for this deal.* > **Tip: Try this now** > **Try this now.** Open `/deals/pipeline`, click any deal, hit *Suggest next step*. Read the auto-generated Close Plan in the left rail before your next call with that account. ### 02. Your morning queue — My Desk *AI-generated decision cards prioritized for the AE. Open it once per morning, work the top of the stack. No dashboards to scan, no manual triage.* **Open it at** - `app.graph8.com/deals/my-desk` — pending decision cards + AI-completed log (with 30-min undo window) My Desk surfaces decision cards from **14 signal sources**. Cards are auto-generated as signals fire (Redis streams + per-org pollers) and addressed to the deal or contact owner. Each card has an autonomy lane: *autopilot* (action fires automatically with 30-min undo), *approve* (queues for your one-click sign-off), or *off*. The four signal sources **shipped 2026-05-21** (the most recent additions to the 14): - **No reply** — an outbound email / SMS / LinkedIn thread you started has had no reply in 5+ days. Card links the deal + last sequence; one-click pause the sequence from the card. - **Intent signal** — a known contact at one of your accounts visited multiple pages on graph8.com in a tight window, returned after a week of silence, or matched a keyword pattern you configured. - **Champion change** — the primary contact on a deal got reassigned to a different owner (in CRM or here). Card on every open deal at that company, addressed to the deal rep. - **Enrichment win** — graph8 just filled a high-value missing field on one of your contacts (mobile phone, direct email, current title, current company, LinkedIn URL). Now's the moment to reach out. The other 10 (older) generators cover: *meeting prep · post-meeting follow-up · at-risk / stalled deal · new lead · re-engagement · overdue task · deal stage suggestion · competitive alert · playbook deviation · coaching insight*. > **Caution: Screenshot pending** > **Fig 2.1** — *Screenshot pending first card emissions on the graph8 tenant (rollout completed 2026-05-22). Expected within hours for intent + no-reply (continuous-poll); champion-change is naturally rare. This caption will be replaced with a live capture once cards land.* Each card carries a **summary** line (e.g. *"No reply for 7d on 'Re: Acme renewal'"*) and the underlying `context_data` the AI used (deal_value, days_since_outbound, matched_fields, etc.). Click the card to expand; approve, dismiss, or let autopilot handle it. > **Tip: Try this now** > **Try this now.** Open `/deals/my-desk` tomorrow morning, sort by priority (highest first), and work top-to-bottom for 15 min. Anything you dismiss without acting on is feedback the AI will learn from — the rate at which generators emit cards self-tunes per org. ### 03. See your book of business + MRR — Customers list *graph8's revenue overlay joins Stripe billing to the company record. You see ARR, MRR, plan, and subscription status next to firmographics — without leaving the CRM.* **Open it at** - `app.graph8.com/deals/l/customers` — paying customers with revenue overlay Live today on the graph8 tenant: **your paying customers**. Real MRR data visible per row — examples observed: `a paying customer $2.5K · a paying customer $2.5K · a paying customer $8.9K · your account $4K · a paying customer $2.4K · a paying customer $499 (past_due) · a paying customer $1.5K · a paying customer $2.5K`. Columns: *Status · MRR · Plan · Subscription · Customer Since · Owner · Stakeholders · Deals · Deal Value*. Actions: *Move stage · Add company*. ![Customers list with MRR per row including a paying customer, a paying customer, a paying customer, your account, a paying customer](../../../../assets/roles/ae/3-customers.webp) ***Fig 3.1** — The revenue tab at `/deals/l/customers`. `CBAccount` overlay on `MashupCompany`, Stripe-synced. Status, MRR, plan, subscription, customer-since, owner, stakeholders, deals, deal value all in one row.* > **Tip: Try this now** > **Try this now.** Open `/deals/l/customers`, sort by MRR desc, find your top 5 paying customers. Now check the Last Activity column — anyone you haven't touched in 30+ days is renewal risk. ### 04. Send a quote, get it signed, bill it — all in graph8 *Quote → eSign → Stripe customer + subscription + invoice + payment link — auto-provisioned the moment the buyer signs. No DocuSign tab. No QBO export.* **Open it at** - `app.graph8.com/deals/quotes` — all quotes with status filters (Draft · Sent · Viewed · Accepted · Expired · Declined · Voided) - Or from a deal detail panel → **Quotes** tab Live today: **10+ quotes** a sample quote through a sample quote, ranging $50–$8,499, Net 30 terms. Multiple **Accepted** quotes confirm the end-to-end signing path works. ![Quotes list at /deals/quotes with status filter chips and a sample quote through a sample quote quotes](../../../../assets/roles/ae/4-quote-detail.webp) ***Fig 4.1** — Quotes list. Filter chips up top: All / Draft / Sent / Viewed / Accepted / Expired / Declined / Voided. *Viewed*-but-not-Signed is your highest-leverage follow-up bucket.* **Inside a quote — the lifecycle is visible** Open quote `a sample quote` (verified live). Status pill: **Accepted**. Lifecycle timeline shows: - **Sent** · May 19, 1:56 PM - **Viewed (×2)** · May 19, 1:56 PM - **Signed** · May 19, 1:56 PM Line items: *10,000 Credits · qty 1 · $500.00 · Net 30 · Valid Until Jun 18, 2026*. Notes field carries the SOW preamble. Tabs: *Overview · Activity · Email*. *Duplicate* button to clone for the next deal. ![a sample quote quote detail panel with your data lifecycle dots and line items](../../../../assets/roles/ae/4-quotes-list.webp) ***Fig 4.2** — a sample quote right slide-out: Accepted pill, Sent → Viewed ×2 → Signed timeline, $500 / 10,000 Credits line item, Net 30, Valid Until Jun 18 2026, SOW notes. Tabs: Overview · Activity · Email. *Duplicate* to clone for the next deal.* > **Tip: Try this now** > **Try this now.** Click *Duplicate* on an Accepted quote, change the line-item quantity, send to a test buyer. Watch the Sent → Viewed → Signed dots populate in real time as they open and sign. ### 05. One inbox + a copilot that answers natural-language CRM questions *Every email/SMS/WhatsApp/Slack/iMessage thread against the contact, with the copilot in the sidebar answering questions like *"how many deals are in my pipeline and what's the total value?"*.* **Open it at** - `app.graph8.com/inbox/email` — focused / other tabs, sentiment tags, sequence attribution headers - `app.graph8.com/profile?tab=apps` — bridge connection setup (WhatsApp · Slack · Roam · iMessage) - Global copilot — sparkle icon top-right, available on every page Live today on the inbox: real reply traffic with sentiment tagging — **Interested** (green), **Out of Office** (yellow). Sequence attribution headers visible above grouped threads (e.g. *GROWTHDEMANDGEN_SAAS_US&UK&AUS&CAN - SEQUENCE*, *VP/HEAD-SALES_SAAS - SEQUENCE*). Thread examples: *"built for teams that move fast"*, *"API-first GTM platform"*, *"ditch the per-seat pricing"*. ![AI Inbox at /inbox/email showing replies with sentiment chips and sequence attribution headers](../../../../assets/roles/ae/5-inbox.webp) ***Fig 5.1** — Inbox at `/inbox/email`. Sentiment chips (**Interested** green, **Out of Office** yellow) and sequence-attribution headers (GROWTHDEMANDGEN_SAAS, SALESLEADERS_AI&ML, VP/HEAD-SALES_SAAS) group every reply by the campaign that sourced it.* **Copilot — verified live response** > **Note: Live copilot exchange** > *Live copilot exchange · 2026-05-20* > > > "How many deals are in my pipeline and what's the total value?" > > 8 open deals with a total pipeline value of $19,488 > > | Deal | Stage | Amount | Close Date | > | --- | --- | --- | --- | > | a sample deal | Engaged | TBD | Oct 31 | > | a sample deal | Engaged | $7,000 | Oct 31 | > | a sample deal | Demo | $500 | Oct 31 | > | a sample deal | Demo | $5,988 | Feb 28 | > | stuff | Discovery Held | $5,000 | May 15 | > | a sample deal | New Meeting | $1,000 | May 31 | > | test | Discovery | TBD | — | > | a sample deal Deal | New Meeting | TBD | — | > > a sample deal ($7,000) is your largest valued deal and was due Oct 31 — it's past close date with no update. a sample deal and a sample deal are also past their Oct close… > > (3 deals have no amount set). ![Inbox + Copilot side panel showing pipeline query response with structured deal table](../../../../assets/roles/ae/6-copilot.webp) ***Fig 5.2** — Copilot side panel answering "how many deals are in my pipeline" with a structured Deal · Stage · Amount · Close Date table. Not just prose.* ![Profile Apps tab Setup sub-tab showing your data, iMessage connected](../../../../assets/roles/ae/7-bridge.webp) ***Fig 5.3** — All four bridges connected at `/profile?tab=apps`. WhatsApp+1 (555) 010-0000 · Slack (thomas) · Roam (a user) · iMessage+1 (555) 010-0000 — Edit / Disconnect buttons per row.* > **Tip: Try this now** > **Try this now.** Click the sparkle icon top-right. Ask the copilot *"which of my deals haven't had a reply in 7 days?"* or *"show me my customers with past_due subscriptions."* The same copilot is reachable from WhatsApp/Slack — connect at `/profile?tab=apps` and DM your own bridge. ### 06. See who's on your site — Visitors, Intent, Analytics *Real-time visitor identification + per-company page-visit timeline + acquisition channel breakdown. Tells the AE *which* account is heating up and *where* the pipeline came from.* **Open it at** - `app.graph8.com/visitors?tab=companies` — identified company visitors with page detail - `app.graph8.com/visitors/companies/{id}` — per-company visit timeline - `app.graph8.com/analytics/acquisition` — channel + source/medium breakdown (Direct, Organic Search, Referral, AI/LLM) Live today: **the identified companies** identified visiting graph8 properties. Real B2B logos verified on screen — *your account, Target, your account, your account, your account, your account, your account, your account, your account*. Pages hit: `/blog/`, `/platform/ai/`, `/credits/`, `/trends/synthetic-patient-data/`, `/pricing`, `/search`. ![the identified companies visiting graph8 properties with page path and visit time columns](../../../../assets/roles/ae/8-visitors.webp) ***Fig 6.1** — the identified companies at `/visitors?tab=companies`. Real B2B logos visible: your account, Target, your account, your account, your account, your account, your account. The *Page Path* column is the AE signal — anyone hitting `/pricing` right now is a call worth making.* ![Per-company visit timeline at /visitors/companies/11617851 showing 2 page views](../../../../assets/roles/ae/9-visitor-detail.webp) ***Fig 6.2** — Per-company visit timeline. Click any company in the list (here: your account, internal id `11617851`) to see every page they've hit, when, and from which referrer. Heads-up: today the header renders as "Company \{id\}" rather than the company name — a known cosmetic gap.* ![Acquisition analytics showing Direct 90.7%, Organic Search 4.7%, Referral 4.5%, AI/LLM 0.2%, your monthly users last 30 days](../../../../assets/roles/ae/10-acquisition.webp) ***Fig 6.3** — Acquisition channels last 30 days: **your monthly users · your monthly sessions · your engagement rate**. the channel breakdown. Source/Medium pivot is below the fold.* > **Tip: Try this now** > **Try this now.** Open `/visitors?tab=companies`, sort by Visit Time desc. Anyone hitting `/pricing`, `/credits/`, or `/platform/ai/` in the last 24h is a call worth making this morning. ## URL cheat-sheet | To do this | Go here | | --- | --- | | See your pipeline | `/deals/pipeline` | | Open a deal & read Deal Intelligence | `/deals/{id}` | | Your morning decision queue | `/deals/my-desk` | | See paying customers + MRR | `/deals/l/customers` | | All quotes (or filter by status) | `/deals/quotes` | | AI Inbox (email + sentiment) | `/inbox/email` | | Connect WhatsApp / Slack / Roam / iMessage | `/profile?tab=apps` | | Identified site visitors (companies) | `/visitors?tab=companies` | | Acquisition channel breakdown | `/analytics/acquisition` | | Open Copilot anywhere | Sparkle icon · top-right | | Search the CRM | Cmd-K | ## Heads-up — what's not in this doc > **Caution:** > **Multi-pipeline**, **stage-checklist v2 programmatic gates**, **QuickBooks / Xero / Chargebee billing**, and the **playbook editor / flywheel** are all in-progress or PRD-only. Don't pitch them yet. > > **Attribution analytics** (`/analytics/attribution`) — the surface exists but graph8 org has no `cb_outbound_sends` rows yet, so it's empty. Don't demo against the graph8 tenant. --- <small>Generated 2026-05-20 · last updated 2026-05-21 to add Section 02 (My Desk) and the 4 new signal generators that shipped with PR [#7621](https://github.com/graph8-com/g8/pull/7621). Verified against `app.graph8.com` (org `your org`) surface by surface</small> --- # graph8 for AI Agent Operator > graph8 field guide for AI Agent Operator. Live URLs, real surfaces, screenshots from a public-data showcase tenant. Source: https://docs.graph8.com/roles/agent-op/ *A field guide for AI Agent Operator. Live URLs, real surfaces. Screenshots captured from a showcase tenant with public-company data only.* ## The five things to use today ### 01. The agent roster — 8 agents across Agent + Twin entities *5 Agents + 3 Twins. Agents use preset voices (David / Michael / Olivia). Twins use voice-cloned UUIDs of real humans (Ari, Din, Nick). Each has role, status, voice, phone, calendar appointment.* **Open it at** - `app.graph8.com/agents` — full roster ![Agents grid showing 8 agents: AI Receptionist, 10k free signup (David Male +1 (555) 010-0001), Hyperpersonalised (Michael Male), Graph8 Chat Agent (Olivia Female +1 (555) 010-0002), Graph8 Support Agent (CSM), Ari (Twin SDR Custom Voice), Din (Twin SDR), Nick (Twin AE Custom Voice) with columns Name/Entity/Role/Status/Phone/Voice/Appointment Event/Company Knowledge/Actions](../../../../assets/roles/agent-op/1-agents-list.webp) ***Fig 1.1** — `/agents`. Columns: *Name · Entity · Role · Status · Phone · Voice · Appointment · Company Knowledge.* All 8 Active.* Agent · Receptionist AI Receptionistroutes inbound calls · phone directory · scheduling Agent · SDR 10k free signup (David, Male)+1 (555) 010-0001 · trial-signup outbound Agent · SDR Hyperpersonalised (Michael, Male)hyper-personalized outreach sequences Agent · SDR/Chat Graph8 Chat Agent (Olivia, Female)+1 (555) 010-0002 · website visitor chat · demo booking Agent · CSM Graph8 Support Agentpost-sale support, escalation Twin · SDR AriCustom voice clone · UUID 8f5bc08c-… · dialer-driven Twin · SDR DinCustom voice clone · many dial sessions Twin · AE NickCustom voice clone · AE close-stage calls ### 02. Agent detail — Overview · Persona · Identity · Skills · Collections · Activity *6 sub-tabs per agent. Configure mission, personality sliders, voice/phone, attached skills/workflows, knowledge collections, and per-agent activity log.* **Open it at** - Click any agent in `/agents` roster ![Graph8 Support Agent (CSM) Overview tab showing name, role CSM, Active badge, Company Knowledge button, Phone/Voice/Calendar (Appointment #0), 4 KPI tiles (Total Calls/Avg Duration/Success Rate/Collections=1), Description, Inbound/Outbound Call Instructions cards, Voicemail Message. Tab nav: Overview, Persona, Identity, Skills, Collections, Activity](../../../../assets/roles/agent-op/2-agent-detail.webp) ***Fig 2.1** — Graph8 Support Agent Overview. **6 tabs:** Overview · Persona · Identity · Skills · Collections · Activity. KPIs (Total Calls · Avg Duration · Success Rate · Collections) — empty until agent runs.* > **Tip: Try this now** > **Operator move.** Per agent monthly: re-read the Persona prompt aloud. Adjust the 3 sliders if customer feedback says the agent is too pushy / too formal / too verbose. Sliders are blunt but effective. ### 03. Skills + Workflows + Company Knowledge *Attach reusable skills (action recipes) and workflows to agents. Plus Company Knowledge (RAG corpus from the 46 brand DNA docs). Today: 0 skills attached across agents — easy to populate.* **Open it at** - Agent detail → Skills sub-tab - Agent detail → Collections sub-tab - Top of agent detail → Company Knowledge button ![Skills tab for Graph8 Chat Agent showing Agent Skills & Workflows header, Attach Skills button, empty state 'No Skills or Workflows Attached', Skill Usage Statistics row at bottom (Total Skills=0, Executed 30d=0, Success Rate=0%)](../../../../assets/roles/agent-op/5-agent-analytics.webp) ***Fig 3.1** — Skills tab. **0 skills attached** across all agents today. Surface ships, ready for population. *Skill Usage Statistics (Total · Executed 30d · Success Rate)* all at 0.* > **Tip: Try this now** > **Operator move.** Populate Skills for the agents that get the most traffic (Graph8 Chat + 10k free signup). Add reusable skills like "book a meeting" · "qualify the lead" · "send a calendar invite" · "create a CRM contact." Track Skill Usage to see which fire most + which fail. ### 04. Twin agents — voice-cloned versions of real reps *Twins differ from Agents: custom voice UUIDs (cloned from a real human), no Delete button (less freely removable), voicemail scripts written in first-person human voice. Today: 3 Twins (Ari/Din/Nick) covering SDR + AE roles.* ![Ari Twin agent detail showing Active, SDR role, Voice with UUID `[voice-clone-id]` (custom voice clone), no standard phone, Voicemail Message populated with personalized script 'this is Ari from Graph8'](../../../../assets/roles/agent-op/7-twin-agent.webp) ***Fig 4.1** — Ari Twin agent. Voice = UUID `8f5bc08c-2c5a-…` (cloned human voice). Voicemail script in first-person: *"\{First_Name\} this is Ari from Graph8…"* **brand rule violation** — "Graph8" should be lowercase. Fix in voicemail textbox.* > **Tip: Try this now** > **Operator move.** Audit each Twin voicemail script monthly for brand rule violations ("graph8" lowercase) + tone drift. Re-record the voicemail if the script has changed. Then re-run a small test dial session to verify the new clone hits the right notes. ### 05. Monitoring — Inbox + Dialer + Copilot *Replies hit the shared Inbox tagged by sequence (not by agent — see caveat). Dialer sessions show Twin-driven campaigns. Copilot can pull weekly agent performance — when the dialer analytics endpoint works (currently broken with HTTP 422).* ![Inbox Focused tab with 6 threads (a contact 'kill the tool sprawl', a contact 'how AI companies are using this' with Not Interested + Draft tags, a contact, a contact, a contact, a contact) tagged by SEQUENCE source (REVOPS_SAAS, SALESLEADERS_AI&ML)](../../../../assets/roles/agent-op/9-dialer-voice-ai.webp) ***Fig 5.1** — Inbox. Replies tagged by SEQUENCE source. **Heads-up:** no explicit "Handled by Olivia" agent-attribution tag — replies land in shared inbox.* ![Copilot panel response to 'How are my AI agents performing this week?' showing pulled agent list with table (10k free signup → Conversion → positive-interest responses + signup; Graph8 Chat Agent → SDR/Chat → Website visitor engagement demo booking), Performance Data section noting dialer analytics endpoint returned HTTP 422 error, offers alternatives](../../../../assets/roles/agent-op/10-copilot-agentop.webp) ***Fig 5.2** — Live copilot. Q: *"How are my AI agents performing this week?"* Returns agent role table + flags **dialer analytics endpoint HTTP 422**. **Real bug** — Operator should escalate to engineering.* > **Tip: Try this now** > **Operator move.** Weekly cycle: check `/dialer` Sessions for Twin/Agent call volume + status (Paused vs Active). Check Inbox for sentiment trends per sequence. Ask copilot *"Which agent had the most positive replies this week?"* — when the analytics endpoint is healthy, you get a real answer. ## URL cheat-sheet | To do this | Go here | | --- | --- | | Agent roster (8 agents) | `/agents` | | Agent detail (6 sub-tabs) | Click any agent row | | Persona prompt + sliders | Agent → Persona tab | | Voice + phone + calendar | Agent → Identity tab | | Skills + Workflows attached | Agent → Skills tab | | Knowledge collections | Agent → Collections tab | | Per-agent activity log | Agent → Activity tab | | Dialer session monitoring | `/dialer` | | Inbox (replies tagged by sequence) | `/inbox/email` | | Brand DNA docs (RAG source) | `/studio?mode=global` | | Open Copilot | Cmd+Shift+K | ## Heads-up — Operator gaps + bugs to escalate > **Caution:** > **1. **BUG** Dialer analytics endpoint returns HTTP 422 to Copilot.** Copilot can't pull weekly agent performance. Escalate to engineering — this blocks the natural-language "how are my agents performing?" question that Operators ask most. > > **2. **BRAND** Ari Twin voicemail script has "Graph8" (capital G).** Should be lowercase "graph8" per brand rule. Audit all 3 Twin voicemails + agent prompts for this. > > **3. Skills attached: 0 across all agents.** Surface shipped but unused. Populate skills + workflows on the top-traffic agents first. > > **4. Inbox doesn't attribute replies to a specific AI agent.** Threads tagged by sequence only — no "Handled by Olivia" tag. Workaround: trace by phone number + timestamp for now. > > **5. Activity tab is "Nothing here yet" across all agents.** Per-agent activity log surface exists but isn't populated. Either no activity is being written to it, or activity is being written elsewhere — confirm with engineering. > > **6. Only 2 agents have phone numbers assigned.** 10k free signup (+1 (555) 010-0001) and Graph8 Chat Agent (+1 (555) 010-0002). Other 6 agents are voice/chat-only. Assign phones to whichever agents should take inbound calls. --- <small>Generated 2026-05-20 against `app.graph8.com` (org `your org`). Surface state verified live</small> --- # graph8 for Campaign Manager > graph8 field guide for Campaign Manager. Live URLs, real surfaces, screenshots from a public-data showcase tenant. Source: https://docs.graph8.com/roles/campaign-manager/ *A field guide for Campaign Manager. Live URLs, real surfaces. Screenshots captured from a showcase tenant with public-company data only.* ## The five things to use today ### 01. Campaign Studio — 30 campaigns, each with 17 generated docs *graph8 doesn't make you build a campaign from scratch. Open Campaign Studio, pick a wedge, and you get the brief + messaging + targeting + sequence + cadence + ad copy + LP + QA rubric all generated. You edit, you don't author.* **Open it at** - `app.graph8.com/studio?mode=campaign` — Campaign Studio list view - Click any campaign → 17-doc workspace ![Campaign Studio list with 30 campaigns including The Broken Stack Benchmark, SalesLeaders, GrowthDemandGen, SDR Headcount, a paying customer series](../../../../assets/roles/campaign-manager/1-studio-campaign.webp) ***Fig 1.1** — `/studio?mode=campaign`. 30 campaigns live including *The Broken Stack Benchmark · SalesLeaders · GrowthDemandGen · SDR Headcount · TaylorHaren PostEng · a paying customer series · Spreadsheet · New CRO/VP Sales.** **The 17 auto-generated campaign documents** - Dashboard - Landing Pages - Campaign Brief - Messaging & Objections - Targeting & Routing - Email Prompt - Emails - Call Script - Voicemail - LinkedIn Copy - Social Prompt - Sequence - Cadence Calendar - Performance Tracker - QA Rubric - Reply Templates - Snippets ![The Broken Stack Benchmark campaign detail showing 17-doc sidebar, Readiness 82, progress chips for Content/Sequence/Edit Copy/Audience/Launch](../../../../assets/roles/campaign-manager/2-campaign-list.webp) ***Fig 1.2** — *The Broken Stack Benchmark* campaign with full doc sidebar. Readiness 82. Progress chips: Content · Sequence · Edit Copy · Audience · Launch. Click any doc in the sidebar to read/edit.* > **Tip: Try this now** > **Try this now.** Open one campaign. Read the *Campaign Brief* first (the wedge + ICP + value prop). Skim the *Messaging & Objections* doc — that's where copy variations live. Then jump to the *QA Rubric*: that's what you grade outbound replies against to know if the campaign is working. ### 02. Sequencer — 30 team sequences with per-step performance *Each campaign has a sequence behind it. The Sequencer shows per-step Progress %, Contacts at this step, Schedule, Replies. Kill underperforming steps in isolation; clone the winners.* **Open it at** - `app.graph8.com/sequencer` — Personal / **Team** / Archived tabs ![Team Sequences table with 30 sequences including Multi-Channel Sequence Test, SalesLeaders_AI&ML, GrowthDemandGen, Ari, Graph8_linkedin_testing](../../../../assets/roles/campaign-manager/3-campaign-docs.webp) ***Fig 2.1** — Sequencer Team tab. 30 sequences with channels / contacts / steps / status columns. Live ones include *Multi-Channel Sequence Test · SalesLeaders_AI&ML · GrowthDemandGen · TaylorHaren · Ari · Graph8_linkedin_testing · SDR/BDR · RevOps · VP/Head-Sales · DecisionMakers_Startups.** ![RevOps_SaaS sequence detail showing 8099 contacts, 3937 active, 3 steps with per-step Progress/Sent/Replied/Bounced/Scheduled stats](../../../../assets/roles/campaign-manager/4-team-sequences.webp) ***Fig 2.2** — *RevOps_SaaS* sequence detail: **many contacts · 3,937 active · 3 steps**. Per-step cards show Progress / Contacts / Sent / Replied / Bounced / Scheduled / Failed. Progress percentages over 100% (676% / 735%) mean the step has cycled the contact pool more than once.* > **Tip: Try this now** > **Try this now.** Open the highest-volume sequence. Sort steps by Reply count. The step with the lowest reply rate is your A/B candidate — clone the sequence, rewrite that step's email, run side-by-side for 14 days, keep the winner. ### 03. Paid channels + landing pages — every wedge gets a click destination *A campaign isn't complete without an ad and a landing page. graph8 ships both: 6-step ad wizard for Meta/LinkedIn/Google/X, plus 41 published LPs with template library (Bold Hero, Benefits Grid, Social Proof First, ROI Calculator, etc.).* **Open it at** - `app.graph8.com/ads` — Create Campaign wizard - `/studio?mode=content&ct=landing_page` — Landing Pages list (19 drafts + 41 published) ![Ads Create Campaign wizard with 6-step flow Platform/Audience/Creative/Budget/Review/Publish and Meta/LinkedIn(selected)/Google/X options](../../../../assets/roles/campaign-manager/5-sequence-detail.webp) ***Fig 3.1** — `/ads` wizard. 6 steps. 4 platforms. LinkedIn pre-selected for B2B campaigns. Each platform needs a one-time Connect Account before audience push-out.* > **Tip: Try this now** > **Try this now.** For your next campaign: clone an existing LP that converted well. Swap the hero copy to the campaign wedge. Launch the LinkedIn ad in `/ads` pointing at the new LP. Add the LP URL as the destination in the Campaign Studio's *Landing Pages* doc so the sequence emails point there too. ### 04. Reply triage with sequence attribution + conversion tracking *Every reply lands in the Inbox tagged with which campaign it came from. Sentiment chips tell you what kind of reply. Conversions analytics rolls up form submissions + meetings booked.* **Open it at** - `app.graph8.com/inbox/email` — sentiment + sequence-attribution chips per reply - `/analytics/conversions` — Goal Completions (Form Submission + Meeting Booked) - `/forms` — 19 forms with target-list mappings (lead capture endpoints) ![Conversions showing 148 total conversions, 0.1% rate, 148 form submissions, 40 meetings booked with Goal Completions breakdown](../../../../assets/roles/campaign-manager/8-forms.webp) ***Fig 4.2** — `/analytics/conversions`. **148 conversions · 108 form submissions · 40 meetings booked.** Two goals tracked.* ![Forms list with 19 forms showing Form ID, Page URL, Total Submissions, Latest Submission, Target List columns](../../../../assets/roles/campaign-manager/9-conversions.webp) ***Fig 4.3** — `/forms`. **19 forms** across graph8.com + a paying customer.com properties. Each form has a Target List that routes new submitters into a sequence — the lead-capture-to-outbound seam.* > **Tip: Try this now** > **Try this now.** Filter `/inbox/email` by the *Interested* sentiment chip + your campaign's name in the attribution. Those are your meeting-eligible replies. Cross-reference with `/analytics/conversions` to confirm meetings booked came from those threads. ### 05. Copilot — ask which campaign is winning, get a structured answer *Natural-language campaign performance queries. The copilot returns structured tables comparing reply rates, send volume, and per-segment performance across all live campaigns.* **Open it at** - Sparkle icon · top-right · any page → Copilot side panel - Also reachable from WhatsApp / Slack / iMessage / Roam via the Bridge (see `/profile?tab=apps`) ![Copilot response to Which campaign has the best reply rate showing SalesLeaders_AI&ML at 0.41% (5 replies / 1215 sent) with AI&ML vs broader SaaS analysis](../../../../assets/roles/campaign-manager/10-inbox-attribution.webp) ***Fig 5.1** — Live copilot response. Q: *"Which campaign has the best reply rate?"* A: **SalesLeaders_AI&ML at 0.41% (5 replies / 1,215 sent)** + analysis comparing AI/ML targeting to broader SaaS segments. The copilot reads from the same data the Performance Reports use.* ![Acquisition channels showing 80.6K users, Direct 90% Organic Search 6% Referral 4% AI/LLM 0%](../../../../assets/roles/campaign-manager/11-copilot-campaign.webp) ***Fig 5.2** — `/analytics/acquisition`. Channel mix the campaign drives traffic into: **80.6K users · Direct 90% · Organic 6% · Referral 4% · AI/LLM 0%.** Watch this rebalance when you launch paid ad campaigns.* > **Tip: Try this now** > **Try this now.** Try campaign-specific copilot queries: *"compare reply rates across all sequences"* · *"which sequence has the highest bounce rate?"* · *"which campaign sent the most emails last week?"* · *"who replied to RevOps_SaaS in the last 7 days?"* ## URL cheat-sheet | To do this | Go here | | --- | --- | | Campaign list (30 active) | `/studio?mode=campaign` | | Open campaign — 17 docs | Click any row in Campaign Studio | | Team sequences | `/sequencer` → Team tab | | Sequence detail (per-step perf) | Click any sequence row | | Ad campaigns (6-step wizard) | `/ads` | | Landing Pages (19 drafts · 41 published) | `/studio?mode=content&ct=landing_page` | | Reply triage with attribution | `/inbox/email` | | Conversions (forms + meetings) | `/analytics/conversions` | | Acquisition channels | `/analytics/acquisition` | | Form submissions + target lists | `/forms` | | Marketing Intelligence + Bridge Funnel | `/analytics/marketing` | | Open Copilot anywhere | Sparkle · top-right | ## Heads-up — known Campaign Manager gaps > **Caution:** > **1. `/campaigns` is 404.** The real path is `/studio?mode=campaign`. Bookmark that URL or click Studio → Campaign in top nav. > > **2. Several `?tab=` + `?ct=` URL params don't auto-apply.** Sequencer Team tab, Analytics tabs, Studio Content type filters — they boot to default and need an in-app click. **Workaround:** bookmark the parent URL and click the sub-tab. > > **3. Ad platforms ship unconnected.** All 4 (Meta · LinkedIn · Google · X) show "Platform not connected" until you OAuth each ad account once. > > **4. Sequence Progress can exceed 100%.** The 676% / 735% per-step Progress means the contact pool has cycled multiple times. Not a bug — just means the sequence has been running longer than the step duration × contact count. > > **5. Attribution analytics is empty for graph8.** `/analytics/attribution` shows "No sender data yet" — use `/analytics/conversions` + sequence-attribution chips in Inbox instead. --- <small>Generated 2026-05-20 against `app.graph8.com` (org `your org`). Surface state verified live before publication</small> --- # graph8 for Content Marketer > graph8 field guide for Content Marketer. Live URLs, real surfaces, screenshots from a public-data showcase tenant. Source: https://docs.graph8.com/roles/content/ *A field guide for Content Marketer. Live URLs, real surfaces. Screenshots captured from a showcase tenant with public-company data only.* ## The five things to use today ### 01. Studio Global — your 45 brand DNA documents *graph8 doesn't make you write a brand brief or messaging house from scratch. It generates 45 docs across 4 buckets (Intelligence · Context · Research · Targeting) from your website + enrichment data. You edit, you don't author.* **Open it at** - `app.graph8.com/studio?mode=global` — global doc workbench - Click any doc to open the full editor ![Studio Global docs grid showing 43/45 Generation Incomplete badge with your data buckets and document table](../../../../assets/roles/content/1-studio-global-docs.webp) ***Fig 1.1** — `/studio?mode=global`. **43 of 45 docs generated** across 4 buckets. Columns: Workflow · Category · Document · Status · Health · Updated. The top bar always tells you how many are done.* **The 45 docs across 4 buckets** - **Intelligence (13)** — Website Scrape, Company Enrichment, Competitor Discovery, Company Keywords, Product Inventory, Organic Keywords, Paid Keywords, Brand News, Brand Sentiment, Audience Questions, Products & Services + more - **Context (21)** — Brand Brief, Value Props, Offer Brief, Personas, ICPs, Messaging House, Proof Catalog, Pricing Matrix, Style Guide, Writing Style Guide, Brand Voice + more - **Research (6)** — Buyer Psychology, Competitive Teardown, GTM Channel, Industry Analyst, Review Sentiment, Voice of Customer - **Targeting (3)** — Targeting docs (audience definitions, segmentation rules) > **Tip: Try this now** > **Try this now.** Open `/studio?mode=global`. Find the 2 docs not yet generated in Context (21/23). Click *Generate*. Then audit the Brand Voice doc — that's the source of truth every other doc + LP + email pulls from. If the voice is wrong, everything downstream is wrong. ### 02. Brand Kit — homepage clone, design system, 362 assets *Studio Content extracts your website (homepage cloned + design system parsed + sections catalogued) and gives you a browsable Asset library — logos, hero images, product shots, screenshots, generated images — all categorized.* **Open it at** - `app.graph8.com/studio?mode=content` — Brand Kit workspace - Sub-sections: *Overview · Homepage · Design System · Sections* - Brand Kit dropdown: Brand Kit · Assets (362) · Website (32) · Landing Pages (41) · LinkedIn (0) · Newsletter (0) > **Tip: Try this now** > **Try this now.** Brand Kit → Sections — graph8 auto-extracts every section (hero, features, pricing, FAQ, footer) from your homepage. Click any section, copy the HTML, paste into a landing page. Instant brand-consistent block reuse. ### 03. Landing Pages — 41 live, 19 drafts, 3 creation paths *Build LPs three ways: custom prompt to AI, pick a layout template, or clone an existing page. The editor is live-preview with publish toggle. Drafts list shows everything in flight.* **Open it at** - `app.graph8.com/studio?mode=content&ct=landing_page` — Landing Pages list - Or click Brand Kit dropdown → Landing Pages > **Tip: Try this now** > **Try this now.** Clone your highest-converting LP from the published list. Open the editor. Swap the hero copy to your next campaign's wedge (pulled from Campaign Studio's Messaging & Objections doc). Save as draft, push to publish when copy review is done. The editor handles responsive layout for you. ### 04. Content traffic + lead capture — Forms, Behavior, Keywords *Where your content lands traffic, what they read, and how you convert them. 19 active forms, top pages by view, intent keywords (still empty for graph8 — easy win).* **Open it at** - `app.graph8.com/forms` — 19 forms with submission counts - `/analytics/behavior` — top pages by views - `/keywords` — intent keywords (still 0 configured) ![Forms list with 19 forms showing Form ID, Page URL, Total Submissions columns; top entries undefined 76, go-data-progressive-form 76, apply-form 17](../../../../assets/roles/content/7-forms-content.webp) ***Fig 4.1** — `/forms`. 19 forms across graph8.com + a paying customer.com properties. Top: *go-data-progressive-form* 76 subs · *apply-form* 17 · *demo-with-vakkii* 12 · *exit-form* 11. Each form has a target list = routes submitters into the right sequence.* ![Analytics Behavior Pages tab showing top pages by view: B2B Contact Database 74376, homepage 2914, graph8 for AI Agents 1808](../../../../assets/roles/content/8-behavior-pages.webp) ***Fig 4.2** — `/analytics/behavior`. **Top: B2B Contact Database 74,376 views** (long-tail SEO) · graph8.com homepage 2,914 · graph8 for AI Agents 1,808. Where your content is winning + losing.* ![Intent Keywords empty state with your data Added/Last Updated/Sync/Actions columns and Add Keyword button](../../../../assets/roles/content/9-keywords.webp) ***Fig 4.3** — `/keywords`. **0 keywords configured.** The fastest content win: add 5-10 keywords (e.g. "replace HubSpot", "sales automation") so the intent dashboard surfaces contacts at companies talking about them.* > **Tip: Try this now** > **Try this now.** Open `/analytics/behavior`, sort by views desc. The B2B Contact Database page at 74K views is a giant audience graph8 isn't capitalizing on (99.7% bounce). Build a soft-pitch form for that page (or a contextual chat-leads widget) to convert that traffic. ### 05. Copilot — draft heroes, headlines, full pages on demand *Natural-language content generation. Ask for a hero, get 2-3 variants with rationale. Ask for a full landing page, get a structured outline. Ask for blog headlines, get a brainstormed list grounded in your Brand Voice.* **Open it at** - Sparkle icon · top-right · any page → Copilot side panel ![Copilot response to 'Generate me a landing page hero for replacing HubSpot' showing Variant B Pain-forward with your data structure plus follow-up offer](../../../../assets/roles/content/10-copilot-content.webp) ***Fig 5.1** — Live copilot exchange. Q: *"Generate me a landing page hero for a campaign about replacing HubSpot."* A: structured Variant B (Pain-forward, recommended) with *Eyebrow · Headline · Subheadline · CTA · Recommendation* + offer to *"turn this into a full landing page document or build out the rest of the page."** > **Tip: Try this now** > **Try this now.** Try these content-marketer queries: *"write 5 LinkedIn post headlines about our wedge against Apollo"* · *"give me 3 blog post outlines grounded in our Voice of Customer doc"* · *"draft an exit-intent popup for graph8.com/pricing"* · *"compare our messaging house against Outreach's homepage"*. The copilot reads from the 45 brand DNA docs so output is on-brand by default. ## URL cheat-sheet | To do this | Go here | | --- | --- | | 45 brand DNA docs | `/studio?mode=global` | | Brand Kit (homepage clone + design system) | `/studio?mode=content` | | Landing Pages (19 drafts · 41 published) | `/studio?mode=content&ct=landing_page` | | Asset library (362 images) | Brand Kit → Assets | | Website pages catalog (32 pages) | Brand Kit → Website | | LinkedIn content (still empty) | Brand Kit → LinkedIn | | Newsletter (still empty) | Brand Kit → Newsletter | | Forms list + submissions | `/forms` | | Top pages by view | `/analytics/behavior` | | Intent keywords (still 0) | `/keywords` | | Open Copilot anywhere | Sparkle · top-right | ## Heads-up — known Content gaps + workarounds > **Caution:** > **1. No /blog route in the app.** Blog content management lives outside (in the `g8web/graph8-web` repo). For in-app content, use Landing Pages — they cover most use cases. > > **2. Studio `?ct=landing_page` param is sometimes stripped.** If direct URL doesn't switch content type, use the Brand Kit dropdown in the UI to select Landing Pages. > > **3. LinkedIn + Newsletter sections are empty.** Brand Kit dropdown shows LinkedIn (0) + Newsletter (0) — surfaces are shipped but unused. Easy quick-win to populate. > > **4. Two Context docs are still pending generation.** Counter shows 43/45 — find the missing 2 in Studio Global and trigger generation manually. > > **5. Keywords are empty.** 0 intent keywords configured. Set up your first 5 to populate the Intent Keywords column in `/contacts` + the Bridge Funnel in `/analytics/marketing`. --- <small>Generated 2026-05-20 against `app.graph8.com` (org `your org`). Surface state verified live before publication</small> --- # graph8 for CRO > graph8 field guide for CRO. Live URLs, real surfaces, screenshots from a public-data showcase tenant. Source: https://docs.graph8.com/roles/cro/ *A field guide for Chief Revenue Officer. Live URLs, real surfaces. Screenshots captured from a showcase tenant with public-company data only.* ## The five things to use today ### 01. Acquisition + Marketing Bridge Funnel — top of the engine *80K visitors, 90% Direct, 481 identified by graph8's outbound bridge, 0 added to sequence. The marketing-to-sales handoff is where most CRO leaks happen — graph8 makes it visible.* **Open these** - `app.graph8.com/analytics` — single-screen marketing pulse - `/analytics/marketing` — Outbound Bridge Funnel - `/analytics/acquisition` — channel mix detail ![Analytics Overview 79989 unique visitors, 79843 new, 83026 sessions, 99699 page views, Direct 90%, Organic 6%, Referral 4%, Bounce 97.1%, Engagement 1.6%](../../../../assets/roles/cro/1-analytics-overview.webp) ***Fig 1.1** — `/analytics`. **79,989 unique visitors · 99,699 page views.** Direct 90% · Organic 6% · Referral 4%. Bounce 97.1% (long-tail SEO landing pages drive that).* ![Marketing Bridge Funnel showing 80165 Web Visitors → 481 Identified → 0 Added to Outbound → 0 Replied → 0 Meetings, 0.8% Identification Rate, 0% Outbound Conversion, 0% Reply Rate](../../../../assets/roles/cro/2-marketing-funnel.webp) ***Fig 1.2** — `/analytics/marketing`. The **Outbound Bridge Funnel**: 80,165 Visitors → 481 Identified (0.8%) → 0 Outbound → 0 Replied → 0 Meetings. **The marketing→sales handoff is wired but unused.** This is a multi-six-figure ARR fix.* ![Acquisition channels table showing 81.1K users, 84.3K sessions, your engagement rate, Direct 73706 (90%), Organic Search 4586 (5.8%), Referral 2673 (4%), AI-LLM 144 (0.2%), Other 18, Organic Social 7](../../../../assets/roles/cro/3-pipeline-kanban.webp) ***Fig 1.3** — `/analytics/acquisition`. Channels detail: **Direct 90% (73.7K) · Organic 5.8% · Referral 4% · AI/LLM 0.2%.** AI/LLM at 144 users is small but growing — track that quarter-over-quarter.* > **Tip: Try this now** > **CRO move.** The Bridge Funnel is the highest-leverage view. 481 identified visitors going nowhere = your team has a routing rule to build. Assign Marketing Ops + RevOps to wire it this week. Watch the Outbound + Reply numbers climb over the next 30 days. ### 02. Pipeline — kanban with $weighted forecast surrogate *Six-stage kanban. $total + $weighted per column. Drag-to-advance. The $weighted sum is the closest thing to a commit forecast graph8 ships today. Per-stage probabilities are configurable.* **Open it at** - `app.graph8.com/deals/pipeline` — defaults to kanban view ![Pipeline kanban Sales Pipeline showing 6 stages with New Meeting $402K weighted, Discovery Held $9K total, Solution Fit $400 total, Closed Won $25K](../../../../assets/roles/cro/4-customers-mrr.webp) ***Fig 2.1** — `/deals/pipeline`. **New Meeting $402K weighted** · Discovery $9K · Solution Fit $400 · Proposal/Verbal Commit empty · Closed Won $25K. *Pipeline is top-heavy* — most value is in early stages, almost nothing in late-stage commit. CRO insight: build cadence on Proposal/Verbal moves.* > **Tip: Try this now** > **CRO move.** Friday afternoon: screenshot the kanban. Diff vs last Friday. If Proposal Sent stagnant, no closes land next week. If Discovery Held isn't growing, SDR pipeline is leaking. Both are leading indicators of the number. ### 03. Book of business + lifecycle states *Customers (paying) · Trials (about to convert or expire) · Churned (lost). All three lifecycle states in one URL pattern. CRO reads all three weekly for the full revenue picture.* **Open these** - `/deals/l/customers` — paying book - `/deals/l/trials` — trials with countdowns - `/deals/l/churned` — ex-customers ![Customers list showing a paying customer $2.5K, a paying customer $2.5K, a paying customer $8.9K, a paying customer $2.4K, a paying customer $499, your account $6.0K, a paying customer $1.5K, a paying customer $2.4K, a paying customer $2.4K](../../../../assets/roles/cro/5-trials.webp) ***Fig 3.1** — `/deals/l/customers`. Top of book: *a paying customer $8.9K · your account $6.0K · a paying customer $2.5K · a paying customer $2.5K · a paying customer $2.4K.* Watch concentration: top 5 customers = ~40% of MRR.* ![Active trials Ecosparkgrowth, Wudi, Shutdownlabs, Qecad, daewon america, techyscouts, Dr Steve Rad MD, Sg Analytics, element dma with trial-ends days, source, referrer](../../../../assets/roles/cro/6-churned.webp) ***Fig 3.2** — `/deals/l/trials`. Active trials with source attribution + countdown timers.* ![Churned customers Gmail, riseup-asia, x-hydration, a sample deal, Lombard Metals, costmenautomail, Quantum Global Airways with churned/paused status badges](../../../../assets/roles/cro/7-reports-landing.webp) ***Fig 3.3** — `/deals/l/churned`. Lost revenue ledger. Cross-reference Churn Reason monthly to find systemic causes.* > **Tip: Try this now** > **CRO move.** Once a week: scan all three lists. Trial Active < 14d remaining = blocker meeting; Customer past_due = AR call; recent churn = post-mortem call. 30 minutes weekly = full revenue ownership. ### 04. Team activity pulse + conversion analytics *Performance Reports landing tells you if the team is doing the work. Conversions tells you if the work converts. 13 sub-reports for drill-down.* **Open these** - `/reports` — Activity Trend + 13 sub-reports (SDR Leaderboard, Meeting Pipeline, Account Health Comparison...) - `/analytics/conversions` — Goal Completions (Form Submission + Meeting Booked) ![Performance Reports landing showing 40423 emails, 9185 dials, 235 replies, 0.6% reply rate, 72 meetings (7d) with Activity Over Time chart and sidebar of 12 sub-reports](../../../../assets/roles/cro/8-acquisition-channels.webp) ***Fig 4.1** — `/reports`. **40.4K emails · 9.2K dials · 235 replies · 0.6% reply rate · 72 meetings (7d).** Left sidebar: SDR Leaderboard · Meeting Pipeline · Talk Time Quality · Cross-Account Drift · etc.* ![Conversions analytics showing 148 Total Conversions, 0.1% rate, 148 Form Submissions, 40 Meetings Booked, Goal Completions table (Form Submission 108 / 59 unique, Meeting Booked 40 / 1 unique)](../../../../assets/roles/cro/9-conversions-goals.webp) ***Fig 4.2** — `/analytics/conversions`. **148 conversions · 0.1% rate · 108 form submissions · 40 meetings booked.** Two goals tracked. Goal-attribution per channel rolls up here.* > **Tip: Try this now** > **CRO move.** Bookmark these as named browser tabs: *"CRO · Activity"* = `/reports`, *"CRO · Conversions"* = `/analytics/conversions`. Open both Monday morning before the leadership call. ### 05. Copilot — full revenue summary with risks-to-the-number, on command *Natural-language exec summary. Pipeline + bookings + churn + 3 risks to hitting the number this quarter, generated in one prompt. CRO-grade output, not chatbot fluff.* **Open it at** - Cmd+Shift+K · OR sparkle icon top-right - Via WhatsApp / Slack / iMessage / Roam Bridge (`/profile?tab=apps`) > **Note: Live copilot exchange** > *Live exchange · 2026-05-20* > > > "Give me a full revenue summary: pipeline, bookings, MRR, churn, and the top 3 risks to hitting the number this quarter." > > Pipeline: > > Top deals: > > Lost Deals: > > Top 3 Risks (Risk #3): > > "Here's the full picture based on live data. Note: MRR isn't tracked natively in Graph8 — I'm inferring from deal data where possible. 13 open deals · $24,490 in named value (3 are TBD/unquantified). a sample deal/Engaged/$7,000/Oct 31 · a sample deal/Demo/$5,988/Feb 28 · stuff/Discovery Held/$5,000/May 15. 23 deals · ~$38K · 21% win rate. Revenue is dangerously concentrated — one deal at $25K = 76% of bookings." ![Copilot CRO response showing live data with Note about MRR not tracked natively, Pipeline section with 13 open deals $24490, deal table with a sample deal/Engaged/$7000/Oct 31, a sample deal/Demo/$5988/Feb 28, stuff/Discovery Held/$5000/May 15](../../../../assets/roles/cro/10-copilot-cro.webp) ***Fig 5.1** — Live copilot response. **Honest about gaps** (MRR not tracked natively — inferring from deals) and **specific about risks** (revenue concentration — 76% from one deal). Use the full output as your weekly board-update first draft.* > **Tip: Try this now** > **CRO move.** Sunday night: paste *"Write me a board-ready Q&A: pipeline, bookings, MRR, churn, top 3 risks, 3 specific actions for next 14 days"* into copilot. Edit for 15 min. You just saved 2 hours. ## The CRO dashboard tour (5 bookmarks) | To do this | Go here | | --- | --- | | **1.** Acquisition pulse | `/analytics` | | Marketing Bridge Funnel | `/analytics/marketing` | | Acquisition channel detail | `/analytics/acquisition` | | **2.** Pipeline + $weighted forecast | `/deals/pipeline` | | **3.** Paying book + MRR | `/deals/l/customers` | | Trials in flight | `/deals/l/trials` | | Churned + win-back candidates | `/deals/l/churned` | | **4.** Team activity pulse | `/reports` | | Conversions (goals) | `/analytics/conversions` | | SDR Leaderboard | `/reports?report=a8` | | Meeting Pipeline | `/reports?report=a5` | | Account Health Comparison | `/reports?report=a14` | | **5.** Copilot for exec summary | Cmd+Shift+K | ## Heads-up — known CRO gaps + workarounds > **Caution:** > **1. MRR not tracked natively.** Copilot is honest about this: "I'm inferring from deal data where possible." Truth lives in `/deals/l/customers` MRR column + `/admin` → Customers tab (raw Stripe IDs). > > **2. Outbound Bridge Funnel shows 0 after Identified.** 481 identified visitors going nowhere = your biggest single leak right now. Marketing-to-sales routing rule missing. > > **3. No commit/best-case/most-likely forecast.** Kanban $weighted is the surrogate. Adjust per-stage probabilities in `/studio/settings` → Revenue → Pipelines to dial in. > > **4. Pipeline is top-heavy.** $402K in New Meeting, $0 in Verbal Commit. Push your AEs on stage advancement — or your forecast is fiction. > > **5. Revenue concentration risk.** Copilot flagged one deal = 76% of bookings. Diversify or hedge. > > **6. /copilot is 404.** Use Cmd+Shift+K from any page, or the sparkle icon. --- <small>Generated 2026-05-20 against `app.graph8.com` (org `your org`). Surface state verified live before publication</small> --- # graph8 for CSM > graph8 field guide for CSM. Live URLs, real surfaces, screenshots from a public-data showcase tenant. Source: https://docs.graph8.com/roles/csm/ *A field guide for Customer Success Manager. Live URLs, real surfaces. Screenshots captured from a showcase tenant with public-company data only.* ## The five things to use today ### 01. Your book of business — Customers, Trials, Churned *The Revenue dropdown is the CSM nav. The full lifecycle is here: Customers (active paying) → Trials (about to pay) → Churned (already lost). Open it every morning sorted by MRR descending.* **Open it at** - `app.graph8.com/deals/l/customers` — paying customers (active book of business) - `/deals/l/trials` — companies trialing (your conversion pipeline) - `/deals/l/churned` — ex-customers (post-mortem + win-back candidates) - Revenue dropdown in top nav also has: Suspects · Leads · Products · Quotes · Deals · Leaderboard · My Desk Live today: **your paying customers** sorted by MRR. Top of the book: *a paying customer $8.9K · your account $4.0K · a paying customer $2.5K (past_due) · a paying customer $2.5K · a paying customer $2.5K · a paying customer $2.5K · a paying customer $2.5K · a paying customer $2.4K · a paying customer $1.5K · a paying customer $499 (past_due)*. ![Customers list at /deals/l/customers sorted by MRR descending showing your paying customers](../../../../assets/roles/csm/1-customers-mrr.webp) ***Fig 1.1** — `/deals/l/customers`. Columns: Company · Domain · Status · MRR · Plan · Subscription · Customer Since · Owner · Stakeholders · Deals · Deal value. Sort MRR desc and your top 10 accounts is your morning call-list.* > **Tip: Try this now** > **Try this now.** Open `/deals/l/customers`, sort by MRR desc, look at *Last Activity* on your top 10. Anyone you haven't touched in 30+ days = highest-leverage call this week. ### 02. Catch churn risk before it lands — Past_due filter *Subscription status filter slices the book to past_due only. Today: 7 of your customers (17%) are past_due — that's your "call before they cancel" list.* **Open it at** - `/deals/l/customers` → filter **Subscription = past_due** Past_due customers live today (verified): *a paying customer $2.5K · a paying customer $2.5K · a paying customer $2.0K · a paying customer $2.0K · a paying customer $2.0K · a paying customer $2.0K · a paying customer $499*. That's **$13.5K of recurring revenue at immediate risk**. ![Customers filtered to past_due showing 7 customers at risk](../../../../assets/roles/csm/2-past-due.webp) ***Fig 2.1** — Past_due filter applied. 7 of your customers in failed-payment state.* > **Tip: Try this now** > **Try this now.** Filter `/deals/l/customers` by *Subscription = past_due*. Open each customer one at a time, look at the Customer Health right rail (next section), call or email the primary contact today. A past_due tells you Stripe couldn't charge — usually a card expired, not intent to cancel. ### 03. The customer detail page — Health, Risk Signals, Expansion Levers *Every customer record has an opinionated Health view: MRR/ARR up top, a Critical/Warning/Healthy score, automatic risk signals, expansion levers based on usage. **This is the CSM killer feature.*** **Open it at** - Click any customer in `/deals/l/customers` → lands on the detail page - Tabs: *Overview · Data · Notes · Tasks · Deal Room · Activity · Contacts · Sites Visited · Jobs · Next Steps* ![your account customer detail showing MRR $4000, ARR $48000, Health Critical Score 0, Risk signals, Expansion levers](../../../../assets/roles/csm/3-customer-detail.webp) ***Fig 3.1** — your account customer detail (live). The right rail is the CSM's panic-button: a Critical health badge + Risk + Expansion grid.* live example — your account, captured 2026-05-20 MRR · ARR$4,000 · $48,000 PlanGTM POD Tenure1 month Customer HealthCritical · Score 0 Engagement counters0 / 0 / 0 / 0 / 0 Risk signal"No meeting on record" Expansion lever"Credits exhausted: 0" Contacts on file2 (BytePlus Pte. Ltd., a contact) The Health card auto-generates: Critical when engagement counters are 0 + no recent meeting, Healthy when sequence-reply + meeting cadence is normal, Warning in between. Risk signals + expansion levers are evidence-backed — you don't have to interpret a number, the system says "Schedule a meeting, this customer has no QBR on record." > **Tip: Try this now** > **Try this now.** Open your account (or any Critical-health customer). The right rail tells you exactly what to do: book a meeting. Click *Next Steps* tab, add a task. Click *Activity* to log the call when it happens. The Health card refreshes within a day. ### 04. Bird's-eye view — Account Health Comparison + Cross-Account Drift *When you don't have time to open 41 customer detail pages, the reports surface gives you a single view of which accounts are drifting + which are healthy.* **Open it at** - `app.graph8.com/reports` → *Account Health Comparison* - `/reports?report=a15` → *Idle / Stalled* (sister report under Cross-Account Drift) - `/analytics/acquisition` → channel breakdown for the marketing site (where your customers came from) The Account Health Comparison shows **your workspaces · your reply rate**. ![Account Health Comparison report showing your workspaces, your reply rate, and per-workspace outbound volume chart](../../../../assets/roles/csm/4-customer-visitors.webp) ***Fig 4.1** — `/reports` Account Health Comparison. Per-workspace outbound volume — a customer doing low volume is either underutilizing graph8 (churn risk) or doing inbound-only (different story).* ![Acquisition channels at /analytics/acquisition showing Direct 90.6%, Organic 4.7%, Referral 4.4% with your monthly users](../../../../assets/roles/csm/5-inbox-customer.webp) ***Fig 4.2** — `/analytics/acquisition` last 30 days. **your monthly users · your monthly sessions · your engagement rate.** Useful CSM context: when a customer asks "how do I get more inbound?", point at this and the answer is "Direct (brand) + Organic Search."* > **Tip: Try this now** > **Try this now.** Open `/reports` → Account Health Comparison. Anyone in the bottom quartile of outbound volume is your "are they actually using graph8?" call. Open the Cross-Account Drift sister report for stalled accounts that USED to be active. ### 05. The CSM toolkit — Visitors · Inbox · Copilot · Support Agent *Four shared surfaces that all four roles use, with CSM-specific framing: which customers are on the site right now, what did they reply, what does the copilot know, and the dedicated Graph8 Support Agent.* **Open it at** - `/visitors?tab=companies` — site visitors (no native "is-customer" filter yet) - `/inbox/email` — replies + sentiment (no native "from-customer" filter yet) - Copilot sparkle icon top-right — natural-language queries - `/agents` — Graph8 Support Agent (CSM role, shipped) ![Visitors at /visitors?tab=companies showing the identified companies including your account, Target, your account](../../../../assets/roles/csm/6-copilot-csm.webp) ***Fig 5.1** — Visitors. **the identified companies on the site** — but no filter to slice to "existing customers only." Manually scan for customer domains (e.g. *a paying customer.com*, *a paying customer.com*) — those are the highest-priority churn signals to act on.* ![Copilot response to Show me my customers with past_due subscriptions](../../../../assets/roles/csm/8-agents-csm.webp) ***Fig 5.3** — Copilot exchange (live). Honest result: *"I don't have a tool to query subscription billing data."* Suggests Stripe/Chargebee/CRM as workaround. **Use the past_due filter from use case 2 instead** — it's faster and authoritative.* ![Agents page at /agents showing Graph8 Support Agent with CSM role and 7 other agents](../../../../assets/roles/csm/9-attribution-csm.webp) ***Fig 5.4** — `/agents`. 8 agents total — **Graph8 Support Agent (CSM, Active)** handles customer-support inbound. SDR + AE + Twin agents handle the upstream funnel.* > **Tip: Try this now** > **Try this now.** Skim `/visitors?tab=companies` for customer domains in the visit log. Anyone hitting `/docs` or `/pricing` or competitor-comparison pages = either expansion intent (good) or shopping for alternatives (churn signal). Reach out same day. ## URL cheat-sheet | To do this | Go here | | --- | --- | | See paying customers (book of business) | `/deals/l/customers` | | Filter to past_due risk | `/deals/l/customers` · Subscription filter | | See trial accounts | `/deals/l/trials` | | See churned customers | `/deals/l/churned` | | Customer detail w/ Health + Risk + Expansion | Click any customer row | | Account Health Comparison report | `/reports` | | Idle / Stalled accounts (Cross-Account Drift) | `/reports?report=a15` | | Site visitors | `/visitors?tab=companies` | | AI Inbox (search by customer domain) | `/inbox/email` | | Graph8 Support Agent | `/agents` | | Acquisition channel breakdown | `/analytics/acquisition` | | Open Copilot anywhere | Sparkle · top-right | | Search the CRM | Cmd-K | ## Heads-up — known CSM gaps to work around > **Caution:** > **1. No customer-status filter on Visitors.** `/visitors?tab=companies` shows all 42 site visitors — but you can't filter "existing customers only." Workaround: keep a mental list of your top customer domains; scan visually each morning for hits. > > **2. No customer-source filter on Inbox.** `/inbox/email` doesn't have a "from-customer" toggle. Workaround: search by customer email domain to find reply threads. > > **3. No dedicated Revenue/Subscription/Billing tab on customer detail.** Stripe sync data (MRR, plan, next billing) lives in the right-rail Customer Health card on the Overview tab — not in its own tab. Tabs you DO have: *Overview · Data · Notes · Tasks · Deal Room · Activity · Contacts · Sites Visited · Jobs · Next Steps*. > > **4. Copilot can't query subscription state directly.** Asking "show me past_due customers" returns "I don't have a tool for that." Use the past_due filter in `/deals/l/customers` instead — it's the authoritative source anyway. > > **5. Only one CSM agent shipped — Graph8 Support Agent.** No phone/voice number assigned to it yet. SDR + AE + Twin agents (Ari, Din, Nick) exist alongside but those are upstream-funnel, not post-sale. --- <small>Generated 2026-05-20 against `app.graph8.com` (org `your org`). Surface state verified live before publication</small> --- # graph8 for Finance > graph8 field guide for Finance. Live URLs, real surfaces, screenshots from a public-data showcase tenant. Source: https://docs.graph8.com/roles/finance/ *A field guide for Finance. Live URLs, real surfaces. Screenshots captured from a showcase tenant with public-company data only.* ## The five things to use today ### 01. The revenue lifecycle — Customer, Trial, Past_due, Churned *graph8 ships every revenue state as a separate dedicated view. One click per state, real Stripe-synced data per row.* **Open it at** - `app.graph8.com/deals/l/customers` — paying customers (MRR-bearing) - `/deals/l/customers` + Subscription = past_due filter — at-risk revenue - `/deals/l/trials` — trial accounts with countdown timers (sub-tabs Active / Expired) - `/deals/l/churned` — ex-customers with Churn Reason column ![Customers list with your paying customers, MRR per row](../../../../assets/roles/finance/1-customers-mrr.webp) ***Fig 1.1** — `/deals/l/customers`. your paying customers. Columns: Company · Domain · Status · MRR · Plan · Subscription · Customer Since · Owner · Stakeholders · Deals · Deal Value. Real Stripe-synced MRR per row.* ![Customers filtered to past_due showing 7 at-risk accounts](../../../../assets/roles/finance/2-past-due.webp) ***Fig 1.2** — Past_due filter applied. **7 customers ≈ $13.5K MRR at immediate risk**: a paying customer $499 · a paying customer $2.0K (×2) · a paying customer $2.5K · a paying customer $2.0K · a paying customer $2.0K · a paying customer $2.0K. *URL param `?subscription=past_due` doesn't work — apply via UI Filter panel.** ![Churned customers list with churn reason and status badges](../../../../assets/roles/finance/3-churned.webp) ***Fig 1.3** — `/deals/l/churned`. Ex-customers with *Churn · Churn Reason · Subscription* columns. Live: Gmail $1.5K · riseup-asia-itc · drinkxhydration · a sample deal.run $500 · Lombard Metals · Quantum Global Airways.* ![Trial accounts with countdown timers (68d, 67d, 65d left) and Active/Expired sub-tabs](../../../../assets/roles/finance/4-trials.webp) ***Fig 1.4** — `/deals/l/trials`. Trial accounts with **countdown timers** (68d / 67d / 65d left to convert). Sub-tabs Active / Expired. Live: Ecosparkgrowth · Wudi · Shutdownlabs · Qecad · daewon america · techyscouts · Dr Steve Rad MD.* > **Tip: Try this now** > **Try this now.** Open `/deals/l/customers`, apply Subscription = past_due filter. That's your call list for AR. Then check `/deals/l/trials` Expired sub-tab — anyone in expired-trial state who hasn't converted is a recovery email or refund decision. ### 02. Per-customer revenue detail with health + risk *Every customer record shows MRR/ARR, plan, renewal status, and a Critical/Warning/Healthy score with auto-generated risk signals. Finance reads this for renewal forecasts + churn-risk dollar exposure.* **Open it at** - Click any customer in `/deals/l/customers` → lands on Overview tab - Tabs available: *Overview · Data · Notes · Tasks · Deal Room · Activity · Contacts · Sites Visited · Jobs · Next Steps* ![your account customer Overview showing MRR $4000, ARR $48000, GTM POD plan, Critical health](../../../../assets/roles/finance/5-customer-revenue-detail.webp) ***Fig 2.1** — Live: your account customer detail. MRR + ARR up top, Plan + Renewal status, Health score with risk signals.* live customer record · your account · captured 2026-05-20 MRR · ARR$4,000 · $48,000 PlanGTM POD RenewalNo renewal data Tenure1 month HealthCritical · Score 0 Engagement0 emails · 0 meetings · 0 calls · 0 visits Risk signal"No meeting on record" > **Tip: Try this now** > **Try this now.** For renewal forecasting, sort `/deals/l/customers` by Customer Since asc to find anyone approaching their annual renewal anniversary. Open each, check Health score — anything Critical with renewal <90 days out gets flagged in the Finance churn-risk register. ### 03. Quotes — your bookings ledger *Every quote with status, payment terms, line items, and the Sent → Viewed → Signed lifecycle timestamps. This is the auditable bookings record.* **Open it at** - `app.graph8.com/deals/quotes` — quotes list with status filter (All / Draft / Sent / Viewed / Accepted / Expired / Declined / Voided) Live today: **quotes a sample quote through a sample quote**, ranging $50–$8,499. Multiple **Accepted**: a sample quote $500, a sample quote $8,499, a sample quote $3,000. All Net 30 terms, Valid Until Jun 18 2026. Auditable timestamps per state transition. ![Quotes list with status filter tabs and visible quotes a sample quote through a sample quote](../../../../assets/roles/finance/6-quotes-list.webp) ***Fig 3.1** — Quotes list. *Accepted* tab is your booked revenue this period; *Sent*/*Viewed* is your pipeline; *Expired*/*Declined* is your lost-revenue reconciliation.* ![a sample quote detail with full lifecycle and line items](../../../../assets/roles/finance/7-quote-revenue.webp) ***Fig 3.2** — a sample quote detail. **Sent May 19 1:00 PM → Viewed ×2 → Signed May 19 1:00 PM.** $500 / 10,000 Credits / Net 30. Auditable timestamps per state. *The Stripe customer + subscription + invoice + payment link are auto-provisioned the moment Signed fires.** > **Tip: Try this now** > **Try this now.** For monthly bookings reconciliation: filter quotes to *Accepted*, then sort by Created Date asc. Cross-reference against the customers list MRR — every Accepted recurring quote should map to an active subscription. Mismatches = billing-provisioning failures worth investigating. ### 04. /admin — COGS, LLM tier pricing, raw Stripe IDs *The graph8-admin panel exposes the lower-level financial data: LLM cost tiers (G1-G4), per-customer raw Stripe Customer ID + Subscription ID, domain pricing, services panel.* **Open it at** - `app.graph8.com/admin` — admin dashboard with 5 tabs - Tabs: **Customers** (715 records with Stripe IDs) · **Services** · **LLM Charges** · **Domain Pricing** · **Custom JS** The *Customers* tab is the only place raw Stripe Customer IDs + Stripe Subscription IDs surface in the UI (715 records, Trialing / Active status). The *LLM Charges* tab is the COGS view — 4 pricing tiers for input/output credits across all models. ![Admin LLM Charges tab showing tier pricing G1-G4 with input and output credit costs](../../../../assets/roles/finance/8-admin-billing.webp) ***Fig 4.1** — `/admin` → LLM Charges tab.* LLM COGS tier pricing · live 2026-05-20 Tier 1 (G1)0.002 credits/input · 0.0097/output · Active Tier 2 (G2)0.0039 · 0.0195 · Active Tier 3 (G3)0.0065 · 0.0325 · Active Tier 4 (G4)0.045 · 0.18 · Active > **Tip: Try this now** > **Try this now.** Open `/admin` → Customers tab. Cross-reference Stripe Subscription IDs against Stripe's own dashboard for the month-end MRR reconciliation. The Services + LLM Charges tabs give you COGS inputs for per-customer gross margin calculation (the join is manual today — see caveats). ### 05. CAC, conversions, and a copilot that can't (yet) query billing *Acquisition analytics + conversion rates give you the inputs for CAC and CAC payback. The copilot is honest about not having billing-data access today.* **Open it at** - `/analytics/acquisition` — channels (Direct / Organic Search / Referral / AI-LLM / Social) - `/analytics/conversions` — Goal Completions (Form Submission, Meeting Booked) + Form Performance - Copilot — sparkle icon top-right (heads-up: no billing tool yet, see caveats) ![Acquisition channels showing 65.9K users, Direct 90.8% share, Organic Search 4.7%, Referral 4.4%, AI/LLM 0.2%](../../../../assets/roles/finance/9-revenue-leaderboard.webp) ***Fig 5.1** — `/analytics/acquisition` last 30 days. **65.9K users · 68.8K sessions · your engagement rate.** Direct 90.8% (60.3K users, 98.7% bounce) · Organic 4.7% · Referral 4.4% · AI/LLM 0.2% · Social 0.0%. Pair this with paid spend per channel to compute CAC.* ![Conversion analytics showing 109 conversions, 108 form submissions (+3300%), 39 meetings (+15.7%)](../../../../assets/roles/finance/10-attribution-revenue.webp) ***Fig 5.2** — `/analytics/conversions`. **109 total conversions · 0.1% rate.** Goal Completions: Form Submission 108 (+3,300% vs prior period, 44 unique submitters), Meeting Booked 39 (+15.7%). Form Performance section below the fold.* ![Copilot response to 'past_due customers and total at risk' showing honest 'no billing tool' answer](../../../../assets/roles/finance/11-acquisition-cac.webp) ***Fig 5.3** — Copilot honest response: *"I don't have access to subscription billing data or payment statuses — there's no billing integration connected to graph8 that I can query."* Recommends Stripe/Chargebee direct OR the past_due filter from use case 1.* > **Tip: Try this now** > **Try this now.** Compute rough CAC: pull paid spend by channel from your ad accounts, divide by attributed conversions from `/analytics/conversions`. Then compute payback months: ARPA from `/deals/l/customers` ÷ CAC. Anything > 18-24 months payback is a channel-mix warning to flag. ## URL cheat-sheet | To do this | Go here | | --- | --- | | Paying customers + MRR | `/deals/l/customers` | | Past_due ($ at risk) | `/deals/l/customers` · Subscription filter | | Trial accounts (countdown timers) | `/deals/l/trials` | | Churned (lost revenue + reason) | `/deals/l/churned` | | Per-customer MRR/ARR/health | Click any customer row | | Quotes (bookings ledger) | `/deals/quotes` | | Quote detail (Sent → Signed timestamps) | Click any quote row | | Admin (715 records + Stripe IDs) | `/admin` | | LLM COGS tier pricing | `/admin` → LLM Charges tab | | Domain pricing (per-org charges) | `/admin` → Domain Pricing tab | | Acquisition channels (CAC inputs) | `/analytics/acquisition` | | Conversions (form fills + meetings) | `/analytics/conversions` | ## Heads-up — known Finance gaps + workarounds > **Caution:** > **1. No revenue Leaderboard yet.** `/deals/leaderboard` resolves to "Deal not found" (treats path as a deal slug); `/leaderboard` returns 404. Sales/MRR-by-rep leaderboard is not shipped. Workaround: pivot the Customers list by Owner column in your own spreadsheet. > > **2. Copilot has no billing tool.** Asking "show me past_due customers" or "what's my MRR" gets an honest *"I don't have access to subscription billing data"* response. Use the customers + past_due UI directly — it's the authoritative source anyway. > > **3. Attribution analytics is empty for graph8.** `/analytics/attribution` exists but shows "No sender data yet — No outbound sends in the last 30 days." Use `/analytics/conversions` for goal-based attribution instead. > > **4. No per-customer COGS aggregation.** `/admin` shows LLM tier pricing (G1-G4) and Services usage in separate tabs — the join to compute "$ COGS per customer this month" is manual today. Hand-export both, do the join in your spreadsheet. > > **5. No "Billing" or "Invoices" sub-tab on customer detail.** Subscription / MRR / plan data lives in the right-rail Customer Health card on the Overview tab — not a dedicated finance tab. The customer detail tabs are Overview · Data · Notes · Tasks · Deal Room · Activity · Contacts · Sites Visited · Jobs · Next Steps. > > **6. MRR column sort header occasionally no-ops.** Click and sometimes the visible order doesn't change — refresh the page or use the Sort UI from the column-header menu instead. > > **7. Plans are free-text, not normalized.** Plan column shows strings like "New price for platform" / "GTM POD" / "Platform" — these aren't a controlled vocabulary. Group manually for cohort analysis. --- <small>Generated 2026-05-20 against `app.graph8.com` (org `your org`). Surface state verified live before publication</small> --- # graph8 for Founder / CEO > graph8 field guide for Founder / CEO. Live URLs, real surfaces, screenshots from a public-data showcase tenant. Source: https://docs.graph8.com/roles/founder/ *A field guide for Founder / CEO. Live URLs, real surfaces. Screenshots captured from a showcase tenant with public-company data only.* ## The five things to use today ### 01. Daily pulse — Analytics Overview + Pipeline + Customers *Three URLs every morning. Site traffic + sessions, deal pipeline + $weighted, customer book + MRR. Done in 90 seconds.* **Open these** - `app.graph8.com/analytics` — single-screen marketing pulse - `/deals/pipeline` — kanban with $weighted per stage - `/deals/l/customers` — paying customers + MRR ![Analytics Overview showing 79,897 unique visitors, 79,752 new, 82,934 sessions, 99,598 page views, 8s avg engagement, 97.1% bounce, top sources Direct 75.9% Organic 16.2% Referral 3.9%](../../../../assets/roles/founder/1-analytics-overview.webp) ***Fig 1.1** — `/analytics`. **79.9K unique visitors · 99.6K page views · 1.6% engagement.** Top sources: Direct 75.9% · Organic 16.2% · Referral 3.9%. Watch the daily trend more than the absolute number.* ![Pipeline kanban with 6 stages New Meeting/Discovery Held/Solution Fit/Proposal Sent/Verbal Commit/Closed Won showing 4 deals with $40K/$9K/$400K weighted totals](../../../../assets/roles/founder/2-pipeline-kanban.webp) ***Fig 1.2** — `/deals/pipeline` kanban. 6 stages with **$total / $weighted per column** = your weighted pipeline number today.* ![Customers list with a paying customer, a paying customer, a paying customer, your account, a paying customer showing MRR and Active status](../../../../assets/roles/founder/3-customers.webp) ***Fig 1.3** — `/deals/l/customers`. The book. your paying customers including *a paying customer $8.6K · your account · a paying customer · a paying customer · a paying customer*.* > **Tip: Try this now** > **Morning routine.** 3 tabs, in this order: Analytics → Pipeline → Customers. Eyeball for anomalies. Anything red? Drill in. Done in 90 seconds, total business pulse. ### 02. Conversion in flight — Trials + Marketing Bridge Funnel *Where revenue comes from next. 9 trials with countdown timers (convert before they expire). Plus the Outbound Bridge Funnel showing 80K visitors → 481 identified → next steps.* **Open these** - `app.graph8.com/deals/l/trials` — Active + Expired sub-tabs, with countdown timers - `/analytics/marketing` — Marketing Intelligence + Outbound Bridge Funnel ![Trials list showing 9 trials with countdown timers (67d/63d/68d left) and Active/Expired sub-tabs](../../../../assets/roles/founder/4-trials-funnel.webp) ***Fig 2.1** — `/deals/l/trials`. 9 trials with **countdown timers** (68d / 67d / 63d left). Active + Expired sub-tabs.* ![Marketing Intelligence Outbound Bridge Funnel showing 80098 web visitors, 481 identified, qualified traffic 481, engagement quality 2%, campaign influence 481, pipeline impact 0](../../../../assets/roles/founder/5-reports-landing.webp) ***Fig 2.2** — `/analytics/marketing`. **Outbound Bridge Funnel: 80,098 visitors → 481 identified → 0 added → 0 replied → 0 meetings.** The big gap = your Marketing-to-Sales plumbing isn't piping identified visitors into sequences. Easy founder-level fix: assign someone to wire that up.* > **Tip: Try this now** > **Founder move.** Open both. Anyone in the Trials list with a countdown < 14 days = call this week. Then ask: *"why are 481 identified visitors not getting outbound?"* The answer to that is probably worth $50-200K ARR. ### 03. Team activity — Performance Reports dashboard *One screen tells you if the SDR/AE team is doing the work. 40K emails / 9K dials / 235 replies / 0.6% reply rate / 72 meetings — last 7 days. If those numbers tank, you have a team or tooling problem.* **Open it at** - `app.graph8.com/reports` — Performance Reports with 13 sub-reports ![Performance Reports landing showing 40434 emails, 9220 dials, 235 replies, 0.6% reply rate, 72 meetings last 7 days with activity-over-time chart and left sidebar of 13 sub-reports](../../../../assets/roles/founder/6-agents.webp) ***Fig 3.1** — `/reports`. **40.4K emails · 9.2K dials · 235 replies · 0.6% reply rate · 72 meetings (7d).** Activity-over-time chart shows daily volume. Left sidebar: 13 sub-reports (Reply Health · Bounce Health · SDR Leaderboard · Talk Time Quality · Meeting Pipeline · etc.) for deeper drill.* > **Tip: Try this now** > **Founder move.** Bookmark `/reports` as the second tab in your morning routine (after `/analytics`). Look at the activity chart's trend line, not the absolute number. Three down-days in a row = ask your Sales Manager what's going on. ### 04. Your AI team + brand DNA — Agents + Studio Global *8 AI agents live (Receptionist, SDR variants, CSM Support, Twin clones of your reps). Plus 46 brand DNA docs auto-generated from your website — Brand Brief, Personas, ICPs, Messaging House, all on tap.* **Open these** - `app.graph8.com/agents` — 8 AI agents - `/studio?mode=global` — 46 brand DNA docs (4 buckets) ![Agents page showing 8 active agents: AI Receptionist, 10k free signup (David SDR), Hyperpersonalized (Michael SDR), Graph8 Chat Agent, Graph8 Support Agent (Olivia CSM), Twin SDR Ari, Twin SDR Din, Twin AE Nick](../../../../assets/roles/founder/7-studio-global.webp) ***Fig 4.1** — `/agents`. **8 agents shipped:** AI Receptionist · 10k free signup (David, SDR) · Hyperpersonalized (Michael, SDR) · Graph8 Chat Agent · Graph8 Support Agent (Olivia, CSM) · Twin SDR Ari · Twin SDR Din · Twin AE Nick. All Active. Voice + phone numbers assigned per agent.* ![Studio Global docs workbench showing 43/46 Generation Incomplete with docs Team Command Center, Audience Questions, Brand News, Brand Sentiment, Company Enrichment, Company Keywords, Competitor Discovery, Organic Keywords, Paid Keywords, Products & Services with workflow type and status](../../../../assets/roles/founder/8-marketing-funnel.webp) ***Fig 4.2** — `/studio?mode=global`. **43 of 46 brand DNA docs generated** across 4 buckets (Intelligence · Context · Research · Targeting). Audience Questions · Brand News · Brand Sentiment · Company Enrichment · Competitor Discovery · Organic Keywords · Paid Keywords · Products & Services. Source of truth for every email/LP/agent script downstream.* > **Tip: Try this now** > **Founder move.** Open `/studio?mode=global`. Read the Brand Brief + Value Props + Personas. If they're wrong, fix them — every downstream artifact (email copy, ads, LP heroes, agent prompts) pulls from these docs. Spend 30 minutes here once a quarter, save 30 hours of "no, that's not how we should describe ourselves" downstream. ### 05. Copilot — your business summarized in a paragraph, on command *Natural-language exec queries. Reads from pipeline, customers, sequences, replies, deals — and writes you a paragraph. Use it as your weekly investor-update first draft.* **Open it at** - Sparkle icon · top-right · any page → Copilot side panel - Also reachable via WhatsApp / Slack / iMessage / Roam Bridge (see `/profile?tab=apps`) > **Note: Live copilot exchange** > *Live exchange · 2026-05-20* > > > "Give me a 1-paragraph summary of my business this week." > > ~9,000+ emails > > 32 replies total > > 0.56% reply rate > > 4 Closed Won deals totaling ~$1,997 > > "This week the outbound engine is running but underperforming: your active campaigns sent across roles like VP/Head of Sales, Account Executives, and RevOps, generating — with AccountExecutives_SaaS leading at a … (BigTapp Analytics, SterlingRisk, MayaKnows, sasa-holdings)…" ![Studio Global with Copilot panel open showing business summary response with email counts, reply rates, Closed Won deals (BigTapp Analytics, SterlingRisk, MayaKnows, sasa-holdings totaling ~$1997)](../../../../assets/roles/founder/9-ads.webp) ***Fig 5.1** — Live copilot answer. Reads from sequences, replies, deals, customer data — synthesizes into a paragraph. **Names specific Closed Won accounts** and the rep + sequence that drove the result. Better than 80% of weekly status emails.* ![Ad Campaign Manager showing 0 active campaigns, $0 spend, 0 impressions, 0 clicks, 0% CTR — empty state with your data/X platform support](../../../../assets/roles/founder/10-copilot-founder.webp) ***Fig 5.2** — `/ads`. Shipped, unused. 0 active campaigns · $0 spend. **Big founder-leverage opportunity** — start with $50/day LinkedIn against your top ICP list. Wire it up once.* > **Tip: Try this now** > **Founder move.** Every Sunday night, paste this into copilot: *"Write me a 5-paragraph weekly investor update covering pipeline, customers, marketing, team activity, and ask."* Edit the output for 10 minutes. Send. Saves 90 minutes of staring at a blank doc. ## The founder URL stack (bookmark this row) | To do this | Go here | | --- | --- | | **Morning pulse** — analytics + pipeline + customers | `/analytics` · `/deals/pipeline` · `/deals/l/customers` | | Trials in flight (with countdown timers) | `/deals/l/trials` | | Churned (post-mortem + win-back) | `/deals/l/churned` | | Marketing Bridge Funnel (where leads die) | `/analytics/marketing` | | Team activity (7-day pulse) | `/reports` | | AI agents (8 shipped) | `/agents` | | 46 brand DNA docs | `/studio?mode=global` | | Ad campaigns (still unused) | `/ads` | | Quotes / bookings ledger | `/deals/quotes` | | Admin — Stripe IDs, LLM COGS, custom JS | `/admin` | | Open Copilot anywhere | Sparkle · top-right | | Copilot via WhatsApp / Slack / iMessage | `/profile?tab=apps` | ## Heads-up — what to know > **Caution:** > **1. Big leverage opportunity: Ads + Bridge Funnel.** `/ads` shows $0 spend, and the Marketing Bridge Funnel shows 481 identified visitors → 0 added to outbound. These two surfaces together are an easy six-figure ARR fix. > > **2. Studio Global is 43/46.** Three docs still pending generation. Fire them so all 46 are ready when you ramp. > > **3. Customer Owner column is empty for graph8 today.** Assign owners on `/deals/l/customers` so renewal accountability is clear. > > **4. Bridge Copilot lets you DM the bot from WhatsApp / Slack / iMessage / Roam.** Set it up once at `/profile?tab=apps` — then ask "what's my MRR" from your phone instead of opening a laptop. > > **5. Bookmark — don't browse.** Every founder doc-of-this-genre fails because nobody can find the 5 URLs that matter. Open this doc once, bookmark all 5, then close the rest of the app and just live in those 5 tabs. --- <small>Generated 2026-05-20 against `app.graph8.com` (org `your org`). Surface state verified live before publication</small> --- # graph8 for GTM Engineer > graph8 field guide for GTM Engineer. Live URLs, real surfaces, screenshots from a public-data showcase tenant. Source: https://docs.graph8.com/roles/gtm-engineer/ *A field guide for GTM Engineer. Live URLs, real surfaces. Screenshots captured from a showcase tenant with public-company data only.* ## The five things to use today ### 01. Event pipeline — Connections, Functions, Destinations *A Jitsu-style event router: capture events from sites and apps, transform them with JavaScript Functions, fan them out to warehouses, ad pixels, CRMs, and analytics destinations.* **Open it at** - `app.graph8.com/connections/overview` — pipeline overview - `/connections/list` — source→destination event routing table - `/connections/streams` — tracking streams (sites/apps) with browser + server write keys - `/connections/functions` — JavaScript event-transform functions - `/connections/destinations` — routing targets (warehouse, GA4, Meta, LinkedIn Tag, OpenReplay, Matomo, etc.) - `/connections/live-events` — real-time event stream with Incoming / API Destinations / Batches tabs - `/connections/tailor` — dynamic page personalization - `/connections/syncs` — data sync jobs ![Connections list showing source-to-destination event routing table with Functions transforms](../../../../assets/roles/gtm-engineer/1-workflows-list.webp) ***Fig 1.1** — Connections list. Each row is a source → (optional Function transform) → destination edge. This is the heart of the GTM pipeline.* **Functions are real JavaScript with test + run** Open `/connections/functions` and any function pops a full code editor with: live JS code, Test Env Vars, Sample Event payload, and a Run button. Write your transform, paste a sample event, click Run, see the output before deploying. ![Function code editor showing JavaScript event-transform with Test Env Vars, Sample Event, Run button](../../../../assets/roles/gtm-engineer/2-workflow-builder.webp) ***Fig 1.2** — Function code editor. Standard JS with access to the incoming event payload + Env Vars. Test before you ship.* > **Tip: Try this now** > **Try this now.** Open `/connections/streams`, copy the browser write key for your test site, drop the snippet into a staging page, then open `/connections/live-events` in another tab. You'll see your test events stream in real-time. Now add a Function to enrich them with UTM data and route to a warehouse Destination. ### 02. CRM + billing integrations with bidirectional field mapping *Connect HubSpot, Salesforce, Zoho, SugarCRM, and Stripe. Map fields both ways. Auto-fill or overwrite per field. Push/Pull/Activity/Appointments toggles per integration.* **Open it at** - `app.graph8.com/studio/settings?tab=integrations` — full integrations catalog Today live: **HubSpot connected** (Salesforce/Zoho/SugarCRM available), **Stripe connected** for billing. Click any integration to open the settings modal — sub-tabs *Push · Pull · Fields · Appointments* with Auto Sync, Activity Sync, and per-record-type controls (Contact / Lead / Company / Deal). ![Integrations tab showing CRM (HubSpot, Salesforce, Zoho, SugarCRM) and Payment (Stripe) integrations](../../../../assets/roles/gtm-engineer/3-integrations.webp) ***Fig 2.1** — `/studio/settings?tab=integrations`. CRM + Payment categories. HubSpot & Stripe live; rest are one-click-connect.* ![HubSpot field mappings showing Graph8 Field to CRM Field with Auto Fill / Overwrite controls](../../../../assets/roles/gtm-engineer/5-custom-fields.webp) ***Fig 2.3** — Field mapping. Per-object (Contact / Lead / Company / Deal) **Graph8 Field ↔ CRM Field** with *Auto Fill* / *Overwrite* radio per row. **This is also the closest thing to a custom-field surface today** — there's no standalone `/fields` route.* > **Tip: Try this now** > **Try this now.** Open the HubSpot integration. Click *Fields* tab. Toggle Auto Fill on for the fields graph8 should own (intent_keywords, last_visit_at, sentiment) and Overwrite off for fields the CRM owns (deal_stage, owner). One mapping pass keeps both systems in sync forever. ### 03. Capture leads — Forms + web tracking snippet *Forms feed straight into the contact directory with full submission history. Web snippet is one HTML tag pointed at `events.flow.graph8.com`.* **Open it at** - `app.graph8.com/forms` — form list + submission tables - `/connections/streams` → click a stream → *Setup Instructions* for the web snippet Forms detail shows the live submission table: *First Name · Email · Received At · Page Title · URL*. Every submission becomes a contact with full attribution to the source page. ![Forms detail view for go-data-progressive-form with submissions table](../../../../assets/roles/gtm-engineer/6-forms.webp) ***Fig 3.1** — `/forms` submissions table. Every row is one form fill, linked back to the contact record.* **Web snippet — paste once, track everything** The snippet ships as a single `<script>` tag with your stream's write-key, pointed at `events.flow.graph8.com`. Drop it in your site's `<head>` once; from then on every pageview, form submit, button click, and custom event flows into the Connections pipeline. ![Site stream Setup Instructions showing HTML script snippet with write-key](../../../../assets/roles/gtm-engineer/7-web-snippet.webp) ***Fig 3.2** — Stream setup. Copy the snippet, paste into `<head>`, watch events appear in `/connections/live-events` within seconds.* > **Tip: Try this now** > **Try this now.** Create a stream in `/connections/streams`. Copy the snippet. Paste into a staging site. Visit the page, then check `/connections/live-events` — you should see your pageview event within 2 seconds. ### 04. Programmatic access — API keys, Webhooks, MCP & CLI *10 API keys provisioned today, webhook endpoints for inbound events, and a full MCP server that exposes 80+ tools to Claude/Copilot via stdio or HTTP.* **Open it at** - `app.graph8.com/studio/settings?tab=api` — API Keys + Webhooks + MCP & CLI config ![API Keys + Webhooks panel showing 10 keys redacted as d325...8e33 and webhook section](../../../../assets/roles/gtm-engineer/8-api-keys.webp) ***Fig 4.1** — `/studio/settings?tab=api`. API keys are displayed redacted (`d325…8e33`) — copy once at creation. Webhook section below for inbound POSTs.* **MCP — Claude/Copilot/Cursor + graph8** The MCP server exposes the same 80+ tools the in-app Copilot uses (search contacts, create lists, launch sequences, query deals, generate quotes, etc.). Two modes: **Local stdio** for dev (running on your laptop) and **GTM Mode** for Claude Desktop. Config is JSON. ![MCP Server and CLI configuration with JSON snippets for Local stdio and GTM Mode](../../../../assets/roles/gtm-engineer/10-mcp-tools.webp) ***Fig 4.2** — MCP & CLI config. Copy the JSON, drop into Claude Desktop / Cursor / your stdio host, restart, get all 80+ graph8 tools natively.* **Tool discovery (heads-up for LLM consumers)** Only ~30 tools are returned in the default `tools/list` — the rest are hidden to keep context lean. Use `g8_tool_search` to load specific categories on demand: g8_tool_search(query="workflow") // workflow / automation building g8_tool_search(query="dialer") // voice / dialer / phone calls g8_tool_search(query="intent") // buyer intent / signals / keywords g8_tool_search(query="skill") // LLM skill authoring g8_tool_search(query="field") // custom fields / properties g8_tool_search(query="form") // forms / form responses g8_tool_search(query="snippet") // web snippet / tracking install g8_tool_search(query="playbook") // playbooks (multi-step guides) > **Tip: Try this now** > **Try this now.** Open `/studio/settings?tab=api`, create an API key for your dev environment. Copy the MCP config block, drop it in `~/Library/Application Support/Claude/claude_desktop_config.json`, restart Claude Desktop. Ask Claude "find me VPs of Sales at SaaS companies with 100+ employees" — it calls `g8_find_contacts` for you. ### 05. Audience sync, ad campaigns, and conversion attribution *Push graph8 audiences to Meta / LinkedIn / Google / X as match-rate-optimized custom audiences. Track form-fill + meeting-booked conversion rates back to ad source.* **Open it at** - `app.graph8.com/ads` — ad campaign manager + Create Campaign wizard - `/analytics/conversions` — Goal Completions + Form Performance - `/analytics/acquisition` — Channel + Source/Medium breakdown - `/analytics/attribution` — Sender performance (empty for graph8 org today) The `/ads` wizard walks: *Platform → Audience → Creative → Budget → Review → Publish*. Audience step pulls saved lists from graph8 and pushes them as custom audiences. Creative step pulls ad images from the brand snapshot. Budget + Review + Publish hand off to the platform's ads API. ![Ads page with Create Campaign wizard showing your data for Meta, LinkedIn, Google, X](../../../../assets/roles/gtm-engineer/11-audience-sync.webp) ***Fig 5.1** — `/ads`. "Manage audience syncs and ad campaigns across Meta, LinkedIn, Google, and X."* ![Conversion analytics at /analytics/conversions showing Goal Completions and Form Performance with submission and conversion rates](../../../../assets/roles/gtm-engineer/12-attribution-config.webp) ***Fig 5.2** — `/analytics/conversions`. Goal Completions (Form Submission, Meeting Booked) + Form Performance with per-form submission + conversion rates.* > **Tip: Try this now** > **Try this now.** Open `/ads`, click *Create Campaign*, select Meta + a saved list + a creative. Push to draft. Now wire `/connections/destinations` → Meta CAPI Destination → enrich pageview events with email so the platform can attribute back. Watch conversions appear in `/analytics/conversions`. ## Discovered routes worth knowing | What | Route | | --- | --- | | **Settings tree** — 14 sub-tabs (Company, Users, Teams, Roles, Domains, Mailboxes, Phone Numbers, SSO, API/MCP, Compliance, Scraping, Integrations, Billing, Revenue, Tasks, Docs, Enrichment) | `/studio/settings?tab=...` | | Site crawl / scraping config | `/studio/settings?tab=scraping` | | Domain purchase, mailbox purchase, phone numbers | `/studio/settings` sub-tabs | | Live event stream (real-time debug) | `/connections/live-events` | | Dynamic page personalization | `/connections/tailor` | | Agents + Twins (Receptionist, SDR, CSM, AE roles) | `/agents` | | graph8-admin dashboard (713 customer orgs, Services, LLM Charges, Custom JS) | `/admin` | | Marketplace (SDR hiring) | `/marketplace` | | 9-tab analytics | `/analytics/\{overview,acquisition,behavior,conversions,attribution,marketing-intelligence,realtime,live-visitors,performance-reports\}` | ## Heads-up — naming + missing routes > **Caution:** > **"Workflows" doesn't exist as a top-level route.** If you're looking for workflow/automation building, that surface is `/connections/functions` (JavaScript transforms) + `/connections/list` (event routing). graph8 does not have a no-code visual workflow builder today — it has code-based event transforms. > > **"Custom fields" doesn't exist as a standalone route.** Field configuration lives inside each CRM integration's *Fields* tab (Fig 2.3). There's no `/fields` or `/properties` page. Per-record-type schema is managed by the CRM you're synced to. > > **Playbooks, Skills, and Web-chat routes don't exist yet.** The MCP server instructions reference `g8_tool_search("playbook")` and `g8_tool_search("skill")` — those tools exist on the API surface but there's no UI route to manage them today. > > **Attribution analytics is empty for graph8.com.** `/analytics/attribution` exists but shows "No sender data yet" because no `cb_outbound_sends` rows exist on graph8's own tenant. Use `/analytics/acquisition` + `/analytics/conversions` instead for channel-level attribution. > > **Most settings are sub-tabs, not routes.** Don't try `/settings/tracking`, `/settings/fields`, `/settings/attribution`, `/developer` — they 404. Settings tree lives at `/studio/settings?tab=…`. --- <small>Generated 2026-05-20 against `app.graph8.com` (org `your org`). Surface state verified live before publication</small> --- # graph8 for Marketing / Demand Gen > graph8 field guide for Marketing / Demand Gen. Live URLs, real surfaces, screenshots from a public-data showcase tenant. Source: https://docs.graph8.com/roles/marketing/ *A field guide for Marketing / Demand Gen. Live URLs, real surfaces. Screenshots captured from a showcase tenant with public-company data only.* ## The five things to use today ### 01. Acquisition — where every visitor comes from, what they read *9 analytics tabs. Start with Overview for the daily pulse, then drill: Acquisition for channel mix, Behavior for top pages, Realtime for "who's on the site right now."* **Open it at** - `app.graph8.com/analytics` — Overview dashboard (single-screen pulse) - `/analytics/acquisition` — channel breakdown - `/analytics/behavior` — top pages by views - `/analytics/realtime` — active visitors right now - `/analytics/live-visitors` — visitor table with journey/referrer/location ![Analytics Overview showing 78K unique visitors, 81K sessions, 97K page views, top traffic sources Direct 74.4% Organic 4.6% Referral 3.5%](../../../../assets/roles/marketing/1-analytics-overview.webp) ***Fig 1.1** — `/analytics` Overview. **78,208 unique visitors · 81,302 sessions · 97,898 page views · 8s avg engagement.** Top sources: Direct 74.4% · Organic 4.6% · Referral 3.5%. Bounce 97% · Engagement 1.6%.* ![Acquisition channels showing Direct 90.2%, Organic Search 5.5%, Referral 4.1%, AI/LLM 0.2% with 79.3K users](../../../../assets/roles/marketing/2-acquisition-channels.webp) ***Fig 1.2** — `/analytics/acquisition`. 30-day window: **Direct 90.2% (72K) · Organic 5.5% · Referral 4.1% · AI/LLM 0.2%.** Direct dominance = brand search + bookmark traffic. Organic + AI/LLM is your SEO/AEO growth lever.* ![Behavior page views showing B2B Contact Database 72,843 views, homepage 2,913, Replace Your Sales Stack 1,609, /startups 746, /credits 707](../../../../assets/roles/marketing/3-conversions.webp) ***Fig 1.3** — `/analytics/behavior/pages`. Top pages: **B2B Contact Database 72.8K views** (99.7% bounce — long-tail SEO landing) · graph8.com homepage 2.9K · *"Replace Your Sales Stack"* 1.6K · `/startups` 746 · `/credits` 707 · `/dialer` 321 · `/pricing` 305.* > **Tip: Try this now** > **Try this now.** Open `/analytics/realtime` right before a campaign push. After firing the email/ad, watch live visitor count climb — instant signal that copy/CTA worked. Then check `/analytics/behavior` to see which page they landed on and how long they stayed. ### 02. The conversion funnel — Marketing Intelligence + Conversions *graph8 ships a unique **Outbound Bridge Funnel**: Web Visitors → Identified → Added → Replied → Meetings. Lets you see exactly where the funnel breaks.* **Open it at** - `app.graph8.com/analytics/marketing` — Marketing Intelligence + Outbound Bridge Funnel - `/analytics/conversions` — Goal Completions + Form Performance - `/analytics/attribution` — Sender Performance + Acquisition Breakdown (empty for graph8 today) ![Marketing Intelligence showing Qualified Traffic 471, Engagement Quality 2%, Campaign Influence 471, Pipeline Impact 0, plus Outbound Bridge Funnel](../../../../assets/roles/marketing/4-behavior.webp) ***Fig 2.1** — `/analytics/marketing`. **Qualified Traffic 471 · Engagement Quality 2% · Campaign Influence 471 · Pipeline Impact $0**. The Outbound Bridge Funnel below shows where the leaks are.* Outbound Bridge Funnel · live 2026-05-20 Web visitors78,470 Identified471 (0.6%) Added to sequence0 Replied0 Meetings booked0 ![Conversions showing 146 total conversions, 146 form submissions, 40 meetings booked](../../../../assets/roles/marketing/5-marketing-intel.webp) ***Fig 2.2** — `/analytics/conversions`. **146 conversions · 0.1% rate · 146 form submissions · 40 meetings booked.** Two goals tracked: Form Submission + Meeting Booked.* > **Tip: Try this now** > **Try this now.** The Outbound Bridge Funnel above shows 471 identified visitors but 0 added to a sequence. That's a Marketing-to-SDR handoff gap. Build a routing rule: *identified visitor + hit /pricing → auto-add to a re-engagement sequence*. Then watch the funnel — Added should climb within a week. ### 03. Paid acquisition — Ads wizard for Meta · LinkedIn · Google · X *6-step campaign builder: Platform → Audience → Creative → Budget → Review → Publish. Pulls audiences from graph8 lists and creatives from the brand snapshot. One UI, four channels.* **Open it at** - `app.graph8.com/ads` — ad campaign manager + Create Campaign wizard ![Ads Create Campaign wizard showing 6-step flow Platform/Audience/Creative/Budget/Review/Publish with Meta/LinkedIn (preselected)/Google/X platform cards](../../../../assets/roles/marketing/6-realtime.webp) ***Fig 3.1** — `/ads` Create Campaign wizard. 6 steps. 4 platforms (LinkedIn pre-selected). Each platform needs a one-time connection ("Platform not connected — Connect Account"). Once connected, audiences from `/lists` push as custom audiences; creatives auto-resize for each platform.* > **Tip: Try this now** > **Try this now.** Connect LinkedIn Ads first (highest B2B intent). Build an audience in `/lists` from your ICP filter (e.g. "VP Sales at SaaS, 100-500 employees"). Open `/ads` → Create Campaign → pick that list as audience. Reuse a graph8 brand-asset for creative. Set a $50/day budget for a 14-day learning run. ### 04. Studio — Landing Pages, Brand Kit, 45 brand DNA docs *Studio is the full content surface: 41 published landing pages + 19 drafts, Brand Kit with extracted homepage clone, and 45 globally-generated brand DNA docs (Intelligence · Context · Research · Targeting buckets).* **Open it at** - `app.graph8.com/studio?mode=content` — Brand Kit + content workspace - `/studio?mode=content&ct=landing_page` — Landing Pages list (the hidden URL) - `/studio?mode=global` — Global brand-DNA doc workbench - Studio modes also: *?mode=campaign* (Campaign Studio) · *?mode=team* (Team Studio) ![Landing Pages with 19 Drafts, 41 Publish counts; Create panel with 3 paths (Custom prompt, From a layout, Clone existing); template list and sidebar named drafts](../../../../assets/roles/marketing/7-live-visitors.webp) ***Fig 4.1** — Landing Pages under Studio → Content → Brand Kit dropdown. **19 drafts · 41 published.** Templates: *Bold Hero + Single CTA · Benefits Grid · Social Proof First.* Named drafts include *ROI calculator · Comparison LP · Free Trial Signup · Pricing-Led · Product Launch · Video Explainer.** ![Studio Content mode with Brand Kit sidebar (Overview/Homepage/Design System/Sections) and graph8.com clone preview](../../../../assets/roles/marketing/7b-attribution.webp) ***Fig 4.2** — `/studio?mode=content`. **Brand Kit** with sub-sections Overview / Homepage / Design System / Sections. graph8.com cloned (extracted 5/5/2026) — *Re-extract* + *Re-capture* buttons keep it fresh.* ![Studio Global Docs showing 43/45 generation with Intelligence 13/13, Context 21/23, Research 6/6, Targeting 3/3 buckets](../../../../assets/roles/marketing/8-ads-wizard.webp) ***Fig 4.3** — `/studio?mode=global`. **43/45 brand DNA docs generated** across 4 buckets: *Intelligence 13/13 · Context 21/23 · Research 6/6 · Targeting 3/3*. Includes Audience Questions · Brand News · Brand Sentiment · Company Enrichment · Competitor Discovery · Organic Keywords · Paid Keywords · Products & Services.* > **Tip: Try this now** > **Try this now.** Open `/studio?mode=global`, check which 2 docs in *Context (21/23)* are still pending. Generate them. Then open a Landing Page draft and re-generate copy — the LP builder pulls from those same brand-DNA docs, so updating Context flows through automatically. ### 05. Lead capture, intent keywords, copilot for sequence performance *19 forms shipping submissions today, intent-keyword tracking (set up your first one), and a copilot that answers "which campaign drove the most replies?" with structured tables.* **Open it at** - `app.graph8.com/forms` — 19 forms with submission counts - `/keywords` — Intent Keywords (Signals → Intent) - Copilot — sparkle icon top-right ![Forms list showing 19 forms with submission counts: undefined 76 on /lists, go-data-progressive-form 75 on a paying customer.com, apply-form 17 on graph8.com/marketplace](../../../../assets/roles/marketing/9-landing-pages.webp) ***Fig 5.1** — `/forms`. **19 forms · per-form submission counts + latest submission + target list.** Top: *undefined* 76 submissions on /lists · *go-data-progressive-form* 75 on a paying customer.com · *apply-form* 17 on graph8.com/marketplace · *demo-with-oleksii* 12 · *exit-form* 11.* ![Intent Keywords UI empty for graph8 org with your data columns](../../../../assets/roles/marketing/10-keywords.webp) ***Fig 5.2** — `/keywords`. **0 keywords configured for graph8 org.** UI ready — add keywords like *"replace HubSpot"*, *"sales automation"*, *"sales tooling consolidation"* to surface contacts at companies actively discussing your wedge.* ![Copilot response showing Email Campaign Performance table with SDR/BDR_SaaS 2350/2085, SalesLeaders_AI&ML 1282/1207, sequence reply rates and key takeaways](../../../../assets/roles/marketing/11-forms.webp) ***Fig 5.3** — Copilot, "Which channels generated the most conversions this month?" Response: **full Email Campaign Performance table** — SDR/BDR_SaaS 2,350 sent / 2,085 delivered · SalesLeaders_AI&ML 1,282/1,207 · SalesEnablement_SaaS 1,707/1,570 · GrowthDemandGen_SaaS 1,281/1,211. Plus takeaways: *"SalesLeaders_AI&ML 0.41% best reply rate. VP/Head-Sales_SaaS 24 replies (highest volume). No channel comparison possible — everything is email."** > **Tip: Try this now** > **Try this now.** Set up your first 5 intent keywords at `/keywords`. Within 24h you'll start seeing companies in `/contacts` with the Intent Keywords column populated. Build a list from those, run it through a sequence. Then ask copilot: *"Which intent keyword had the best reply rate?"* ## Discovered nav map (the full marketing surface) | Top-nav bucket | Sub-routes you'll use | | --- | --- | | **Studio** | `?mode=global` (brand DNA) · `?mode=content` (Brand Kit + LPs) · `?mode=campaign` · `?mode=team` | | **Signals** | Visitors (Contacts · Companies · Chat · Campaigns) · Intent (`/keywords`) · Forms · Hiring | | **Engage** | Sequencer · Nurture · Inbox · Dialer · Web Chat · Appointments · Newsletter | | **Revenue** | Trial / Customers / Churned / Quotes / Products / Deals | | **Agents** | AI agent management (Receptionist, SDR, CSM, Twins) | | **Data** | Search / Contacts / Companies / Lists / Suppressions / Workbench / Data Pipelines | ## URL cheat-sheet | To do this | Go here | | --- | --- | | Daily analytics pulse | `/analytics` | | Channel acquisition breakdown | `/analytics/acquisition` | | Top pages by views | `/analytics/behavior` | | Marketing Intelligence + Bridge Funnel | `/analytics/marketing` | | Conversions (forms + meetings) | `/analytics/conversions` | | Attribution (sender / acquisition) | `/analytics/attribution` | | Active visitors right now | `/analytics/realtime` | | Live visitor table | `/analytics/live-visitors` | | Performance Reports (13 sub-reports) | `/reports` | | Ad campaigns (Meta/LI/Google/X) | `/ads` | | Landing Pages list | `/studio?mode=content&ct=landing_page` | | Brand Kit + Homepage clone | `/studio?mode=content` | | 45 brand-DNA docs (global) | `/studio?mode=global` | | Forms list + submissions | `/forms` | | Intent Keywords | `/keywords` | | Chat (was Chat Leads) | Signals → Visitors → Chat tab | | Hiring Signals (job-posting intent) | Signals → Hiring | | Open Copilot anywhere | Sparkle · top-right | ## Heads-up — known Marketing gaps + workarounds > **Caution:** > **1. Landing Pages URL is non-obvious.** `/landing-pages`, `/studio/landing-pages`, `/lp`, `/pages` all 404. The real URL is `/studio?mode=content&ct=landing_page` or click Studio → Content → Brand Kit dropdown. > > **2. No /blog route in the app.** Blog content management lives outside the product (in the `g8web/graph8-web` repo). The app doesn't have a blog post editor — for in-app content, use Landing Pages. > > **3. Attribution is empty for graph8 today.** `/analytics/attribution` shows "No sender data yet" because no `cb_outbound_sends` rows exist on graph8's own tenant. Use `/analytics/conversions` + `/analytics/marketing` instead for goal-based + funnel attribution. > > **4. Intent keywords are empty.** 0 keywords configured at `/keywords`. UI ready — set up your first 5-10 keywords today so the Bridge Funnel can start surfacing intent-driven contacts. > > **5. The Outbound Bridge Funnel shows 0 Added / 0 Replied / 0 Meetings.** 471 visitors identified but no auto-routing to sequences. That's a Marketing-to-SDR plumbing gap, not a tool gap — fix with a routing rule. > > **6. Ad platforms need one-time connection.** All 4 platforms (Meta · LinkedIn · Google · X) ship as "Platform not connected" until you authenticate the ad account. Connect once, reuse forever. > > **7. The Studio docs counter shows 43/45.** Two Context docs are still pending generation. Run them so all 45 are ready when you fire the next campaign. --- <small>Generated 2026-05-20 against `app.graph8.com` (org `your org`). Surface state verified live before publication</small> --- # graph8 for Marketing Operations > graph8 field guide for Marketing Operations. Live URLs, real surfaces, screenshots from a public-data showcase tenant. Source: https://docs.graph8.com/roles/mops/ *A field guide for MOps. Live URLs, real surfaces. Screenshots captured from a showcase tenant with public-company data only.* ## The five things to use today ### 01. Event pipeline — Connections, Functions, Streams, Destinations, Live Events *Jitsu-style architecture. Sources (Streams) → Transforms (Functions) → Destinations. 11 destination types shipped including warehouses + ad pixels + analytics + CRM-side triggers.* **Open these** - `app.graph8.com/connections/list` — full source→destination routing - `/connections/functions` — JS event-transform code editor - `/connections/streams` → click a site → Setup Instructions — web snippet - `/connections/destinations` — 11 destination types - `/connections/live-events` — real-time event debug stream ![Connections list with sources graph8.com, heymech-graph8, calendamation.com routed to Forms Sync Trigger, Campaign Visitor Trigger, Warehouse, Identify, Tailor, OpenReplay, Twitter Ad Conversion](../../../../assets/roles/mops/1-connections-list.webp) ***Fig 1.1** — `/connections/list`. Each row = source → (optional Function transform) → destination edge.* ![Live Events page showing real-time event stream with Incoming Events/API Destinations/Functions Logs/Batches sub-tabs](../../../../assets/roles/mops/2-functions.webp) ***Fig 1.2** — `/connections/live-events`. Real-time debug. *Incoming Events* · *API Destinations & Functions Logs* · *Batches & Data Warehouse Events*.* ![Destinations list with Identify (13 sites), Warehouse (14 sites), Chat (10), Form Tracker (11), Forms Sync Trigger (12), Campaign Visitor Trigger (13), Tailor, OpenReplay, Twitter Ad Conversion, X Conversion Event (Not connected), LinkedIn Tag (1 site)](../../../../assets/roles/mops/3-streams-snippet.webp) ***Fig 1.3** — `/connections/destinations`. 11 destination types with per-destination site counts.* > **Tip: Try this now** > **MOps move.** Open `/connections/live-events` when you push a tracking change. Watch events stream in within seconds — instant validation. Then check *Functions Logs* sub-tab if transforms aren't behaving as expected. ### 02. JavaScript Functions — write your own enrichment *Real JS code editor with Test Env Vars + Sample Event + Run button. Two live functions today: *Filter Eda Submission Form Trigger* and *Campaign Visitor Event Filter*. Drop in your own to enrich, route, or filter events.* **Open it at** - `app.graph8.com/connections/functions` ![Functions list showing 2 active functions: Filter Eda Submission Form Trigger and Campaign Visitor Event Filter](../../../../assets/roles/mops/4-live-events.webp) ***Fig 2.1** — Functions list. Each opens a code editor with test panel + sample event. JS goes from idea to deployed without filing a ticket.* > **Tip: Try this now** > **MOps move.** Common transform recipes: (1) Parse UTMs and add as event fields. (2) Filter out internal IP ranges. (3) Hash email before sending to Meta CAPI. (4) Compute lead score from event payload. Build once, attach to any source. ### 03. Web snippet install — one tag, every event *The script ships as one async tag pointed at `events.flow.graph8.com` with your stream's write-key. Drop in `<head>`, get pageviews + form submits + custom events automatically.* **Open it at** - `/connections/streams` → click your site → Setup Instructions sub-tab ![Stream detail showing Setup Instructions tab with HTML tracking snippet script async src=https://events.flow.graph8.com/p.js data-write-key=...](../../../../assets/roles/mops/5-destinations.webp) ***Fig 3.1** — One `<script>` tag. Drop in `<head>` on every page of your site. **Heads-up:** `?tab=setup` URL param isn't recognized — drill in via the site row.* > **Tip: Try this now** > **MOps move.** Add the snippet to staging first. Open `/connections/live-events`. Visit your staging site. You should see your pageview within 2 seconds. Validate before pushing to prod. ### 04. CRM connections + form routing *5 CRMs supported (HubSpot · Salesforce · Zoho · SugarCRM · Stripe) with per-field bidirectional mapping. Plus 19 forms with Target List routing — each form fill auto-routes to the configured list.* **Open these** - `/studio/settings?tab=integrations` — CRM + payment connectors - `/forms` — 19 forms with Target List config ![Studio Settings Integrations showing HubSpot Connected (expires May 18 2026), Salesforce, Zoho CRM, SugarCRM, Stripe Connected](../../../../assets/roles/mops/6-integrations.webp) ***Fig 4.1** — `?tab=integrations`. **HubSpot Connected** · **Stripe Connected**. Open HubSpot gear → Fields tab for the field mapping schema.* ![Forms list with 19 forms showing Form ID, Page URL, Total Submissions, Latest Submission, Target List columns; top entries undefined 78, go-data-progressive-form 78, apply-form 17](../../../../assets/roles/mops/7-forms.webp) ***Fig 4.2** — `/forms`. 19 forms — top performers *go-data-progressive-form 78 subs · apply-form 17 · demo-with-vekkii 12.* **Target List column empty** for most rows = form-to-list routing not configured. MOps fix.* > **Tip: Try this now** > **MOps move.** The Target List on each form is the seam between marketing (form fill) and sales (sequence enrollment). Configure these so every form submission lands in the right SDR sequence within minutes. Then watch `/analytics/marketing` → Bridge Funnel for "Added to Outbound" climbing. ### 05. Attribution + Intent Keywords + Copilot *Intent keyword config (currently empty for graph8 — set up first). Attribution analytics (also empty until outbound runs). Copilot honest about the attribution gap.* **Open these** - `/keywords` — Intent Keyword config - `/analytics/attribution` — Sender + Acquisition Breakdown - `/analytics/conversions` — Goal Completions (Form Submission + Meeting Booked) - Copilot — Cmd+Shift+K ![Intent Keywords page with empty table, columns Keyword/Contacts/Companies/Date Added/Last Updated/Sync/Actions, Add Keyword button](../../../../assets/roles/mops/8-keywords.webp) ***Fig 5.1** — `/keywords`. **0 keywords configured.** Add your first 10 so the intent dashboard surfaces companies talking about your wedge.* ![Analytics Attribution showing 'No sender data yet — no outbound sends in the last 30 days. Once the rewriter starts writing cb_outbound_sends rows, sender performance will appear' with Sender performance / Acquisition breakdown sub-tabs](../../../../assets/roles/mops/9-attribution.webp) ***Fig 5.2** — `/analytics/attribution`. Empty. Workaround until populated: `/analytics/conversions` for goal-attribution.* ![Copilot response to 'Show me my conversion rate by traffic source this month' explaining it lacks website analytics/traffic source attribution and offering alternatives: Campaign reply rates, Deal conversion by stage, Pipeline by source if deals have CRM source attribution](../../../../assets/roles/mops/10-copilot-mops.webp) ***Fig 5.3** — Live copilot response. Honest: *"Lacks website analytics / traffic-source attribution data."* Offers alternatives (campaign reply rates, deal conversion by stage). **This is the MOps gap to close**: wire web events to deal source attribution.* > **Tip: Try this now** > **MOps move.** The MOps roadmap: (1) configure intent keywords today. (2) wire form→list routing on the 19 forms. (3) once outbound runs, validate `/analytics/attribution` populates. (4) build the web→deal source attribution loop (Function transform writing utm_source to the contact record). ## URL cheat-sheet | To do this | Go here | | --- | --- | | Event routing (source → destination) | `/connections/list` | | JS event transforms | `/connections/functions` | | Web snippet install | `/connections/streams` → click site | | 11 destination types | `/connections/destinations` | | Real-time event debug | `/connections/live-events` | | Dynamic page personalization | `/connections/tailor` | | CRM + Stripe connectors | `/studio/settings?tab=integrations` | | Forms + Target List routing | `/forms` | | Intent Keywords | `/keywords` | | Attribution analytics | `/analytics/attribution` | | Goal-based conversions | `/analytics/conversions` | | Marketing Bridge Funnel | `/analytics/marketing` | ## Heads-up — known MOps gaps > **Caution:** > **1. Form Target List column is empty for most forms.** 19 forms shipping submissions but routing-to-sequence not configured. Biggest MOps quick-win. > > **2. Intent keywords empty.** 0 configured at `/keywords`. Set up your first 10. > > **3. Attribution analytics empty.** Will populate when outbound sends start logging rows. Use `/analytics/conversions` in the meantime. > > **4. No native web→deal attribution.** Copilot is honest: web traffic source data is not joined to deal records today. Build a Function transform that writes `utm_source`/`utm_campaign` to the contact record so deal-conversion-by-source becomes queryable. > > **5. `?tab=setup` doesn't work on `/connections/streams`.** Drill in by clicking a site row. --- <small>Generated 2026-05-20 against `app.graph8.com` (org `your org`). Surface state verified live</small> --- # graph8 for Onboarding / Implementation > graph8 field guide for Onboarding / Implementation. Live URLs, real surfaces, screenshots from a public-data showcase tenant. Source: https://docs.graph8.com/roles/onboarding/ *A field guide for Onboarding / Implementation. Live URLs, real surfaces. Screenshots captured from a showcase tenant with public-company data only.* ## The 30-day playbook (5 steps) ### 01. Day 1-3 — Generate the brand DNA foundation *graph8 auto-generates 46 brand DNA docs from the customer's website + enrichment. Every downstream artifact (emails, LPs, ads, agent scripts) pulls from these docs. Foundation must come first.* **Open it at** - `app.graph8.com/studio?mode=global` — 46 doc workbench ![Studio Global showing Generation Incomplete 42/46 with Intelligence/Context (20/20)/Research/Targeting bucket counters and document table with Context Pending and Intelligence v1.0.0 Done docs](../../../../assets/roles/onboarding/1-streams-setup.webp) ***Fig 1.1** — `/studio?mode=global`. Mature org snapshot: 42 of 46 docs generated. New customer onboarding starts at 0/46. Run generation in this order: *Intelligence → Context → Research → Targeting.** - Confirm customer website crawled (`/studio/settings?tab=scraping` — set max crawl depth) - Trigger Intelligence bucket (13 docs: scrape, enrichment, competitors, keywords) - Trigger Context bucket (21 docs: Brand Brief, Brand Voice, Personas, ICPs, Messaging House, etc.) - Trigger Research bucket (6 docs: Buyer Psychology, Voice of Customer, etc.) - Trigger Targeting bucket (3 audience definition docs) - Review Brand Brief + Brand Voice with customer — if these are wrong, fix them before doing anything else > **Tip: Try this now** > **Onboarding move.** Block a 90-min "Brand Voice review" call with the customer in week 1. Walk through the auto-generated Brand Voice doc together. Edit live. Locks in the foundation everything else depends on. ### 02. Day 4-7 — Install tracking + connect CRM *Web snippet → first events flowing within hours. CRM integration → bidirectional contact/deal sync. Field mapping → per-object schema agreement.* **Open these** - `/connections/streams` → click site → Setup Instructions (snippet) - `/studio/settings?tab=scraping` — site crawl config - `/studio/settings?tab=integrations` — connect HubSpot/SF/Zoho/SugarCRM/Stripe - Click connected integration → *Fields* tab — map each object's fields ![Streams Sites view showing list of tracking sites including graph8.com, heyreach-graph8, calendaration.com, app.graphill.com with destinations counts](../../../../assets/roles/onboarding/2-integrations.webp) ***Fig 2.1** — `/connections/streams` Sites view. Each site = one tracking property. Click a row → *Setup Instructions* sub-tab for the snippet.* ![Integrations showing HubSpot Connected with ahaha@graph8.com expiring May 18 2026, Salesforce/Zoho/SugarCRM Connect cards, Stripe Connected](../../../../assets/roles/onboarding/3-integration-fields.webp) ***Fig 2.2** — `?tab=integrations`. **HubSpot Connected** · **Stripe Connected**. Connect customer's CRM here first.* > **Tip: Try this now** > **Onboarding move.** The integration field-mapping pass takes 30-60 min per CRM but saves quarters of "why isn't this syncing?" tickets. Do it once, do it carefully. Document the decision rationale per field in customer notes. ### 03. Day 7-10 — Auth, domains, team *Lock down the auth domain allow-list. Set default role for new joiners. Invite the customer team. 33 users on graph8's own org for reference scale.* **Open these** - `/studio/settings` → Domains — auth allow-list + join settings - `/studio/settings` → Users — invite team members - `/studio/settings` → Roles — configure access levels ![Team Members table showing 33 users with your data login/Joined/External/Phone/Department columns, most users Admin role active](../../../../assets/roles/onboarding/7-studio-global-generation.webp) ***Fig 3.2** — Users. **33 team members** on graph8 (reference scale). Invite User top-right. Stagger invites by role so new joiners land in the right Default Role.* > **Tip: Try this now** > **Onboarding move.** Send the customer admin a list of every email they want invited. Set Default Role = SDR (lowest privilege). Then walk them through promoting specific users to Manager / Admin individually — minimum-privilege rule + clear paper trail. ### 04. Day 10-20 — Sequences + contacts *Clone graph8's 30 proven team sequences as customer starting templates. Import the customer's first contact list. Launch sequence #1 by end of week 2.* **Open these** - `app.graph8.com/sequencer` — 30 team-sequence templates to clone - `/contacts` — 2.3M contact directory (or import customer list) - `/lists` — create customer's first prospect list ![Sequencer Team Sequences tab showing 30 sequences including Multi-Channel Sequence Test, SalesLeaders_AI&ML_US/UK, GrowthDemandGen_SaaS_US, RevOps_SaaS, VPHead-Sales_SaaS with channels/contacts/steps/status columns](../../../../assets/roles/onboarding/8-sequencer-clone.webp) ***Fig 4.1** — `/sequencer`. 30 proven templates. Clone *SalesLeaders_AI&ML* or *GrowthDemandGen_SaaS* as starting sequences. Customer edits copy + targets without rebuilding from scratch.* > **Tip: Try this now** > **Onboarding move.** Don't let the customer build their first sequence from scratch — clone a high-reply-rate template (e.g. SalesLeaders_AI&ML at 0.41%). Walk them through the 3 steps, edit each step's email body together, save. First sequence launched ≤ week 2 milestone. ### 05. Day 20-30 — Customer accounts + copilot orientation *Configure the customer book in `/deals/l/customers` (add their existing customers). Run a copilot walkthrough so the customer team knows the natural-language interface exists.* **Open these** - `app.graph8.com/deals/l/customers` — Add company → seed initial customer list - Copilot — Cmd+Shift+K · introduce to customer team ![Customers list at /deals/l/customers showing a paying customer, a paying customer, a paying customer, a paying customer, a paying customer, your account, a paying customer, a paying customer, a paying customer networks with your data columns, Add company button](../../../../assets/roles/onboarding/9-customers-add.webp) ***Fig 5.1** — `/deals/l/customers`. *Add company* top-right. Seed customer's existing accounts so the Customer Health card has tenure context from day one.* ![Copilot response to 'What should I set up first for a new customer?' showing structured response: 1. Global Context (Foundation First) - Brand Brief, Brand Voice, ICP Research, Value Props, Offer Brief; followed by sections 4. Contact List + Sequence and 5. Accounts](../../../../assets/roles/onboarding/10-copilot-onboarding.webp) ***Fig 5.2** — Copilot recommended onboarding order: *"1. Global Context (Foundation First) — powers everything downstream. Brand Brief · Brand Voice · ICP Research · Value Props · Offer Brief."* Same order this doc follows.* > **Tip: Try this now** > **Onboarding move.** Last call of the 30-day onboarding: open Copilot, ask the customer to type their first question. *"What's our highest-value deal in pipeline?"* Always works. Land the "this isn't just a CRM, it's a query interface" moment. ## The 30-day onboarding checklist (printable) | Phase | Steps | | --- | --- | | **Week 1 — Foundation** | Crawl site (`?tab=scraping`) · Generate brand DNA at `/studio?mode=global` · Review Brand Voice w/ customer · Install snippet (`/connections/streams`) | | **Week 2 — Integrations** | Connect CRM at `?tab=integrations` · Configure field mapping per object · Connect Stripe if relevant · Set up event destinations | | **Week 3 — Team + Sequences** | Configure Domains (auto-join · default role) · Invite team at `?tab=users` · Clone first sequence from `/sequencer` · Build first list at `/lists` · Launch sequence | | **Week 4 — Accounts + Copilot** | Seed customer accounts at `/deals/l/customers` · Copilot walkthrough with team · Hand off to CSM | ## URL cheat-sheet | To do this | Go here | | --- | --- | | Generate brand DNA (46 docs) | `/studio?mode=global` | | Customer site crawl config | `/studio/settings?tab=scraping` | | Install web snippet | `/connections/streams` → click site | | Connect CRM + billing | `/studio/settings?tab=integrations` | | Field mapping per integration | Integration gear → Fields tab | | Auth domain allow-list + default role | `/studio/settings` → Domains | | Invite team members | `/studio/settings` → Users | | Clone sequences (30 templates) | `/sequencer` | | Build first contact list | `/lists` | | Add customer accounts | `/deals/l/customers` | | Copilot walkthrough | Cmd+Shift+K | ## Heads-up — onboarding gotchas > **Caution:** > **1. `?tab=setup` doesn't work on Streams.** Click the site row to reach Setup Instructions. > > **2. Radix tabs in integration modals need pointer events.** Plain JS `.click()` doesn't switch tabs in the HubSpot Settings modal. If you're scripting, use full pointerdown→mousedown→pointerup→mouseup→click sequence. > > **3. Brand DNA generation takes time.** Don't sit waiting — start it, do the integration setup in parallel, come back to review when generation is > 80% complete (e.g. graph8's own org at 42/46 took several weeks to fully populate). > > **4. Default Role must match the customer's actual first user.** If their first joiner needs Admin, set Default Role = Admin temporarily, then lower it after. Otherwise the joiner gets confused-looking SDR UI. > > **5. Field-mapping changes are reactive, not retroactive.** If you change a mapping mid-onboarding, only new records get the new rules. Existing records keep old mapping until you trigger a sync. --- <small>Generated 2026-05-20 against `app.graph8.com` (org `your org`). Surface state verified live</small> --- # graph8 for Product Marketing > graph8 field guide for Product Marketing. Live URLs, real surfaces, screenshots from a public-data showcase tenant. Source: https://docs.graph8.com/roles/pmm/ *A field guide for Product Marketing Manager. Live URLs, real surfaces. Screenshots captured from a showcase tenant with public-company data only.* ## The five things to use today ### 01. Studio Global — your 46 brand DNA docs *Brand Brief · Brand Voice · Style Guide · Writing Style Guide · Personas · ICPs · Messaging House · Value Props · Offer Brief · Pricing Matrix · Proof Catalog · Competitive Teardown · Voice of Customer · Buyer Psychology. Auto-generated from website + enrichment. PMM owns these.* **Open it at** - `app.graph8.com/studio?mode=global` ![Studio Global workbench showing 43/45 generation incomplete with your data buckets and documents table](../../../../assets/roles/pmm/1-studio-global-docs.webp) ***Fig 1.1** — `/studio?mode=global`. **46 docs across 4 buckets** · Intelligence 13 · Context 21 · Research 6 · Targeting 3. Each row clicks open to a full doc editor.* **The PMM-critical docs across buckets** - **Brand Brief** — Mission · Vision · Brand Essence - **Brand Voice** — tone · style · what to avoid - **Style Guide** — visual + verbal identity - **Writing Style Guide** — copy rules - **Personas** — buyer archetypes - **ICPs** — ideal customer profiles - **Messaging House** — positioning framework - **Value Props** — feature → value translation - **Offer Brief** — packaging + pricing positioning - **Pricing Matrix** — package comparison - **Proof Catalog** — case studies + testimonials - **Competitive Teardown** — your wedge vs competitors - **Voice of Customer** — what customers actually say - **Buyer Psychology** — what makes them choose > **Tip: Try this now** > **PMM move.** Quarterly: open all 14 PMM-critical docs in tabs, read top-to-bottom. Anything wrong = fix at source. Every email, LP, ad, agent script downstream pulls from these. One bad Brand Voice doc = a quarter of off-brand outbound. ### 02. Campaign Studio — 17 artifacts per campaign for launches *Each campaign auto-generates: Campaign Brief · Messaging & Objections · Targeting & Routing · Email Prompt · Emails · Call Script · Voicemail · LinkedIn Copy · Social Prompt · Sequence · Cadence Calendar · Performance Tracker · QA Rubric · Reply Templates · Snippets. PMM owns the messaging stack within these.* **Open it at** - `app.graph8.com/studio?mode=campaigns` — campaign list (note plural "campaigns") > **Tip: Try this now** > **PMM move.** For each product launch: create a campaign in Campaign Studio with the launch wedge. Let graph8 auto-generate the 17 artifacts. Then PMM polish: *Campaign Brief*, *Messaging & Objections*, *Reply Templates*. Hand off polished campaign to Sales for activation. Replaces the launch-tier doc + sales-enablement deck + email-copy spreadsheet. ### 03. Brand Kit — homepage clone, design system, sections *graph8 extracts your website into a Brand Kit: homepage clone, design system (colors / typography / components), reusable sections. PMM uses this to keep on-brand copy + visuals consistent across LPs + ads + sales decks.* **Open it at** - `app.graph8.com/studio?mode=content` — Brand Kit with your data System / Sections sub-tabs > **Tip: Try this now** > **PMM move.** When the homepage hero changes (you launch a new positioning), update graph8's Brand Kit clone within 24h. Re-extract from *Overview* tab. The new hero copy auto-flows into LP templates + agent scripts. ### 04. Landing Pages + content surfaces for sales enablement *41 published LPs + 19 drafts. Templates: Bold Hero · Benefits Grid · Social Proof First · ROI Calculator. Plus Asset library (362 images), Content pages, LinkedIn + Newsletter surfaces (empty — easy fills).* **Open these** - `/studio?mode=content&ct=landing_page` — 41 published LPs + 19 drafts - Brand Kit dropdown: Assets (362) · Website (32) · Landing Pages · LinkedIn · Newsletter - `/forms` — 19 forms (lead capture endpoints) Templates available for one-click LP creation: **Bold Hero + Single CTA · Benefits Grid · Social Proof First.** Named LP drafts already in flight: *ROI calculator · Comparison LP · Free Trial Signup · Pricing-Led · Product Launch · Video Explainer.* > **Tip: Try this now** > **PMM move.** For your next product launch: clone the "Product Launch" LP draft. Update hero copy from the launch's Messaging & Objections doc. Add 3 customer testimonials from Proof Catalog. Publish. ~30 min vs the 3-hour Webflow build. ### 05. Copilot for PMM — competitive briefs, messaging audits, launch checklists *Natural-language PMM queries grounded in your 46 brand DNA docs + 17 artifacts per campaign + Brand Kit. Ask anything from "compare our positioning" to "draft 5 launch headlines."* **Open it at** - Cmd+Shift+K · or sparkle icon top-right ![Studio Global with copilot right panel open showing 'How can I help you today?' prompt with Create campaign / Create contact list quick actions and Message g8 chat input at bottom](../../../../assets/roles/pmm/10-copilot-pmm.webp) ***Fig 5.1** — Copilot panel. Quick actions *"Create campaign"* + *"Create contact list"*. The panel reads from all 46 brand DNA docs, so answers stay on-brand by default.* > **Tip: Try this now** > **PMM move.** Try these queries: *"Compare our messaging against Outreach + Apollo + ZoomInfo"* · *"Draft 5 launch headlines for a campaign about replacing the whole sales stack"* · *"What's the most-mentioned objection in our Voice of Customer doc?"* · *"Generate an enablement one-pager for AEs to use on cold calls about our new pricing"*. ## URL cheat-sheet | To do this | Go here | | --- | --- | | 46 brand DNA docs | `/studio?mode=global` | | Campaign Studio (17 artifacts per campaign) | `/studio?mode=campaigns` | | Brand Kit (homepage clone + design system) | `/studio?mode=content` | | Landing Pages (41 published · 19 drafts) | `/studio?mode=content&ct=landing_page` | | Asset library (362 images) | Brand Kit dropdown → Assets | | Forms with lead-capture submissions | `/forms` | | Intent keywords (for positioning research) | `/keywords` | | Conversion analytics | `/analytics/conversions` | | Behavior (top pages by views) | `/analytics/behavior` | | Open Copilot | Cmd+Shift+K | ## Heads-up — known PMM gaps > **Caution:** > **1. `/studio?mode=campaign` (singular) redirects to global.** Use plural: `?mode=campaigns`. > > **2. Doc detail views are not URL-deep-linkable.** Brand Brief / Personas / Messaging House etc require clicking the row in the workbench. Can't bookmark a specific doc. *Workaround:* bookmark `/studio?mode=global` and click in. > > **3. Two docs are still pending generation.** Counter shows 43/45 or 43/46 depending on day. Find pending in workbench + trigger generation. > > **4. LinkedIn + Newsletter content surfaces are empty.** Brand Kit shows LinkedIn (0) and Newsletter (0). Easy quick-win for PMM to populate. > > **5. Voice of Customer is one of the most valuable docs nobody opens.** Pulls real customer language. Read it before every messaging revision — beats internal brainstorms 10×. --- <small>Generated 2026-05-20 against `app.graph8.com` (org `your org`). Surface state verified live</small> --- # graph8 for Recruiter > graph8 field guide for Recruiter. Live URLs, real surfaces, screenshots from a public-data showcase tenant. Source: https://docs.graph8.com/roles/recruiter/ *A field guide for Recruiter. Live URLs, real surfaces. Screenshots captured from a showcase tenant with public-company data only.* ## The five things to use today ### 01. Hiring Signals + Job Changes — talent market intel *Two signals every recruiter wants: companies posting new sales roles (Hiring Signals — talent-market temperature) + your champions/prospects moving to new companies (Job Changes — candidate signal + outbound prospect signal).* **Open these** - `app.graph8.com/signals/jobs` — Hiring Signals (saved campaigns view) - `/signals/job-changes` — Job Changes feed (with date range + employer filter) ![Hiring Signals Saved Campaigns page with description 'Each saved campaign is a hiring-signals query the daily resolver replays against fresh job postings' showing empty state with + Add Query CTA](../../../../assets/roles/recruiter/1-hiring-signals.webp) ***Fig 1.1** — `/signals/jobs`. *"Each saved campaign is a hiring-signals query the daily resolver replays against fresh job postings."* Empty for graph8 today. Set up your first query.* ![Job Changes surface with date range filter (From/To) and 'Employer contains' search, empty state for graph8 org, columns Contact/New title/Previous/New employer/Days in prev/Detected](../../../../assets/roles/recruiter/2-contacts-talent.webp) ***Fig 1.2** — `/signals/job-changes`. Tracks *Contact · New title · Previous · New employer · Days in prev · Detected*. **/job-changes (no prefix) 404s** — use the `/signals` path.* > **Tip: Try this now** > **Recruiter move.** Set up 3 Hiring Signals queries in `/signals/jobs`: (1) "companies hiring VP Sales SaaS last 30 days," (2) "companies hiring SDR Manager," (3) "your top customer competitors hiring anyone." Then set the Job Changes feed filter to your candidate pool's company list. Daily resolver does the rest. ### 02. Contact + Company directory — talent sourcing at scale *231K contacts with Job Title + Company + Intent Keywords + Visits columns. 68K companies with employee count + industry + founded date. Filter, save to list, sequence.* **Open these** - `/contacts` — 231,124 contacts (page 1 of 2,312) - `/companies` — 68,643 companies (page 1 of 687) ![/companies directory showing 68643 companies page 1 of 687 with columns Company, Website, Owner, Phone, Social Media, Founded, Description, Industry, Employees (1001-5000, 1-10, 10000+)](../../../../assets/roles/recruiter/4-job-changes.webp) ***Fig 2.2** — `/companies`. **68,643 companies.** Columns include *Employees · Industry · Founded*. Filter by employee range (e.g. 50-200) for stage-appropriate candidate companies.* > **Tip: Try this now** > **Recruiter move.** Build candidate-source lists in `/contacts`: (1) "VP Sales at Series B SaaS, 50-200 employees" — companies likely to lose a VP to your client's offer. (2) "SDRs at companies with 18+ month tenure" — ready to level up. Save each as a Dynamic List in `/lists` so it refreshes daily. ### 03. Lists + Sequencer — talent outreach campaigns *271 saved lists across 14 pages. 30 team sequences ready to clone. Run candidate outreach the same way SDRs run sales outreach — at scale, with attribution.* **Open these** - `/lists` — 271 lists (Contacts + Companies types) - `/sequencer` — 30 team sequences to clone for candidate outreach ![/sequencer Team Sequences tab showing 30 sequences including VPHead-Sales_SaaS-Sequence (8351 contacts Live), DecisionMakers_Startups (982 Completed), Sales_Leaders_KIBM_SU (1413 Live), RevOps_SaaS-Sequence (3099 Live)](../../../../assets/roles/recruiter/6-sequencer-recruit.webp) ***Fig 3.2** — `/sequencer` Team tab. 30 sequences. *VPHead-Sales_SaaS* hitting many contacts — clone-able as a candidate outreach template (just swap the wedge from "sales tool buyer" to "career opportunity").* > **Tip: Try this now** > **Recruiter move.** Clone a sequence with the highest reply rate (per the SDR doc that's *SalesLeaders_AI&ML* at 0.41%). Swap the email body from sales pitch to candidate pitch ("noticed you've been at [Company] for 18 months — would you be open to a 15-min chat about a VP Sales role with [Client]?"). Launch. ### 04. SDR Hiring Marketplace — outsource the hiring itself *graph8 ships a built-in marketplace of 20 vetted SDR/BDR providers across 5 countries with public pricing. Browse, schedule meetings, hire, manage contracts and statements — all inside graph8.* **Open it at** - `app.graph8.com/marketplace` — SDR Hiring Marketplace ![/marketplace SDR Hiring Marketplace with sub-tabs Browse/My Talent/Meetings/Messages/Contracts/Statements, 20 providers across countries (Nigeria, North Macedonia, US, Kenya, Australia), pricing $1500-$4500/mo base + $50-$600/meeting, Strategic/Data-Driven badges, View Profile/Hire CTAs](../../../../assets/roles/recruiter/7-marketplace.webp) ***Fig 4.1** — `/marketplace`. **20 providers · 5 countries** (Nigeria · North Macedonia · US · Kenya · Australia). **Pricing: $1,500-$4,500/mo base + $50-$600/meeting.** Sub-tabs: Browse · My Talent · Meetings · Messages · Contracts · Statements.* > **Tip: Try this now** > **Recruiter move.** For your client (or for graph8 itself): when you have an open SDR role and budget pressure, skip the FTE hire — pick 2-3 marketplace providers (e.g. one US-based at $4K/mo + one Kenya-based at $1.5K/mo for cost-blended capacity), set up paid trials, hire whichever delivers most meetings/month. ### 05. Outreach replies + bulk enrichment + Copilot *Candidate replies hit the same Inbox as sales replies (sentiment-tagged + sequence-attributed). Enrichment Workbench bulk-appends phone/email/firmographics to talent lists. Copilot helps narrow searches with natural language.* **Open these** - `/inbox/email` — Inbox with sentiment chips - `/enrichment/staging` — 34 workbenches for bulk enrichment - Copilot — Cmd+Shift+K ![/enrichment/staging Workbench list with 34 workbenches including 'GTM Consultant Whitelabel Motion - Personalization' (229 rows × 313 cols), 'Signals - Dialer - Found Contacts' (549×12), 'Webhooked dialer companies' (2489×7), + New Workbench CTA](../../../../assets/roles/recruiter/9-enrichment.webp) ***Fig 5.2** — `/enrichment/staging`. **34 workbenches.** Bulk-enrich a talent list with phones + verified emails + firmographics before sequencing — way higher contact rate than cold scrape.* ![/contacts with Copilot sidebar open (Cmd+Shift+K) showing 'Copilot ▸ Contacts' greeting 'How can I help you today?' with suggested actions Create a campaign / Create a contact list](../../../../assets/roles/recruiter/10-copilot-recruiter.webp) ***Fig 5.3** — Copilot in context of `/contacts`. Suggested actions: *"Create a campaign"* · *"Create a contact list."* Type your candidate-search query into Message box.* > **Tip: Try this now** > **Recruiter move.** Try these recruiter-flavored copilot queries: *"Find me VPs of Sales at SaaS companies 100-500 employees in California"* · *"Which contacts in my directory have changed jobs in the last 90 days?"* · *"Find SDR Managers who joined a new company in the last 6 months"*. ## URL cheat-sheet | To do this | Go here | | --- | --- | | Hiring Signals (company hiring intent) | `/signals/jobs` | | Job Changes (candidate moves) | `/signals/job-changes` | | 231K contact directory | `/contacts` | | 68K company directory | `/companies` | | Saved lists (271 today) | `/lists` | | Team sequences (clone for candidate outreach) | `/sequencer` → Team tab | | SDR Hiring Marketplace (20 providers) | `/marketplace` | | Inbox with sentiment chips | `/inbox/email` | | Bulk enrichment workbenches | `/enrichment/staging` | | Copilot for talent queries | Cmd+Shift+K | ## Heads-up — recruiter gaps + workarounds > **Caution:** > **1. Hiring Signals + Job Changes both empty for graph8 today.** The surfaces ship — daily resolver runs once queries are saved. Set up your first 3-5 queries today. > > **2. `/job-changes` without the /signals prefix 404s.** Use `/signals/job-changes`. > > **3. `/copilot` is also a 404.** Copilot is the global sidebar — Cmd+Shift+K from any page. > > **4. Inbox isn't filtered by "candidate vs sales" reply.** All replies land in the same inbox. Workaround: tag your candidate sequences with a distinct name prefix (e.g. *"RECRUITING_VP_SALES"*) so sequence-attribution chips make filtering trivial. > > **5. No dedicated ATS-style stage tracking.** The Deals pipeline could in theory be repurposed (Discovery = Screen Call, Solution Fit = Final Round, Closed Won = Hired). But that conflates your sales pipeline. Better: keep an external lightweight ATS (Notion/Airtable) and use graph8 only for the sourcing + outreach layer. --- <small>Generated 2026-05-20 against `app.graph8.com` (org `your org`). Surface state verified live</small> --- # graph8 for RevOps > graph8 field guide for RevOps. Live URLs, real surfaces, screenshots from a public-data showcase tenant. Source: https://docs.graph8.com/roles/revops/ *A field guide for Revenue Operations. Live URLs, real surfaces. Screenshots captured from a showcase tenant with public-company data only.* ## The five things to use today ### 01. Pipeline + stage configuration — the source of truth for every deal *10 configurable stages with probabilities, types, stage checklists, and revenue rules. Plus 8 sibling sub-tabs covering Buying Committees, Lifecycle, Stage Docs, Lead Qualification, Products, Quotes, Form Settings, Leaderboard.* **Open it at** - `app.graph8.com/studio/settings` → *Revenue* → *Pipelines* - Sibling sub-tabs under Revenue: *Buying Committees · Lifecycle · Stage Docs · Lead Qualification · Products · Quotes · Form Settings · Leaderboard* ![Pipelines configuration page showing 10 stages with your data Checklist, Revenue, Actions columns](../../../../assets/roles/revops/1-pipeline-rules.webp) ***Fig 1.1** — *Sales Pipeline (Default)* with all 10 stages + probabilities. Edit per-stage checklists, lifecycle hooks, and revenue rules from here.* **The 10 default stages + probabilities (live)** New Meeting10% Discovery Held20% Solution Fit40% Proposal Sent60% Verbal Commit80% Closed Won100% Closed Lost0% Long Term Nurture— Weak Responsiveness— Engaged— > **Tip: Try this now** > **Try this now.** Audit stage probabilities monthly vs actual conversion rates. If 40% of Solution-Fit deals close, your probability is right; if only 20% do, you're inflating the kanban $weighted forecast — adjust the probability column down. That's the simplest forecast-accuracy lever in the system. ### 02. CRM-to-billing reconciliation — /admin Customers + Services + Customers list *Three views of the same revenue truth. `/admin` has the raw Stripe Customer + Subscription IDs (715 records). `/deals/l/customers` is the operational CRM view. `/admin` Services is the 37-item product catalog with credit pricing.* **Open it at** - `app.graph8.com/admin` → *Customers* tab — raw Stripe IDs, 715 records - `/admin` → *Services* tab — 37-item product catalog with credit pricing - `/deals/l/customers` — operational CRM view with your data ![Admin Customers tab showing 715 records with Stripe Customer ID, Stripe Subscription ID, Joined, Status columns](../../../../assets/roles/revops/2-customers-list-pivot.webp) ***Fig 2.1** — `/admin` → Customers. **715 records.** Columns: Customer (email) · Credits · Usage · **Stripe Customer (cus_xxx)** · **Stripe Subscription (sub_xxx)** · Joined · Status (Trialing / Active) · Product. *This is the only UI surface showing the raw Stripe joins.** ![Admin Services tab showing 37 services with Usage, Credits per unit, Description](../../../../assets/roles/revops/3-fields-mapping-integrations-list.webp) ***Fig 2.2** — `/admin` → Services. 37 services priced in credits. Examples: *Inbox Placement Test* 20 credits per test · *Voice AI* 20 credits per minute · *Meetings* 20 credits per booked meeting · *Web Visitor* 20 per resolution. This is the product-pricing source of truth.* > **Tip: Try this now** > **Try this now.** Monthly reconciliation pass: export both views (CRM customer list + /admin raw Stripe). Join on email. Any row in /admin without a matching CRM row = orphaned subscription (Stripe billing for a customer not in the CRM). Any CRM row without a Stripe ID = unbilled customer (revenue leak). ### 03. Integrations — CRM, billing, and the field mapping schema *HubSpot, Salesforce, Zoho CRM, SugarCRM, Stripe. Each connection has Push/Pull/Fields/Appointments sub-tabs with per-record-type field mapping (Graph8 Field ↔ CRM Field with Auto Fill / Overwrite controls).* **Open it at** - `app.graph8.com/studio/settings?tab=integrations` ![Integrations page with HubSpot connected card, Salesforce/Zoho/SugarCRM connect cards, Stripe connected card](../../../../assets/roles/revops/7-attribution-conversions.webp) ***Fig 3.1** — Integrations parent surface. **HubSpot Connected** (shaharyar@graph8.com, May 18 2026) · Salesforce / Zoho CRM / SugarCRM available · **Stripe Connected**. Click the gear icon on a connected integration to drill into Push / Pull / **Fields** / Appointments sub-tabs.* The **Fields** sub-tab on HubSpot is the RevOps schema surface — per-object (Contact / Lead / Company / Deal) mapping of every graph8 field ↔ HubSpot property, with per-row Auto Fill (graph8 fills if HubSpot is blank) and Overwrite (graph8 always wins) controls. *This is also the closest thing to a custom-field surface today — there's no standalone `/fields` route.* > **Tip: Try this now** > **Try this now.** Open HubSpot integration → Fields tab → Contact object. Decide ownership per field: graph8 owns enrichment-style fields (intent_keywords, sentiment, last_visit), HubSpot owns CRM-state fields (lifecycle_stage, deal_stage, owner). Toggle Auto Fill on for graph8-owned, Overwrite off for HubSpot-owned. One pass keeps both systems consistent forever. ### 04. Performance Reports + Attribution / Conversions *13 sub-reports for daily ops monitoring, plus 9 analytics tabs spanning Overview / Acquisition / Behavior / Conversions / Attribution / Performance Reports / Marketing Intelligence / Realtime / Live Visitors.* **Open it at** - `app.graph8.com/reports` — Performance Reports (Activity Trend default) - `/analytics/attribution` — sender-performance attribution (empty for graph8 today) - `/analytics/conversions` — Goal Completions + Form Performance (populated) ![Performance Reports landing with hero KPIs and left sidebar of 13 sub-reports](../../../../assets/roles/revops/7-attribution.webp) ***Fig 4.1** — `/reports` hero KPIs (39,815 emails / 9,639 dials / 232 replies / 0.6% reply rate / 71 meetings, last 7 days) + Activity-over-time line chart. Left sidebar enumerates all 13 sub-reports.* **The 13 sub-reports** - **Activity Trend** (default) - Reply Health - LinkedIn Health - Bounce Health - Mailbox Health Board - Connect Rate - SDR Leaderboard - SDR Grade Trends - SDR Daily Heatmap - Disposition Mix - Talk Time Quality - Meeting Pipeline - Inbound Activity ![Conversion analytics showing 146 conversions, 146 form submissions, 40 meetings booked](../../../../assets/roles/revops/8-admin-customers.webp) ***Fig 4.2** — `/analytics/conversions`. 146 conversions · 0.1% rate · 106 form submissions · 40 meetings booked. Form Performance breakdown below the fold.* > **Tip: Try this now** > **Try this now.** Build the weekly RevOps digest: `/reports` Activity Trend + Reply Health + Bounce Health screenshots, plus `/analytics/conversions` goal totals. Post to your team channel every Monday. Add anomaly callouts (reply-rate drop, bounce-rate spike) when they breach a threshold. ### 05. Systems config — Domains, Scraping, Custom JS *The three configuration surfaces RevOps owns end-to-end: auth domain allow-list, content scraping rules, and approved custom-code injection on customer sites.* **Open it at** - `app.graph8.com/studio/settings` → *Domains* - `/studio/settings?tab=scraping` — content crawl config - `/admin?tab=custom-js` — custom JS approval queue ![Custom JS admin tab with your data tabs showing 5 pending injections](../../../../assets/roles/revops/12-api-keys-parent.webp) ***Fig 5.3** — `/admin?tab=custom-js`. Approval queue for customer-site JS injections. 5 pending requests visible from `org_3hN6lL7aHJNYV8al`. **Pending / Approved / Rejected / Revoked** tabs — audit trail for every injection.* > **Tip: Try this now** > **Try this now.** Weekly hygiene: clear the `/admin?tab=custom-js` Pending queue. Run a fresh scrape from `?tab=scraping` if the last crawl > 30 days. Verify the auth domain allow-list still matches your org's email policy. ## URL cheat-sheet | To do this | Go here | | --- | --- | | Pipeline stages + probabilities | `/studio/settings` → Revenue → Pipelines | | Buying Committees / Lifecycle / Stage Docs | Revenue sub-tabs | | Lead Qualification + Products + Quotes config | Revenue sub-tabs | | Raw Stripe IDs + 715 records | `/admin` → Customers | | 37-service product catalog (credit pricing) | `/admin` → Services | | Custom JS approval queue | `/admin?tab=custom-js` | | LLM cost tiers (G1-G4) | `/admin` → LLM Charges | | Domain pricing (per-org charges) | `/admin` → Domain Pricing | | CRM + billing integrations | `/studio/settings?tab=integrations` | | Field mapping (per-integration) | Integration gear → Fields tab | | Performance Reports (13 sub-reports) | `/reports` | | Conversion analytics | `/analytics/conversions` | | Attribution (sender / acquisition) | `/analytics/attribution` | | Auth domain allow-list + default role | `/studio/settings` → Domains | | Content scraping rules | `/studio/settings?tab=scraping` | | API keys + Webhooks + MCP/CLI config | `/studio/settings` → API, MCP & CLI | ## The 18 Studio Settings sub-tabs ## Heads-up — known RevOps gaps + workarounds > **Caution:** > **1. No duplicate / merge surface yet.** `/duplicates` and `/merge` both 404. **Workaround:** dedupe in the CRM side of the integration (HubSpot has merge tools); push the canonical record back via the Fields sync. > > **2. Most `/studio/settings?tab=` slugs don't work.** *Works:* `revenue · integrations · domains · scraping`. *Silently falls back to last tab:* `api · api-keys · mailboxes · roles · compliance · billing · tasks · docs · enrichment · phone-numbers · sso · purchase-domain`. **Workaround:** bookmark `/studio/settings` as the entry point and click sidebar. > > **3. `/reports` sub-reports require sidebar click.** No URL routing for SDR Leaderboard, Talk Time Quality, Mailbox Health Board, etc. **Workaround:** same as above — bookmark `/reports` entry, click in. > > **4. Attribution analytics is empty for graph8.** `/analytics/attribution` shows "No sender data yet" because no `cb_outbound_sends` rows exist on graph8's own tenant. Use `/analytics/conversions` for goal-based attribution instead. > > **5. No standalone /fields route.** Custom-field configuration lives inside each CRM integration's *Fields* tab. Per-org schema is the CRM you're synced to. > > **6. No `/copilot` or `/chat` route.** Copilot is a floating panel via the sparkle icon top-right, not a URL. --- <small>Generated 2026-05-20 against `app.graph8.com` (org `your org`). Surface state verified live before publication</small> --- # graph8 for Sales Engineer > graph8 field guide for Sales Engineer. Live URLs, real surfaces, screenshots from a public-data showcase tenant. Source: https://docs.graph8.com/roles/sales-engineer/ *A field guide for Sales Engineer. Live URLs, real surfaces. Screenshots captured from a showcase tenant with public-company data only.* ## The five demo arcs ### 01. The AE-buyer demo — Deal Intelligence sidebar *"Open a deal. Look at this sidebar. graph8 auto-generated Deal Risks, Next Steps, Close Plan, ROI Analysis, Competitive Analysis — without the rep typing a word." Drop the mic.* **Show this** - `app.graph8.com/deals/pipeline` → click any deal - Left rail: **DEAL INTELLIGENCE** (Strategy + Analysis stacks, 5/5 generated) - Top: Deal Progress + Stage Readiness gauge + *"Suggest next step"* button ![a sample deal deal with DEAL INTELLIGENCE sidebar showing Strategy and Analysis stacks all generated 5/5](../../../../assets/roles/sales-engineer/1-deal-intelligence.webp) ***Fig 1.1** — a sample deal. Sidebar: Strategy (Deal Risks · Next Steps · Close Plan) + Analysis (ROI · Competitive) — all auto-generated. Stage Readiness 100%. *"Suggest next step"* button. This is your AE-objection demolisher: "How is this different from SFDC?" — answer this slide.* > **Tip: Try this now** > **Demo move.** Pre-load a real a sample deal-quality deal. Have the prospect read the Close Plan out loud. Then ask: "How long would it take your AE to write that?" Land the time-saved math. ### 02. The "does this work with our CRM" demo — Integrations + Fields *5 connectors live (HubSpot · Salesforce · Zoho · SugarCRM · Stripe). Each has Push/Pull/Fields/Appointments sub-tabs with per-record-type field mapping. Auto Fill vs Overwrite per row.* **Show this** - `/studio/settings?tab=integrations` — connector grid - Click HubSpot (or whichever the buyer uses) → drill into *Fields* tab ![Integrations tab showing HubSpot (Connected), Salesforce, Zoho CRM, SugarCRM, Stripe](../../../../assets/roles/sales-engineer/2-integrations.webp) ***Fig 2.1** — Integrations grid. **HubSpot Connected** · Salesforce · Zoho · SugarCRM · **Stripe Connected**. Click gear → Push / Pull / **Fields** / Appointments sub-tabs.* > **Tip: Try this now** > **Demo move.** If the buyer is a HubSpot shop: open HubSpot Fields. Show how *intent_keywords* can be Auto-Fill (graph8 fills HubSpot) while *lifecycle_stage* can be Overwrite-off (HubSpot wins). Defuses "but we already use HubSpot" — answer is "graph8 makes HubSpot smarter, not redundant." ### 03. The technical-buyer demo — Connections, Functions, Snippet *Jitsu-style event pipeline. Streams (sources) → Functions (JS transforms) → Destinations (warehouses, CRMs, ad pixels). Plus the web snippet drops in one line.* **Show this** - `/connections/list` — source → destination routing table - `/connections/functions` — JavaScript event-transform code editor - `/connections/streams?tab=setup` — web tracking snippet install ![Connections list showing source-to-destination routing with Form Sync Trigger, Campaign Visitor Trigger, Warehouse destinations](../../../../assets/roles/sales-engineer/3-connections-list.webp) ***Fig 3.1** — `/connections/list`. Each row is a source → (optional Function transform) → destination edge. Shipped destinations include Form Sync Trigger, Campaign Visitor Trigger, Warehouse, OpenReplay, Meta CAPI, LinkedIn Tag.* ![Functions code editor showing async JavaScript event-transform function with Test Env Vars and Sample Event panels](../../../../assets/roles/sales-engineer/4-functions.webp) ***Fig 3.2** — `/connections/functions`. Real JS code editor: `export default async function(event, {log})` with Test Env Vars + Sample Event payload + Run button. SEs love this — "you can write your own enrichment without filing a ticket."* ![Stream setup showing HTML script tag with data-write-key pointing at events.flow.graph8.com](../../../../assets/roles/sales-engineer/5-mcp-config.webp) ***Fig 3.3** — `/connections/streams?tab=setup`. One `<script>` tag: `<script async src="https://events.flow.graph8.com/p.js" data-write-key="...">`. Drop in `<head>`, get pageviews + form submits + custom events flowing in seconds.* > **Tip: Try this now** > **Demo move.** Open the Functions code editor in front of a developer-buyer. Live-edit a transform to enrich the event payload (e.g. parse UTMs, add a computed field). Click Run. Show the output. This is the moment dev-buyers stop comparing to ZoomInfo and start comparing to Segment. ### 04. The dev-tools demo — API keys + MCP + CLI *80+ tools exposed over MCP. Two modes: Remote (hosted, OAuth) for Claude Desktop / Cursor / Windsurf, or Local (stdio, API key) for dev. Plus regular REST API keys + Webhooks.* **Show this** - `/studio/settings` → *API, MCP & CLI* sub-tab - Remote MCP: `https://be.graph8.com/mcp/` (Cursor / Claude Code / Windsurf compatible) - Local mode: `g8-mcp-server` stdio + `G8_API_KEY` + `G8_MCP_MODE=dev` ![MCP Server and CLI configuration showing Remote JSON config for Cursor/Claude Code/Windsurf at https://be.graph8.com/mcp/ and Developer Mode Local config with g8-mcp-server, G8_API_KEY, G8_MCP_MODE=dev](../../../../assets/roles/sales-engineer/6-api-keys.webp) ***Fig 4.1** — MCP & CLI config. **Remote mode (hosted):** add the `url` to your client's MCP config (`.cursor/mcp.json`, `~/.claude/mcp.json`) — or, in Claude Desktop / Claude.ai, add it as a custom connector — then OAuth in and get all 80+ tools. **Local mode (dev):** stdio binary with API key for laptop-side automation.* ![API Keys table showing keys for Eric Goldstein, Alex Reyes with Key ID and Expires columns, plus Webhooks panel](../../../../assets/roles/sales-engineer/7-snippet.webp) ***Fig 4.2** — API keys for REST access + Webhooks for inbound events. Per-user keys with revocation + expiry.* **Tool discovery (heads-up for dev-buyers using MCP)** // Only ~30 tools in the default tools/list — rest are loaded on demand g8_tool_search(query="workflow") // workflow / automation g8_tool_search(query="dialer") // voice / phone / dialer g8_tool_search(query="intent") // buyer intent / signals / keywords g8_tool_search(query="skill") // LLM skill authoring g8_tool_search(query="field") // custom fields / properties g8_tool_search(query="form") // forms / responses g8_tool_search(query="snippet") // web tracking g8_tool_search(query="playbook") // multi-step playbooks > **Tip: Try this now** > **Demo move.** If the dev buyer has Claude Desktop or Cursor open: paste the Remote MCP config block in front of them. Restart their client. Ask the in-Claude copilot a graph8 question ("find me VPs of Sales at SaaS, 100-500 employees"). Watch the tool calls fire. This is the show-don't-tell for "your team is going to love this." ### 05. The marketing/CRO demo — Ads wizard, Copilot, channel intel *When the buyer is marketing-focused, switch arcs. Show the 6-step ad wizard (Meta · LinkedIn · Google · X), the copilot answering rich questions about the product, and the channel-attribution dashboard.* **Show this** - `/ads` → Create Campaign wizard - Copilot sparkle top-right — ask broad capability questions - `/analytics/acquisition` — channel + source/medium breakdown ![Copilot response to Show me everything you can do for an account executive listing Research & Prospecting and Activity Tracking capabilities](../../../../assets/roles/sales-engineer/9-copilot-demo.webp) ***Fig 5.2** — Copilot: *"Show me everything you can do for an account executive."* Returns categorized capability map (Pipeline & Deals · Account Intelligence · Research & Prospecting · Activity Tracking). Great for buyers who don't know what to ask.* ![Acquisition channels showing 81.1K users, Direct, Organic Search, Referral, AI/LLM breakdown](../../../../assets/roles/sales-engineer/10-acquisition.webp) ***Fig 5.3** — `/analytics/acquisition`. **81.1K users · 84.3K sessions · your engagement rate.** CRO-buyer demo: shows the full marketing site analytics, channel mix, and bounce rates — graph8 sees attribution end-to-end.* > **Tip: Try this now** > **Demo move.** If the buyer is marketing/CRO: skip the deal demo. Open Copilot, ask *"What's our marketing-attributed pipeline value?"* or *"Which channel converted to revenue best this quarter?"* Then jump to `/analytics/marketing` and walk the Outbound Bridge Funnel. End at `/ads` showing them the multi-platform campaign launch. ## SE cheat-sheet — demo URLs by buyer persona | Buyer persona | Start the demo here | | --- | --- | | AE / Sales Manager | `/deals/pipeline` → open a deal | | SDR / SDR Manager | `/sequencer` + `/reports` | | CSM | `/deals/l/customers` + customer detail Health card | | RevOps / SalesOps | `/studio/settings` Revenue → Pipelines + `/admin` | | GTM Engineer / Developer | `/connections/functions` + `/studio/settings` API/MCP/CLI | | Marketing / Demand Gen | `/analytics/marketing` + `/studio?mode=campaign` | | CRO / Founder | `/analytics` overview + `/deals/l/customers` + Copilot | | Finance | `/admin` Customers (715 records, Stripe IDs) + LLM Charges | | Content Marketer | `/studio?mode=global` (45 docs) + Landing Pages | | Campaign Manager | `/studio?mode=campaign` → open campaign (17 docs) | ## Heads-up — known SE-demo gaps > **Caution:** > **1. Copilot responses can be longer than viewport.** When asking broad "show me everything" questions, scroll the copilot panel up to show the full categorized response. > > **2. Some SPA tabs don't deep-link via URL.** `?tab=setup` works for streams; many sub-tabs in settings + analytics don't. Always have a fallback: navigate to the parent + click in the UI. > > **3. Ad platforms ship unconnected.** The wizard demos great but live launch requires OAuth per platform. Don't promise the buyer they can launch in 60 seconds without the connection. > > **4. Some surfaces empty for graph8 tenant.** `/analytics/attribution` (no sender data) and `/keywords` (0 configured) are empty examples. Demo against a customer tenant where they have data, or set up sample data in the demo org first. > > **5. The "Claude started debugging" banner.** Some screenshots include the dev-tools banner — irrelevant for production but worth flagging if you grab screenshots from your demo for follow-up emails. --- <small>Generated 2026-05-20 against `app.graph8.com` (org `your org`). Surface state verified live before publication</small> --- # graph8 for Sales Manager > graph8 field guide for Sales Manager. Live URLs, real surfaces, screenshots from a public-data showcase tenant. Source: https://docs.graph8.com/roles/sales-manager/ *A field guide for Sales Manager. Live URLs, real surfaces. Screenshots captured from a showcase tenant with public-company data only.* ## The five things to use today ### 01. Pipeline at a glance — Kanban + List with $weighted per stage *Two views of the same pipeline: kanban for the morning stand-up, list with Owner column for the 1:1. Each stage column on the kanban shows **$total and $weighted** — that's your forecast surrogate today.* **Open it at** - `app.graph8.com/deals/pipeline` — list view by default, kanban via the view toggle - 6 stage columns on the kanban: *New Meeting · Discovery Held · Solution Fit · Proposal Sent · Verbal Commit · Closed Won* ![Pipeline kanban with 6 stage columns each showing $total and $weighted, deal cards with owner avatars](../../../../assets/roles/sales-manager/1-pipeline-team.webp) ***Fig 1.1** — Kanban view. Each column header carries *$total / $weighted*. Drag-and-drop to advance a stage. Cards show owner avatars. **$weighted per stage = the closest thing to a commit-forecast graph8 ships today** (see caveats for what's missing).* > **Tip: Try this now** > **Try this now.** Open `/deals/pipeline`, toggle to kanban, read the $weighted totals across all 6 columns. That's your weighted pipeline number for the period. Compare against last week's screenshot to spot stage-stalls (e.g. Proposal Sent didn't grow = no closes inbound). ### 02. Coach the deal, not the rep — Deal Intelligence + Stage Readiness *Every deal auto-generates Strategy (Deal Risks · Next Steps · Close Plan) and Analysis (ROI · Competitive). Read it before the 1:1, ask the rep about the risks the system flagged.* **Open it at** - Click any deal from `/deals/pipeline` → lands at `/deals/{deal-id}` - Left rail: DEAL INTELLIGENCE (Strategy + Analysis stacks, "5/5 generated" badge) - Top: Deal Progress bar + Stage Readiness gauge + **Suggest next step** button ![a sample deal deal with DEAL INTELLIGENCE sidebar showing Strategy and Analysis stacks all generated 5/5](../../../../assets/roles/sales-manager/3-deal-intelligence.webp) ***Fig 2.1** — DEAL INTELLIGENCE sidebar. Auto-generated: Deal Risks · Next Steps · Close Plan (Strategy) + ROI Analysis · Competitive Analysis (Analysis). Use these as your 1:1 talking points — don't ask the rep "how's the deal going," ask "what's your plan to mitigate the deal risk graph8 flagged?"* > **Tip: Try this now** > **Try this now.** Before each 1:1, open every deal that rep owns. Skim the DEAL INTELLIGENCE sidebar — pick the deal with the highest unaddressed Deal Risk. That's your 1:1 agenda item. Beats "walk me through your top 3." ### 03. The /reports center — 15+ sub-reports for QBR prep *graph8 ships a rich reports surface most people miss. Account Health Comparison, Cross-Account Drift, SDR Leaderboard, Talk Time Quality, Meeting Pipeline. This is where you build the board deck.* **Open it at** - `app.graph8.com/reports` — left sidebar with 7 report categories Categories + sub-reports visible today: - Activity & Top-line - Reply & Engagement - Deliverability & Health - **Calling & SDR Performance:** SDR Leaderboard - SDR Grade Trends - SDR Daily Heatmap - Disposition Mix - Talk Time Quality - Pipeline & Inbound - Sequences & Lists - **Cross-Account Drift:** Account Health Comparison - Idle / Stalled - Meeting Pipeline ![Reports center showing Account Health Comparison with sidebar categories and per-workspace outbound volume chart](../../../../assets/roles/sales-manager/5-stage-history.webp) ***Fig 3.1** — `/reports` Account Health Comparison. Header KPIs (your workspaces, your reply rate) + *Outbound volume per workspace* bar chart. Use this for the "are my customers actually using graph8?" QBR slide.* > **Tip: Try this now** > **Try this now.** Open `/reports`, click *Calling & SDR Performance* → *SDR Leaderboard*. That's your weekly SDR ranking. Then *Talk Time Quality* for call coaching. Then *Cross-Account Drift* → *Idle / Stalled* for renewal-risk + deal-rescue prep. ### 04. Copilot as your pipeline-hygiene buddy *Natural-language pipeline queries the copilot actually answers well. Stalled deals, missing close dates, pipeline by stage, deals without next steps — all one prompt away.* **Open it at** - Sparkle icon · top-right · any page → Copilot side panel > **Note: Live copilot exchange** > *Live exchange · 2026-05-20* > > > "Show me deals stalled more than 14 days in their current stage." > > | Deal | Stage | Amount | Close Date | > | --- | --- | --- | --- | > | stuff | Discovery Held | $5K | (past) | > | a sample deal | New Meeting | $1K | May 31 | > | a sample deal Deal | New Meeting | TBD | — | > | test | discovery | TBD | — | > > …"*test and a sample deal have no close dates set — these are pipeline hygiene issues. Want me to open the Deals view so you can inspect stage history directly?*" > ![Copilot side panel answering 'stalled deals more than 14 days' with structured table and pipeline hygiene analysis](../../../../assets/roles/sales-manager/6-forecast.webp) ***Fig 4.1** — The copilot doesn't just list deals — it flags hygiene issues (missing close dates, overdue stage moves) and offers to drill in.* > **Tip: Try this now** > **Try this now.** Try these manager queries: *"deals over 30 days in current stage"*, *"deals without a next-step task"*, *"my team's pipeline grouped by stage"*, *"which reps haven't logged a call this week?"*. Treat the copilot as the SDR-ops-analyst you don't have. ### 05. Don't forget renewals — the customers list with Owner *If your AEs own renewals (most do), the customers list is your bird's-eye on the post-sale book. your paying customers with your data status, Owner column.* **Open it at** - `app.graph8.com/deals/l/customers` — your paying customers (MRR + Owner) - `/deals/l/trials` — trial accounts (about to convert or churn) - `/deals/l/churned` — ex-customers (rescue / win-back candidates) ![Customers list with MRR, plan, subscription, Owner column visible](../../../../assets/roles/sales-manager/7-leaderboard.webp) ***Fig 5.1** — `/deals/l/customers`. your customers. Filter Owner = a specific rep for 1:1 prep on their book. Sort by MRR desc to find the highest-stakes renewals first.* > **Tip: Try this now** > **Try this now.** Filter by Subscription = past_due. Anyone there with no Owner assigned is unowned at-risk revenue — assign immediately. Cross-reference against `/reports` → *Idle / Stalled* for accounts that need rescue calls this week. ## URL cheat-sheet | To do this | Go here | | --- | --- | | Pipeline list with Owner column | `/deals/pipeline` | | Pipeline kanban with $weighted per stage | `/deals/pipeline` · view toggle | | Open a deal — read DEAL INTELLIGENCE | `/deals/{deal-id}` | | Reports center (15+ sub-reports) | `/reports` | | SDR Leaderboard | `/reports` → Calling & SDR Performance | | Account Health Comparison | `/reports` → Cross-Account Drift | | Idle / Stalled accounts | `/reports` → Cross-Account Drift | | Meeting Pipeline | `/reports` → Pipeline & Inbound | | Customers (renewal book) | `/deals/l/customers` | | Trial accounts | `/deals/l/trials` | | Churned accounts | `/deals/l/churned` | | Open Copilot anywhere | Sparkle · top-right | | Search the CRM | Cmd-K | ## Heads-up — known Sales Manager gaps + workarounds > **Caution:** > **1. No dedicated /deals/forecast view.** All variants 404 or treat *forecast* as a deal slug. **Workaround:** the kanban's per-stage *$total / $weighted* headers are your forecast surrogate today. Multi-category forecast (commit / best-case / most-likely) is not shipped. > > **2. No AE / deal-volume leaderboard.** `/leaderboard`, `/deals/leaderboard`, `/studio/leaderboard` all 404. The leaderboard that *does* exist is SDR-focused (under `/reports` → Calling & SDR Performance). **Workaround:** pivot `/deals/pipeline` by Owner column manually for AE ranking. > > **3. No team-wide My Desk.** `/deals/my-desk` is per-user only — you can't see "all my reps' decision cards" in one view. **Workaround:** have reps share screen during the 1:1. > > **4. No team activity feed.** Activity logs are per-deal, not cross-team. **Workaround:** `/reports` Calling & SDR Performance for team-level activity rollups. > > **5. No stage transition history audit.** When a deal regresses from Proposal back to Discovery, there's no built-in "stage history" timeline in the UI. **Workaround:** ask the copilot — it can infer from close-date drift and the data backing the deal. > > **6. Customer Owner column is empty for graph8's own org.** The column exists, but no owner is currently assigned on the 41 customer accounts. **Workaround:** assign owners as part of your weekly hygiene pass. > > **7. No 1:1 / coaching docs surface.** No dedicated place to store coaching notes per rep. **Workaround:** Notes tab on individual deals + DEAL INTELLIGENCE Strategy stack are your coaching memory. --- <small>Generated 2026-05-20 against `app.graph8.com` (org `your org`). Surface state verified live before publication</small> --- # graph8 for SDR > graph8 field guide for SDR. Live URLs, real surfaces, screenshots from a public-data showcase tenant. Source: https://docs.graph8.com/roles/sdr/ *A field guide for SDR. Live URLs, real surfaces. Screenshots captured from a showcase tenant with public-company data only.* ## The five things to use today ### 01. Find prospects — Contacts + Lists *2.3 million contacts in the directory, filterable. 264 lists already built. Start here every morning.* **Open it at** - `app.graph8.com/contacts` — full contact directory with filters - `app.graph8.com/lists` — saved lists / dynamic audiences The Contacts directory shows **2,310,898 prospects** with columns: *Name · Job Title · Company · Website · Owner · Source · Intent Keywords · Visits · Domain*. Filter / Add / Views toolbar up top. Save anything interesting to a list so you can sequence it. ![Contacts directory at /contacts showing 2.3M prospects with filter toolbar and column structure](../../../../assets/roles/sdr/1-contacts.webp) ***Fig 1.1** — `/contacts`. The *Intent Keywords* and *Visits* columns are SDR gold — they tell you which prospects are already showing buying signals before you even reach out.* ![Saved lists at /lists with 264 lists, types, statuses, dynamic-list flags, sources](../../../../assets/roles/sdr/2-lists.webp) ***Fig 1.2** — `/lists`. 264 saved lists with *Type · Status · Dynamic · Source · Total Records · Tags · Date Created*. Dynamic lists keep refreshing as new contacts match.* > **Tip: Try this now** > **Try this now.** Open `/contacts`, filter by *Intent Keywords*. Anyone matching keywords related to graph8's positioning (sales automation, outbound, CRM) is a higher-priority outreach. Save to a list, then sequence in step 2 below. ### 02. Run the outbound — Sequences + Dialer *Multi-step sequences with email + dial, real-time progress and reply tracking per step. Plus a click-to-call dialer with session history.* **Open it at** - `app.graph8.com/sequencer?tab=team` — Team Sequences (live shared campaigns) - `app.graph8.com/sequencer` — Personal Sequences (your own) - `app.graph8.com/dialer` — Dialer session history Team sequences live and running today include *Salesleaders_AI&ML_US&UK&AUS&CAN*, *GROWTHDEMANDGEN_SAAS*, and *VP/HEAD-SALES_SAAS*. Columns: *Created By · Channels · Contacts · Steps · Status*. Status pill: **Active** or **Completed**. ![Team Sequences list at /sequencer?tab=team showing real campaign names, channels, contact counts, statuses](../../../../assets/roles/sdr/3-sequences.webp) ***Fig 2.1** — Team Sequences. Click into any to see step-by-step progress.* **Inside a sequence — every step has real stats** Open a sequence (verified live): **many contacts · 1,340 active · 0 paused · 3 steps**. Each step card shows: *Email type · Progress % · Contacts at this step · Schedule · Replies received*. Reply rate visible per step — kill underperforming steps fast. ![Sequence detail showing 1413 contacts and 3 step cards with email stats](../../../../assets/roles/sdr/4-sequence-detail.webp) ***Fig 2.2** — Step-level performance. Use this to decide which sequence to clone for your next campaign.* ![Dialer Sessions list at /dialer showing user, list name, total records, campaign, status, created date](../../../../assets/roles/sdr/5-dialer.webp) ***Fig 2.3** — `/dialer` session history. Pick a list, start a session, click-to-call through it.* > **Tip: Try this now** > **Try this now.** Clone the highest-reply-rate team sequence into your personal sequencer, swap the contacts for a list you just built, and launch. Then open `/dialer` and run a parallel dial-session against the same list — pick up any phones the email didn't. ### 03. Triage replies fast — AI Inbox + sentiment chips *Every reply tagged with sentiment so you can route in seconds. **Interested** = book the meeting. **Out of Office** = snooze. *Not now* = nurture sequence.* **Open it at** - `app.graph8.com/inbox/email` — Focused / Other tabs, sentiment chips on every thread, sequence attribution headers The inbox auto-tags every reply with sentiment (**Interested** green / **Out of Office** yellow / Draft gray / Not Now / etc.) and groups them by the sequence that sourced them. You triage 30+ replies in 5 minutes instead of 30. ![AI Inbox at /inbox/email with Focused / Other tabs and sentiment chips visible per thread](../../../../assets/roles/sdr/6-inbox-sentiment.webp) ***Fig 3.1** — Focused tab pre-filters out the noise. *Other* tab catches the rest. Sentiment chips visible on every thread; click one to filter the whole inbox to that sentiment.* > **Tip: Try this now** > **Try this now.** Open `/inbox/email`, click the *Interested* filter chip. Every green-tagged reply is meeting-eligible. Book the calls before you do anything else today. ### 04. Call the warm ones first — Visitors + Intent *Real-time identification of which companies are on the site and which pages they hit. If Acme just visited `/pricing`, that's a call worth making before lunch.* **Open it at** - `app.graph8.com/visitors?tab=companies` — identified company visitors - `app.graph8.com/keywords` — intent keyword tracking (Signals → Intent) - `app.graph8.com/signals/jobs` — Hiring Signals (companies hiring for relevant roles = expansion intent) The Visitors tab shows real-time site traffic with the killer column: *Page Path*. Visible right now: prospects hitting */platforms*, *Sales Automation Checklist*, */p/graph8/52000 Free Contact Credits*. Those are warm calls. ![Visitors with intent at /visitors?tab=companies showing Page Title, Page Path, Page Host, Browser, Visit Time](../../../../assets/roles/sdr/7-visitors-intent.webp) ***Fig 4.1** — Visitors with intent. Sort by Visit Time desc, then call anyone who hit `/pricing`, `/credits`, or a comparison page in the last hour.* ![Intent Keywords UI at /keywords showing empty state with your data columns](../../../../assets/roles/sdr/8-intent-keywords.webp) ***Fig 4.2** — Intent Keywords UI at `/keywords`. Add keywords here (e.g. *"replace HubSpot"*, *"sales automation"*) and graph8 surfaces contacts at companies actively talking about them. *graph8 org has 0 keywords configured today — set yours up first.** ![Hiring Signals at /signals/jobs showing saved campaigns with daily resolver replays against fresh job postings](../../../../assets/roles/sdr/9-job-changes.webp) ***Fig 4.3** — Hiring Signals at `/signals/jobs`. Different from "champion job-change tracking" — this monitors companies posting new job listings (e.g. hiring a VP Sales) as an expansion signal.* > **Tip: Try this now** > **Try this now.** Open `/visitors?tab=companies`, sort by Visit Time desc. Anyone hitting `/pricing` or `/credits` in the last 24h gets a phone call this morning — they're already in the buying mindset. ### 05. Ask the copilot anything + enrich + know your sources *Natural-language SDR queries, a workbench for bulk enrichment, and acquisition analytics so you know which channels source your warmest leads.* **Open it at** - **Copilot** — sparkle icon top-right, any page - `app.graph8.com/enrichment/staging` — Data Workbench (import, enrich, export) - `app.graph8.com/analytics/acquisition` — channel breakdown for the marketing site Ask the copilot SDR questions: *"Which contacts visited pricing in the last 7 days?"*, *"Show me my hottest prospects this week"*, *"Which sequence has the best reply rate?"*. It calls the right MCP tool and returns structured data. ![Copilot side panel showing response to Which contacts visited pricing in the last 7 days?](../../../../assets/roles/sdr/10-copilot-sdr.webp) ***Fig 5.1** — Live copilot response to "Which contacts visited pricing in the last 7 days?" — points you straight to the Intent dashboard with the right filter applied.* ![Data Workbench at /enrichment/staging with 30 workbenches including Webhooked dialer companies 2489 rows](../../../../assets/roles/sdr/11-enrichment.webp) ***Fig 5.2** — Data Workbench at `/enrichment/staging`. 30 workbenches: import → enrich → export. Bulk lookup phones/emails/firmographics on a list before you sequence it.* ![Acquisition analytics at /analytics/acquisition showing Direct 90.7%, Organic 4.7%, Referral 4.5%, AI/LLM 0.2%, 65.5K users](../../../../assets/roles/sdr/12-attribution.webp) ***Fig 5.3** — Acquisition channels last 30 days. **65.5K users · 68.5K sessions · your engagement rate.** the channel breakdown.* > **Tip: Try this now** > **Try this now.** Run any new list through the Workbench before sequencing — append phones, validate emails, drop junk rows. Bad data is the #1 reason SDR sequences underperform. ## URL cheat-sheet | To do this | Go here | | --- | --- | | Browse the 2.3M contact directory | `/contacts` | | See your saved lists | `/lists` | | Team sequences (shared) | `/sequencer?tab=team` | | Your personal sequences | `/sequencer` | | Dialer session history | `/dialer` | | Triage replies by sentiment | `/inbox/email` | | Identified site visitors | `/visitors?tab=companies` | | Intent keyword tracking | `/keywords` | | Hiring Signals (job posting intent) | `/signals/jobs` | | Bulk enrichment workbench | `/enrichment/staging` | | Acquisition channel breakdown | `/analytics/acquisition` | | Open Copilot anywhere | Sparkle · top-right | | Search the CRM | Cmd-K | ## Heads-up — what to know before you start > **Caution:** > **Intent Keywords are empty.** graph8's own org has 0 keywords configured at `/keywords` right now. The UI is shipped and ready — but until someone configures keywords, the Intent Keywords column in `/contacts` will be empty. **Set up your keywords first thing.** > > **Hiring Signals ≠ champion job-change tracking.** The `/signals/jobs` route is "Hiring Signals" — it watches for new job postings at target companies (a company hiring a VP Sales is an expansion buying signal). If you want to track when your champion at a customer moves to a new company, that's a separate feature still on the roadmap. > > **Sequence performance is per-step.** Don't judge a sequence by overall reply rate — open the detail view (Fig 2.2) and look at reply % per step. Kill underperforming steps individually. --- <small>Generated 2026-05-20 against `app.graph8.com` (org `your org`). Surface state verified live before publication</small> --- # graph8 for SDR Manager > graph8 field guide for SDR Manager. Live URLs, real surfaces, screenshots from a public-data showcase tenant. Source: https://docs.graph8.com/roles/sdr-manager/ *A field guide for SDR Manager. Live URLs, real surfaces. Screenshots captured from a showcase tenant with public-company data only.* ## The five things to use today ### 01. Performance Reports — your morning dashboard *The single screen that shows your entire team's last-7-days output: emails sent, dials placed, replies received, reply rate, meetings booked. Plus a left sidebar of 13 drill-down sub-reports.* **Open it at** - `app.graph8.com/reports` — Performance Reports landing ![Performance Reports landing with hero KPIs Emails 39724, Dials 9665, Replies 232, Reply rate 0.6%, Meetings 72, and activity-over-time line chart](../../../../assets/roles/sdr-manager/1-team-sequences.webp) ***Fig 1.1** — `/reports`. Hero KPI strip (last 7 days) + activity-over-time line chart (Emails / Dials / Replies / Meetings) + LinkedIn aggregate cards. The line chart shows daily volume so you can spot dips, post-holiday catch-ups, or quiet Fridays.* **The 13 sub-reports in the left sidebar** - **Activity Trend** (default landing) - Reply Health - LinkedIn Health - Bounce Health - Mailbox Health Board - Connect Rate - **SDR Leaderboard** — per-rep ranking - **SDR Grade Trends** — quality scoring over time - **SDR Daily Heatmap** — best hours / days to call - **Disposition Mix** — call outcome breakdown per SDR - **Talk Time Quality** — call coaching surface - **Meeting Pipeline** — meetings booked → held → showed - Inbound Activity > **Tip: Try this now** > **Try this now.** Open `/reports` every morning before stand-up. Eyeball reply rate vs the 7-day trend. If today's spike or dip is >25%, drill into *Reply Health* and *Bounce Health* to find the cause (sequence change? mailbox issue? holiday in target market?). ### 02. Team Sequencer — which campaigns are live, who built them, how big *29 sequences across the team. Personal / Team / Archived tabs. Status pills tell you what's currently active vs draft vs completed. This is your campaign portfolio view.* **Open it at** - `app.graph8.com/sequencer` — Personal / **Team** / Archived sub-tabs ![Sequencer table with 10 visible sequences including your data, columns Name/Created By/Created At/Channels/Contacts/Steps/Status, Page 1 of 3, 29 total](../../../../assets/roles/sdr-manager/3-reports-landing.webp) ***Fig 2.1** — Sequencer at `/sequencer`. Real sequences live today: *SalesLeaders_AI&ML_USA*, *GrowthDemandGen_SaaS*, *TaylorHaren_PostEngagers*, *GraphB_linkedin_testing*, *SDR/BDR_SaaS-Sequence*, *RevOps_SaaS-Sequence*. Created by talha.zubair, anal, musa@. Status: **Live** · **Draft** · Completed. **29 sequences total across 3 pages.*** > **Tip: Try this now** > **Try this now.** Audit live sequences weekly: anything in *Draft* > 7 days is a stalled launch — push the owner. Anything in *Completed* with strong reply rate is a clone-candidate for the next quarter. Open `/reports` → *Reply Health* to find your highest-performing sequence and clone it. ### 03. Dialer Sessions — per-SDR call audit + parallel-dial visibility *14 missed · 96 voicemails · 426K minutes logged. 39 dialer sessions across the team with per-user attribution. This is your daily activity audit.* **Open it at** - `app.graph8.com/dialer` — Dialer / Call Logs / Review / Performance tabs ![Dialer Sessions table with 14 real sessions, User column showing hamza.najeeb, abdullah.gr, nikola.bucallo, din.sacic; statuses Paused/Completed; top counters 14 Missed / 96 Voicemails / 426381 mins](../../../../assets/roles/sdr-manager/3-sdr-leaderboard.webp) ***Fig 3.1** — `/dialer` Sessions view. 14 sessions in frame, 39 total. Each row: session name + User + list + status. Visible operators: *hamza.najeeb · abdullah.gr · nikola.bucallo · din.sacic*. **Parallel-dial sessions** like *parallel_session_Ari_177815887082* show up too — useful to spot who's leveraging that capability.* > **Tip: Try this now** > **Try this now.** Every Friday, pull this view and look for SDRs with zero *Completed* sessions all week — they're either skipping dials or hitting a tooling issue. Cross-check with the *SDR Daily Heatmap* sub-report in `/reports` to see when calls are actually landing in target time zones. ### 04. Inbox — reply-quality coaching with sentiment chips *Every reply tagged: **Interested** · **Out of Office** · *Not Interested* · sequence-attribution labels. SDR Managers coach off the "Not Interested" + low-quality replies, not just the wins.* **Open it at** - `app.graph8.com/inbox/email` — Focused / Other tabs ![Inbox Focused view with 5 emails, one labeled Not Interested (a contact thread), sequence labels visible per email](../../../../assets/roles/sdr-manager/4-talk-time-quality.webp) ***Fig 4.1** — Inbox Focused tab. Visible: *"kill the tool spree!"* · *"how AI companies are using this"* (**Not Interested** chip) · *"replace Clay, ZoomInfo, Orum, and Smartlead"* · *"stop waiting on SDRs"* (ACCOUNT/COMPETITIVE/SEQUENCE label) · *"your GTM stack, consolidated"*. Sequence-attribution chips on each thread tell you which campaign drove the reply.* > **Tip: Try this now** > **Try this now.** Filter to *Not Interested*. Read the replies as a batch. If you see a pattern (e.g. multiple "we use Outreach, not interested") your sequence is targeting the wrong segment OR pitching the wrong wedge. Coach the SDR to test a different value-prop angle in their next sequence variant. ### 05. Hiring Signals — fuel for the SDR pipeline + your own hiring *Companies posting new sales jobs are double-signals: (1) they're growing the sales function so they need tools, (2) for managers hiring SDRs, those companies are where the best talent is mobile.* **Open it at** - `app.graph8.com/signals/jobs` — Hiring Signals (saved campaigns view) Today: **0 saved hiring queries** in graph8's own org — the surface is shipped but unused. Set up your first query: "companies hiring VP Sales in SaaS, last 14 days" or "companies hiring SDR Manager + 50-500 employees." A daily resolver replays your query against fresh job postings. No screenshot of populated data — the live org has zero saved queries. Once you create your first, the resolver populates results within 24h. > **Tip: Try this now** > **Try this now.** Click *+ Add Query* at `/signals/jobs`. Configure: industry filter (SaaS), role keyword ("VP Sales" OR "Director, Sales"), geo, recency (last 14 days). Save. Tomorrow you'll have a fresh prospect list AND a talent-market temperature read. ## URL cheat-sheet | To do this | Go here | | --- | --- | | Daily activity dashboard | `/reports` | | SDR ranking | `/reports` → SDR Leaderboard | | SDR coaching grades over time | `/reports` → SDR Grade Trends | | Best hours/days to dial | `/reports` → SDR Daily Heatmap | | Call outcome mix per rep | `/reports` → Disposition Mix | | Call quality coaching | `/reports` → Talk Time Quality | | Meetings booked / held / showed | `/reports` → Meeting Pipeline | | Mailbox warm-up + deliverability | `/reports` → Mailbox Health Board | | Bounces (deliverability triage) | `/reports` → Bounce Health | | Connect rate (dial-to-connect) | `/reports` → Connect Rate | | Team sequence portfolio | `/sequencer` → Team tab | | Dialer sessions audit | `/dialer` | | Reply-quality coaching | `/inbox/email` | | Hiring intent signal queries | `/signals/jobs` | | Open Copilot anywhere | Sparkle · top-right | ## Heads-up — known SDR Manager gaps + workarounds > **Caution:** > **1. `/reports` sub-reports require clicking the sidebar.** You can't deep-link to *SDR Leaderboard* or *Talk Time Quality* via URL — they're state-based not route-based. **Workaround:** bookmark `/reports` as the entry point and click into sub-reports from there. > > **2. `/sequencer?tab=team` URL param is ignored.** Page defaults to Personal Sequences even when you pass `?tab=team`. **Workaround:** click the "Team Sequences" tab in the UI after landing. > > **3. Hiring Signals is empty for graph8.** The shipped UI works; just no saved queries exist for graph8's own org. Set up your first one to start the daily resolver. > > **4. No automated "SDR not-calling-this-week" alert.** You have to spot zero-activity SDRs by eye in `/dialer` + `/reports`. **Workaround:** ask the copilot *"which SDRs haven't logged a dial in the last 5 days?"* as part of your Friday review. > > **5. `/copilot` is not a route.** Copilot is reachable via the sparkle icon top-right on any page, OR via the "Ask Gemini" button. **Workaround:** use the top-right sparkle. > > **6. No per-SDR scorecard surface.** No dedicated "rep card" view aggregating dials + emails + meetings + grade trend per person. **Workaround:** pivot the Performance Reports sub-views by SDR in your head, or export to a spreadsheet for 1:1 prep. --- <small>Generated 2026-05-20 against `app.graph8.com` (org `your org`). Surface state verified live before publication</small> --- # graph8 for Trial Specialist > graph8 field guide for Trial Specialist. Live URLs, real surfaces, screenshots from a public-data showcase tenant. Source: https://docs.graph8.com/roles/trial/ *A field guide for Trial Specialist. Live URLs, real surfaces. Screenshots captured from a showcase tenant with public-company data only.* ## The five things to use today ### 01. Active trials — your conversion-pipeline list with countdowns *Trial Specialist home. Active trials with *Trial Ends in N days* countdown + Source + Referrer + First Touch + Users-on columns. Sort by days remaining ascending.* **Open it at** - `app.graph8.com/deals/l/trials` — Active sub-tab (default) - Companies / Contacts toggle visible at the top ![Active trials list showing your data, daewon america, techyscouts, Dr Steve Rad Md, Sg Analytics, element dma with columns Company/Domain/Status (trial)/Trial Ends (67d-63d left)/Signup/Source/Referrer/First Touch/Users on, Active/Expired sub-tabs visible](../../../../assets/roles/trial/1-trials-active.webp) ***Fig 1.1** — `/deals/l/trials` Active. Columns: Company · Status · **Trial Ends** · Signup · **Source · Referrer · First Touch** · Users on. Source attribution column means you can see which channel sourced the trial.* > **Tip: Try this now** > **Trial Specialist move.** Morning ritual: sort by Trial Ends asc. Anyone < 14 days = call or email today. Anyone < 7 days = personal outreach + offer demo/discount. Anyone < 3 days = last-chance message + management awareness. ### 02. Per-trial detail — Trial Health, Activation Pulse, Conversion Blockers *Click into any trial. Opinionated health view: Days Left · Activation % · Health pill · Activation Pulse (0/5 steps) · Conversion Blockers (auto-detected). Tells you exactly why a trial isn't converting.* **Open it at** - `/deals/l/trials/companies/{numeric-id}` — click any row live trial · Ecosparkgrowth · captured 2026-05-20 Days Left66 Activation0% Trial HealthCritical Activation Pulse0 of 5 steps Conversion BlockersNo open backs · No recorded touches · No meetings yet Role AssignmentsSDR · AE · CSM — all Unassigned > **Tip: Try this now** > **Trial Specialist move.** The Conversion Blockers row tells you exactly what to do next. *"No meetings yet"* = book one. *"No recorded touches"* = send an email today. *"No open backs"* = the trial user opened the email but didn't click — try a different CTA. ### 03. Expired trials — re-engagement + win-back *Trials that ran out without converting. Don't write them off — Apple, Notion, Slack, Booking Holdings all currently sit here for graph8. These are warm-but-unconverted accounts worth a 90-day re-engage email.* **Open it at** - `/deals/l/trials?status=expired` — Expired sub-tab ![Expired trials list showing Pall International, monte carlo, apple, booking holdings, notion, Motive, Slack, Covisian, jam-king with trial_expired status and Expired X days ago column](../../../../assets/roles/trial/4-trial-attribution.webp) ***Fig 3.1** — Expired sub-tab. Notable names: *Apple · Notion · Slack · Motive · Booking Holdings · Pall International · Covisian · Monte Carlo*. **These were warm enough to start a trial** — re-engagement campaign worth running.* > **Tip: Try this now** > **Trial Specialist move.** Build a "trial-expired-30-day" re-engagement sequence: (1) "we saw you tried graph8 last month — what stopped you?" (2) "here are 3 things we shipped since you left" (3) "want a 1:1 walkthrough?" Run it monthly against the Expired list. ### 04. Conversion mechanics — Quotes, Inbox, Customers list *When the trial user is ready to buy: send a quote. Triage replies in Inbox. Track newly-converted customers in `/deals/l/customers` by Customer Since date.* **Open these** - `/deals/quotes` — quotes for trial-to-paid conversion - `/inbox/email` — replies from trial users - `/deals/l/customers` — sort by Customer Since desc to see recent conversions ![Quotes list with your data tabs showing graph8 Inc quotes with $500-$7050 totals, Net 30 terms, Accepted/Draft/Voided badges](../../../../assets/roles/trial/5-conversion-quote.webp) ***Fig 4.1** — `/deals/quotes`. Build a quote for the trial user ready to convert. Net 30 terms default. *Accepted* tab is auto-provisioned in Stripe — the trial flips to paying customer immediately.* ![Customers list showing recent conversions: a paying customer $2.5K Customer Since Feb 4, a paying customer $2.5K Apr 1, a paying customer $8.9K Apr 1, a paying customer $499 past_due Mar 26, your account $6.0K Apr 22, a paying customer $1.5K Apr 26, a paying customer, a paying customer networks, a paying customer](../../../../assets/roles/trial/7-trial-acquisition.webp) ***Fig 4.3** — `/deals/l/customers` sorted by Customer Since desc. Recent conversions: *your account Apr 22 · a paying customer Apr 26 · a paying customer Apr 1 · a paying customer Apr 1.* Your monthly conversion count = your KPI.* > **Tip: Try this now** > **Trial Specialist move.** When a trial user replies *"how do I upgrade?"*: open `/deals/quotes`, clone an Accepted quote, swap the customer, send. Quote-to-paid takes minutes, not days. Then track them in `/deals/l/customers` for the first 30 days post-conversion to ensure activation. ### 05. Acquisition source + Conversions + Copilot *Where do trials come from? Which channel converts? Channel breakdown + Goal Completions answer those. Copilot is honest about gaps.* **Open these** - `/analytics/acquisition` — channel mix that sources trials - `/analytics/conversions` — Goal Completions (Form Submission + Meeting Booked) - Copilot — Cmd+Shift+K ![Conversions showing 148 Total Conversions, 0.1% rate, 148 Form Submissions, 40 Meetings Booked, Goal Completions table with Form Submission 108 unique and Meeting Booked 40 (1 unique)](../../../../assets/roles/trial/9-customers-just-converted.webp) ***Fig 5.2** — `/analytics/conversions`. **148 conversions · 108 form submissions · 40 meetings.** Form Submission is the trial-signup funnel; meetings booked is the demo funnel. Both feed your trials list.* > **Tip: Try this now** > **Trial Specialist move.** Don't rely on the copilot for trial-state queries — it's not wired to the trial data model. Use `/deals/l/trials` sorted by Trial Ends asc. For conversion-rate-by-source: pivot Trials list manually by Source column. ## URL cheat-sheet | To do this | Go here | | --- | --- | | Active trials (with countdowns) | `/deals/l/trials` | | Expired trials (re-engagement) | `/deals/l/trials?status=expired` | | Trial detail (Health + Pulse + Blockers) | `/deals/l/trials/companies/{id}` | | Quotes for trial→paid conversion | `/deals/quotes` | | Inbox (trial replies + sentiment) | `/inbox/email` | | Recent conversions (Customer Since desc) | `/deals/l/customers` | | Acquisition channels | `/analytics/acquisition` | | Conversion goals (form/meeting) | `/analytics/conversions` | | Open Copilot | Cmd+Shift+K | ## Heads-up — Trial gaps + workarounds > **Caution:** > **1. Copilot has no native trial-state tool.** Asking "which trials expire next 14 days" gets an honest "I don't have a trial management module" response. **Workaround:** sort the `/deals/l/trials` list manually by Trial Ends column. > > **2. Role Assignments empty on most trials.** SDR/AE/CSM all show Unassigned on the Ecosparkgrowth example. **Workaround:** assign yourself as Trial Specialist owner on every active trial. > > **3. Source/Referrer/First Touch columns visible but values sparse.** The attribution columns exist on the trials list but values aren't populated for most rows. **Workaround:** ensure the web snippet is firing UTMs into the contact record on signup (MOps surface). > > **4. Activation Pulse (0/5 steps) — figure out what the 5 steps are.** The Activation Pulse mechanism exists but isn't documented in the UI. Likely tied to in-product usage events. Check with engineering to confirm what counts as activation step 1-5. > > **5. Conversion Blockers are auto-detected but not always accurate.** "No recorded touches" might mean you DID touch them via SMS/WhatsApp/Slack but the system only sees email. Triple-check the Activity log on each trial detail. --- <small>Generated 2026-05-20 against `app.graph8.com` (org `your org`). Surface state verified live</small> --- # graph8 for VP Sales / Head of Sales > graph8 field guide for VP Sales / Head of Sales. Live URLs, real surfaces, screenshots from a public-data showcase tenant. Source: https://docs.graph8.com/roles/vp-sales/ *A field guide for VP Sales. Live URLs, real surfaces. Screenshots captured from a showcase tenant with public-company data only.* ## The five things to use today ### 01. Total pipeline + weighted forecast — the exec kanban *Six-stage kanban with **$total and $weighted per column**. That's your weighted commit number today. Drag-and-drop advances. Owner column on the list view shows which rep owns each deal.* **Open it at** - `app.graph8.com/deals/pipeline` — defaults to kanban view - 6 stages: *New Meeting · Discovery Held · Solution Fit · Proposal Sent · Verbal Commit · Closed Won* ![Pipeline kanban with 6 stages showing $4K New Meeting / $9K Discovery / $400 Solution Fit / $25K Closed Won with $total and $weighted per column](../../../../assets/roles/vp-sales/1-pipeline-all-teams.webp) ***Fig 1.1** — `/deals/pipeline` kanban. Per-stage *$total / $weighted*. Sum the $weighted column across stages = your weighted pipeline. Compare against quota — gap = what your team needs to source/close this period.* > **Tip: Try this now** > **VP move.** Take a screenshot of this view every Friday. Diff week-over-week: which stages grew, which shrank? If Proposal Sent didn't grow, no closes are landing next week. If New Meeting didn't grow, SDR pipeline is leaking — drill into use case 2. ### 02. The reports center — 13 sub-reports at `?report=aN` *graph8's `/reports` surface ships with 13 strategic dashboards. SDR Leaderboard, Meeting Pipeline, Account Health Comparison, Talk Time Quality, Connect Rate. Bookmark the report-ID URLs — deep-linkable.* **Open these** - `/reports` — Activity Trend (default landing) - `/reports?report=a8` — **SDR Leaderboard** - `/reports?report=a5` — **Meeting Pipeline** - `/reports?report=a14` — **Account Health Comparison** - `/reports?report=a4` — Connect Rate - `/reports?report=a9` — SDR Grade Trends - `/reports?report=a10` — SDR Daily Heatmap - `/reports?report=a12` — Sequence Performance ![Meeting Pipeline report showing 73 booked, 3 cancelled, 70 net, 2 channels, 8 active workspaces, booked-vs-cancelled chart over 7d](../../../../assets/roles/vp-sales/3-customers-mrr-leaderboard.webp) ***Fig 2.2** — `?report=a5` Meeting Pipeline. **73 booked · 3 cancelled · 70 net · 8 active workspaces.** Tracks the leading indicator of AE pipeline conversion. (Heads-up: *Held* rate not tracked yet per backend note.)* ![Account Health Comparison showing 25 workspaces, 0.6% avg reply rate, top accounts graph8 9939, a paying customer 7412, a paying customer 3653, a paying customer, your account](../../../../assets/roles/vp-sales/4-trial-pipeline.webp) ***Fig 2.3** — `?report=a14` Account Health Comparison. **25 workspaces · 0.6% avg reply rate.** Top by outbound volume: *graph8 9,939 · a paying customer 7,412 · a paying customer 3,653 · a paying customer · your account*. Use for cross-customer health audits.* > **Tip: Try this now** > **VP move.** Build a Notion / Slack canvas with all 13 report URLs as named links. Now every Monday's exec meeting starts with "here are the 13 dashboards — anything red?" instead of "let me pull up the data." ### 03. Daily activity pulse — Performance Reports landing *The 7-day trend chart that tells you if the team is working. 40K emails · 9.2K dials · 235 replies · 0.6% reply rate · 72 meetings. If those tank for 3 days, something broke.* **Open it at** - `app.graph8.com/reports` — Activity Trend (default) ![Performance Reports Activity Trend with 40,423 emails, 9,185 dials, 235 replies, 0.6% reply rate, 72 meetings (7d), activity-over-time chart, sidebar of 12 sub-reports](../../../../assets/roles/vp-sales/5-reports-landing-kpis.webp) ***Fig 3.1** — `/reports` Activity Trend. Hero KPIs (7d) + activity-over-time chart (Emails / Dials / Replies / Meetings). Watch the trend line, not the absolute number.* > **Tip: Try this now** > **VP move.** Pin `/reports` as your homepage. Every morning's first action: glance at the trend chart. Anomalies = real signal worth investigating before they snowball into a missed quarter. ### 04. Book of business + acquisition source — where the revenue comes from *your paying customers ranked by MRR (revenue concentration risk view). Trials in flight (conversion pipeline). Acquisition channels (where the next batch sources).* **Open these** - `/deals/l/customers` — paying book + MRR - `/deals/l/trials` — trials with countdown timers - `/analytics/acquisition` — channel mix ![Customers list with MRR column showing a paying customer $2.5K, a paying customer $2.5K, a paying customer $8.8K, your account $4.0K, a paying customer $2.5K with plans, status, owner](../../../../assets/roles/vp-sales/6-reports-sdr-leaderboard.webp) ***Fig 4.1** — `/deals/l/customers`. Top of book: *a paying customer $8.8K · your account $4.0K · a paying customer $2.5K · a paying customer $2.5K · a paying customer $2.5K*. Read this monthly for concentration risk: if your top 5 customers are 50%+ of MRR, that's a board-level conversation.* ![Trial accounts with your data and 67d/63d countdown timers](../../../../assets/roles/vp-sales/7-reports-meeting-pipeline.webp) ***Fig 4.2** — `/deals/l/trials`. Countdowns visible. Anyone < 14 days = your highest-leverage conversion call.* > **Tip: Try this now** > **VP move.** Cross-reference top-5 MRR customers (Fig 4.1) against the Activity feed on each one. Anyone in the top 5 hasn't been touched in 30+ days = renewal risk. Assign yourself + the rep + CSM to a 45-min QBR within the week. ### 05. Copilot — your strategic Q&A engine *Natural-language exec queries: weighted pipeline, sequence comparison, deals at risk, rep performance vs peers. Opens via Cmd+Shift+K from any page.* **Open it at** - **Cmd+Shift+K** — global keyboard shortcut - OR sparkle icon · top-right - OR via Bridge — WhatsApp / Slack / iMessage / Roam (set up at `/profile?tab=apps`) ![Copilot expanded view with chat history sidebar showing past VP-style queries: Show me my expansion, Show me my customer, Which contacts visited, How many deals are in, Build a contact list, plus main panel with How can I help you today + Create campaign/Create contact list chips](../../../../assets/roles/vp-sales/9-analytics-acquisition.webp) ***Fig 5.1** — Copilot opened via `Cmd+Shift+K`. Sidebar shows past query history (real exec patterns): *"Show me my expansion candidates"* · *"Which contacts visited pricing"* · *"How many deals are in my pipeline"* · *"Build a contact list"*. The chat history doubles as a query-template library.* > **Tip: Try this now** > **VP move.** Try these strategic copilot queries: *"Compare reply rates across all sequences this month"* · *"Which deals are stuck more than 14 days in Proposal Sent?"* · *"What's our weighted pipeline this quarter vs last?"* · *"Show me my top 5 reps by closed-won this month."* ## The 13 report URLs (deep-linkable) | Report | Direct URL | | --- | --- | | Activity Trend (landing) | `/reports` | | Connect Rate | `/reports?report=a4` | | Meeting Pipeline | `/reports?report=a5` | | SDR Leaderboard | `/reports?report=a8` | | SDR Grade Trends | `/reports?report=a9` | | SDR Daily Heatmap | `/reports?report=a10` | | Sequence Performance | `/reports?report=a12` | | Account Health Comparison | `/reports?report=a14` | | Idle / Stalled accounts | `/reports?report=a15` | ## Full URL cheat-sheet | To do this | Go here | | --- | --- | | Total pipeline + weighted kanban | `/deals/pipeline` | | Customers + MRR ranking | `/deals/l/customers` | | Trials in flight | `/deals/l/trials` | | Churned (lost revenue) | `/deals/l/churned` | | Daily activity pulse | `/reports` | | Acquisition channels | `/analytics/acquisition` | | Marketing Bridge Funnel | `/analytics/marketing` | | Conversions (forms + meetings) | `/analytics/conversions` | | Open Copilot anywhere | Cmd+Shift+K · or sparkle top-right | ## Heads-up — known VP gaps + workarounds > **Caution:** > **1. No commit / best-case / most-likely categorization.** The kanban's $weighted is the only forecast surrogate. **Workaround:** use the per-stage probabilities (configurable in `/studio/settings` → Revenue → Pipelines) as your weighting truth. > > **2. The 13 reports are state-routed but URL-deep-linkable.** Use the `?report=aN` URLs above to jump directly. The sidebar click works too. > > **3. Meeting Pipeline doesn't track Held rate yet.** Booked / Cancelled / Net only. Add manual "held" logging if it matters. > > **4. Copilot routes via Cmd+Shift+K, not /copilot.** /copilot is 404. The global shortcut works on any page. > > **5. Customer Owner column is empty for graph8 today.** Customer Owner assignments would make /deals/l/customers a per-AE renewal book by default. Right now you pivot in your head. --- <small>Generated 2026-05-20 against `app.graph8.com` (org `your org`). Surface state verified live before publication</small> --- # API, MCP & CLI > Create and manage API keys, connect MCP tools, and set up the CLI. Source: https://docs.graph8.com/settings/api/ Generate API keys to integrate graph8 with your tools and workflows. Connect AI assistants via MCP, or use the CLI for command-line access. > **Note:** > This page is for **org/admin-managed keys** in **Settings**. > > If you are setting up MCP or CLI for yourself as an end user, go to **Profile -> Developer** and use your **personal API key** instead. See [Authentication](https://docs.graph8.com/developers/authentication/), [MCP Server](https://docs.graph8.com/developers/mcp/), and [CLI](https://docs.graph8.com/developers/cli/). ## Personal vs Org Credentials | Type | Where | Best for | |------|-------|----------| | Personal API key | Profile -> Developer | Individual user MCP or CLI setup | | Org API key | Settings -> API (this page) | Shared integrations, backend jobs, admin-managed automation | ## Creating Keys ### Generate a New Key 1. Go to **Settings → API** 2. Click **Create API Key** 3. Name the key (e.g., "CRM Sync", "Internal Dashboard") 4. Select the permission scopes 5. Click **Generate** 6. Copy the key immediately — it won't be shown again ### Permission Scopes Each key can be limited to specific API areas: - **Contacts** — read/write contact records - **Companies** — read/write company records - **Lists** — read/write lists and audiences - **Pipeline** — read/write deals and stages - **Sequences** — read/write sequences and enrollment - **Analytics** — read analytics data - **Full Access** — unrestricted access to all endpoints Grant only the scopes your integration needs. ## Key Management ### Viewing Keys The API settings page shows all active keys with: - **Name** — the label you assigned - **Scopes** — which API areas the key can access - **Created** — when the key was generated - **Last Used** — the most recent API call using this key ### Rotating a Key To replace a key without downtime: 1. Create a new key with the same scopes 2. Update your integration to use the new key 3. Verify the integration works 4. Revoke the old key ### Revoking a Key 1. Find the key in the list 2. Click **Revoke** 3. Confirm Revoked keys stop working immediately. Any integration using the key will receive authentication errors. ## Rate Limits API requests are rate-limited per key and plan: | Plan | Requests/Minute | Requests/Day | |------|-----------------|--------------| | Starter | 60 | 10,000 | | Pro | 120 | 50,000 | | Enterprise | 300 | Unlimited | When you exceed the limit, the API returns a `429 Too Many Requests` response with a `Retry-After` header. ## Authentication Include your API key in the request header: ``` Authorization: Bearer your-api-key-here ``` All API requests must be made over HTTPS. ### Example Request ```bash curl -H "Authorization: Bearer your-api-key-here" \ -H "Content-Type: application/json" \ https://be.graph8.com/v1/contacts ``` ## Webhooks Receive real-time notifications when events happen in graph8. ### Setting Up Webhooks 1. Go to **Settings → API → Webhooks** 2. Click **Add Webhook** 3. Enter your endpoint URL (must be HTTPS) 4. Select the event types to subscribe to 5. Save ### Event Types - **contact.created** — a new contact is added - **contact.updated** — a contact record is modified - **deal.stage_changed** — a deal moves to a new stage - **deal.won** / **deal.lost** — a deal is closed - **sequence.completed** — a contact finishes a sequence - **meeting.booked** — an appointment is scheduled ### Retry Policy If your endpoint returns a non-2xx response, graph8 retries: - 3 retry attempts - Exponential backoff (1 min, 5 min, 30 min) - After 3 failures, the webhook is paused and you're notified ### Webhook Security Each webhook includes a signature header for verification. Use the signing secret (shown during webhook creation) to verify that requests come from graph8. ## MCP Server The graph8 MCP (Model Context Protocol) server lets AI assistants like Claude Desktop, Cursor, Windsurf, and Claude Code interact with your graph8 data directly. ### Modes graph8 MCP runs in two modes depending on your workflow: - **Developer Mode** — for developers building products. Includes repo scanning, GTM infrastructure install, campaign generation, and knowledge base search. All tools require a `repo_id`. - **GTM Mode** — for campaign managers and marketers. Browse campaigns, edit copy, search the knowledge base, enrich contacts, and launch outreach — no `repo_id` needed. ### Remote (Streamable HTTP) Clients that support remote MCP endpoints (Cursor, Claude Code, Windsurf) can connect directly. Authentication is handled via OAuth — no API key required in the config. ```json { "mcpServers": { "graph8": { "url": "https://be.graph8.com/mcp/" } } } ``` Your client will prompt you to sign in via OAuth when you first connect. ### Local (stdio) For clients that require a local process (Claude Desktop), install the MCP server and provide your API key: ```bash pip install g8-mcp-server ``` **Developer Mode:** ```json { "mcpServers": { "graph8": { "command": "uvx", "args": ["g8-mcp-server"], "env": { "G8_API_KEY": "your-api-key-here", "G8_MCP_MODE": "dev" } } } } ``` **GTM Mode:** ```json { "mcpServers": { "graph8": { "command": "uvx", "args": ["g8-mcp-server"], "env": { "G8_API_KEY": "your-api-key-here", "G8_MCP_MODE": "gtm" } } } } ``` ### Tool Annotations Every MCP tool is annotated as **read-only**, **write**, or **destructive** so MCP clients (Claude Desktop, Cursor, Claude Code, etc.) can prompt the user before any non-trivial action runs. | Annotation | Meaning | Example tools | |------------|---------|---------------| | **read-only** | Reads data, never modifies | `g8_list_fields`, `g8_voice_get_dialer_stats`, `g8_search_contacts` | | **write** | Creates or updates data | `g8_create_field`, `g8_set_field_value`, `g8_voice_create_dialer_session` | | **destructive** | Deletes or stops something | Field deletes, `g8_voice_stop_session` | Most clients show a confirmation prompt the first time a write or destructive tool is called so you can approve the action before it touches your data. ### Available Tools #### Shared (all modes) | Tool | Description | |------|-------------| | `g8_search_contacts` | Search contacts by email, name, or list | | `g8_get_contact` | Get full contact profile | | `g8_search_companies` | Search companies by domain or industry | | `g8_get_company` | Get full company profile | | `g8_lookup_person` | Instant person lookup by email or LinkedIn | | `g8_lookup_company` | Instant company lookup by domain | | `g8_enrich_contacts` | Start enrichment for a batch of contacts | | `g8_verify_email` | Verify email deliverability | | `g8_create_sequence` | Create a new outreach sequence | | `g8_get_sequence_preview` | Preview sequence steps before launching | | `g8_update_sequence` | Update sequence metadata | | `g8_update_sequence_step` | Update a single step in a sequence | | `g8_pause_sequence` | Pause a live sequence | | `g8_resume_sequence` | Resume a paused sequence | | `g8_get_sequence_analytics` | Get sequence performance metrics | | `g8_delete_sequence` | Archive a sequence (soft delete) | | `g8_list_inbox` | List reply threads across email, SMS, LinkedIn | | `g8_get_reply` | Get a single reply thread with messages | | `g8_assign_reply` | Assign a reply to a team member | | `g8_tag_reply` | Tag a reply thread | | `g8_get_reply_draft` | Generate an AI reply draft (charges credits) | | `g8_send_reply` | Send a reply via email, SMS, or LinkedIn | | `g8_list_audience_syncs` | List audience sync configs | | `g8_create_audience_sync` | Create audience sync to ad platform | | `g8_get_audience_sync` | Get audience sync config details | | `g8_update_audience_sync` | Update audience sync config | | `g8_delete_audience_sync` | Delete an audience sync (soft delete) | | `g8_trigger_audience_sync` | Manually trigger a sync run | | `g8_get_audience_sync_runs` | View sync run history | | `g8_get_audience_sync_errors` | View sync error logs | | `g8_list_crm_syncs` | List connected CRM integrations | | `g8_push_to_crm_contact` | Push contacts to a CRM | | `g8_push_to_crm_company` | Push companies to a CRM | | `g8_push_to_crm_list` | Push list memberships to a CRM | | `g8_get_crm_fields` | Discover CRM field mappings | | `g8_get_crm_status` | Check CRM connection health | | `g8_list_fields` | Discover custom columns/fields on contacts and companies | | `g8_create_field` | Create a new custom column on contacts or companies | | `g8_set_field_value` | Set a custom column value on a specific contact or company | | `g8_voice_list_dialer_sessions` | List parallel-dialer sessions and their state | | `g8_voice_get_dialer_stats` | Read dialer analytics (dials, connections, talk time, dispositions) | | `g8_voice_list_agents` | List voice agents available for calls | | `g8_voice_create_dialer_session` | Create a new dialer session bound to a list | | `g8_voice_pause_session` | Pause an active dialer session | | `g8_voice_resume_session` | Resume a paused dialer session | | `g8_voice_stop_session` | Stop a dialer session permanently | #### Developer Mode | Tool | Description | |------|-------------| | `g8_connect_repo` | Connect a GitHub/GitLab repository | | `g8_scan_repo` | Record the tech stack and GTM readiness you read from the code | | `g8_get_scan_results` | Get scan results for a repo | | `g8_status` | Get current repo status | | `g8_doctor` | Report the graph8-side state of a repo's GTM install | | `g8_install_spine` | Generate GTM install patches | | `g8_apply_install` | Apply generated patches to the codebase | | `g8_list_campaigns` | List campaigns for a repo | | `g8_get_campaign` | Get campaign details | | `g8_search_kb` | Search the repo knowledge base | | `g8_list_kb_documents` | List all KB documents | #### GTM Mode | Tool | Description | |------|-------------| | `g8_list_campaigns` | List campaigns for the organization | | `g8_get_campaign` | Get full campaign details and documents | | `g8_get_campaign_document` | Get document content (briefs, copy, etc.) | | `g8_create_campaign` | Create a new campaign | | `g8_update_campaign` | Update campaign fields | | `g8_launch_campaign` | Launch a campaign for outreach | | `g8_search_kb` | Search the knowledge base | | `g8_list_kb_documents` | List all KB documents | ## CLI The graph8 CLI provides the same MCP tools as a command-line interface. Install it alongside the MCP server: ```bash pip install g8-mcp-server ``` Run a tool directly: ```bash g8 status --repo-id <repo-id> g8 scan --repo-id <repo-id> g8 doctor --repo-id <repo-id> # Sequence management g8 create-sequence --name "Q2 Outbound" --steps '[...]' g8 pause-sequence --sequence-id <id> g8 sequence-analytics --sequence-id <id> # Inbox g8 inbox-list --channel email --limit 20 g8 inbox-draft --reply-id <thread-id> g8 inbox-send --reply-id <thread-id> --channel email --body "Thanks for your reply" # Audience & CRM sync g8 sync-audience-list g8 sync-audience-trigger --config-id <id> g8 sync-crm-push --provider hubspot --records '[...]' ``` Set your API key as an environment variable: ```bash export G8_API_KEY=your-api-key-here ``` ## Frequently Asked Questions ### Can I have multiple API keys? Yes. Create separate keys for each integration or environment (production, staging, testing). ### What happens if I lose my API key? API keys can't be recovered after creation. Revoke the lost key and generate a new one. ### Are there SDKs available? Check the graph8 API documentation for available client libraries and SDK references. ### Can I test webhooks locally? Use a tunneling service to expose your local endpoint during development. Point the webhook URL to your tunnel address. ### Do I need an API key for remote MCP connections? No. Remote MCP connections (Cursor, Claude Code, Windsurf) use OAuth for authentication. You only need an API key for local stdio mode (Claude Desktop) or direct API calls. ### Which MCP mode should I use? Use **Developer Mode** if you're building a product and want to scan repos, install GTM infrastructure, and generate campaigns from code. Use **GTM Mode** if you're managing campaigns, writing copy, or doing outreach — it doesn't require a repository. --- > **Tip:** > Create separate API keys per integration so you can revoke one without affecting others. --- # Audience Sync > Sync contact lists to ad platforms - push audiences to Meta, LinkedIn, Google Ads, and X for targeted advertising. Source: https://docs.graph8.com/settings/audience-sync/ Audience Sync pushes your graph8 contact lists directly to ad platforms - run targeted ads on Meta, LinkedIn, Google Ads, and X using your existing prospect and customer data. ## Supported Platforms | Platform | What You Can Sync | |----------|-------------------| | **Meta (Facebook/Instagram)** | Custom audiences for ad targeting | | **LinkedIn** | Matched audiences for sponsored content | | **Google Ads** | Customer match audiences | | **X (Twitter)** | Tailored audiences | ## Connecting an Ad Platform 1. Go to **Settings → Integrations → Ad Platforms** 2. Click **Connect** next to the platform you want 3. Complete the OAuth authorization flow 4. Once connected, the platform shows a green **Connected** status > **Note:** > Ad platform connections use OAuth through Nango. You may need admin permissions on the ad platform to authorize the connection. ## Syncing an Audience 1. Go to any contact or company list in **Data** 2. Click **Sync to Ads** (or find it in the list actions menu) 3. Select the target platform (Meta, LinkedIn, Google Ads, X) 4. Choose the audience name on the platform (or create a new one) 5. Click **Sync** graph8 pushes the list's email addresses and identifiers to the ad platform for matching. The platform matches against its own user base. ## Sync Cadence Audience sync runs automatically on a polling cadence: - **Initial sync** - contacts pushed immediately on first sync - **Ongoing sync** - contacts added or removed from the list are synced every 60 seconds When contacts are added to or removed from the source list in graph8, those changes automatically propagate to the ad platform. ## Use Cases - **Retarget website visitors** - sync your visitor list to Meta for retargeting ads - **ABM campaigns** - sync your target account list to LinkedIn for sponsored content - **Exclude existing customers** - sync your customer list as an exclusion audience - **Lookalike audiences** - push your best customers list, then build lookalikes on the platform > **Tip:** > Combine audience sync with graph8's enrichment to fill in missing emails before syncing. Higher match rates on ad platforms mean better targeting and lower ad costs. ## Troubleshooting | Issue | Fix | |-------|-----| | Platform not connecting | Ensure you have admin permissions on the ad platform account | | Low match rates | Enrich contacts with work emails before syncing - personal emails match poorly on LinkedIn and Google | | Sync not updating | Check that the source list is still active and the platform connection is healthy | ## Related - [Integrations →](https://docs.graph8.com/settings/integrations/) - Connect CRMs and other tools - [Managing Lists →](https://docs.graph8.com/data/managing-lists/) - Create and manage contact lists - [Enrichment →](https://docs.graph8.com/data/enrichment/) - Fill in missing contact data --- # Billing > Manage subscription, credits, and payment. Source: https://docs.graph8.com/settings/billing/ Manage your graph8 subscription, credits, and payment methods. The Billing page lives under **Settings → Organization → Billing**. ## The Billing page The top of the page shows your account at a glance: - **Customer Account** - your account email and status badge (**Active** for paid accounts, **Trialing** during a free trial) - **Member since** and **Last updated** dates (trial accounts see their **Trial ends** date here too) - **Credits Balance** - your total credits, split into **Available**, **Used**, and **Held**, with a usage bar ![The Billing page under Settings → Organization, showing the credits balance and billing tabs](https://docs.graph8.com/images/settings/billing-overview.png) Below that, the page is organized into tabs: | Tab | What it shows | |-----|---------------| | **Upgrade & Topup** | Available plan upgrades and credit top-ups | | **Services** | Add-on products available for your plan | | **Transactions** | Every credit charge, filterable by date, with CSV export | | **Service Charges** | The live credit cost for each platform service | | **LLM Charges** | Credit cost per AI model tier, charged per input and output token | | **Domain Pricing** | Credit cost for purchasing domains, by TLD | The **Invoices & More** button (top right) opens the secure graph8 billing portal, where you manage invoices, payment methods, billing information, and cancellation. ## Plans Every new organization starts on a free trial with full feature access - no credit card required. ### Free Trial (Pay As You Go) Sign up with a work email and your organization starts on a free trial of the **Pay as you go** plan. - **Free credits:** 2,500 (one-time) - **Trial period:** your trial end date is shown at the top of the Billing page - **Features:** Full platform access - same as the paid plans - **Requirements:** Work email (personal email providers like Gmail, Yahoo, etc. are not accepted) After your free trial ends, the service pauses until a payment method is added. To keep going, add a payment method in the billing portal or upgrade to a paid plan (see [Trial accounts and activation](#trial-accounts-and-activation)). ### Team ($99/month) One flat price for the whole team. - **Credits:** 10,000/month, pooled across the whole team - **Contacts:** Free on every paid plan (fair use 50,000/month) - **Overage:** Execution beyond your monthly credits bills at $0.01/credit - **Billing:** Monthly, cancel anytime ### Platform ($499/month) For teams doing regular outreach - the highest included monthly credit allowance. - **Credits:** 75,000/month included - **Contacts:** Unlimited contact data across every workflow - **Support:** Priority support, cancel anytime - **Overage:** Execution beyond your monthly credits bills at pay-as-you-go rates ### Credit Cost Per Action The authoritative, always-current rates are in the app: go to **Settings → Organization → Billing** and open the **Service Charges**, **LLM Charges**, and **Domain Pricing** tabs. Common examples: | Action | Credits | |--------|---------| | Enrich a company profile | 1 | | Process 1 intent signal | 1 | | Agent Inbox / textual agents (AI generation) | 1 per 1,000 tokens | | Voice AI | 20 per minute | | Meeting scheduled | 20 | | Inbox placement test | 20 | | Web visitor resolution | 20 | ### Viewing Your Plan 1. Go to **Settings → Organization → Billing** 2. Your account status and credit balance are shown at the top 3. For your subscription details and next billing date, click **Invoices & More** to open the billing portal ## Subscription ### Trial accounts and activation While your organization is on a free trial, the Billing page shows a **Trialing** badge and your **Trial ends** date. There are two ways to keep your account active past the trial: **Upgrade to a paid plan (recommended):** 1. Go to **Settings → Organization → Billing** 2. On the **Upgrade & Topup** tab, pick **Team** or **Platform** under **Upgrade Your Subscription** 3. Complete the checkout - your status changes to **Active** **Or stay on Pay as you go:** 1. Click **Invoices & More** (top right) - the billing portal opens 2. Click **Add payment method** and save a card Without either, the service pauses when the trial ends. Note that credit top-ups are not available during the trial - they appear once you're on a paid plan. ### Upgrading 1. Go to **Settings → Organization → Billing** 2. On the **Upgrade & Topup** tab, available upgrades are listed under **Upgrade Your Subscription** 3. Select the plan and confirm Upgrades take effect immediately. New features and limits are available right away. To downgrade or switch to a different plan, contact support. ### Cancellation Cancellation happens in the billing portal, not on the Billing page itself: 1. Go to **Settings → Organization → Billing** 2. Click **Invoices & More** (top right) - the graph8 billing portal opens 3. Under **Current Subscription**, click **Cancel subscription** 4. Confirm the cancellation ![The graph8 billing portal showing the current subscription with the Cancel subscription button](https://docs.graph8.com/images/settings/billing-portal-cancel.png) The same path works for trial accounts - the portal shows **Cancel subscription** during the trial too. After cancellation, your account remains active until the end of the paid period. Data is retained for 30 days after the account expires. ## Credits Credits power enrichment, AI features, and premium actions across the platform. ### Credit Balance The top of the Billing page shows your balance in three parts: - **Available** - credits ready to spend - **Used** - credits already consumed - **Held** - credits reserved for jobs currently in progress ### Credit Usage Credits are consumed by: - **Contact enrichment** - waterfall enrichment across data providers - **AI features** - AI email composition, call transcription, voice AI - **Premium actions** - domains, inbox placement tests, and other advanced features Check the **Service Charges** tab for the live per-service rates, **LLM Charges** for AI model rates, and **Domain Pricing** for domain costs. ### Topping Up On a paid plan, buy additional credits anytime: 1. Go to **Settings → Organization → Billing** 2. On the **Upgrade & Topup** tab, find **Add Credits to Your Plan** 3. Select how many credits to add - the price is shown on the card 4. Click **Top-up** 5. Credits are added to your balance after payment Top-ups are not available during a free trial - upgrade to a paid plan first. ### Transactions Every credit movement is listed on the **Transactions** tab: the action, the service that charged it, and the amount. Filter by date range or click **Export CSV** to download the ledger for your records. ![The Transactions tab on the Billing page listing credit charges per service](https://docs.graph8.com/images/settings/billing-transactions.png) ## Payment Methods Payment methods are managed in the billing portal: 1. Go to **Settings → Organization → Billing** 2. Click **Invoices & More** - the billing portal opens 3. Under **Payment Method**, click **Add payment method** to add a card, or update the existing one The saved payment method is used for subscription renewals and top-ups. ## Invoices ### Viewing Invoices 1. Go to **Settings → Organization → Billing** 2. Click **Invoices & More** - the billing portal opens 3. Scroll to **Invoice History** to browse your billing history 4. Each invoice shows date, amount, and status - click one to view or download the PDF ![The billing portal showing billing information and invoice history](https://docs.graph8.com/images/settings/billing-portal-invoices.png) ### Failed Payments If a payment fails: 1. You'll receive an email notification 2. graph8 retries the payment automatically 3. Update your payment method in the billing portal if the card is expired or declined 4. Contact support if issues persist ### Billing Information Invoices are issued to the name and email shown under **Billing Information** in the billing portal. Click **Update information** there to change them. ## Frequently Asked Questions ### What happens if I run out of credits? **Trial accounts:** Upgrade to a paid plan - top-ups are not available during the trial. Features that require credits are paused until your balance is replenished. **Paid plans:** Top up from the **Upgrade & Topup** tab, or rely on automatic overage billing where your plan includes it. ### Can I switch from the trial to a paid plan? Yes. Go to **Settings → Organization → Billing** and pick a plan on the **Upgrade & Topup** tab. Your remaining free credits are cleared when the first paid invoice processes, and your plan credits are granted immediately. No additional free credits are granted on upgrade. ### Do free credits expire? Trial credits are available for the length of your trial. After the trial ends, add a payment method or upgrade to a paid plan to keep using the platform. ### Can I get a refund? Contact support for refund requests. Refunds are evaluated on a case-by-case basis within the first 30 days. ### How do I update my billing email? Invoices are sent to the email under **Billing Information** in the billing portal. Go to **Settings → Organization → Billing**, click **Invoices & More**, then click **Update information**. --- > **Tip:** > Keep an eye on the usage bar at the top of the Billing page and top up before your balance runs out to avoid interruptions in enrichment and AI features. --- # Bug Reports > Report bugs directly from graph8 - attach screenshots, track status, and communicate with the team. Source: https://docs.graph8.com/settings/bug-reports/ Bug Reports lets you report issues directly from inside graph8. Attach screenshots, describe the problem, and track the status of your report - all without leaving the app. ## Submitting a Bug Report 1. Click the **Bug Report** icon in the app (or go to **Settings → Bug Reports**) 2. Fill in the report: - **Title** - a short description of the issue - **Description** - what happened and what you expected - **Steps to reproduce** - how to trigger the bug (optional but helpful) - **Severity** - how impactful the issue is (see below) - **Screenshots** - attach screenshots to show the problem 3. Click **Submit** graph8 automatically captures the page URL where you encountered the issue. ## Severity Levels | Severity | When to Use | |----------|------------| | **Critical** | Feature is completely broken, data loss, or security concern | | **High** | Major feature not working, significant workflow blocker | | **Medium** | Feature partially broken, workaround available | | **Low** | Minor UI issue, cosmetic problem, or nice-to-have improvement | ## Tracking Your Reports View all your submitted bug reports in **Settings → Bug Reports**: - **Open** - submitted and awaiting review - **In Progress** - the team is working on it - **Resolved** - fix has been deployed - **Closed** - report closed (fixed or won't fix) Click any report to see its full details, screenshots, and conversation thread. ## Comments Add comments to an open report to provide additional context or ask for updates. The graph8 team can also respond directly on your report. ## Screenshots Attach screenshots when submitting or updating a report. Screenshots are uploaded and stored securely - they're visible in the report detail view. > **Tip:** > Include screenshots whenever possible. A screenshot showing the exact error or unexpected behavior helps the team resolve issues much faster than a text description alone. ## Related - [Company Settings →](https://docs.graph8.com/settings/company/) - Organization configuration - [Users →](https://docs.graph8.com/settings/team/) - Invite and manage organization users --- # Company Settings > Configure your organization's core settings - name, domain, timezone, industry, and branding defaults. Source: https://docs.graph8.com/settings/company/ Company Settings controls your organization's core configuration in graph8. These settings affect how your org appears across the platform and set defaults for scheduling, campaigns, and reports. ## How to Configure 1. Go to **Settings → Company Settings** 2. Update your organization details 3. Click **Save** ## Organization Details | Setting | Description | Impact | |---------|-------------|--------| | **Company name** | Your organization's display name | Appears in reports, notifications, and team views | | **Domain** | Your primary website domain (e.g., `acme.com`) | Used for website tracking, email domain matching, and brand detection | | **Timezone** | Default timezone for your organization | Affects scheduling, report timestamps, and sequence send windows | | **Industry** | Your industry category | Used for benchmark comparisons and AI context | | **Logo** | Your company logo | Displayed in the app header and shared content | ## How These Settings Are Used - **Domain** - graph8 uses your domain to identify your website visitors, match inbound emails, and configure brand assets for campaigns - **Timezone** - all scheduled sends, analytics dashboards, and appointment booking default to this timezone (individual users can override with their own timezone) - **Industry** - AI features use your industry for relevant benchmarking and competitive context in content generation > **Note:** > Company settings are org-wide. Only admins can modify these settings. Changes take effect immediately across all users. ## Related - [Users →](https://docs.graph8.com/settings/team/) - Invite and manage organization users - [Roles & Permissions →](https://docs.graph8.com/settings/roles/) - Configure role-based access - [Billing →](https://docs.graph8.com/settings/billing/) - Manage your subscription - [Domains →](https://docs.graph8.com/settings/domains/) - Configure email sending domains --- # Compliance > Email compliance and suppression management. Source: https://docs.graph8.com/settings/compliance/ Manage email compliance to protect your sender reputation and respect recipient preferences. ## Global Suppression List The global suppression list prevents emails from being sent to addresses that have opted out, bounced, or filed complaints. ### Automatic Additions Addresses are automatically added when: - A recipient clicks an unsubscribe link - A recipient marks your email as spam - An email hard-bounces — **only if** your org has opted in to global suppression on hard bounce (see note below) - You manually add an address > **Note:** > A sequencer **hard bounce always stops further sends to that contact within the sequence**, but by default it does **not** add the address to the global suppression list. To also add hard-bouncers to the org-wide list, enable the hard-bounce suppression toggle in the **Suppression policy** section under **Settings → Channels → Preferences**. This setting is **off by default**, and only hard (permanent) bounces qualify — soft bounces never trigger global suppression. ### Viewing the List 1. Go to **Settings → Compliance** 2. Browse or search the suppression list 3. Filter by reason (opt-out, bounce, complaint, manual) ### Manual Management - **Add** — enter an email address to suppress - **Remove** — remove an address to re-enable sending (use with caution) - **Import** — upload a CSV of addresses to suppress - **Export** — download the full suppression list Removing a previously opted-out address may violate email regulations. Only remove addresses if you have explicit, documented consent. ## DNC (Do Not Contact) The Do Not Contact list goes beyond email — it prevents all outreach channels (email, phone, SMS) from reaching specified contacts. ### Adding to DNC Contacts are added to DNC when: - An SDR marks a call disposition as "DNC" - You manually add a contact or domain - A contact requests removal through any channel ### DNC by Domain Block all contacts at a specific company: 1. Go to **Settings → Compliance → DNC** 2. Click **Add Domain** 3. Enter the domain (e.g., `company.com`) 4. All contacts with emails at that domain are suppressed ## Unsubscribe Handling ### One-Click Unsubscribe graph8 automatically includes a one-click unsubscribe mechanism in outbound emails. This meets requirements from Gmail and Yahoo for bulk senders. ### Unsubscribe Link Customize the unsubscribe link in your email templates: - **Link text** — the visible text (default: "Unsubscribe") - **Landing page** — optional custom unsubscribe confirmation page ### Processing When a recipient unsubscribes: 1. Their address is added to the global suppression list 2. They're removed from all active sequences 3. Future sends to this address are blocked 4. The unsubscribe is logged in the compliance audit trail ## Compliance Rules ### CAN-SPAM graph8 helps you comply with CAN-SPAM requirements: - Physical mailing address included in emails - Clear identification of the message as an advertisement (when applicable) - Unsubscribe mechanism in every commercial email - Opt-out requests honored within 10 business days ### GDPR For contacts in the EU: - Lawful basis for processing (legitimate interest for B2B outreach) - Right to erasure — delete contact data on request - Data portability — export contact data on request - Record of processing activities ## Audit Trail Every compliance-related action is logged: - Suppression additions and removals - Unsubscribe events - DNC additions - Compliance setting changes - Who made the change and when Access the audit trail from **Settings → Compliance → Audit Log**. ## Frequently Asked Questions ### Can I send to someone who previously unsubscribed if they re-opt in? Yes, but only with explicit, documented consent. Remove them from the suppression list and keep a record of their re-opt-in. ### What's the difference between suppression and DNC? The suppression list blocks email only. DNC blocks all channels — email, phone, SMS, and LinkedIn outreach. ### Are bounce removals automatic? A hard bounce always stops further sends to that contact within the sequence. Adding the address to the org-wide **global suppression list** on hard bounce is an opt-in setting (**Settings → Channels → Preferences**, off by default). Soft bounces are treated as transient and never trigger global suppression automatically. ### How do I handle a GDPR data deletion request? Delete the contact record and all associated activity data. The deletion is logged in the audit trail. --- > **Tip:** > Review your suppression list monthly to keep it clean and avoid accidental sends. --- # Deals > Configure deal stages in pipelines. Source: https://docs.graph8.com/settings/deals/ Set up and manage deal stages that track your sales process from first contact to close. ## Pipeline Stages A pipeline is a series of stages that represent your sales process. Deals move through stages as they progress toward closing. ### Viewing Stages Go to **Settings → Deals** to see all stages in your pipeline, displayed in order from first to last. Each stage shows: - **Name** — the stage label visible across the platform - **Type** — Open, Won, or Lost - **Probability** — the likelihood of closing at this stage (used in forecasting) - **Color** — visual identifier in pipeline views ### Adding a Stage 1. Click **Add Stage** 2. Enter the stage name 3. Set the stage type: - **Open** — deal is still in progress - **Won** — deal is closed-won - **Lost** — deal is closed-lost 4. Set the probability percentage 5. Choose a color 6. Save ### Editing a Stage 1. Click the stage in the list 2. Update name, type, probability, or color 3. Save Existing deals in the edited stage update to reflect the new settings. ### Reordering Stages Drag and drop stages to change their order. The order determines how stages appear in the pipeline board view and reports. ### Deleting a Stage 1. Click the delete icon next to the stage 2. Choose where to move existing deals in this stage 3. Confirm You cannot delete a stage without reassigning its deals. ## Multiple Pipelines Create separate pipelines for different sales processes (e.g., New Business, Renewals, Partnerships). ### Creating a Pipeline 1. Go to **Settings → Deals** 2. Click **Add Pipeline** 3. Name the pipeline 4. Add stages specific to this pipeline 5. Save ### Switching Pipelines Each pipeline operates independently. Switch between pipelines from the dropdown in the Deals section. ## Default Pipeline Set one pipeline as the default. New deals are created in the default pipeline unless specified otherwise. 1. Go to **Settings → Deals** 2. Find the pipeline 3. Click **Set as Default** ## Frequently Asked Questions ### Can I move deals between pipelines? Yes. Edit any deal and change its pipeline assignment. The deal will be placed at the first stage of the new pipeline. ### What happens to reports if I change stage names? Historical data is preserved. Reports reference the stage as it was when the deal was in that stage. Current views reflect the updated name. ### How many pipelines can I create? There's no hard limit. Create as many as your sales process requires. --- > **Tip:** > Keep pipelines simple — fewer stages with clear criteria help your team move deals forward. --- # Docs > Control default document generation and meeting transcript extraction by scope. Source: https://docs.graph8.com/settings/docs/ Use **Settings -> Docs** to control which AI-generated documents and library folder templates are enabled by default for each scope. ## Tabs The Docs settings area is split into five top-level tabs: - **Contacts** - person-level intelligence documents - **Companies** - customer and prospect account-document defaults - **Campaigns** - campaign document defaults - **Global** - Global Context document defaults - **Meetings** - meeting transcript grounding and extraction rules Each top-level tab includes a secondary menu: - **Contacts**: `Defaults`, `Library Folder Tree` - **Companies**: `Customers Defaults`, `Prospects Defaults`, `Library Folder Tree` - **Campaigns**: `Defaults`, `Library Folder Tree` - **Global**: `Defaults`, `Library Folder Tree` - **Meetings**: `Meeting Grounding` plus extraction scopes for `Shared`, `Customer Calls`, `Prospect Calls`, and `Internal` ## How Defaults Work Each defaults tab shows the documents available for that scope. When a document is checked, it becomes part of the organization's default set for that scope. - Changes save automatically - Required documents cannot be unchecked - Estimated generation time is shown inline with the secondary menu and updates as you change the selection ## Company Scopes Customer and prospect companies can have different defaults. - **Customers Defaults** are intended for active accounts - **Prospects Defaults** are intended for lighter-weight research on target accounts If a company is still marked as a prospect, the account document generator uses the **Prospects Defaults** selection. ## Folder Templates Every top-level scope includes a **Library Folder Tree** tab. These folder templates are stored at the org level and can be edited by adding, renaming, reordering, or removing folders. Locked defaults remain enforced. Company records share one library template across customers and prospects. By default, the company Library includes: - `Library` - `Library/Transcripts` - `Library/Call Recordings` - `Library/Contracts and SOW` - `Library/Objectives and Plan` - `Library/Weekly Client Summaries` - `Library/Handoff and Next Steps` - `Library/Reporting and QBR` - `Library/Client Materials` - `Library/Security Questionnaires` Campaign and Global each have their own default folder presets. Contacts currently support folder-template management even though their document defaults are still evolving. ## Meetings Tab The **Meetings** tab controls how graph8 interprets and summarizes meeting transcripts for your organization. ### Meeting Grounding Use **Meeting Grounding** to define how graph8 should recognize your company and participants in transcripts: | Setting | What It Controls | |---------|-----------------| | **Canonical Company Name** | Your preferred internal-side company name when transcript wording is ambiguous | | **Internal Domains** | Domains treated as your internal team in meetings | | **Brand / Alias Resolution** | Maps alternate names or brands to a canonical company name | | **Participant Interpretation Rules** | Org-specific rules that bias transcript interpretation | ### Extraction Scopes Meetings extraction is organized into four scopes: - **Shared** - rules that apply to all meetings - **Customer Calls** - extraction priorities for active customer conversations - **Prospect Calls** - extraction priorities for sales conversations - **Internal** - extraction priorities for internal team meetings Each scope lets you configure: | Control | What It Does | |---------|--------------| | **Always Extract** | Fields that should always be structured from the meeting | | **Listen For** | Topics and signals the extractor should prioritize | | **Custom Fields** | Org-specific findings to capture in meeting output | | **Vocabulary / Terms** | Acronyms and terminology that need added context | Meeting settings are saved at the org level and influence how transcript summaries, key topics, and structured meeting findings are generated. ## Priority Tab The **Priority** tab controls the default priority assigned to documents in the library. Priority determines how strongly the copilot weighs a document when generating content - higher priority documents are preferred over lower ones when there is a conflict or overlap. | Setting | Default | What It Controls | |---|---|---| | **Uploaded documents priority** | High | Priority assigned to documents you upload manually | | **AI-generated documents priority** | Medium | Priority assigned to documents created by AI workflows | Changes apply to all new documents created after saving. Existing documents keep their current priority unless updated individually. ### Priority Levels | Level | When to Use | |---|---| | **High** | Source-of-truth documents - approved brand guidelines, legal-reviewed materials, finalized campaign briefs | | **Medium** | AI-generated drafts, research documents, working materials | | **Low** | Reference materials, archived versions, supplementary context | ## Notes - Customer and prospect document defaults are already used by company document generation flows - Folder-template settings are exposed through the org settings API for `companies`, `contacts`, `campaigns`, and `global` - Meetings settings are used by meeting transcript extraction and structured meeting summaries - Priority settings apply org-wide - individual document priority can be overridden per document in the library --- # Domains > Manage email sending domains and DNS. Source: https://docs.graph8.com/settings/domains/ Configure your sending domains to ensure reliable email delivery from graph8. ## Adding a Domain Connect a domain you own to use it for outbound email. 1. Go to **Settings → Domains** 2. Click **Add Domain** 3. Enter your domain name (e.g., `outreach.yourcompany.com`) 4. graph8 generates the required DNS records 5. Add the records to your DNS provider 6. Click **Verify** Using a subdomain (like `outreach.yourcompany.com`) is recommended to protect your primary domain's reputation. ## DNS Records graph8 requires three types of DNS records for email authentication: ### SPF (Sender Policy Framework) Tells receiving mail servers that graph8 is authorized to send email on your behalf. - **Record type**: TXT - **Host**: your sending domain - **Value**: provided by graph8 during setup ### DKIM (DomainKeys Identified Mail) Adds a cryptographic signature to outbound emails, proving they haven't been tampered with. - **Record type**: CNAME or TXT - **Host**: selector provided by graph8 - **Value**: provided by graph8 during setup ### DMARC (Domain-based Message Authentication) Tells receiving servers what to do with emails that fail SPF or DKIM checks. - **Record type**: TXT - **Host**: `_dmarc.yourdomain.com` - **Value**: your DMARC policy (graph8 provides a recommended starting policy) ## Verification After adding DNS records, click **Verify** to check that everything is configured correctly. ### Verification Statuses - **Verified** — all DNS records are correct and active - **Pending** — records are added but DNS propagation is in progress (can take up to 48 hours) - **Failed** — one or more records are missing or incorrect ### Troubleshooting If verification fails: - Confirm records are added to the correct domain/subdomain - Check for typos in record values - Wait for DNS propagation (up to 48 hours) - Verify your DNS provider doesn't strip or modify record values ## Domain Health Once verified, graph8 monitors your domain's sending reputation. ### Health Indicators - **Reputation Score** — overall sending reputation - **Bounce Rate** — percentage of emails bouncing - **Spam Complaints** — reports from recipients - **Warmup Status** — whether the domain is still in warmup ### Warmup New domains start with a warmup period where sending volume gradually increases. This builds a positive reputation with email providers. - Warmup runs automatically when enabled - Sending limits increase gradually over the warmup period - graph8 manages warmup email volume and pacing ## Placement Tests Placement tests show where your emails land — inbox, spam, or promotions — across major email providers. This helps you understand your deliverability and identify issues before running campaigns. ### Running a Placement Test 1. Go to **Settings → Domains** 2. Select a domain 3. Click **Run Placement Test** graph8 sends test emails and measures where they land for each provider. ### Results Results include: - Inbox placement rate - Spam placement rate - Provider-level breakdown (e.g., Gmail, Outlook) Use these insights to adjust your sending setup and improve deliverability before launching sequences. ## Multi-Domain Strategy Sending outbound at scale from a single domain is risky — one deliverability issue can shut down your entire outbound operation. A multi-domain setup distributes risk and lets you scale without burning reputation. ### When to Use Multiple Domains | Outbound Volume (emails/day) | Recommended Domains | |-----------------------------|---------------------| | 0–200 | 1 sending domain | | 200–1,000 | 2–3 sending domains | | 1,000–5,000 | 4–8 sending domains | | 5,000+ | 10+ sending domains, rotated | ### Domain Naming Patterns Use subdomains or lookalike domains that preserve brand association without risking your primary: | Pattern | Example (for `acme.com`) | Notes | |---------|--------------------------|-------| | **Subdomain** | `go.acme.com`, `try.acme.com` | Shares some reputation with parent; usually fine | | **Hyphenated** | `acme-team.com`, `get-acme.com` | Completely separate reputation | | **TLD variation** | `acme.io`, `acme.co` | Fully isolated; easy to provision | | **Product-themed** | `acmesales.com`, `hello-acme.com` | Good for multi-product orgs | > **Caution:** > Never send outbound from your primary company domain. A single spam complaint can affect your CEO's email deliverability. ### Per-Domain Warmup Every new domain requires its own warmup period — reputation doesn't transfer across domains even if they look related. Warmup takes 2–3 weeks per domain. Stagger domain launches so you always have a pool of warm domains available. See [Email Warmup →](https://docs.graph8.com/settings/email-warmup/) for the full warmup process. ## Domain Rotation Domain rotation sends from a pool of domains so no single domain handles all your volume. graph8 can auto-rotate or you can set rotation rules. ### Rotation Modes | Mode | Behavior | |------|----------| | **Round-robin** | Each email picks the next domain in the pool | | **Volume-weighted** | Higher-reputation domains get more volume | | **Campaign-pinned** | Each campaign uses one dedicated domain | | **Persona-pinned** | Enterprise vs. SMB outbound use different domains | ### Configuring Rotation 1. Go to **Settings → Domains → Rotation** 2. Select the domains to include in the pool 3. Pick the rotation mode 4. Set daily caps per domain (e.g., 500/day max) 5. Save Rotation applies to any mailbox attached to a rotation-enabled pool. Individual sequences can override the pool and pin to specific domains. ### Retiring a Domain If a domain's reputation drops below recovery (placement score < 50% for 2+ weeks): 1. Pause all mailboxes on the domain 2. Remove the domain from active rotation pools 3. Let the domain sit for 30 days minimum (reputation partially heals) 4. Re-warm from scratch before reusing — or retire permanently ## DNS Troubleshooting When verification fails, these are the most common root causes in order of frequency: ### SPF Issues | Symptom | Cause | Fix | |---------|-------|-----| | "SPF record not found" | No `v=spf1` TXT record on the domain | Add the graph8-provided SPF record | | "Multiple SPF records" | More than one `v=spf1` TXT record | Merge into one record with multiple `include:` directives | | "SPF too long" | Record exceeds 255 characters | Split using `redirect=` to a dedicated SPF domain | | "SPF include lookup limit" | More than 10 DNS lookups via `include:` | Flatten nested includes or use a professional SPF tool | ### DKIM Issues | Symptom | Cause | Fix | |---------|-------|-----| | "DKIM record not found" | CNAME or TXT record missing | Add the graph8-provided DKIM record | | "DKIM signature mismatch" | Wrong key in the DNS record | Copy the key from graph8's setup page exactly — no modifications | | "DKIM alignment fail" | Sending domain doesn't match DKIM domain | Use the correct selector provided by graph8 | | "DKIM selector conflict" | Another service uses the same selector | Use a unique selector like `g8._domainkey` | ### DMARC Issues | Symptom | Cause | Fix | |---------|-------|-----| | "DMARC record missing" | No `_dmarc` TXT record | Add `v=DMARC1; p=none; rua=mailto:dmarc@yourdomain.com` to start | | "DMARC too strict too early" | `p=reject` set before verification | Change to `p=none` during setup, move to `p=quarantine` then `p=reject` over time | | "DMARC reports flooding inbox" | High `rua` report volume | Use a DMARC report aggregator instead of a direct mailbox | ### Propagation Issues | Symptom | Likely Cause | |---------|-------------| | "Verified, then failed the next day" | TTL too low on an older record; DNS cache served stale data | | "Took 48+ hours to verify" | DNS provider has slow propagation (common with some registrars) | | "Verifies from one check tool but not another" | Records are propagating unevenly; wait 24 hours | ### DNS Provider Quirks Some providers require extra steps: - **Cloudflare** — if "Proxy" is enabled (orange cloud) on a CNAME, graph8 can't verify. Set to "DNS only" (gray cloud). - **GoDaddy** — wildcard records (`*`) can interfere. Use explicit hostnames. - **Google Domains** — recent transitions to Squarespace Domains may require re-adding records. - **Namecheap** — "Email forwarding" can conflict with MX; disable if not needed. ## Health Alerts graph8 actively monitors your domains and sends alerts before problems become severe. ### Alert Triggers | Alert | Threshold | |-------|-----------| | **Reputation drop** | Score drops 15+ points in 7 days | | **Bounce spike** | Bounce rate >5% on a single day | | **Spam complaint spike** | >0.1% complaint rate (Gmail's limit is 0.3%) | | **Placement drop** | Inbox placement drops below 80% | | **Blacklist listed** | Domain appears on Spamhaus, SORBS, or SpamCop | | **DNS record removed** | SPF, DKIM, or DMARC record disappears from DNS | ### Responding to Alerts When you receive an alert: 1. **Pause outbound** for the affected mailboxes immediately 2. **Check the root cause** — recent content change? List quality issue? Volume spike? 3. **Run a placement test** to confirm severity 4. **Let warmup rebuild** for 1–2 weeks before resuming 5. **Don't "push through" alerts** — reputation damage compounds quickly Configure alert delivery (email, Slack, webhook) at **Settings → Alerts**. ## Frequently Asked Questions ### Should I use my primary domain or a subdomain? Always use a subdomain for outbound sales email. This protects your primary domain's reputation. If the sending subdomain's reputation drops, your main company email isn't affected. ### How long does DNS propagation take? Most DNS changes propagate within a few hours, but it can take up to 48 hours. If verification fails, wait and try again. ### Can I add multiple domains? Yes. Add as many sending domains as you need. Each domain requires its own DNS records, verification, and warmup. See the Multi-Domain Strategy section above for when to add more. ### What happens if my domain health drops? graph8 alerts you when reputation metrics decline. Reduce sending volume, review your contact lists for invalid addresses, and check that your email content follows best practices. If health stays low for 2+ weeks, rotate the domain out of active sending and warm it back up. ### How do I migrate mailboxes between domains? You can't transfer mailbox reputation between domains — each mailbox is tied to its domain. To switch domains, create new mailboxes on the new domain and start a fresh warmup. Retire the old mailboxes gradually. ### Can I send from a domain I don't own? No. graph8 verifies domain ownership via DNS. You must control the DNS records to add a domain. If you're sending on behalf of a client, they need to add the DNS records on their end. --- > **Tip:** > Monitor domain health weekly and address issues early before they impact deliverability. Set up weekly placement tests across your top 3 domains — the cost is trivial compared to the cost of having a campaign go to spam for weeks before anyone notices. ## Related - [Purchase Domains →](https://docs.graph8.com/settings/purchase-domains/) — Buy and auto-configure domains in graph8 - [Email Warmup →](https://docs.graph8.com/settings/email-warmup/) — Warm up new domains before sending - [Mailboxes →](https://docs.graph8.com/settings/mailboxes/) — Connect email accounts to your domains --- # Email Warmup > Build sender reputation for new mailboxes with automated email warmup - configure daily limits, ramp-up speed, and monitor deliverability. Source: https://docs.graph8.com/settings/email-warmup/ Email Warmup automatically builds sender reputation for new mailboxes by sending and receiving engagement emails at a controlled pace. This ensures your outbound emails land in the inbox, not spam. ## Why Warmup Matters New email accounts have no reputation with inbox providers (Gmail, Outlook, Yahoo). Sending a high volume of cold emails from a new account triggers spam filters. Warmup solves this by gradually increasing send volume while generating positive engagement signals (opens, replies, moves from spam to inbox). ## How Warmup Starts For purchased domains and mailboxes, **warmup starts automatically** in the background. A background worker enrolls the mailbox in warmup as soon as it's provisioned; no manual action required. If you need to start warmup manually (or restart it after a pause), you can do so from the Purchase Domain & Mailboxes section. ## Manual Start (Purchased Mailboxes) Manual start is available **only from the Purchase Domain & Mailboxes page**, not from the standard Mailboxes page or the Domains page. 1. Go to **Settings → Channels → Purchase Domain/Mailboxes** 2. On the **Domains** tab, click the domain that owns the mailbox 3. Switch to the **Mailboxes** tab for that domain 4. Click the mailbox you want to warm up 5. Open the **Warmup** tab 6. Click **Settings** to configure warmup parameters, or **Refresh** to pull the latest stats ## Warmup Settings From the mailbox's **Warmup** tab, click **Settings** to adjust: | Setting | Description | Recommended | |---------|-------------|-------------| | **Daily limit** | Maximum emails sent per day during warmup | Start at 5, ramp to 40+ | | **Ramp-up speed** | How quickly daily volume increases | Gradual (2-3 weeks to full volume) | | **Reply rate** | Target percentage of warmup emails that get replies | 30–40% | > **Tip:** > Keep warmup running for at least 2-3 weeks before sending cold outbound. Even after outbound starts, keep warmup active to maintain reputation. ## Outbound Sending Limits During Warmup Warmup emails and sequence emails are separate. While a mailbox builds reputation, graph8 also limits how many **cold outbound (sequence) emails** it can send per day, based on the mailbox's age: | Mailbox age | Sequence emails per day | |-------------|------------------------| | First 2 weeks | 0 (warm-up only, no cold sends) | | Week 3 | 5 | | Week 4 | 10 | | Week 5 and beyond | 15 (fully warmed) | Key points: - The ramp is anchored to when the mailbox was created in graph8 - It applies to cold outbound sequences only; nurture campaigns are exempt - Once fully warmed, each mailbox sends up to **15 sequence emails per day** (a deliverability safeguard that keeps volume well under mailbox provider limits) - The ramp is enabled by default and can be adjusted for your organization - To send more total volume per day, add more mailboxes to your sequences; there is no limit on how many mailboxes a sequence can use ## Warmup Providers graph8 supports two warmup providers: | Provider | Best For | |----------|----------| | **Instantly** | Default provider, works with most mailboxes | | **SmartLead** | Alternative provider, used with purchased domains | Provider selection happens at the domain level when the domain is purchased. ## Monitoring Warmup Progress The **Warmup** tab on a mailbox shows real-time analytics. ### Summary (Last 7 Days) Four headline cards summarize recent activity: | Metric | What It Shows | |--------|---------------| | **Warmup emails sent** | Total warmup messages delivered in the last 7 days | | **Landed in inbox** | Count of warmup emails that landed in the primary inbox | | **Saved from spam** | Count of warmup emails that were marked "not spam" by the warmup network | | **Emails replied** | Count of warmup emails that received a reply | ### Daily Chart: Warmup Emails Sent, Spam & Replied A daily stacked bar chart shows: | Series | Meaning | |--------|---------| | **Sent** | Warmup emails delivered that day | | **Replied** | Warmup emails that received a reply | | **Landed in Spam** | Warmup emails that landed in spam; should trend toward zero | | **Saved from Spam** | Warmup emails recovered from spam by the warmup network | Click **Refresh** to pull the latest data from the warmup provider. ### What a Healthy Warmup Looks Like | Indicator | Healthy Range | |-----------|---------------| | **Daily volume** | Steadily increasing for the first 2–3 weeks, then steady | | **Reply rate** | Above 25% | | **Inbox placement** | Above 90% | | **Spam volume** | Trending toward zero | If spam volume stays high for several days, pause outbound sending and let warmup rebuild reputation. ## Placement Testing Placement testing measures where your emails actually land (primary inbox, promotions, spam, or quarantine) across Gmail, Outlook, Yahoo, and other major providers. This is the single most reliable deliverability signal; reputation scores lag, but placement tests reveal problems immediately. ### When to Run a Test | Scenario | Recommended Frequency | |----------|----------------------| | **New domain warmup** | Weekly during the first 4 weeks | | **Ongoing outbound at scale** | Weekly | | **Before a high-volume campaign** | 24–48 hours before launch | | **After a deliverability incident** | Daily until 3 consecutive weeks pass with ≥90% inbox | | **After DNS or infrastructure change** | Within 24 hours of the change | ### Running a Test 1. Go to **Settings → Channels → Purchase Domain/Mailboxes** 2. Click a domain 3. Open the **Placement** tab 4. Configure the test (mailbox, frequency, receivers, content) 5. Start the test Results arrive within 5–15 minutes. graph8 sends a seeded email to test mailboxes in each provider and reports where it landed. ### Reading Results Test results show placement per provider: | Provider | Inbox | Promotions | Spam | Other | |----------|-------|-----------|------|-------| | Gmail | 95% | 2% | 3% | 0% | | Outlook | 88% | - | 10% | 2% | | Yahoo | 92% | - | 5% | 3% | | iCloud | 90% | - | 7% | 3% | | Column | What It Means | |--------|---------------| | **Overall score** | Weighted average across all providers (B2B inboxes weighted higher) | | **Inbox** | Delivered to primary inbox; the only "good" outcome | | **Promotions** | Gmail-only tab; better than spam but filtered out of most reading sessions | | **Spam** | Landed in junk folder; recipient unlikely to see it | | **Other** | Quarantine, bulk, blocked, or not delivered | ### Placement Score Benchmarks | Overall Score | Health | Action | |---------------|--------|--------| | **≥95%** | Excellent | Keep warmup running; send confidently | | **85–94%** | Healthy | Normal sending; monitor weekly | | **70–84%** | Warning | Reduce daily send volume by 30–50%; investigate content and list hygiene | | **50–69%** | Unhealthy | Pause outbound; resume warmup-only for 1–2 weeks | | **Below 50%** | Critical | Stop sending from this mailbox; review DNS, content, list quality, and consider a new domain | > **Caution:** > If placement drops below 80% inbox rate, pause outbound sending and let warmup rebuild reputation for 1-2 weeks before resuming. ### Diagnosing Low Placement Match the symptom to the most likely cause: | Symptom | Likely Cause | Fix | |---------|-------------|-----| | **High spam rate across all providers** | Domain reputation or content | Check content for spam triggers; slow ramp | | **Gmail inbox drops, others fine** | Engagement with Gmail recipients is low | Increase reply rate in warmup; improve subject lines | | **Gmail moves emails to Promotions tab** | Content looks promotional (images, tracking, CTAs) | Remove images/buttons; use plain-text format | | **Outlook spam spikes** | SFB (smart network data) flagged your IP | Check SNDS score; consider pausing Outlook sending for a week | | **Yahoo/AOL bounce spike** | Dedicated IP reputation issue | Check for hard-bounce volume; clean list | | **Corporate (B2B) domains reject** | Missing DKIM alignment or strict DMARC policy | Verify DKIM is aligned; align `From` domain with DKIM domain | ### Scheduled Tests Set up recurring placement tests to catch deliverability issues early: 1. In the placement test configuration, select **Recurring** 2. Choose frequency (daily, weekly, bi-weekly) 3. Set **Alert threshold**: if inbox drops below this, graph8 sends an email or Slack alert 4. graph8 runs tests automatically and alerts you if placement drops Scheduled tests count against your monthly test quota. See **Settings → Billing** for current quota. ### Multi-Mailbox Rotation Testing If you rotate across multiple mailboxes in a sequence, test each mailbox independently; placement varies per mailbox even on the same domain. Create one recurring test per mailbox and compare scores. Retire mailboxes that consistently score below 80%. ## Best Practices - **Trust the automatic start**: for purchased domains, warmup begins immediately on provisioning - **Don't skip warmup on purchased domains**: new domains have zero reputation - **Keep warmup running alongside outbound**: warmup maintains reputation during active sending - **Monitor weekly**: check the Warmup tab and run placement tests - **Use multiple domains**: spread sending across domains to protect reputation - **Pause outbound before warmup**: if reputation drops, stop outbound first; warmup-only sending recovers reputation faster ## Troubleshooting | Issue | Fix | |-------|-----| | Warmup not running for a new mailbox | Verify the mailbox is fully provisioned. The background worker enrolls mailboxes within 5–10 minutes of activation | | Want to restart warmup manually | Open **Settings → Channels → Purchase Domain/Mailboxes → [domain] → Mailboxes → [mailbox] → Warmup** and adjust **Settings** | | Low inbox placement | Reduce daily send volume in warmup settings, check DNS records (SPF, DKIM, DMARC), pause outbound for 1–2 weeks | | Warmup paused unexpectedly | Check mailbox health; provider may have flagged the account. Reconnect OAuth if expired | | Stats not updating | Click **Refresh** on the Warmup tab to pull the latest data from the warmup provider | | Can't find Warmup controls | Manual warmup management is only available for purchased mailboxes via **Settings → Channels → Purchase Domain/Mailboxes**. Self-connected mailboxes use automatic warmup only | ## Related - [Mailboxes →](https://docs.graph8.com/settings/mailboxes/) - Connect and configure email accounts - [Domains →](https://docs.graph8.com/settings/domains/) - DNS setup and domain management - [Purchase Domains →](https://docs.graph8.com/settings/purchase-domains/) - Buy domains with auto-configured DNS --- # Integrations > Connect third-party tools and services. Source: https://docs.graph8.com/settings/integrations/ Connect graph8 with your CRM, communication tools, and other services. ## CRM Integrations ### HubSpot Sync contacts, companies, deals, and activities with HubSpot. **Connecting:** 1. Go to **Settings → Integrations** 2. Click **HubSpot** 3. Click **Connect** 4. Sign in to your HubSpot account 5. Authorize graph8 access ### Salesforce Sync leads, contacts, accounts, opportunities, and activities with Salesforce. **Connecting:** 1. Click **Salesforce** 2. Click **Connect** 3. Sign in to your Salesforce org 4. Authorize graph8 access Connect with a Salesforce user that can see and edit the Lead, Contact, and Account fields you want synced. Using a dedicated integration user? Give it the right permissions first, then click **Re-check** in the Salesforce settings if graph8 reports fields it cannot access. See the [Salesforce integration user permissions guide →](https://docs.graph8.com/settings/salesforce-integration-user-permissions/) ### Pipedrive Sync contacts (persons), organizations, deals, leads, and activities with Pipedrive. Pipedrive **labels** (Customer, Hot lead, and so on) appear in graph8 as lists, so you can pull or push exactly the records carrying a given tag. **Connecting:** 1. Click **Pipedrive** 2. Click **Connect** 3. Sign in to your Pipedrive account (use a Pipedrive admin account) 4. Authorize graph8 access For the full setup guide, including pull and push configuration, label-based list sync, and field mapping, see the [Pipedrive integration guide →](https://docs.graph8.com/settings/pipedrive/) ### Zoho CRM Sync contacts, companies, leads, deals, and activities with Zoho CRM. **Connecting:** 1. Click **Zoho** 2. Click **Connect** 3. Sign in to your Zoho account 4. Authorize graph8 access ### SugarCRM Sync contacts, companies, leads, deals, appointments, and activities with SugarCRM. Unlike other CRMs, SugarCRM connects directly using your instance credentials rather than OAuth. **Connecting:** 1. Click **SugarCRM** 2. Click **Connect** 3. Enter your SugarCRM instance URL (e.g., `https://yourcompany.sugarondemand.com`) 4. Enter your SugarCRM username and password 5. Click **Connect** **Supported entities:** Contacts, companies (Accounts), leads, deals (Opportunities), appointments (Meetings), and activities (calls, notes, tasks). **Field mapping:** Standard fields are auto-mapped for contacts, companies, and deals. Custom field mappings can be configured in the Settings modal under the Field Mapping tab. **Disconnecting:** Disconnecting clears stored credentials immediately. Reconnecting requires re-entering your instance URL and credentials. ### What Syncs | Data Type | HubSpot | Salesforce | Pipedrive | Zoho CRM | SugarCRM | |-----------|---------|------------|-----------|----------|----------| | Contacts | Yes | Yes | Yes (Persons) | Yes | Yes | | Companies | Yes | Yes (Accounts) | Yes (Orgs) | Yes | Yes (Accounts) | | Leads | Yes | Yes | Yes | Yes | Yes | | Deals | Yes | Yes (Opportunities) | Yes | Yes | Yes (Opportunities) | | Activities | Yes | Yes | Yes | Yes | Yes | | Appointments | — | — | Yes (Activities) | Yes | Yes (Meetings) | | Lists | Yes | Yes | Yes (Labels) | — | — | ## Communication Integrations ### Slack Receive notifications and alerts in Slack channels. **Setup:** 1. Click **Slack** 2. Click **Connect** 3. Select the Slack workspace 4. Choose default notification channels 5. Configure which events trigger notifications **Notification types:** - New leads and contacts - Deal stage changes - Sequence completions - Meeting bookings - Team alerts ### Email Providers Configure email sending through connected providers. See [Mailboxes](https://docs.graph8.com/settings/mailboxes/) for email account setup. ## Enrichment Integrations Connect data providers for contact and company enrichment. graph8 uses a waterfall approach — querying multiple providers in sequence to maximize data coverage. Configuration is managed automatically. Enrichment runs when contacts are added or updated. ## Calendar ### Google Calendar Sync meetings and appointments with Google Calendar. **Connecting:** 1. Click **Google Calendar** 2. Sign in with your Google account 3. Select which calendars to sync 4. Configure sync direction (one-way or two-way) ### Microsoft Outlook Sync with Outlook calendars via Microsoft 365. **Connecting:** 1. Click **Microsoft Outlook** 2. Sign in with your Microsoft account 3. Authorize access 4. Configure sync settings ## Meeting Integrations ### Roam Sync meeting transcripts and recordings into your inbox automatically. **Connecting:** 1. Go to **Settings → Integrations** 2. Click **Roam** 3. Click **Connect** 4. Sign in and authorize graph8 access Once connected, graph8 automatically syncs your meeting transcripts and recordings into the inbox. | Data | Sync Direction | |------|---------------| | Transcripts | Roam → graph8 | | Recordings | Roam → graph8 | > **Note:** > If you connected Roam before March 2026, disconnect and reconnect to pick up the required scopes for transcripts and recordings. ## Custom Integrations ### Webhooks Send data to any service via webhooks. See [API](https://docs.graph8.com/settings/api/) for webhook configuration. ### Zapier / Make Connect graph8 to thousands of apps through Zapier or Make using the graph8 API. ## Managing Integrations ### Connection Status Each integration shows its current status: - **Connected** — active and syncing - **Disconnected** — connection lost or revoked - **Error** — sync issue requiring attention ### Re-authorizing If a connection expires or permissions change: 1. Click the integration 2. Click **Re-authorize** 3. Sign in and grant permissions again ### Disconnecting 1. Click the integration 2. Click **Disconnect** 3. Confirm Disconnecting stops all syncing. Previously synced data remains in graph8. ### Sync Status View sync details for each integration: - **Last sync** — when data last synced - **Records synced** — count of records in the latest sync - **Errors** — any records that failed to sync ## Frequently Asked Questions ### Can I connect multiple CRMs? Yes, you can connect multiple CRM instances. Each syncs independently. Be mindful of duplicate records when using multiple CRMs. ### What happens to my data if I disconnect an integration? Data already synced into graph8 remains. No new data flows in or out. Reconnecting resumes syncing from where it left off. ### How often does data sync? Sync frequency depends on the integration and your plan. Most integrations sync every 15 minutes to 1 hour. Real-time sync is available on Enterprise plans. ### Can I control which fields sync? Yes. Each CRM integration has field mapping settings where you can choose which fields sync in each direction. See [Connections](https://docs.graph8.com/engage/connections/) for detailed sync configuration. --- > **Tip:** > Start with your CRM integration — it's the foundation for keeping graph8 and your sales tools in sync. --- # Mailboxes > Connect and manage email mailboxes. Source: https://docs.graph8.com/settings/mailboxes/ Connect email accounts to send and receive messages through graph8. ## Opening Mailbox Settings Open **Settings** from your profile menu (top-right avatar), then go to **Channels → Mailboxes**. You can also open [app.graph8.com/studio/settings?tab=mailboxes](https://app.graph8.com/studio/settings?tab=mailboxes) directly. ## Mailbox List ![The Mailboxes page under Settings → Channels, showing the mailbox table with provider, daily limit, warm-up, and status columns](https://docs.graph8.com/images/settings/mailboxes-list.png) The mailbox list displays all connected mailboxes in a single table view. Switch between tabs to filter: - **All**: every mailbox you can access - **Team Mailboxes**: all mailboxes across the organization - **Archived**: deactivated mailboxes Each row shows the mailbox email, provider, signature status, daily send limit, auto-respond setting, warm-up progress, and current **Status**. Use the **Columns** sidebar to customize which columns are visible. Click any mailbox to view or edit its details. Users manage their own personal mailboxes in **Profile → Apps**. ## Adding vs. Purchasing Mailboxes There are two ways to get a mailbox into graph8: - **Add a mailbox**: connect an email account you already own (Gmail or any SMTP/IMAP provider) using the steps below. - **Purchase mailboxes**: buy brand-new mailboxes from graph8. Purchase a domain first, then create mailboxes on it. Purchased mailboxes come with automatic DNS configuration and warmup enrollment. See [Purchase Domain & Mailboxes](https://docs.graph8.com/settings/purchase-domains/) for details. ## Connecting Mailboxes ![The Add Mailbox menu open on the Mailboxes page, showing the Sign in with Google and New SMTP Connection options](https://docs.graph8.com/images/settings/mailboxes-add-menu.png) ### Gmail (OAuth) Connect a Gmail or Google Workspace account with one-click OAuth: 1. Open **Settings** from your profile menu, then go to **Channels → Mailboxes** 2. Click **Add Mailbox** 3. Select **Sign in with Google** 4. Sign in with your Google account 5. Grant the required permissions 6. The mailbox appears in your list ### SMTP/IMAP ![The New SMTP Mailbox dialog with SMTP and IMAP credential fields](https://docs.graph8.com/images/settings/mailboxes-smtp-dialog.png) Connect any email provider that supports SMTP and IMAP: 1. Click **Add Mailbox** 2. Select **New SMTP Connection** 3. Enter your email address 4. Configure SMTP settings (host, port, username, password) 5. Configure IMAP settings (host, port, username, password) 6. Test the connection 7. Save Contact your email provider for the correct SMTP/IMAP server details. ## Mailbox Settings Each connected mailbox has configurable settings: ### Sending Limits - **Daily limit**: maximum emails sent per day from this mailbox - **Hourly limit**: maximum emails sent per hour - Limits protect your sender reputation by preventing over-sending For outbound sequences, each mailbox sends up to **15 emails per day** once fully warmed up. New mailboxes ramp up gradually over their first 5 weeks. See [Email Warmup →](https://docs.graph8.com/settings/email-warmup/#outbound-sending-limits-during-warmup) for the full ramp schedule. ### Signature - Set a default email signature for the mailbox - Signatures are appended to outbound emails automatically - HTML formatting is supported ### Reply-To Address - Optionally set a different reply-to address - Replies go to the specified address instead of the sending mailbox ## Warmup Warmup is available for mailboxes created through **Settings → Channels → Purchase Domain/Mailboxes**. See [Purchase Domain & Mailboxes](https://docs.graph8.com/settings/purchase-domains/#warmup) for warmup setup, settings, and validation rules. ## Health Monitoring Track the health of each mailbox from the mailboxes list. ### Metrics - **Bounce rate**: percentage of emails that bounce back - **Reputation**: estimated sender reputation score - **Warmup progress**: current warmup status and volume - **Last sync**: when the mailbox last synced with the email server ### Alerts graph8 alerts you when: - Bounce rate exceeds safe thresholds - Reputation score drops significantly - Sync fails or connection is lost - Warmup detects deliverability issues ## Shared Mailboxes Share a mailbox across team members for collaborative sending. ### Workspace Assignment Assign mailboxes to workspaces so team members in that workspace can use them: 1. Click the mailbox 2. Go to **Sharing** 3. Select the workspace(s) to share with 4. Save ### Access Control - **Owner**: the person who connected the mailbox; full control - **Shared users**: can send from the mailbox but cannot modify settings ## Bulk Updates You can update multiple mailboxes at once to save time when managing large setups. Select multiple mailboxes, then click **Bulk Update** to apply changes. You can update: - Profile pictures - Email signatures Changes apply to all selected mailboxes simultaneously. ## Frequently Asked Questions ### Can I buy mailboxes from graph8 instead of connecting my own? Yes. Purchase a domain first, then create mailboxes on it. See [Purchase Domain & Mailboxes](https://docs.graph8.com/settings/purchase-domains/) for the full flow. ### How many mailboxes can I connect? Depends on your plan. Check **Settings → Billing** for your mailbox limit. ### Why is my mailbox showing a sync error? Common causes: password changed, OAuth token expired, email provider blocking access. Reconnect the mailbox to resolve. ### Should I warmup every mailbox? Yes, warmup is recommended for all mailboxes used in outbound sequences. It's less critical for mailboxes used only for one-to-one replies. ### Can I use one mailbox in multiple sequences? Yes, but respect the daily sending limit. graph8 automatically manages send volume across sequences using the same mailbox. ### How many emails can each mailbox send per day in sequences? Up to **15 per day** once the mailbox is fully warmed up (about 5 weeks after it was created in graph8). Newer mailboxes send less while they build reputation: nothing in the first 2 weeks, then 5, 10, and 15 per day in the following weeks. See [Email Warmup →](https://docs.graph8.com/settings/email-warmup/#outbound-sending-limits-during-warmup). ### How many mailboxes can I add to a sequence? There is no limit. Sends rotate across all mailboxes assigned to the sequence, so adding more mailboxes is how you increase total daily volume. For example, 4 fully warmed mailboxes can send up to 60 emails per day combined. --- > **Tip:** > Connect multiple mailboxes and rotate them across sequences to distribute sending volume. --- # Phone Numbers > Manage phone numbers for calls and SMS. Source: https://docs.graph8.com/settings/phone-numbers/ Provision and manage phone numbers for outbound calls, inbound calls, and SMS. ## Provisioning Numbers Purchase phone numbers directly in graph8. ### Buying a Number 1. Go to **Settings → Phone Numbers** 2. Click **Add Number** 3. Select the country code to browse numbers in that region 4. Select the number type: - **Local** — a number with a local area code - **Toll-Free** — a toll-free number (e.g., 800, 888) 5. Search by area code or region 6. Select a number from available options 7. Click **Purchase** graph8 supports phone numbers in the US and internationally. Local regulations may apply — some countries require address verification before provisioning. ### Number Types - **Local numbers** — best for outbound sales calls (higher answer rates with local presence) - **Toll-free numbers** — best for inbound lines and SMS campaigns ## Number Assignment Every number has one owner: a person or a workspace. The owner decides who can call and text from it and who is rung when it receives a call. ### Assigning to a person 1. Click the **Assigned To** cell of the number 2. Under **People**, choose the team member 3. Save Each SDR can have one or more numbers assigned. When they make calls or send SMS through graph8, the assigned number is used, and inbound calls to it ring that person first. ### Assigning to a workspace 1. Click the **Assigned To** cell of the number 2. Under **Workspaces**, choose the workspace 3. Confirm. If the number was assigned to a person, it is taken from them A workspace number is shared by every member of that workspace: - it appears in each member's caller ID list in the Dialer and rotates like their own numbers - an inbound call rings the member who last called that prospect; if they are offline it rings every member, and if nobody is online it rings the whole org - members see it in this list; people outside the workspace do not You can also add numbers from the workspace itself under **Settings → Workspaces → Channels → Phone numbers**. Removing a number there leaves it unassigned. ### Unassigning 1. Click the **Assigned To** cell 2. Click **Reset** 3. Save. Nobody can call from an unassigned number until it is assigned again ## Caller ID Configure how your outbound calls appear to recipients. ### Setting Caller ID 1. Click the number 2. Go to **Caller ID Settings** 3. Set the display name 4. Save Caller ID settings depend on the carrier and destination. Not all carriers display custom names. ## SMS Configuration Enable SMS sending on your phone numbers. ### Enabling SMS 1. Click the number 2. Toggle **SMS** on 3. Complete A2P (Application-to-Person) registration if required ### A2P Registration For US phone numbers, A2P registration is required for SMS: - **Brand registration** — register your company information - **Campaign registration** — describe your SMS use case - **Approval** — carriers review and approve your registration Registration is required by US carriers to ensure compliance and prevent spam. graph8 guides you through the registration process. ### SMS Limits - Daily and monthly SMS caps based on your plan and carrier limits - Messages exceeding limits are queued until the next period ## Number Porting Bring existing phone numbers into graph8. ### Porting Process 1. Go to **Settings → Phone Numbers** 2. Click **Port Number** 3. Enter the number to port 4. Provide your current carrier details 5. Authorize the transfer 6. graph8 initiates the port ### Timeline - Porting typically takes 7–14 business days - During porting, the number continues to work with your current carrier - You'll be notified when the port completes ## Frequently Asked Questions ### Can I use one number for both calls and SMS? Yes, most numbers support both voice and SMS. Enable SMS on any voice-capable number. ### What countries are supported? Phone number availability depends on the region. US, Canada, UK, and many other countries are supported. Check availability during the number search. ### How do I remove a phone number? Release the number from **Settings → Phone Numbers**. Released numbers cannot be recovered and may be reassigned to another customer. ### Do I need separate numbers for inbound and outbound? Not required, but recommended for high-volume operations. Using dedicated inbound numbers avoids conflicts with outbound caller ID. --- > **Tip:** > Use local numbers matching your prospects' area codes to improve answer rates. --- # Pipedrive Integration > Connect Pipedrive to graph8 to pull and push contacts, organizations, deals, and leads using Pipedrive labels. Source: https://docs.graph8.com/settings/pipedrive/ The Pipedrive integration keeps graph8 and Pipedrive in sync: pull persons, organizations, deals, and leads into graph8, push graph8 lists into Pipedrive, and sync activities in both directions. ![The CRM Integrations page with the Pipedrive card](https://docs.graph8.com/images/settings/pipedrive-connect.png) ## How Pipedrive Works with graph8: Labels as Lists Pipedrive does not have static contact lists. Its "filters" are dynamic saved searches, so records cannot be manually added to or removed from them. The static way to group records in Pipedrive is **labels**: the color-coded tags you see on people, such as **Customer**, **Hot lead**, **Warm lead**, and **Cold lead**. graph8 uses labels as the list mechanism for Pipedrive: - Every Pipedrive label appears in graph8 as a list you can sync. Want to pull everyone tagged **Hot lead**? Select that tag and graph8 pulls exactly those records. - When you push a graph8 list to Pipedrive, the pushed records are tagged with a Pipedrive label of your choice (an existing one or a new one graph8 creates for you). - Labels are per record type: persons, organizations, deals, and leads each have their own set of labels, and graph8 handles each separately. > **Note:** > graph8 only ever adds or removes its own tag on a record. Any other labels already on the record are always preserved. ## Connecting Pipedrive 1. Click your profile icon in the top-right corner and select **Settings** 2. In the Settings sidebar, expand **Developers** and click **Integrations** 3. Find **Pipedrive** under **CRM Integrations** and click **Connect** 4. A Pipedrive authorization window opens. Choose the Pipedrive company to connect, review the access request, and click **Continue to the App** 5. The Pipedrive card now shows **Connected**, along with the connected account email and connection date ![The profile menu with the Settings option](https://docs.graph8.com/images/settings/profile-menu-settings.png) > **Caution:** > Connect with a Pipedrive **admin** account. Pipedrive shows a warning during authorization if your user lacks the "Administer account" permission, and some sync features (such as creating labels or custom fields) may not work for regular users. Once connected, the card gives you: | Control | What it does | |---------|--------------| | Settings (gear icon) | Opens the Pipedrive Settings dialog with the Push, Pull, Fields, and Appointments tabs | | Refresh | Re-checks the connection | | Disconnect | Stops all syncing. Data already synced into graph8 remains | | Migrate to graph8 | Launches the one-time migration wizard to bring your full Pipedrive database into graph8 | ## Pulling from Pipedrive Open the settings dialog (gear icon) and select the **Pull** tab. You can pull all four record types (persons, organizations, deals, and leads) and control what they become in graph8 with the **Create new records as** selector. ![The Pull tab of the Pipedrive Settings dialog](https://docs.graph8.com/images/settings/pipedrive-pull-settings.png) ### Pull options | Setting | What it does | |---------|--------------| | **Auto Sync** | Automatically re-syncs every 30 minutes | | **Enrich from graph8** | Matches pulled contacts by work email or LinkedIn URL (companies by domain) and fills missing fields from your graph8 data | | **Activity Sync** | Pulls activities (notes, calls, emails, meetings, tasks) from Pipedrive into graph8 | | **Create new records as** | Choose whether pulled records are created as Contacts, Leads, Companies, or Deals in graph8 | ### Pull a specific label (tag) 1. Under **Add Pipedrive Tag to Sync**, open the tag dropdown. It lists your Pipedrive labels, for example **Customer**, **Hot lead**, **Warm lead**, **Cold lead** 2. Select the tag you want to pull. Only records carrying this tag are pulled 3. Choose the graph8 list the records should land in 4. Click **Add** ![The Pipedrive tag dropdown listing labels as lists](https://docs.graph8.com/images/settings/pipedrive-tag-dropdown.png) Each tag-to-list pair appears in the sync table with its **CRM List ID**, **Pipedrive Tag**, **graph8 List**, **Entity Type**, **Sync Started** time, **Status**, and **Actions**. With Auto Sync on, every pair re-syncs on the 30-minute schedule. ### Pull everything Click **Pull All** to pull all records of the selected type (for example, all persons), then choose the graph8 destination list. > **Caution:** > **Large pulls and pushes take time.** Pipedrive's API does not support batch operations, so graph8 syncs records individually. Pulling a tag also requires scanning your full Pipedrive database, because Pipedrive cannot filter by label on its side. For accounts with many thousands of records, expect a sync to run for a while. It continues safely in the background, and the sync table shows the status of each run. ## Pushing to Pipedrive Select the **Push** tab in the same dialog. Like pull, push works for all four record types: use the record type selector to push **Contacts**, **Leads**, **Companies**, or **Deals**. 1. Choose the record type to push 2. Click **Add graph8 List to Sync** and choose the graph8 list to push 3. The **Select Pipedrive Tag** dialog opens with three options: - **Existing Tags**: pick one of your Pipedrive labels. Every pushed record gets this label, so you can find them instantly with a label filter - **Create New**: type a name and graph8 creates the label in Pipedrive for you - **None**: click **Continue Without Tag** to push the records without applying any label 4. Click **Add**, or use **Push All** to push all records of the selected type Push settings mirror the pull side: **Auto Sync** re-pushes every 30 minutes, and **Activity Sync** pushes activities created in graph8 to Pipedrive. ### Rich data fields are created automatically Pipedrive's built-in schema is minimal (persons have little more than name, email, and phone). When graph8 pushes enrichment data, it automatically creates the matching custom fields in Pipedrive, including **Job title**, **LinkedIn**, **Department**, **Seniority**, **Mobile**, and for organizations **Website**, **Industry**, **Annual revenue**, **Employee count**, and more. If a field with the same name already exists in your Pipedrive account, graph8 reuses it instead of creating a duplicate. ## Field Mapping Select the **Fields** tab to control which Pipedrive fields map to which graph8 fields. Mappings are managed per record type using the **Contact Mappings**, **Lead Mappings**, **Company Mappings**, and **Deal Mappings** tabs. ![The Fields tab showing auto-mapped field mappings with Auto Fill and Overwrite options](https://docs.graph8.com/images/settings/pipedrive-fields-mapping.png) ### Automatic mapping graph8 pre-maps the standard fields for you: | Record type | Auto-mapped fields | |-------------|-------------------| | Contacts (Persons) | Email, first name, last name, phone, plus the linked organization's name and address details | | Companies (Organizations) | Name, address, city, state, country, postal code | | Deals | Title, value, stage, expected close date, owner, currency, pipeline | | Leads | Title, source, value, expected close date, plus the linked person's identity fields | Pipedrive stores rich data (LinkedIn URL, industry, revenue, employee count, and similar) as per-account **custom fields**. graph8 detects these by their field name and suggests the mapping automatically, for example a custom field named "LinkedIn" maps to the graph8 LinkedIn URL field. Every suggested row is marked as auto and can be removed before saving. ### Custom mapping Use **Add Mapping** to map any Pipedrive field (including custom fields) to any graph8 field, and **Refresh Schema** to re-read the field list from Pipedrive after you add fields there. Each mapping has two behavior options (one per mapping): | Behavior | What it does | |----------|--------------| | **Auto Fill** | Only fills empty fields in the destination. Values you already have are never touched | | **Overwrite** | Replaces existing values with the source data | ## Appointments The **Appointments** tab controls meeting sync. Meetings booked through graph8 are created in Pipedrive as activities of type "meeting" on the matched person, and cancellations or reschedules update the same activity. ## What Syncs | graph8 | Pipedrive | Pull | Push | |--------|-----------|------|------| | Contacts | Persons | Yes | Yes | | Companies | Organizations | Yes | Yes | | Deals | Deals | Yes | Yes | | Leads | Leads | Yes | Yes | | Activities | Activities (calls, emails, meetings, notes, tasks) | Yes | Yes | | Lists | Labels | Yes | Yes | ## Frequently Asked Questions ### Why don't I see my Pipedrive filters in graph8? Filters are dynamic saved searches, and Pipedrive does not allow records to be added to them directly. graph8 syncs with **labels** instead, which behave like proper lists. To sync a group of records, give them a label in Pipedrive and select that tag in graph8. ### How often does data sync? With Auto Sync enabled, every configured tag/list pair syncs every 30 minutes. You can also trigger a sync manually at any time from the sync table, or use Pull All / Push All. ### What happens when I disconnect? Syncing stops immediately. Data already pulled into graph8 remains, and labels graph8 created in Pipedrive stay on your records. > **Tip:** > Start by pulling one label (for example **Hot lead**) into a graph8 list and enriching it with **Enrich from graph8**. You get an immediate picture of the data quality uplift before syncing your whole database. ## Related - [Integrations Overview →](https://docs.graph8.com/settings/integrations/) - [Audience Sync →](https://docs.graph8.com/settings/audience-sync/) - [Managing Lists →](https://docs.graph8.com/data/managing-lists/) --- # Purchase Domain & Mailboxes > Buy domains and set up mailboxes directly in graph8. Source: https://docs.graph8.com/settings/purchase-domains/ Purchase sending domains and create mailboxes without leaving graph8. ## Domain Search Find and buy domains directly from the platform. 1. Go to **Settings → Channels → Purchase Domain/Mailboxes** 2. Enter a keyword or desired domain name 3. Browse available domains with pricing 4. Select the domains you want Search results show domain availability, TLD options (.com, .io, .co, etc.), and pricing per year. ## Purchasing Domains ### Single Domain 1. Select a domain from search results 2. Review the price 3. Click **Purchase** 4. Complete payment ### Bulk Purchase Buy multiple domains at once for large outbound campaigns: 1. Select multiple domains from search results 2. Review the total cost 3. Click **Purchase All** 4. Complete payment ### Auto-Configuration Purchased domains are automatically configured with: - SPF, DKIM, and DMARC records — no manual DNS setup required - Domain verification — instant, no propagation wait - Warmup enrollment — begins automatically This eliminates the manual DNS configuration needed when bringing your own domain. ## Upload Domains via CSV You can upload a list of domains in bulk when purchasing domains. Click **Upload CSV** in the purchase flow, then upload a file with one domain per row. graph8 will: - Check availability - Show pricing for each domain - Let you select which domains to purchase This is useful when setting up multiple sending domains at once. ## Mailbox Creation Create mailboxes on your purchased domains. ### Creating a Mailbox 1. Select a purchased domain 2. Click **Add Mailbox** 3. Enter the mailbox name (e.g., `alex`, `sarah.jones`) 4. Set the display name 5. Save The full email address is formed as `name@yourdomain.com`. ### Bulk Mailbox Setup Create multiple mailboxes at once: 1. Select the domain 2. Click **Bulk Add** 3. Enter mailbox names (one per line or comma-separated) 4. Set display names 5. Save ### Mailbox Configuration Each mailbox includes: - **Sending limits** — daily send cap - **Warmup** — automatic warmup enrollment - **Signature** — default email signature ## Warmup Email warmup gradually builds your mailbox's sender reputation by exchanging emails with a network of real mailboxes. Purchased mailboxes are enrolled in warmup automatically. ### How Warmup Works 1. graph8 sends emails from your mailbox to the warmup network 2. Recipients open, read, and reply to these emails 3. Positive engagement signals build your reputation with email providers 4. Sending volume increases gradually over the warmup period ### Managing Warmup 1. Go to **Settings → Channels → Purchase Domain/Mailboxes** 2. Click on a mailbox 3. Select the **Warmup** tab 4. Click **Setup** to enable warmup, or adjust settings if already active ### Warmup Settings - **Daily warmup volume** — number of warmup emails per day (minimum 1) - **Ramp-up speed** — how quickly volume increases (minimum 5 emails per day) - **Reply rate target** — percentage of warmup emails that receive replies (must be between 20% and 100%, defaults to 30%) - graph8 manages the warmup schedule automatically ### Validation Rules Warmup settings are validated automatically when saved: - **Daily rampup** must be at least **5** — values below this are adjusted up to 5 - **Reply rate** must be between **20%** and **100%** — values outside this range are clamped to the nearest limit - **Daily warmup volume** must be at least **1** If a value falls outside the allowed range, graph8 adjusts it to the nearest valid value rather than rejecting the input. All warmup settings are synced to SmartLead when saved. ## Frequently Asked Questions ### What TLDs are available? Common TLDs include .com, .io, .co, .net, and .org. Availability depends on the domain name. Regional TLDs may also be available. ### Can I transfer a purchased domain out of graph8? Contact support for domain transfer requests. ### How does auto-configuration differ from manual setup? Purchased domains have DNS records configured automatically and are verified instantly. Manually added domains require you to set up DNS records with your provider and wait for propagation. ### Is there a limit on mailboxes per domain? Plan limits apply. Check **Settings → Billing** for your plan's mailbox allowance. --- > **Tip:** > Purchase multiple domains with varied names to spread sending volume and protect reputation. --- # Roles > Define custom roles and permissions. Source: https://docs.graph8.com/settings/roles/ Control what each team member can see and do in graph8 with role-based permissions. ## Default Roles graph8 ships a set of built-in roles tuned to common revenue functions. Each one comes pre-configured with sensible permissions, and you can clone or adjust any of them with [custom roles](#custom-roles). | Role | Designed for | |------|--------------| | **Admin** | Full access to every feature, all data, and all settings (users, roles, billing, integrations). | | **CRO** | Org-wide revenue visibility — reads all records and sees every conversation and transcript; limited write for coaching context. | | **Sales Manager** | Team oversight with full read/write across all records. | | **SDR Manager** | Leads the SDR team — team-scoped read/write on contacts, campaigns, tasks, mailboxes, and phone numbers; can assign leads. | | **Account Executive (AE)** | Works an assigned book — reads and edits their **own** contacts, companies, and deals (including close-won/lost), with broad list visibility. | | **SDR** | Outbound prospecting — reads and writes their **own** contacts and lists; hands deals off to an AE. | | **Commission SDR** | Marketplace SDR with strict row-level isolation — reads and writes **only their own** records, no settings access. | | **CSM** | Owns existing customer relationships — renewals, expansion, and customer meetings on customer-revenue pipelines. | | **Campaign Manager** | Generates pipeline through campaigns, forms, and inbound; tracks attribution from their programs. | | **Support Agent** | Replies to support conversations — own scope on contacts they touch, team scope on the support inbox, no deals. | | **Finance** | Revenue read-only (all deals and companies for reporting) plus billing, credits, and invoicing. | | **View Only** | Read-only across team records — cannot create, edit, delete, or change settings. Useful for executives, auditors, and analysts. | The **Admin** role (and the marketplace **Service Provider** role) are locked — their permissions can't be edited, so you always have a guaranteed full-access role. ## Permissions Permissions fall into two groups: **per-object scopes** (the CRM and engagement objects) and **capability toggles** (workflows, billing, settings, and team management). Both are configured per role in the role editor grid at **Settings → Roles**. ### Object scopes For each object below, a role gets an independent **View** scope and **Edit** scope, plus optional **Delete**, **Assign**, and **Manage** toggles: | Scope | Meaning | |-------|---------| | **Off** | No access to this object | | **Own** | Only records the user owns or is assigned to | | **Team** | Records owned by anyone on the user's team | | **All** | Every record in the organization | | **Manage** | Same as All, and also includes records that have no owner | View and Edit are set separately, so a role can read at one scope and write at a narrower one (for example, View = Team, Edit = Own). **Delete**, **Assign** (reassign owners), and **Manage** are destructive or escalation actions and are flagged **Critical** in the editor. The objects with per-row scoping are: **Contacts · Companies · Deals · Campaigns · Lists · Sequences · Tasks · Meetings · Mailboxes · Phone Numbers · Inbox Workspaces · AI Agents** ### Capability toggles Non-object permissions are grouped into capability areas: | Area | Toggles | |------|---------| | **Workflows** | View · Run · Manage | | **Billing** | View Credits · Purchase Credits · Manage Billing | | **Settings** | Access Admin · View · Edit | | **Users** | View · Edit Own · Edit All · Invite · Manage | | **Roles** | Manage Roles | | **Teams** | View · Manage | **Access Admin** is the master gate for the entire **Settings** area — a role without it can't reach any settings page, regardless of the more granular toggles. By default it's on for Admin, GTM Engineer, and Finance; admins can grant it to other roles on demand. ### How scopes affect what users see A user's role scope determines which records appear in their lists and detail pages — an Own-scope user sees only their own records, a Team-scope user sees their team's. graph8 is rolling enforcement out object-by-object, so if a list looks narrower (or a page is hidden) than before, check the user's role scope for that object in the role editor. ## Custom Roles Create roles tailored to your organization's structure. ### Creating a Custom Role 1. Go to **Settings → Roles** 2. Click **Create Role** 3. Name the role and add a description 4. Set the View/Edit scope and action toggles for each object, plus the capability toggles 5. Save ### Editing a Custom Role 1. Find the role in the list 2. Click **Edit** 3. Adjust permissions as needed 4. Save — changes apply immediately to all users with this role ### Deleting a Custom Role 1. Find the role in the list 2. Click **Delete** 3. Reassign users currently on this role to another role 4. Confirm ## Assigning Roles Roles are assigned when inviting users or from the Users page: - **On invite** — select the role in the invitation dialog - **After join** — change the role from **Settings → Users** by clicking the role dropdown next to any user A user can have only one role at a time. Changing roles takes effect immediately. ## Frequently Asked Questions ### Can I create a role with admin access to just one feature? Yes. Custom roles let you toggle permissions per feature area. For example, you could create a "Sequence Manager" role with full access to sequences but read-only access to everything else. ### What happens when I change someone's role? The change takes effect immediately. The user's current session updates to reflect the new permissions. No logout is required. ### Can I duplicate an existing role? Use the **Duplicate** option next to any role to create a copy. Rename it and adjust permissions as needed. ### Why can't I grant a permission I don't have myself? To prevent privilege escalation, a non-admin can only create, edit, or assign roles that grant permissions they already hold. If you try to give a role a permission you lack, the change is rejected. Organization Owners and Admins are exempt and can grant anything. ### Why can't I edit the Admin role? The **Admin** role (and the marketplace **Service Provider** role) are locked so there's always a guaranteed full-access role and the marketplace flows keep working. Create a custom role if you need a tailored high-access role. --- > **Tip:** > Start with the built-in roles and create custom roles only when you need more granular control. --- # Salesforce: Permissions for the graph8 Integration User > The Salesforce objects and fields the user who connects graph8 must be able to see and edit, and how to grant them with a permission set. Source: https://docs.graph8.com/settings/salesforce-integration-user-permissions/ graph8 works inside Salesforce as the user who authorised the connection. Salesforce never lets a connected app see more than that user's own profile and permission sets allow, so any field the connecting user cannot access is silently left out of the sync. This page lists exactly what that user needs, so a Salesforce admin can set up a dedicated integration user in one pass. ## Why the connecting user's permissions matter - When you click **Connect**, graph8 receives a token for the Salesforce user who signs in. Every read and write graph8 makes runs as that user. - The OAuth grant cannot exceed the user's own permissions. If the user cannot see the **Title** field on Lead, graph8 cannot read or write job titles. There is no error; the field simply never syncs. - Salesforce best practice is a dedicated integration user with a minimal profile. That is the right approach, as long as the user is granted the objects and fields below. - graph8 checks this for you. After connecting, open the Salesforce settings: any field graph8 uses that the connected user cannot access is listed in a banner at the top, and the field appears greyed out in the **Fields** tab. The sync keeps running with whatever the user can see; nothing is blocked. > **Tip:** > Create a user named something like **graph8 Integration** with a Salesforce licence, a minimal profile, and the permission set described below. Avoid connecting with a personal account: if that person leaves or their permissions change, the sync changes with them. ## Object access Grant the integration user access to these objects: | Object | Access | Used for | |---|---|---| | Lead | Read, Create, Edit | Lead sync: pushing qualified leads, pulling leads and their opt-outs | | Contact | Read, Create, Edit | Pulling and pushing contacts | | Account | Read, Create, Edit | Pulling and pushing companies | | Campaign | Read | Pulling leads and contacts by campaign, and adding pushed leads to a campaign | | Campaign Member | Read, Create, Edit | Adding pushed leads to the campaign you choose in the Lead sync settings | | User | Read | Matching Salesforce record owners to graph8 team members. Salesforce grants this by default; only relevant if your org restricts user visibility | | Task, Event | Read, Create, Edit | Only when activity sync is on | > **Note:** > If you want graph8 to keep updating leads after a rep converts them, also grant the **View and Edit Converted Leads** system permission. Without it, Salesforce rejects updates to converted leads. ## Field access Field-level security applies on top of object access. Grant **Read** and **Edit** on every field in the tables below for the objects you sync. The **Needed for** column tells you when a field matters: - **Always**: whenever that object is synced. - **Lead sync**: the Salesforce Lead sync is on in the **Push** tab. - **Opt-out suppressions**: **Suppress from opt-outs** is on in the Lead pull settings, so Salesforce **Email Opt Out** and **Do Not Call** flags become graph8 suppressions. - **Contact push**: a contact push to Salesforce is configured. ### Lead | Salesforce field | API name | Needed for | |---|---|---| | Email | `Email` | Always | | First Name | `FirstName` | Always | | Last Name | `LastName` | Always | | Company | `Company` | Always | | Title | `Title` | Always | | Phone | `Phone` | Always | | Mobile Phone | `MobilePhone` | Always | | Website | `Website` | Always | | Industry | `Industry` | Always | | Street | `Street` | Always | | City | `City` | Always | | State/Province | `State` | Always | | Zip/Postal Code | `PostalCode` | Always | | Country | `Country` | Always | | Lead Status | `Status` | Always | | Lead Source | `LeadSource` | Always | | Lead Owner | `OwnerId` | Always | | Employees | `NumberOfEmployees` | Always | | Email Opt Out | `HasOptedOutOfEmail` | Lead sync / Opt-out suppressions | | Do Not Call | `DoNotCall` | Lead sync / Opt-out suppressions | ### Contact | Salesforce field | API name | Needed for | |---|---|---| | Email | `Email` | Always | | First Name | `FirstName` | Always | | Last Name | `LastName` | Always | | Title | `Title` | Always | | Phone | `Phone` | Always | | Mobile Phone | `MobilePhone` | Always | | Account Name | `AccountId` | Always | | Mailing City | `MailingCity` | Always | | Mailing State/Province | `MailingState` | Always | | Mailing Country | `MailingCountry` | Always | | Contact Owner | `OwnerId` | Always | | Email Opt Out | `HasOptedOutOfEmail` | Contact push | | Do Not Call | `DoNotCall` | Contact push | ### Account | Salesforce field | API name | Needed for | |---|---|---| | Account Name | `Name` | Always | | Website | `Website` | Always | | Industry | `Industry` | Always | | Employees | `NumberOfEmployees` | Always | | Billing City | `BillingCity` | Always | | Billing State/Province | `BillingState` | Always | | Billing Country | `BillingCountry` | Always | | Account Owner | `OwnerId` | Always | Custom fields you map in the **Fields** tab need the same field-level access. graph8 does not list them in the banner because it cannot tell a hidden custom field from one that does not exist in your org. ## Grant the permissions with a permission set 1. In Salesforce, open **Setup** and search for **Permission Sets** in the Quick Find box 2. Click **New**, name it (for example `graph8 Integration`), leave **License** as **None**, and click **Save** 3. Under **Apps**, click **Object Settings**, then open **Leads** 4. Click **Edit**. Under **Object Permissions**, tick **Read**, **Create** and **Edit**. Under **Field Permissions**, tick **Read Access** and **Edit Access** for every field in the Lead table above, then click **Save** 5. Repeat step 4 for **Contacts**, **Accounts**, **Campaigns**, **Campaign Members** and, if activity sync is on, **Tasks** and **Events** 6. Back on the permission set, click **Manage Assignments → Add Assignment**, select the integration user, and click **Assign** 7. In graph8, go to **Settings → Integrations**, click the gear icon on the Salesforce card, and click **Re-check** in the banner. The banner disappears once every field is accessible > **Note:** > You do not need to disconnect and reconnect. **Re-check** reads the connected user's current permissions straight from Salesforce. ### Download the permission set A ready-made permission set file that grants exactly these permissions is coming soon; until then, follow the steps above. ## Related - [Integrations →](https://docs.graph8.com/settings/integrations/) - Connect Salesforce and other CRMs --- # Schedule & Availability > Set your working hours and availability for meetings, appointments, and sequence scheduling. Source: https://docs.graph8.com/settings/schedule-availability/ Schedule & Availability defines when you're available for meetings and when sequences can send on your behalf. Connected calendars and the appointment booking system use these settings to show open time slots. ## Setting Your Availability 1. Go to **Settings → Schedule & Availability** 2. Set your working hours for each day of the week 3. Toggle days on or off (e.g., disable weekends) 4. Set break periods to block off lunch or focus time 5. Click **Save** ## Availability Settings | Setting | Description | |---------|-------------| | **Working hours** | Start and end time for each day of the week | | **Day toggles** | Enable or disable individual days (Mon-Sun) | | **Break periods** | Block time within working hours (e.g., 12pm-1pm lunch) | | **Timezone** | Your personal timezone (overrides org default) | | **Buffer time** | Minimum gap between consecutive meetings | ## How Availability Is Used ### Meeting Booking When prospects book meetings through graph8 appointment links, they only see time slots within your available hours. Buffer time prevents back-to-back meetings. ### Sequence Scheduling Sequences respect your availability settings when scheduling email sends, call steps, and LinkedIn actions. Messages only send during your defined working hours and timezone. ### Calendar Sync When you connect Google Calendar or Outlook, existing calendar events automatically block those times. This prevents double-booking. > **Tip:** > Set your availability before launching your first sequence. Without availability configured, sequences may attempt to send outside business hours, which reduces engagement rates. ## Related - [Appointments →](https://docs.graph8.com/engage/appointments/) - Meeting scheduling that uses your availability - [Sequencer →](https://docs.graph8.com/engage/sequencer/) - Sequences respect your sending windows - [Company Settings →](https://docs.graph8.com/settings/company/) - Organization-wide timezone default --- # Scraping > Configure website scraping behavior and exclusions. Source: https://docs.graph8.com/settings/scraping/ Control how graph8 scrapes websites and which pages to include or exclude. ## Scraping Settings graph8 can scrape website data to enrich contact and company records. Configure global scraping preferences for your organization. ### Global Preferences 1. Go to **Settings → Scraping** 2. Configure your scraping behavior: - **Enable/Disable** — toggle scraping on or off for your organization - **Concurrency** — number of simultaneous scrape requests - **Delay** — wait time between requests to the same domain ## Skip Rules Define which URLs or patterns should be excluded from scraping. ### Adding Skip Rules 1. Click **Add Rule** 2. Choose the rule type: - **Exact URL** — skip a specific page - **URL Pattern** — skip pages matching a pattern (e.g., `/blog/*`) - **Domain** — skip an entire domain 3. Enter the URL or pattern 4. Save ### Common Skip Patterns - Login and authentication pages - Terms of service and legal pages - Internal tool URLs - Social media profile pages - Pages with sensitive information ### Managing Rules - View all active skip rules in the rules list - Toggle rules on/off without deleting them - Edit patterns as your needs change - Delete rules that are no longer needed ## Allowed Domains Restrict scraping to specific domains for targeted data collection. ### Whitelist Mode When enabled, graph8 only scrapes domains you've explicitly allowed: 1. Toggle **Whitelist Mode** on 2. Add domains to the allowed list 3. Only these domains are scraped When disabled, all domains are scraped except those matching skip rules. ## Rate Limits Control scraping speed to avoid overwhelming target websites. ### Settings - **Requests per second** — maximum scrape requests per second per domain - **Concurrent connections** — maximum simultaneous connections - **Retry attempts** — how many times to retry a failed scrape graph8 automatically respects `robots.txt` directives on target websites. ## Data Handling Configure how scraped data maps to your records. ### Field Mapping Scraped data can populate: - **Company fields** — website description, industry, technologies, employee count - **Contact fields** — job title, social profiles, bio ### Data Quality - graph8 deduplicates scraped data against existing records - New data only fills empty fields unless you enable overwrite mode - All scraped data is logged for audit purposes ## Frequently Asked Questions ### Does graph8 respect robots.txt? Yes. graph8 follows `robots.txt` directives by default. Pages disallowed by robots.txt are not scraped. ### What happens if a scrape fails? Failed scrapes are retried based on your retry settings. After all retries are exhausted, the URL is logged as failed and skipped until the next scheduled run. ### Can I schedule scraping? Scraping runs as needed (e.g., when a new company is added or during enrichment). Recurring schedules can be configured for batch operations. ### Will scraping affect my website's performance? graph8 only scrapes external websites (your prospects' sites), not your own. Rate limits ensure scraping doesn't overload target sites. --- > **Tip:** > Start with conservative rate limits and increase gradually based on your needs. --- # SSO > Single sign-on configuration (Enterprise). Source: https://docs.graph8.com/settings/sso/ Configure SAML-based single sign-on so your team can log in with their existing identity provider. ## Supported Providers graph8 supports SAML 2.0 SSO with: - **Okta** - **Azure AD (Microsoft Entra ID)** - **OneLogin** - **Google Workspace** - Any SAML 2.0–compliant identity provider SSO is available on Enterprise plans. ## Setup ### Step 1: Gather graph8 SAML Details 1. Go to **Settings → SSO** 2. Copy the following values to use in your identity provider: - **ACS URL** (Assertion Consumer Service) - **Entity ID** (SP Entity ID) - **Login URL** ### Step 2: Configure Your Identity Provider In your identity provider (Okta, Azure AD, etc.): 1. Create a new SAML application 2. Paste the ACS URL and Entity ID from graph8 3. Configure attribute mapping: - **email** — user's email address (required) - **firstName** — user's first name - **lastName** — user's last name 4. Download the IdP metadata XML or copy the: - **IdP SSO URL** - **IdP Entity ID** - **X.509 Certificate** ### Step 3: Complete Configuration in graph8 1. Return to **Settings → SSO** 2. Upload the IdP metadata XML, or manually enter: - IdP SSO URL - IdP Entity ID - X.509 Certificate 3. Click **Save** 4. Click **Test Connection** to verify ### Step 4: Enable SSO Once the test connection succeeds: 1. Toggle SSO to **Enabled** 2. Choose the enforcement mode (see below) 3. Save ## Enforcement ### Optional SSO Users can log in with either SSO or email/password. This is useful during rollout. ### Required SSO All organization members must use SSO to log in. Email/password login is disabled. - Admins can still use email/password as a fallback (to prevent lockout) - New users are automatically prompted to use SSO on their first login ### Enabling Enforcement 1. Go to **Settings → SSO** 2. Set enforcement to **Required** 3. Notify your team about the change 4. Save ## Troubleshooting ### Common Issues **"SAML response is invalid"** - Check that the ACS URL in your IdP matches the one shown in graph8 - Verify the X.509 certificate hasn't expired - Ensure the clock on your IdP server is synchronized (SAML is time-sensitive) **"User not found"** - The email in the SAML assertion must match a user's email in graph8 - Verify the attribute mapping sends the correct email field **"Certificate error"** - Re-download the certificate from your IdP - Upload the new certificate in graph8 - Some IdPs rotate certificates — check your IdP's rotation schedule ### Certificate Expiry SAML certificates expire periodically (typically every 1–3 years). When your certificate approaches expiry: 1. Generate a new certificate in your IdP 2. Upload the new certificate in **Settings → SSO** 3. Test the connection 4. The old certificate is replaced ## Frequently Asked Questions ### Can I use multiple identity providers? Currently, one IdP per organization. All users must authenticate through the same provider. ### What happens if SSO goes down? Admins retain email/password access as a fallback. Contact support for emergency access if all admins are locked out. ### Do new users need to be manually invited? With SSO enabled, new users can be auto-provisioned on first login. Their role defaults to Member unless configured otherwise. ### Does SSO work with two-factor authentication? Yes. 2FA from your identity provider is enforced during the SSO login flow. graph8's built-in 2FA is bypassed when SSO is active. --- > **Tip:** > Test SSO with a small group before enforcing it organization-wide. --- # Users > Invite and manage the users in your graph8 organization - roles, status, contact details, and invitations. Source: https://docs.graph8.com/settings/team/ Invite, manage, and remove the users in your graph8 organization. The Users page lives under **Settings → Organization → Users**. ## Users list The Users page (its header reads **Team Members**) lists everyone in your organization. Use the column filters to find users quickly in larger teams. ![The Users page under Settings → Organization, with the Invite User dialog open](https://docs.graph8.com/images/settings/users.png) Each row shows: - **User** - name and email - **Status** - **Active**, or **Invited** for a pending invitation that hasn't been accepted yet - **Role** - the user's assigned role (configurable - see [Roles](https://docs.graph8.com/settings/roles/)) - **Last Login** - when they last logged in - **Joined** - when they were added to the organization - **Extension**, **Phone Number**, **Department**, **Availability** - dialer and routing details you can edit inline - **Actions** - remove the user, or revoke a pending invite ## Inviting users 1. Go to **Settings → Organization → Users** 2. Click **Invite User** 3. Enter the user's **email** 4. Select a **role** 5. Click **Invite** If the person already has a graph8 account, they're **added to your organization immediately**. Otherwise they receive an email invitation to create their account and join with the role you chose. Invite one user at a time - each invite takes a single email and role. ### Pending invitations Invited users appear in the list with an **Invited** status until they accept. To cancel a pending invite, use the **revoke** action in that user's row. ### Seats & billing Your plan determines how many users you can add. See **Settings → Billing** for your current usage and available seats. ## Managing users ### Change a user's role Click the **Role** dropdown in the user's row and pick a new role. The change takes effect immediately. See [Roles](https://docs.graph8.com/settings/roles/) for what each role can access. ### Set dialer & contact details Click the **Extension**, **Phone Number**, **Department**, or **Availability** cell in a user's row to edit it inline - press **Enter** to save or **Esc** to cancel. These feed the dialer and team routing. ### Remove a user 1. Click the **remove** (trash) icon in the user's row 2. Confirm in the **Remove User** dialog Removing a user revokes their access to the organization. This can't be undone. ## Frequently Asked Questions ### How do I add new users? Open the **Users** tab at [app.graph8.com/studio/settings?tab=users](https://app.graph8.com/studio/settings?tab=users) (**Settings → Organization → Users**), click **Invite User**, enter the person's email, pick a role, and click **Invite**. If they already have a graph8 account they're added to your organization immediately; otherwise they receive an email invitation. See [Inviting users](#inviting-users) above for the full flow. ### Can I invite more than one user at once? Invites are sent one at a time - each invite takes a single email and role. Repeat the **Invite User** flow for each person. ### What happens if an invited user doesn't accept? They stay in the list with an **Invited** status. You can revoke the pending invite from their row's actions and re-invite if needed. ### Where do I manage roles and permissions? Roles are configured on the **Settings → Organization → Roles** page. See [Roles](https://docs.graph8.com/settings/roles/). --- > **Tip:** > Follow the principle of least privilege - give users only the role they need. Set up custom roles on the [Roles](https://docs.graph8.com/settings/roles/) page. --- # Buying Committees > See account-level buying intent rolled up across the people who actually make the decision - the economic buyer, champion, end user, and technical evaluator. Source: https://docs.graph8.com/signals/buying-committees/ A single contact showing intent is a weak signal. A whole **buying committee** lighting up is a real one. Buying Committees roll your existing intent signals up from individual contacts to the people who collectively make a purchase decision, then to a single account-level read you can sort and prioritize on. ## How it works graph8 organizes the known contacts at each account into four persona categories, based on their job title: | Category | Who it is | |----------|-----------| | **E** - Economic Buyer | Holds the budget and signs off | | **C** - Champion | Internally advocates for the purchase | | **U** - End User | Uses the product day to day | | **T** - Technical | Evaluates fit, security, integration | Intent then rolls up in three tiers: 1. **Contact** - each contact carries intent signals (keyword and topic hits from the last 30 days). 2. **Committee** - contacts are grouped into the four categories, and each category gets a strength reading based on how many distinct people in it are showing signals. 3. **Account** - the four category readings combine into one **Account Strength** label. ### How strength is calculated Each persona category is rated by how many distinct contacts in it are showing intent: | Category reading | Distinct contacts with signals | |------------------|-------------------------------| | Hot | 5 or more | | Warm | 2 - 4 | | Cool | 1 | | Silent | 0 | Those four readings combine into the account's overall strength: | Account strength | When | |------------------|------| | Very strong | 3 or more Hot categories | | Strong | 2 Hot, or 1 Hot + 2 Warm | | Moderate | 1 Hot, or 2 or more Warm | | Weak | only Cool / Silent categories | An account is also flagged **High intent** when its total signal count crosses the threshold (7+) or any single category is Hot. ## Awareness vocabulary The strength labels come in two interchangeable vocabularies. The default, **awareness**, frames each level as a buying stage; the legacy **barometer** set uses temperature words. Toggle between them and the labels update everywhere at once. | Strength | Awareness (default) | Barometer | |----------|--------------------|-----------| | Very strong | **Selecting** | Very strong | | Strong | **Interested** | Strong | | Moderate | **Aware** | Moderate | | Weak | **Identified** | Weak | A green **High intent** pill appears next to the label whenever the account meets the high-intent bar. ## Where you see it ### Buying Committee tab (company page) On any company at `/companies/[id]`, open the **Buying Committee** tab. It shows one panel per category (E / C / U / T) with: - The category strength bar and label - A count of contacts in your ICP for that category, with example titles - A contact table: **Contact, Title, Signal (Strong / Light / No signal), Topics of interest, Last active** Contacts with no signals are collapsed behind a "+N more in ICP without signals" expander so the active buyers stay front and center. ### Grid columns (Companies, Contacts, Deals) Three columns appear across the Companies grid, the Contacts grid, and the lifecycle deal grids (`/deals/trials`, `/deals/customers`, `/deals/churned`, `/deals/prospects`): | Column | Shows | |--------|-------| | **Account strength** | The four-bar meter, the vocabulary label, and the High intent pill | | **High intent** | A pill when the account is high intent, otherwise blank | | **Buying committee** | A four-square E / C / U / T matrix, color-coded by category strength; hover any square for its signal and contact counts | On the Contacts grid, each contact inherits the rollup from its company, so you can see the strength of the account a person belongs to without leaving the contact view. ## Enabling Buying Committees Buying Committees is gated behind an allowlist during rollout. If it is not enabled for your org, the Buying Committee tab shows an "ABM Intent not enabled" message and the grid columns do not appear. Contact your graph8 admin to request access. > **Note:** > Buying Committees reads the intent signals you already collect - it does not generate new ones. Make sure [Intent Tracking](https://docs.graph8.com/signals/intent-tracking/) keywords are configured so contacts have signals to roll up. ## Related - [Intent Tracking →](https://docs.graph8.com/signals/intent-tracking/) - the keyword and topic signals that feed committee strength - [Signals Overview →](https://docs.graph8.com/signals/overview/) - how graph8 scores intent per contact and company --- # Forms > Capture leads from website form submissions and automatically add them to your contact lists. Source: https://docs.graph8.com/signals/forms/ graph8 tracks form submissions on your website and converts them into contacts. Every time someone fills out a form — a demo request, contact form, newsletter signup, or booking form — graph8 captures the submission and can automatically add the lead to a list for outreach. ## How Form Tracking Works graph8's tracking script monitors form submissions on your website. When a visitor submits a form, the tracker captures: - **Form fields** — all field names and values (passwords and sensitive fields are excluded) - **Page context** — the URL, page title, and referrer where the form was submitted - **Visitor data** — IP address, country, and session information - **Timestamp** — when the submission occurred This works with any HTML form on your website — no custom integration needed. The tracker also supports AJAX forms and popular form builders like WPForms. ## Viewing Form Submissions Go to **Signals → Forms** to see all tracked forms. Each form shows: - **Form ID** — the HTML form identifier - **Page URL** — where the form lives on your site - **Total submissions** — how many times it's been filled out - **Last submitted** — when the most recent submission came in Click into a form to see individual submissions with all captured field data. ### Filtering Submissions Filter submissions by: - Date range - Country - Page URL or path - IP address ## Mapping Forms to Contacts The key feature is mapping form fields to contact fields so submissions automatically create or update contacts in graph8. ### Setting Up a Mapping 1. Go to **Signals → Forms** and select a form 2. Click **Map Fields** 3. For each form field, select the corresponding contact field: | Form Field | Maps To | |-----------|---------| | email | Email | | first_name | First Name | | last_name | Last Name | | company | Company | | phone | Phone | | job_title | Job Title | 4. **Preview** the mapping to see how submissions would look as contacts 5. **Select a target list** — choose which list new contacts should be added to 6. Click **Save** ### Automatic List Population Once a mapping is saved, graph8 automatically processes new submissions: 1. A visitor submits the form on your website 2. graph8 captures the submission data 3. The field mapping is applied to extract contact information 4. The contact is created (or updated if they already exist) in your target list This runs in real time — new form submissions appear as contacts within seconds. > **Tip:** > Map your highest-traffic forms first — demo request forms and contact forms are usually the best starting point. These capture the strongest buying signals. ## Appointment Forms graph8 also tracks booking and appointment forms. When a prospect books a meeting through your website, the submission is captured the same way as regular forms and contributes to their signal score. ## Form Signals Every form submission generates a signal on the contact's profile: - Form submissions are weighted at **25 points** — the highest-value signal type - Recent submissions (last 3 days) get an additional **+20 point** recency bonus - A single demo form submission can push a contact's signal score above 40 This means contacts who fill out your forms automatically rise to the top of your priority list. ## Next Steps - [Signals Overview →](https://docs.graph8.com/signals/overview/) — How signal scoring works - [Website Visitors →](https://docs.graph8.com/signals/website-visitors/) — Track and identify anonymous visitors - [Intent Tracking →](https://docs.graph8.com/signals/intent-tracking/) — Set up keyword and topic tracking --- # Hiring Signals > Track companies that are actively hiring and people who change jobs - including talent moving in and out of your competitors. Source: https://docs.graph8.com/signals/hiring/ Hiring Signals watches the labor market for you. You create saved **listeners** that fire when something relevant happens, in two flavors: - **Hiring Wave** - companies posting jobs that match your keywords and filters. A company hiring for roles tied to your ICP is signaling budget, growth, and a new initiative. - **Talent Moves** - people changing jobs. Watch a contact list, or name a competitor and get alerted whenever someone joins or leaves them. Matches surface as signals on the contact and in your Signals feed, and can auto-enroll the contact into a campaign audience. ## Where to find it Open **Signals -> Hiring**. The page route is `/signals/hiring`. It mirrors the Social Listener layout: - Two type tabs: **Hiring Wave** and **Talent Moves** - A **Listeners ↔ Results** toggle - A "New Hiring Wave" / "New Talent Move" button for the active type ## Hiring Wave A Hiring Wave listener is a saved job search. graph8 re-runs it on a daily cadence and surfaces matching companies. ![Hiring Wave listeners — a set of active saved job searches (SDR & RevOps, Series B AEs, Enterprise AE expansion, RevOps leaders, demand gen) each tracking a target job-title keyword.](https://docs.graph8.com/images/signals/hiring-listeners.png) **Listener fields:** | Field | What it does | |-------|--------------| | Keywords | Job-title and posting keywords to match (chips) | | Location / window | Geographic scope and how far back to look | | Contacts per company | How many decision-makers to pull per matched company | | Title / seniority / department | Narrow which roles count as a match | **Results view** opens a live grid with four sub-tabs: | Sub-tab | Shows | |---------|-------| | **Jobs** | The matching job postings | | **Companies** | Companies posting those jobs (your ICP, actively hiring) | | **Decision-makers** | Resolved contacts at those companies | | **Recruiters** | The recruiting contacts behind the postings | ## Talent Moves A Talent Moves listener detects job changes. Pick one of two watchlist sources: | Source | What it watches | |--------|-----------------| | **Contact list** | A graph8 list or audience - alerts when anyone in it changes jobs | | **Company (competitor)** | A named company - scans the global job-change feed and fires on both **joins** and **leaves**, no pre-built list needed | Both sources accept role filters: title contains, seniority floor, department, minimum days in the previous role, and an employer allow/blocklist. Seniority and department are matched against the new job title, so phrase filters as title keywords. Detected moves appear on the contact's **Hiring tab** in the contact drawer (with a job-change badge) and in your Signals feed. Talent Moves are resolved on an hourly cadence. ## Your first listeners (auto-seed) If you have no listeners yet, the page shows a first-run hero with an **Auto-seed** option: - **Hiring Wave** listeners are proposed from your Studio docs (personas / ICP) - **Talent Moves** listeners are proposed from your existing contact lists The onboarding modal lists the proposed listeners with checkboxes - pick the ones you want and create them in one click. You can also create listeners manually. ## Auto-enroll into campaigns Each listener can optionally sync matches into a campaign audience. When auto-sync is enabled, a matched contact is upserted into the chosen audience, which triggers your existing sequence-enrollment flow. Turn this on from the listener's edit form. ## How signals flow downstream 1. The resolver emits a `job_change` (Talent Moves) or hiring (Hiring Wave) signal. 2. The signal appears on the contact's **Hiring tab** and in the Signals feed. 3. If auto-sync is on, the contact is added to the target audience and enrolled in the linked sequence. ## Related - [Intent Tracking →](https://docs.graph8.com/signals/intent-tracking/) - keyword and topic intent on resolved contacts - [Social Listener →](https://docs.graph8.com/signals/social-listener/) - monitor X (Twitter) for buying signals - [Sequencer →](https://docs.graph8.com/engage/sequencer/) - reach matched contacts with outbound --- # Intent Tracking > Detect buying intent from your own website and from across the web using keyword and topic tracking. Source: https://docs.graph8.com/signals/intent-tracking/ Intent tracking lets you define the keywords and topics that matter to your business. graph8 then detects when contacts and companies engage with content matching those terms — both on your website and across the web. ## Two Types of Intent | Type | Source | What It Detects | |------|--------|----------------| | **First-party** | Your own website | Contacts visiting pages on your site that match your keywords | | **Web intent** | External websites | Contacts and companies reading articles, reviews, and pages across the web that match your keywords | Both types contribute to a contact's signal score and appear in the same intent views. ## First-Party Intent First-party intent comes from visitors on your own website. When someone visits a page on your site whose URL or title matches one of your tracked keywords, graph8 creates an intent signal. For example, if you track the keyword "pricing" and a contact visits `yoursite.com/pricing`, that's a first-party intent signal. This requires the graph8 tracking script on your website (see [Website Visitors](https://docs.graph8.com/signals/website-visitors/)). ## Web Intent Web intent detects when contacts and companies engage with content matching your keywords **on other websites** — news articles, review sites, comparison pages, industry blogs, and more. ### How It Works 1. You add keywords to track 2. graph8 searches the web for articles and pages matching those keywords 3. graph8 monitors whether any of your contacts or target companies visit those pages 4. When a match is found, a web intent signal is created This means you can detect that a prospect is researching your product category, reading competitor reviews, or following industry news — even though they haven't visited your site yet. ## Adding Keywords 1. Go to **Signals → Intent** 2. Click **Add Keywords** 3. Enter the keywords you want to track **Examples of high-intent keywords:** | Category | Keywords | |----------|----------| | **Buying** | pricing, demo, trial, quote, plans | | **Comparison** | vs, alternative, comparison, review, competitor | | **Problem** | solution, how to, fix, improve, optimize | | **Your product** | your brand name, product names, feature names | | **Industry** | your market category, vertical terms | Each keyword shows the number of resolved contacts and companies that matched it, so you can see which terms are generating the most signal. ## High-Value Pages Certain pages carry stronger buying intent. graph8 automatically boosts the relevance score for pages whose URLs or titles match patterns like: - Pricing pages - Demo or trial pages - Comparison or "vs" pages - Review pages - Competitor-related pages These pages receive a scoring bonus so they rank higher in a contact's intent signal list. ## Topic Tracking Topics are broader than keywords — they represent industry verticals and technology categories. graph8 includes 1,000+ predefined topics across areas like: - **Industries** — SaaS, Healthcare, Financial Services, Manufacturing - **Technologies** — AI/ML, Cloud Infrastructure, API Management, DevOps - **Business functions** — Sales Automation, Marketing Analytics, HR Tech When a contact or company engages with content related to a tracked topic — on your site or across the web — graph8 captures the topic intent signal. ## Viewing Intent Data ### Resolved Contacts Go to **Signals → Intent → Contacts** to see all contacts who triggered your keyword or topic intents. Each row shows: - Contact name and company - Which keywords/topics matched - Number of intent signals - Most recent signal date - Signal intensity (High / Medium / Low) Use filters to narrow by keyword, topic, intensity, or date range. ### Resolved Companies Go to **Signals → Intent → Companies** to see company-level intent. This aggregates intent from: - Identified contacts at the company - Anonymous visitors resolved to the company via IP This is useful for identifying accounts showing interest even before individual contacts are known. Each company row shows a **logo** alongside the name and domain so you can scan the list visually. When a logo isn't available, graph8 falls back to a name-and-domain block so the row still reads cleanly without a broken image. ### Sites Visited The **Sites Visited** tab on contact and company pages shows the web pages a person or organization has visited. Each entry includes: - **URL** — the page that was visited - **Commercial intent score** — how likely the visit indicates buying intent - **Audience classification** — the type of visitor based on behavior Use this view to identify prospects actively researching relevant topics and prioritize outreach accordingly. ### Web Research Signals Web Research signals show pages your contacts or target companies have visited across the web — not just your own website. Each entry includes: - **Relevance score** — how closely the page matches your target topics - **Visit count** — how often the page was visited - **Matched keywords** — which intent signals triggered the match - **External link** — direct access to the page These signals come from third-party data and indicate active research behavior beyond your owned channels. ## Intent Search Intent Search lets you search across millions of enriched web pages to find what your prospects are researching. You can use keyword, semantic, or hybrid search, then filter results by audience, page type, and commercial intent level. Results include page URLs, intent scores, and matched keywords to help identify high-intent opportunities. ### Intent Keywords as Filters You can use intent keywords directly as filters on the Search page. Select a keyword to narrow results to contacts or companies actively showing interest in that topic. The number next to each keyword represents the number of matching records. > **Tip:** > Start with 10–15 high-intent keywords related to your product and buying process. Monitor which ones generate the most resolved contacts, then refine your list over time. Add competitor names and comparison terms to catch prospects evaluating alternatives. ## Next Steps - [Signals Overview →](https://docs.graph8.com/signals/overview/) — How signal scoring works - [Website Visitors →](https://docs.graph8.com/signals/website-visitors/) — Track and identify anonymous visitors - [Forms →](https://docs.graph8.com/signals/forms/) — Capture leads from website form submissions --- # Signals Overview > Understand buying signals — how graph8 detects intent from website visitors, form submissions, and keyword tracking. Source: https://docs.graph8.com/signals/overview/ Signals show you which prospects and companies are actively interested in your product. graph8 captures intent from three sources — **website visitors**, **form submissions**, and **keyword/topic tracking** — then scores each contact and company so you can prioritize outreach. ## The Three Signal Sources | Source | What It Captures | |--------|-----------------| | **Visitors** | Who's browsing your website — identified contacts and anonymous companies resolved via IP | | **Forms** | Who's submitting forms on your site — demo requests, contact forms, newsletter signups | | **Intent** | Who's engaging with content matching your keywords — both on your site (first-party) and across the web (web intent from news articles, reviews, competitor pages) | Together, these give you a complete picture of buying behavior — from your own website and across the web. ### Signals Tab The **Signals** tab on contact, company, and deal pages shows all signal activity in one place. This includes: - **Intent Keywords** — topics the contact or company is researching - **Web Research** — pages visited across the web - **Intent Topics** — grouped intent categories - **Page Visits** — activity on your website - **Email Engagement** — opens, clicks, and replies - **Form Submissions** — captured form activity - **Meeting Bookings** — scheduled meetings A signal score (0–100) and trend indicator (rising, stable, or declining) summarize overall engagement and intent. ## Signal Score Every contact and company gets a **signal score from 0 to 100** based on their combined activity. The score is weighted toward recent, high-value actions: | Activity | Weight | |----------|--------| | Form submission | 25 points | | Email reply | 20 points | | Keyword intent match | 15 points | | Topic intent match | 10 points | | Page visit | 5 points | ### Recency Bonus Recent activity scores higher: | Last Activity | Bonus | |--------------|-------| | Within 3 days | +20 points | | Within 7 days | +10 points | | Within 14 days | +5 points | | Older than 14 days | No bonus | A contact who submitted a demo form yesterday scores much higher than one who visited a page three weeks ago. ### Signal Trends graph8 compares the current score to the 30–60 day average and flags the trend: - **Rising** — score increased 20%+ (prospect is heating up) - **Stable** — score within 20% of baseline - **Declining** — score dropped 20%+ (prospect going cold) ## Where Signals Appear ### Contact Profiles Open any contact to see their signal panel: - Overall signal score and trend - Keyword and topic intents with intensity badges (High / Medium / Low) - Recent page visits with timestamps - Email engagement timeline - Form submissions and meeting bookings ### Company Profiles Company signals aggregate activity from **all contacts at that company**. If three people from the same company visited your pricing page this week, the company signal reflects that combined intent. Company signals also include anonymous visitor data — website visits from the company's IP range that haven't been attributed to a specific contact yet. ## Signal Intensity Individual signals are rated by recency: | Intensity | Meaning | |-----------|---------| | **High** | Activity in the last 7 days | | **Medium** | Activity 8–14 days ago | | **Low** | Activity 15–30 days ago | Signals older than 30 days are excluded from scoring. ## Next Steps - [Website Visitors →](https://docs.graph8.com/signals/website-visitors/) — Track and identify anonymous visitors - [Forms →](https://docs.graph8.com/signals/forms/) — Capture leads from website form submissions - [Intent Tracking →](https://docs.graph8.com/signals/intent-tracking/) — Set up keyword and topic tracking ## More help - [SEO and AI visibility](https://docs.graph8.com/signals/seo/) - [Competitive Radar](https://docs.graph8.com/signals/radar/) --- # Competitive Radar > Review competitors, monitored content, gaps and opportunities. Source: https://docs.graph8.com/signals/radar/ Open **Signals → Radar** to review competitive intelligence and market monitoring. Start with the competitors and domains that matter to your market. ## Work from evidence 1. Check the selected competitor or monitored domain. 2. Open the result or comparison relevant to your question. 3. Review the underlying page, observation or source and its date. 4. Record the opportunity or gap you want to address. 5. Use the available initiative or follow-up controls to track the response, then compare later evidence with the original finding. ## What you can investigate Radar's tools cover competitor records, tracked pages and page history, content and advertising monitoring, traffic estimates, keyword gaps, feature comparisons, opportunities and initiatives. A result depends on the sources collected and the permissions or services enabled for your organization. Treat estimated traffic and generated comparisons as inputs to a decision. Check the source before turning a finding into a customer-facing claim. ## Refresh and review A scan or discovery action can start collection work. Check the returned status and later results rather than assuming the request itself completed the collection. Avoid starting duplicate scans while an earlier one is still running. ## Related - [SEO and AI visibility](https://docs.graph8.com/signals/seo/) - [Social listening](https://docs.graph8.com/signals/social-listener/) - [MCP tool reference](https://docs.graph8.com/developers/mcp-tools/) --- # SEO and AI visibility > Research keywords, track rankings, audit your site, and inspect AI visibility. Source: https://docs.graph8.com/signals/seo/ Open **Signals → SEO** to work on search visibility and understand how AI answer engines describe your organization. ## Choose a task | Tab | Use it for | |---|---| | **Overview** | Review the SEO summary before choosing work. | | **Keyword Research** | Find and evaluate keywords. | | **Rank Tracking** | Follow rankings for the keywords you track. | | **Site Audit** | Inspect issues found on your site. | | **AI Visibility** | Review how AI answer engines describe your organization. | ## Research and track keywords 1. Open **Keyword Research** and enter the query or domain requested by the form. 2. Review the returned terms and their metrics in the selected market. 3. Select the keywords relevant to your audience. 4. Use **Track selected** when offered to continue into **Rank Tracking**. 5. Check later observations in the same market and time context before judging a change. ## Review a site audit Open **Site Audit**, choose the site and inspect the reported findings. Prioritize issues that affect your important pages, fix the underlying page or configuration, and rerun the audit when appropriate. An audit finding is a recommendation to investigate, not evidence that a fix has shipped. ## Read AI visibility results Use **AI Visibility** to inspect the available observations. Check the prompt, engine and observation time shown with the result. Results from one engine or prompt do not describe every buyer's experience. ## Related - [Competitive Radar](https://docs.graph8.com/signals/radar/) - [Content](https://docs.graph8.com/studio/content/) - [Website visitors](https://docs.graph8.com/signals/website-visitors/) --- # Social Listener > Monitor X (Twitter) for brand mentions, competitors, keywords, and high-intent buying signals - on graph8's shared X API or your own X Developer App. Source: https://docs.graph8.com/signals/social-listener/ Social Listener turns X (Twitter) into a live signal source. Track mentions of your brand, monitor keywords your buyers use, and surface high-intent posts ("just launched", "looking for a CRM", etc.) inside a daily inbox. Author handles are resolved to contacts in your CDP so signals flow into My Desk decisions and Sequencer drafts. ## How It Works 1. Open **Signals → Social** - no setup needed, graph8's shared X API works out of the box 2. Create one or more listeners (Brand / Keyword / Competitor / Engagement / User) 3. The worker polls X every 10-30 minutes, classifies posts, and routes matches into the right tab 4. Optionally connect your own X Developer App in **Settings → Integrations** for a higher quota (see BYO below) > **Note:** > Social Listener now lives under the **Signals** menu as **Social**. ## Listener Types | Type | What It Does | Default Poll | |------|--------------|--------------| | **Brand** | Tracks mentions of your brand, product names, and domain. Auto-builds the query from your brand snapshot. | 10 min | | **Keyword** | Free-form X query syntax with filters (language, min followers, exclude replies). | 10 min | | **Competitor** | Tracks mentions and activity around a named competitor so you can spot their customers and prospects. | 10 min | | **Engagement** | Keyword listener with intent operators baked in ("just launched", "looking for", "frustrated with"). Routes matches to the Engagement Inbox sorted by classifier score. | 15 min | | **User** | Polls a specific X user's timeline. Useful for tracking key prospects, partners, or competitors. | 30 min | > **Tip:** > When creating a listener, use the **plain-English query assist** to describe what you want to track in normal language and have graph8 generate the X query syntax for you. You can also toggle a **verified-only** filter to limit matches to verified accounts. ## Shared mode vs BYO mode graph8 supports two auth models for the X API and picks the right one automatically per org: | Mode | Who pays X | When to use | |------|-----------|-------------| | **Shared (default)** | graph8 pays X, bills you graph8 credits per post-read | Out-of-the-box for every org. No setup required. Capped per-month at the graph8 shared quota. | | **BYO (opt-in)** | You pay X directly | Higher quota, no graph8 markup on X usage. Recommended once you exceed the shared cap or want X billing in your own account. | Both modes are TOS-safe: tweet text is stored only within your org row, never indexed cross-tenant. ## Setup: Use graph8's shared X API (default) There is nothing to set up. Just visit **Signals → Social Listener** and create your first listener. graph8 polls X using its shared Developer App and bills your org graph8 credits per post-read (see Pricing below). ## Setup: Connect your own X Developer App (BYO upgrade) When you want the full per-app quota or want X to bill you directly: 1. Go to [developer.x.com](https://developer.x.com) and create a Developer Project + App 2. In the App's **Keys and Tokens** tab, copy the **API Key** and **API Secret** (graph8 derives the Bearer Token automatically via the OAuth2 Client Credentials grant) 3. In graph8, go to **Settings → Integrations** and find the **X API** card 4. Click **Connect your own X Developer App**, paste API Key + API Secret, click **Test connection** 5. Once verified, click **Connect** to save > **Note:** > Connecting and disconnecting your X app now has a single home: the **X API** card under **Settings → Integrations**. Admin capture rules (below) live separately under **Settings → Signals → Social**. graph8 stores the credentials encrypted (Fernet) and uses them only for Social Listener polling. If you later disconnect your BYO app, graph8 automatically falls back to shared mode and re-activates any listeners that were paused. ## Your First Listeners (auto-seed) If you have no listeners yet, the page shows a first-run hero with one-click **auto-seed**. graph8 reads your Studio docs (brand snapshot, competitors, ICP keywords) and proposes a starter set: - One **Brand** listener from your brand name, product names, and domain - A **Competitor** listener for each competitor it finds - **Keyword** listeners for your tracked terms - A couple of **Engagement** listeners with intent operators baked in Review the proposed listeners, then create them in one click. You can refine any query, filter, or poll cadence afterward, or create listeners manually instead. ## The Engagement Inbox The Engagement Inbox is the daily-action surface. It shows matched posts from all your engagement-type listeners, sorted by classifier score (highest intent first). ![Social Listener matched-post inbox — each row shows the author profile (avatar, handle, verified badge, follower count), the post, engagement metrics, and per-row actions (mark reviewed / dismiss / save as contact)](https://docs.graph8.com/images/signals/social-listener-results.png) Each match shows: - Author info (avatar, handle, verified, follower count) - The full post text - Engagement metrics (likes / replies / reposts / views) - A classifier badge (showcasing / asking / complaining / announcing) and a 0-100 intent score - Resolved contact link if the author is already in your CDP Actions per row: | Action | What It Does | |--------|--------------| | **Mark reviewed** | Acknowledges the post; removes from "New" filter | | **Dismiss** | Hides the post from future runs of the same query | | **Reply on X** | Opens a pre-filled reply to the post on x.com | | **Save as contact** | Creates a contact from the author profile if not already in your CDP | | **Add to list** | Adds the author (resolving or creating the contact) to a list | | **Enroll in sequence** | Enqueues the resolved contact into a Sequencer sequence | | **Open in X** | Opens the post on x.com | ## Source Lists The **Lists** tab turns a graph8 list into a tracked source: graph8 watches the X handles of the contacts on that list and pulls their posts. The list grid shows each list's status, contact count, how many handles are being tracked, how many contacts have no X URL, whether the list is dynamic, and when it last synced. You can add a list even before any of its contacts have X handles - tracking starts as soon as handles are filled in. ## Signals from Every Matched Author Social Listener now creates a signal for **every** matched author, not just authors already in your CRM. When a post matches a listener: - If the author is a known contact, the signal attaches to them directly. - If not, graph8 stub-creates a contact, adds them to a per-org **Social Signals** list (which kicks off auto-enrichment), and still fires an org-level signal. This means broad listeners surface intent even from accounts you have never seen before. ### Contact Capture Settings (admin) Admins control this from **Settings → Signals → Social**. The **Contact capture** panel has: - A master **Auto-create contacts** toggle - Per-listener-type rules: enable/disable capture, a **minimum follower** floor, and an **only when classified** option (only capture authors whose post the classifier scored as intent) Use these to keep low-quality or high-noise listeners from flooding your CDP with stub contacts. ## The Contact Social Tab When a contact has an X (Twitter) URL, a **Social** tab appears on their detail panel showing a read-only feed of the matched posts graph8 has captured for them. You can add or edit a contact's **Twitter / X URL** from the contact overview, and Twitter URL columns are available on the contacts and companies grids. ## Pricing Model Costs differ by auth mode: | Charge | Shared mode | BYO mode | |--------|-------------|----------| | Post read (`/2/tweets/search/recent`, `/2/users/:id/tweets`) | 7 credits per post returned | 0 graph8 credits - your X bill | | User lookup (`/2/users/by/username/:name`) | 14 credits per lookup | 0 graph8 credits - your X bill | | Post classification (per matched post) | 1 credit | 1 credit | | Brand-query auto-build (one-off, cached) | 5 credits | 5 credits | Under shared mode, graph8 pays X (`$0.005` per read, `$0.010` per lookup) and bills your org graph8 credits at a 40% margin. Under BYO mode, you pay X directly at the same rates and graph8 only bills for classifier + brand-query passes. The X Spend Banner inside the Social Listener page shows your running monthly X spend alongside graph8 credit burn. When usage crosses 80% of your configured cap, the banner turns into a warning; at 100%, listeners auto-pause until the next cycle. Shared-mode listeners also auto-pause when either graph8's global shared quota or your per-org shared cap is reached - upgrade to BYO to lift those caps. ## TOS-Safe Storage graph8 keeps tweet text strictly scoped to your org row. We never index post text cross-tenant, never redistribute it, and never share it between customers. See the [internal compliance doc](https://github.com/graph8-com/g8/blob/main/docs/compliance/social-listener-tos.md) for the full row-scoping architecture. > **Tip:** > Cost-preview pill: while editing a listener query, watch the pill below the query field. It shows estimated posts/day, monthly X spend, and monthly graph8 credits before you save. This prevents runaway cost from broad queries. ## Related - [Intent Tracking →](https://docs.graph8.com/signals/intent-tracking/) - first-party + web intent signals on resolved contacts - [Sequencer →](https://docs.graph8.com/engage/sequencer/) - send replies / outbound to matched authors --- # Trends > Track industry trends with growth scores, company correlations, and category breakdowns to identify demand signals. Source: https://docs.graph8.com/signals/trends/ Trends surfaces emerging industry topics and maps them to companies showing buying signals - so your team can target prospects while demand is rising. ## How Trends Work graph8 tracks trends across 15 industry categories, scoring each trend by growth velocity and market relevance. Trends are updated continuously and linked to companies that show activity in those areas. ## Browsing Trends Go to **Signals → Trends** to explore: - **Category filter** - narrow by industry (SaaS, Healthcare, Finance, etc.) - **Search** - find trends by keyword - **Sort by growth** - see fastest-growing trends first Each trend card shows: | Field | What It Means | |-------|---------------| | **Trend name** | The topic or technology being tracked | | **Growth score** | 0-100 indicating how fast the trend is growing | | **Growth %** | Percentage growth over the tracking period | | **Category** | Industry classification | | **Segments** | Sub-categories within the trend | ## Trend Details Click any trend to see its detail page: ### Company Correlations See which companies are associated with this trend - based on their web activity, content publishing, job postings, and technology adoption. ### Segment Breakdown Trends are divided into segments showing which sub-topics within the trend are gaining traction. ## Using Trends for Prospecting Trends connect to your prospecting workflow: 1. **Identify a rising trend** in your target industry 2. **View correlated companies** showing activity in that area 3. **Add companies to a list** for outreach 4. **Reference the trend** in your messaging for relevance > **Tip:** > Combine Trends with Intent Signals for maximum targeting precision. A company showing both trend activity and website visits is a high-priority prospect. ## Related - [Intent Tracking →](https://docs.graph8.com/signals/intent-tracking/) - Track buyer intent signals - [Website Visitors →](https://docs.graph8.com/signals/website-visitors/) - See who's visiting your site - [Prospecting →](https://docs.graph8.com/data/prospecting/) - Search and build targeted lists --- # Website Visitors > Track anonymous website visitors, identify companies, and turn traffic into actionable leads. Source: https://docs.graph8.com/signals/website-visitors/ graph8 tracks visitors to your website and resolves them to contacts and companies. See which companies are browsing your site, which pages they're viewing, and turn anonymous traffic into actionable signals. ## How Visitor Tracking Works graph8 uses a tracking script on your website to capture page visits, form submissions, and other engagement events. The tracking collects: - Pages visited (URL, title, timestamp) - Visitor location (city, country) - Referral source - Session duration and page count - Form submissions and their data Visitor data flows into graph8 in real time, so signals update as prospects browse your site. ## Identifying Visitors ### Known Visitors When a visitor is already in your graph8 workspace (e.g., they clicked a link in an email sequence), their activity is automatically attributed to their contact record. Every page they visit adds to their signal score. ### Company Identification For anonymous visitors, graph8 uses IP-to-company resolution to identify which company the visitor belongs to. This means even before you know the individual, you can see that someone from a target account is actively researching your product. Company identification shows: - Company name and domain - Number of anonymous visitors from that company - Pages visited across all visitors - First and most recent visit dates - Geographic location ## Visitor Views ### People Visitors Go to **Signals → Visitors → People** to see identified individual visitors. Each row shows: - Contact name and email - Company - Pages visited (with timestamps) - Location - Source/referrer ### Company Visitors Go to **Signals → Visitors → Companies** to see company-level visitor data. This is especially valuable for identifying new accounts that are showing interest but haven't filled out a form yet. ### Campaign Visitors Track which of your campaigns are driving website visits. Go to **Signals → Visitors → Campaigns** to see visitor engagement broken down by campaign, including: - Which campaign pages received visits - Visitor counts per campaign - Conversion from visit to form submission ## Enriching Visitor Lists Turn anonymous company visitors into actionable prospect lists: 1. Go to **Signals → Visitors → Companies** 2. Filter to companies matching your ICP (size, industry, location) 3. Click **Enrich** to fill in company details and find contacts 4. Save enriched contacts to a list for outreach This workflow lets you convert website traffic into targeted prospect lists without waiting for visitors to fill out a form. > **Tip:** > Check your company visitors weekly to spot accounts that are researching your product. Companies with multiple visitors or repeat visits are strong candidates for proactive outreach. ## Next Steps - [Signals Overview →](https://docs.graph8.com/signals/overview/) — How signal scoring works - [Intent Tracking →](https://docs.graph8.com/signals/intent-tracking/) — Set up keyword and topic tracking - [Forms →](https://docs.graph8.com/signals/forms/) — Capture leads from website form submissions --- # Companies > Run AI-powered company intelligence, generate deal strategy documents, and manage your sales pipeline. Source: https://docs.graph8.com/studio/accounts/ The Companies section of Studio focuses on individual target companies. Run AI intelligence on any company to generate strategic documents, then manage deals through your pipeline with signal-informed context. ## Company Intelligence Company intelligence is a multi-step AI workflow that researches a target company and produces actionable documents for your sales team. ### How It Works 1. Go to **Studio → Companies** and select a company 2. Click **Run Intelligence** 3. Studio executes a multi-step workflow: - **Website scrape** — analyzes the company's website - **Competitor analysis** — identifies their competitive landscape - **Content analysis** — reviews their public content and messaging - **API enrichment** — pulls firmographic and technographic data - **Synthesis** — combines everything into strategic documents The workflow runs in the background and updates in real time as each step completes. ### Company Documents Intelligence generates 10+ documents per company: | Document | What It Provides | |----------|-----------------| | **Company Brief** | Strategic overview of the company | | **Prospecting Brief** | Initial outreach guidance and angles | | **Qualification Checklist** | Deal qualification criteria | | **Pain Points** | Specific business challenges the company faces | | **Value Proposition** | Tailored value message for this company | | **Competitive Analysis** | How you position against their current solutions | | **Trigger Events** | Buying signals and timing indicators | | **ROI Analysis** | Business case and potential return | | **Deal Risks** | Risk factors and mitigation strategies | | **Next Steps** | Recommended action plan | Each document draws from your global context (brand, messaging, personas) combined with company-specific research. > **Tip:** > Run company intelligence before your first outreach to a target company. The prospecting brief and pain points documents give your team personalized angles that generic templates can't match. ## Deals & Pipeline Manage your sales pipeline directly within Studio with full signal integration. ### Pipeline Views | View | Best For | |------|----------| | **Board** | Drag-and-drop deals across stages | | **List** | Sortable table for filtering and bulk actions | | **Forecast** | Revenue projections by stage, rep, or time period | ### Deal Stages graph8 ships with 7 default stages, each with a win probability for forecasting: | Stage | Probability | |-------|------------| | Lead | 10% | | Qualified | 20% | | Meeting | 40% | | Proposal | 60% | | Negotiation | 80% | | Closed Won | 100% | | Closed Lost | 0% | Customize stages in **Settings → Pipeline** — add, rename, remove stages, adjust probabilities, set required fields per stage, or create multiple pipelines for different products or regions. ### Creating Deals Create a deal from: - **A company profile** — click **Create Deal** on any company - **The pipeline board** — click **+** in any stage column - **CRM sync** — deals synced from Salesforce or HubSpot appear automatically ### Deal Signals Each deal shows aggregated signals from all associated contacts: - Combined signal score across all deal contacts - Recent intent activity (are contacts visiting your site?) - Email engagement (are they responding to sequences?) - Meeting activity (bookings and cancellations) Use deal signals during pipeline reviews to spot at-risk opportunities early. ### Forecasting The forecast view projects revenue based on deal amounts weighted by stage probability: - **Weighted Pipeline** — deal amount multiplied by stage probability - **Commit** — deals your reps have committed to closing this period - **Best Case** — deals with a reasonable chance of closing ## CRM Integrations Sync your pipeline with your CRM: | CRM | Sync | |-----|------| | **Salesforce** | Two-way sync | | **HubSpot** | Two-way sync | Set up in **Settings → Integrations** — connect your CRM, map deal fields, set sync direction, and configure conflict resolution rules. ## Next Steps - [Global Context →](https://docs.graph8.com/studio/global/) — Set up org-level intelligence - [Campaigns →](https://docs.graph8.com/studio/campaigns/) — Create campaigns targeting specific companies - [Content →](https://docs.graph8.com/studio/content/) — Manage and optimize website content - [Team →](https://docs.graph8.com/studio/team/) — Assign team members as company owners --- # Ads > Design on-brand ad creatives with AI and launch paid campaigns to Meta, LinkedIn, Google Ads, and X from one workspace. Source: https://docs.graph8.com/studio/ads/ The Ads workspace is graph8's ad creative studio and campaign launcher in one place. Design on-brand ad copy and images with AI, refine them in a visual editor, then launch paid campaigns to Meta, LinkedIn, Google Ads, and X using your own contact data as the audience. Open it from **Studio → Content → Ads**. The workspace has four sections in the left sidebar: | Section | What it does | |---------|--------------| | **Create** | Generate new ad creatives (copy + designed image) for a platform and format | | **Drafts** | Work-in-progress creatives: edit, review, regenerate, and finalize | | **Gallery** | Finished creatives, ready to use in campaigns or export | | **Campaigns** | Connect ad accounts, build audiences, and launch paid campaigns | ![Ads Create tab](https://docs.graph8.com/images/ads/create-tab.png) ## Supported Platforms | Platform | Formats | Headline limit | Body text limit | |----------|---------|----------------|-----------------| | **Meta (Facebook/Instagram)** | Single image, square, portrait, story, carousel card, and more | 40 characters | 125 characters | | **LinkedIn** | Single image, square, vertical, carousel card | 70 characters | 600 characters (the feed shows about 150 before "see more") | | **Google Ads** | Landscape (Responsive), Square (Responsive), Portrait (Demand Gen) | 30 characters | 90 characters | | **X (Twitter)** | Single image, square, website card, vertical | 70 characters | 280 characters | graph8 enforces each platform's character limits and call-to-action options automatically, so generated ads always fit the spec. > **Tip:** > Generated ads pull from your Brand Kit: colors, fonts, logo, and design tokens. Set up **Studio → Brand Kit** first for the best on-brand results. Ads still generate without it, but with generic styling. ## Creating an Ad 1. Go to **Studio → Content → Ads → Create** 2. Pick a starting point in the **Start from** shelf: - **Starters**: pre-built briefs like Product launch, Webinar / event, Retargeting offer, Lead magnet, Free trial, and Social proof - **My templates**: creatives you saved as templates earlier - **Blank**: start from an empty form 3. In the **Details** form: - **What's this ad about?**: an optional short brief that steers both the copy and the image - **Platform**: Meta, LinkedIn, Google, or X - **Format**: platform-specific formats appear after you pick a platform - **Name** (optional): a custom title for the creative 4. Choose the **Image** source: - **AI generate** (default): graph8 designs an image to the format's exact dimensions. Optionally pick reference images from your library to guide style and subject - **Pick from library**: use an existing image from your asset library 5. Click **Generate with AI** The ad lands in **Drafts** within a moment and generation continues in the background. Generation consumes credits. ![LinkedIn format options](https://docs.graph8.com/images/ads/linkedin-formats.png) ## Campaign Sets Switch the mode bar from **Single ad** to **Campaign set** to generate a matrix of ads across personas and platforms in one batch. 1. Write an optional **Brief** that steers copy and image across the whole set 2. Select **Personas**: pick from your Studio personas or add one manually 3. Select **Platforms**: any combination of Meta, LinkedIn, Google, and X 4. Set **Variants per cell** to generate multiple options per persona and platform pair 5. Click **Generate set** The counter at the bottom shows the math: personas × platforms × variants = creatives. A batch is capped at 24 creatives. ![Campaign set matrix](https://docs.graph8.com/images/ads/campaign-set.png) ## Concepts The **Concepts** mode explores brand-aligned creative directions before you commit to a full render. Describe what you are promoting, click **Generate concepts**, and pick from a board of directions (each with a name, hook, tone, and layout). The picked concept seeds a single ad or a campaign set. ![Concepts mode](https://docs.graph8.com/images/ads/concepts.png) ## Working with Drafts The **Drafts** tab lists every creative that is not yet finalized, with platform counts across the top and status per row: | Status | Meaning | |--------|---------| | **Generating** | Copy and image are being created in the background | | **Ready** | Generation finished; the creative can be edited and finalized | | **Failed** | Generation hit an error; use the retry action to run it again | ![Drafts list](https://docs.graph8.com/images/ads/drafts.png) Click **Edit** on any draft to open the editor. ## The Ad Editor The editor shows your creative on a canvas with three views (**Canvas**, **In-feed**, and **Raw**) and an AI panel on the right. ![Ad editor](https://docs.graph8.com/images/ads/editor.png) What you can do here: - **Chat editing**: describe any change in plain language ("make it punchier", "lead with the discount") and the AI rewrites the copy. Quick chips cover common edits: Make it punchier, Lead with the stat, Shorten everything, More urgency - **Click a pin to edit**: text elements in the image carry pins; click one to edit that exact text in place. Click anywhere else on the image and describe the change you want - **Re-skin**: swap the creative to another brand color combination in one click - **Alt copy**: browse alternative copy variants generated alongside the original - **All sizes**: fan the finished creative out to the platform's other formats while keeping the approved copy - **Versions**: every edit snapshots a revision; restore any previous version - **Persona lens**: preview how a persona would react to the ad - **Review**: run an AI quality check (character limits, call-to-action rules, brand casing, placeholder text) with one-click fixes - **Export**: download the creative as SVG (editable in Figma or Canva) or PNG, or copy it to the clipboard When the creative is ready, click **Finalize → Gallery**. > **Note:** > AI edits (chat editing, field rewrites, deep review) consume credits. Deterministic actions like re-skin, versions, and export are free. ## Gallery and Templates The **Gallery** tab holds finished creatives. From here you can: - **View** the creative - **Save template**: keep the creative as a reusable starting point; it appears under **My templates** on the Create tab - Use it in a paid campaign: in the campaign wizard's Creative step, toggle **Use an existing creative** to auto-fill the headline, copy, call to action, and image from a gallery creative ![Gallery](https://docs.graph8.com/images/ads/gallery.png) ## Launching Paid Campaigns The **Campaigns** tab is the launcher: connect your ad accounts, build an audience from your contact data, attach a creative, set a budget, and publish. See the step-by-step platform guides: - [LinkedIn Ads Setup](https://docs.graph8.com/studio/ads-linkedin/) - [Google Ads Setup](https://docs.graph8.com/studio/ads-google/) ![Campaigns tab](https://docs.graph8.com/images/ads/campaigns.png) ## Permissions Ads access is controlled by four permissions under **Roles** (open the profile icon menu in the top right → **Settings → Roles**, then find the **Ads** row): | Permission | Allows | |------------|--------| | **View** | See creatives, campaigns, and metrics | | **Create** | Generate new creatives and save campaign drafts | | **Edit** | Edit drafts, and regenerate copy and images | | **Launch** | Publish campaigns, pause or resume them, delete them, and move creatives to the gallery | Launch is the money-spending permission: publishing pushes ads live on the platform and starts billing against your connected ad account. By default, admins, sales managers, and campaign managers can launch; other roles get view-only access. > **Tip:** > Team members with Create access but not Launch can still build campaigns end to end and click **Save Draft** in the wizard. Someone with Launch permission can then resume the draft from the Campaigns tab and publish it. --- # Google Ads Setup > Set up a Google Ads account, connect it to graph8, launch a Google campaign, and verify your ads inside Google Ads. Source: https://docs.graph8.com/studio/ads-google/ This guide walks through everything you need to run Google ads from graph8: preparing your Google Ads account, connecting it, launching a campaign, and checking the results inside Google Ads. ## Before You Start: Google Ads Requirements To launch Google campaigns from graph8 you need: 1. **A Google account** (Gmail or Google Workspace) 2. **A Google Ads account** with billing set up 3. **Access to the Google Ads account** for the Google user who connects graph8. Users are managed under **Admin → Access and security** inside Google Ads; only users with **Admin** access can invite others ### Create a Google Ads Account If you do not have an account yet: 1. Go to [ads.google.com](https://ads.google.com) and click **Start now**, signing in with your Google account 2. Enter your business name and website URL 3. Google pushes you toward creating your first campaign, but you can **skip all campaign recommendations and create an account without a campaign**. Look for the skip option; this gives you a standard account without forcing a Smart campaign 4. Select your **Billing country** and **time zone** > **Caution:** > The billing country determines the available payment options and currency, and the time zone affects all your reports, statistics, and billing. Both are hard to change later, so choose carefully. 5. Enter payment details (card, PayPal, or bank, depending on country) and click **Submit** ### Find Your Customer ID Your customer ID identifies the Google Ads account. Sign in to Google Ads and click your profile picture in the top right corner; the customer ID is displayed there. Keep it handy when working across multiple accounts. ## Connect Google Ads to graph8 1. Go to **Studio → Content → Ads → Campaigns** 2. Click **Create Campaign** 3. On the **Platform** step, select the **Google Ads** card 4. If the account is not connected yet, click **Connect**. A Google authorization window opens: pick the Google account that has access to your Google Ads account and approve the consent screen 5. Once authorized, the card shows a green **Connected** badge ![Campaign wizard platform step](https://docs.graph8.com/images/ads/wizard-platform.png) > **Note:** > The connection uses OAuth: graph8 never sees your Google password. You can review or revoke the connection anytime at [myaccount.google.com/connections](https://myaccount.google.com/connections), and also manage ad platform connections from the profile icon menu in the top right → **Settings → Integrations**. ## Design Your Google Ad Create the ad creative first in **Studio → Content → Ads → Create** (see the [Ads guide](https://docs.graph8.com/studio/ads/)). Google supports three formats: | Format | Aspect ratio | Recommended size | |--------|--------------|------------------| | **Landscape (Responsive)** | 1.91:1 | 1200 × 628 | | **Square (Responsive)** | 1:1 | 1200 × 1200 | | **Portrait (Demand Gen)** | 4:5 | 960 × 1200 | ![Google format options](https://docs.graph8.com/images/ads/google-formats.png) graph8 keeps the copy within Google's limits (30-character headlines and 90-character descriptions for responsive display ads) and restricts the call to action to Google's preset options; Google does not allow free-text buttons on responsive ads. When the creative is done, click **Finalize → Gallery** so the campaign wizard can pick it up. ## Launch a Google Campaign Go to **Studio → Content → Ads → Campaigns** and click **Create Campaign**. The wizard has six steps. ### 1. Platform Enter a **Campaign name** (shown in your graph8 dashboard and inside Google Ads) and select **Google Ads**. ### 2. Audience Define who sees your ads with the Audience Builder: - **Keywords**: add targeting keywords that define interest criteria - **By List / By Segment / By Individual**: include contacts from your graph8 lists, segments, or hand-picked people - **Suppression Lists** (optional): exclude contacts; suppressed contacts are never synced to the ad platform Add at least one keyword, list, segment, or contact to continue. graph8 syncs matched contacts to Google as a **Customer Match** audience using securely hashed identifiers. ![Campaign wizard audience step](https://docs.graph8.com/images/ads/wizard-audience.png) ### 3. Creative Design the ad content: - Toggle **Use an existing creative** to auto-fill the headline, copy, call to action, and image from a finished Google creative in your gallery - Or fill the fields manually: **Ad Image** (upload or pick a creative), **Headline**, **Ad Copy**, call to action, and destination URL The live **Ad Preview** on the right shows how the ad renders. ![Campaign wizard creative step](https://docs.graph8.com/images/ads/wizard-creative.png) ### 4. Budget Set your spend: - **Daily Budget** and currency (USD, EUR, or GBP) - **Start Date** and optional **End Date** - **Customer Match mode** (Google only): choose how the synced audience is applied - **Observation**: the campaign serves broadly while Google reports how your matched audience performs - **Targeting**: ads serve only to the matched audience - **Exclusion**: the matched audience is excluded from the campaign Without an end date, the campaign runs continuously at your daily budget until paused. ![Campaign wizard budget step](https://docs.graph8.com/images/ads/wizard-budget.png) ### 5. Review Confirm the summary of campaign, audience, creative, and budget. From here you can: - **Save Draft**: store everything without launching (needs Create permission); resume later from the Campaigns tab's Drafts list - **Publish Campaign**: sync the audience and launch the ads on Google (needs Launch permission) ![Campaign wizard review step](https://docs.graph8.com/images/ads/wizard-review.png) ### 6. Publish graph8 creates the campaign in your Google Ads account and reports the result. The new campaign appears in the **Active Campaigns** table with its status, budget, spend, and performance metrics. ## Check Your Ads in Google Ads After publishing, verify the campaign inside Google Ads: 1. Sign in at [ads.google.com](https://ads.google.com) 2. Google organizes ads as **Account → Campaign → Ad group → Ad** 3. Open the **Campaigns** menu to find your campaign by the name you set in graph8, then open **Ads** to see each ad's review status in the **Status** column Key Google ad statuses: | Status | Meaning | |--------|---------| | **Under review** | Google is evaluating the ad; editing an ad restarts its review | | **Eligible** | Passed review and can run (the campaign must be active) | | **Disapproved** | The ad violates a Google Ads policy and cannot show | | **Not eligible** | The campaign is paused or ended, or the ad group is incomplete | > **Tip:** > Most Google ads are reviewed within one business day. If an ad is still under review after two full business days, check its status in Google Ads. To see full policy details, open the Ads page, click the **Columns** icon → **Modify columns → Attributes → Policy details → Apply**. ## Manage the Campaign from graph8 The **Active Campaigns** table in **Studio → Content → Ads → Campaigns** shows each campaign's platform status (Active, Paused, Pending, Under review, Rejected, Completed, or Failed) alongside budget, spend, impressions, clicks, and CTR. Row actions let you **Edit**, **Pause**, **Resume**, or **Delete** a campaign (Launch permission required). Use the **Refresh** button to pull the latest status and metrics from Google. --- # LinkedIn Ads Setup > Set up LinkedIn Campaign Manager, connect it to graph8, launch a LinkedIn campaign, and verify your ads inside LinkedIn. Source: https://docs.graph8.com/studio/ads-linkedin/ This guide walks through everything you need to run LinkedIn ads from graph8: preparing your LinkedIn ad account, connecting it, launching a campaign, and checking the results inside LinkedIn Campaign Manager. ## Before You Start: LinkedIn Requirements To launch LinkedIn campaigns from graph8 you need: 1. **A personal LinkedIn profile**: Campaign Manager uses your normal LinkedIn login 2. **A LinkedIn Company Page** you admin (or the ability to create one during setup) 3. **A Campaign Manager ad account** with billing set up 4. **The right role on the ad account**: to let graph8 create campaigns, the LinkedIn user who connects must have the **Campaign Manager**, **Account Manager**, or **Billing Admin** role on the ad account. The **Viewer** role is read-only and cannot launch ads, even after connecting ### Create a LinkedIn Ad Account If you do not have an ad account yet: 1. Go to [linkedin.com/campaignmanager](https://www.linkedin.com/campaignmanager) and sign in with your LinkedIn credentials 2. New advertisers see a welcome screen for creating an ad account. LinkedIn pre-fills the account name, associated Company Page, and currency 3. Edit the **Account name** if needed 4. Click **Change** next to the suggested Page to pick a different Company Page, or **Add new** to create one 5. Click **Change** next to the currency if needed 6. Agree to the LinkedIn Ads Agreement and click **Create account** > **Caution:** > The currency and the linked Company Page cannot be changed after the account is created. The person who creates the account automatically gets account manager and billing admin access. ### Add Billing Only the ad account's billing admin can add payment details: 1. In Campaign Manager, open your ad account and go to **Account settings → Billing** 2. On the **Billing setup** tab, find **How you'll pay** and click **Add payment details** 3. Enter your credit card details and click **Save and continue** LinkedIn may place a temporary authorization hold that clears within 5 to 7 business days. ### Add a Teammate to the Ad Account (Optional) If the person connecting graph8 is not on the ad account yet, an account manager can add them: **Account Settings → Manage access → Add user**, enter their name or profile URL, pick a role (Campaign Manager or higher), and click **Add**. ## Connect LinkedIn to graph8 1. Go to **Studio → Content → Ads → Campaigns** 2. Click **Create Campaign** 3. On the **Platform** step, select the **LinkedIn Ads** card 4. If the account is not connected yet, click **Connect**. A LinkedIn authorization window opens: sign in and approve the requested permissions 5. Once authorized, the card shows a green **Connected** badge and the message "LinkedIn Ads is connected and ready" ![Campaign wizard platform step](https://docs.graph8.com/images/ads/wizard-platform.png) > **Note:** > The connection uses OAuth: graph8 never sees your LinkedIn password. You can also manage ad platform connections from the profile icon menu in the top right → **Settings → Integrations**. ## Design Your LinkedIn Ad Create the ad creative first in **Studio → Content → Ads → Create** (see the [Ads guide](https://docs.graph8.com/studio/ads/)). LinkedIn supports four formats: | Format | Aspect ratio | Recommended size | |--------|--------------|------------------| | **Single Image** | 1.91:1 | 1200 × 628 | | **Square** | 1:1 | 1200 × 1200 | | **Vertical** | 4:5 | 720 × 900 | | **Carousel Card** | 1:1 | 1080 × 1080 | graph8 keeps the headline within LinkedIn's 70-character limit (longer headlines get truncated in the feed with no way to expand) and allows intro text up to 600 characters. LinkedIn shows roughly the first 150 characters in the feed before the "see more" cutoff, so put the hook up front. When the creative is done, click **Finalize → Gallery** so the campaign wizard can pick it up. ## Launch a LinkedIn Campaign Go to **Studio → Content → Ads → Campaigns** and click **Create Campaign**. The wizard has six steps. ### 1. Platform Enter a **Campaign name** (shown in your graph8 dashboard and inside LinkedIn) and select **LinkedIn Ads**. ### 2. Audience Define who sees your ads with the Audience Builder: - **Keywords**: add targeting keywords that define interest criteria - **By List / By Segment / By Individual**: include contacts from your graph8 lists, segments, or hand-picked people - **Suppression Lists** (optional): exclude contacts; suppressed contacts are never synced to the ad platform Add at least one keyword, list, segment, or contact to continue. graph8 syncs matched contacts to LinkedIn as a Matched Audience using securely hashed identifiers. ![Campaign wizard audience step](https://docs.graph8.com/images/ads/wizard-audience.png) ### 3. Creative Design the ad content: - Toggle **Use an existing creative** to auto-fill the headline, copy, call to action, and image from a finished LinkedIn creative in your gallery - Or fill the fields manually: **Ad Image** (upload or pick a creative), **Headline**, **Ad Copy**, call to action, and destination URL The live **Ad Preview** on the right shows how the ad renders. ![Campaign wizard creative step with existing creative toggle](https://docs.graph8.com/images/ads/wizard-existing-creative.png) ### 4. Budget Set your spend: - **Daily Budget** and currency (USD, EUR, or GBP) - **Start Date** and optional **End Date** Without an end date, the campaign runs continuously at your daily budget until paused. ![Campaign wizard budget step](https://docs.graph8.com/images/ads/wizard-budget.png) ### 5. Review Confirm the summary of campaign, audience, creative, and budget. From here you can: - **Save Draft**: store everything without launching (needs Create permission); resume later from the Campaigns tab's Drafts list - **Publish Campaign**: sync the audience and launch the ads on LinkedIn (needs Launch permission) ![Campaign wizard review step](https://docs.graph8.com/images/ads/wizard-review.png) ### 6. Publish graph8 creates the campaign in your LinkedIn ad account and reports the result. The new campaign appears in the **Active Campaigns** table with its status, budget, spend, and performance metrics. ## Check Your Ads in LinkedIn After publishing, verify the campaign inside LinkedIn: 1. Go to [linkedin.com/campaignmanager](https://www.linkedin.com/campaignmanager) and select your ad account 2. LinkedIn organizes ads as **Ad account → Campaign → Ad set → Ad** (LinkedIn renamed this hierarchy in late 2025; older help articles may still say "campaign groups") 3. Find your campaign by the name you set in graph8, then open the **Ads** tab to see each ad's status Key LinkedIn ad statuses: | Status | Meaning | |--------|---------| | **In Review** | LinkedIn is reviewing the ad; it starts running if approved | | **Active** | Reviewed and approved; runs once the start date is reached | | **Paused** | Not delivering; resume from graph8 or LinkedIn | | **Rejected** | The ad does not meet LinkedIn's advertising guidelines and cannot run | > **Tip:** > Every LinkedIn ad goes through review, usually within 24 hours. LinkedIn recommends setting campaigns up at least 48 hours before the start date to leave room for the review. ## Manage the Campaign from graph8 The **Active Campaigns** table in **Studio → Content → Ads → Campaigns** shows each campaign's platform status (Active, Paused, Pending, Under review, Rejected, Completed, or Failed) alongside budget, spend, impressions, clicks, and CTR. Row actions let you **Edit**, **Pause**, **Resume**, or **Delete** a campaign (Launch permission required). Use the **Refresh** button to pull the latest status and metrics from LinkedIn. --- # Campaigns > Create AI-powered campaigns — from ideation through document generation to launching into your outreach sequences. Source: https://docs.graph8.com/studio/campaigns/ Campaigns in Studio take you from idea to execution. The AI generates campaign concepts, writes all the copy and collateral, and pushes the finished sequence directly into Engage for outreach. ## Campaign Overview A campaign brings together all the documents, steps, and insights needed to launch and run your go-to-market strategy. Each campaign has a unique URL, so you can share it directly with your team. Opening the link takes you to the exact campaign view, even after refresh. This makes collaboration and review easy across teams. ![Campaign list](./screenshots/campaigns/campaign-list.png) --- ## Campaign Ideation Start by letting the AI generate campaign ideas based on your global context and intelligence. 1. Go to **Studio → Campaign** 2. Click **Generate Ideas** 3. The AI produces ranked campaign concepts, each with: - Campaign name and category - Core concept and primary hook - Secondary hooks and proof points - Target channels (email, LinkedIn, phone, voice, etc.) - Execution complexity and expected impact ![Generate campaign ideas](./screenshots/campaigns/generate-ideas.png) ### Campaign Categories Studio supports 40+ campaign types organized into groups: | Group | Examples | |-------|---------| | **Outbound** | Social proof, trigger-based, ABM, event, authority, insight, persona-led | | **Partnership** | Integration, marketplace, co-sell | | **Product-Led** | API, freemium, usage-based, feature launch, developer | | **Lifecycle** | Expansion, renewal, win-back, cross-sell | | **Co-Marketing** | Reseller, affiliate | | **Thought Leadership** | Community, podcast, authority building | Select an idea to turn it into a full campaign. --- ## Campaign Documents When you create a campaign from an idea, Studio generates **20 documents** automatically — everything your team needs to execute. ### Core Strategy | Document | Purpose | |----------|---------| | **Campaign Brief** | Strategic overview with structured data | | **Targeting & Routing** | Audience targeting and routing rules | | **Messaging & Objections** | Key messaging themes and objection handling | ### Channel Copy | Document | Purpose | |----------|---------| | **Email Copy** | Email sequences with subject lines, body, and CTAs | | **Email Prompt** | Generation guidelines for email variations | | **LinkedIn Copy** | Connection notes, DMs, and comment templates | | **Social Prompt** | Social copy guidelines | | **Phone Call Script** | Talking points for live calls | | **Voicemail Script** | Voicemail drop scripts | | **Voice AI Script** | Scripts for AI voice calls | ### Shared Assets | Document | Purpose | |----------|---------| | **QA Rubric** | Quality assurance checklist | | **Snippets** | Reusable messaging blocks | | **Reply Templates** | Frameworks for handling responses | | **Cadence Calendar** | Timing and scheduling plan | | **Performance Tracker** | Metrics and KPI tracking | ### Operations | Document | Purpose | |----------|---------| | **Competitive Battlecard** | Competitive positioning for reps | | **Meeting Prep Checklist** | Sales enablement for booked meetings | | **Handoff Protocol** | Team handoff procedures | All documents are editable. Changes are versioned automatically. --- ## Step Editor The step editor lets you define the sequence of actions in your campaign: - **Steps** — each step has a channel (email, LinkedIn, phone, voice AI), timing (day), and conditions - **Modes** — email, connection note, DM, call, voicemail - **Personalization** — set the personalization level (low/medium/high) per step - **Conditions** — stop on reply, skip if already connected, etc. - **Reordering** — drag steps to change the sequence The step editor and channel copy documents stay in sync — when you edit steps, the corresponding copy documents update. ### AI-Generated Step Content When editing a campaign step, click **Generate** to have AI draft the content for you. You can choose from different positioning angles — each generates a unique hook and messaging approach for the same step. ### LinkedIn AI Instructions (HeyReach) For LinkedIn steps powered by HeyReach, you can use **AI Instructions** mode. Instead of using a fixed message, Copilot generates the message at send time using the contact’s latest data. This results in more personalized outreach compared to static templates. ### Push to Sequencer Click **Push to Sequencer** to send your campaign steps to Engage. The system maps each campaign step to a sequence step and creates the sequence automatically. The connection between campaign and sequence is preserved for tracking performance. --- ## Campaign Dashboard Every campaign gets an AI-generated dashboard with strategic insights: - **Readiness Score** — overall campaign viability rating - **Strategic Assessment** — confidence level and risk analysis - **Audience Profile** — target persona breakdown and buying signals - **Messaging Summary** — hook effectiveness and positioning angles - **Channel Strategy** — multi-channel approach and timing coordination - **Risk Assessment** — competitive threats, execution risks, and mitigations The dashboard regenerates when you make significant changes to campaign documents. --- ## Launching to Engage When your campaign is ready: 1. Click **Launch** on the campaign 2. Studio previews the sequence that will be created in Engage 3. Confirm — Studio pushes the campaign to the Engage sequencer 4. The campaign runs as an active multi-step outreach sequence The link between the Studio campaign and the Engage sequence is maintained, so you can trace performance back to the original campaign. --- ## AI Suggested List Name When building an audience, Studio automatically suggests a list name based on your targeting criteria. The suggested name is pre-filled when saving the audience. You can edit it or accept it as-is. --- > **Tip:** > Use the campaign dashboard to identify gaps before launching. If the readiness score flags missing proof points or weak messaging, refine those documents first. --- ## Next Steps - [Copilot →](https://docs.graph8.com/studio/copilot/) - [Global Context →](https://docs.graph8.com/studio/global/) - [Companies →](https://docs.graph8.com/studio/accounts/) - [Content →](https://docs.graph8.com/studio/content/) - [Team →](https://docs.graph8.com/studio/team/) --- # Content > Manage your website content inventory, analyze SEO health, and use AI to refresh or create content at scale. Source: https://docs.graph8.com/studio/content/ The Content section is an SEO and AI content engine. Import your website pages, analyze their SEO health, and use AI to refresh underperforming content or create new pages — all managed from a single workspace. ## Content Inventory The content inventory tracks all your website pages with SEO metrics and status. Each page shows: | Field | What It Tracks | |-------|---------------| | **URL** | Page address | | **Title** | Page title tag | | **Meta description** | SEO meta description | | **H1** | Primary heading | | **Word count** | Content length | | **Primary keyword** | Target keyword for the page | | **SEO score** | 0–100 health rating | | **Content type** | Website, blog, landing page, LinkedIn, newsletter | | **Status** | Idea, in progress, done, published | ## Importing Pages Bring your existing website content into the inventory: ### Deep Crawl 1. Go to **Studio → Content → Import** 2. Select **Crawl Website** 3. Enter your website URL — graph8 validates it against your registered domain 4. Optionally filter by path (e.g., only crawl `/blog` or exclude `/author`) 5. Click **Start** — graph8 crawls up to 5,000 pages The crawl runs in the background. Track progress in real time: pages found, scraped, imported, skipped, and failed. ### Sitemap Import 1. Select **Import from Sitemap** 2. Enter your sitemap URL (e.g., `yoursite.com/sitemap.xml`) 3. Filter by glob patterns if needed 4. Click **Import** — graph8 parses the sitemap and scrapes each URL ### Manual Add Click **Add Page** to manually add a URL to your inventory. ### Deleting Pages Open any page row in the Content workspace and use the **Delete** action to remove it from your inventory. Deleting a page: - Removes the inventory entry (URL, SEO score, metrics) - Detaches the page from any content grids it was part of - Does **not** affect the live page on your website — graph8 only manages the inventory record, not the actual published URL Bulk delete is available from the workspace table — select multiple rows and click Delete to clean up old or unwanted entries in one pass. ## SEO Analysis graph8 analyzes every imported page and produces an SEO score (0–100) based on: - Title tag presence and length - Meta description presence and length - H1 heading presence - Word count (300+ words recommended) - Internal and external link counts - Mobile friendliness signals - Page speed signals ### SEO Summary Dashboard The SEO summary gives you an at-a-glance view of your content health: - **Total pages** and pages with/without issues - **Issue breakdown** — missing titles, thin content, duplicate meta descriptions, etc. - **Severity levels** — critical, warning, info - **Score distribution** — excellent (80+), good (60–79), needs work (40–59), poor (below 40) ## Content Workspace The workspace is where you manage your content day-to-day. Filter and sort pages by: - Domain - Content type (blog, landing page, etc.) - SEO health (score ranges) - Intent (buy, compare, best) - Refresh status - Page type Update any page's metadata — primary topic, target keyword, intent, owner, and refresh scope. ## Content Grids Content grids let you run batch AI operations on multiple pages at once. ### Grid Types | Operation | What It Does | |-----------|-------------| | **Refresh** | AI rewrites or updates existing content to improve SEO | | **Create** | AI generates new content for target keywords | | **Analyze** | AI reviews content and provides optimization recommendations | ### Creating a Grid 1. Go to **Studio → Content → Grids** 2. Click **New Grid** and select the operation type 3. Add pages (rows) to the grid 4. Assign a target keyword and author per row 5. Run the grid — AI processes each page Each row tracks before/after SEO scores so you can measure improvement. ## Integrations ### Google Search Console Connect GSC to pull real search performance data into your content inventory: - **Clicks** — how often your page is clicked in search results - **Impressions** — how often it appears - **CTR** — click-through rate - **Average position** — where you rank for your target keywords ### SEO Data graph8 enriches your pages with deeper competitive SEO data: - **Authority score** — domain and page authority - **Backlinks** — number of incoming links - **Referring domains** — unique domains linking to you - **Keyword position** — ranking for tracked keywords - **Keyword volume** — monthly search volume > **Tip:** > Start by importing your blog or key landing pages. Focus on pages with high impressions but low CTR — these are your biggest quick wins for SEO improvement. ## Next Steps - [Global Context →](https://docs.graph8.com/studio/global/) — Set up org-level intelligence - [Campaigns →](https://docs.graph8.com/studio/campaigns/) — Create campaigns that drive traffic to your content - [Companies →](https://docs.graph8.com/studio/accounts/) — Company intelligence and deal strategy - [Team →](https://docs.graph8.com/studio/team/) — Assign authors to content pieces ## More help - [NPS and CSAT surveys](https://docs.graph8.com/studio/surveys/) --- # Copilot > Use Studio Copilot to create, edit, and analyze your campaign documents with AI-powered skills. Source: https://docs.graph8.com/studio/copilot/ Studio Copilot is your AI assistant inside graph8. It helps you create, edit, and analyze campaign documents, answer questions, and guide your go-to-market strategy. ## Copilot Overview Copilot is available across Studio and automatically adapts based on what you are working on. You can use Copilot in two modes: - **Global mode** — ask general questions or get strategic guidance without opening a document - **Document mode** — work inside a specific document with a specialized skill Copilot also surfaces insights like meetings, activity, and suggestions through its dashboard view, helping you stay focused on what matters. ![Copilot dashboard](./screenshots/copilot/copilot-dashboard.png) The dashboard provides an overview of your activity and priorities, while the Copilot panel allows you to interact with AI in real-time. ![Copilot panel](./screenshots/copilot/copilot-panel.png) ## Copilot Memory Copilot remembers context across your work in Studio. As you interact with documents, campaigns, and actions, Copilot builds memory automatically — storing key decisions, preferences, and organizational context. This means you don’t need to repeat instructions. Copilot uses past context to provide more relevant and consistent responses over time. --- ## How Skill Routing Works Studio Copilot automatically selects a specialist skill based on the document you are working on. When you open a document and start chatting, Copilot detects the document type and switches to the matching skill. The active skill is shown in the Copilot panel header. | When you open... | Copilot uses... | |---|---| | No document (global mode) | **global-advisor** - general GTM strategy | | Brand Brief, Brand Voice, Style Guide | **brand-strategist** | | Competitors, Positioning Matrix, Market Insights | **market-analyst** | | Pains & Gains, Buying Journey | **audience-expert** | | Offer Brief, Value Props, Proof Catalog, Pricing Matrix | **offer-architect** | | Messaging House, Elevator Pitch, Narrative Templates | **messaging-expert** | | ICP research documents | **icp-expert** | | Persona documents | **persona-expert** | | TAM research documents | **tam-expert** | | Campaign Ideas | **ideas-expert** | | Email Copy, Email Prompt | **email-writer** | | LinkedIn Copy | **linkedin-writer** | | Social Prompt | **social-writer** | | Phone Scripts, Voicemail, Voice AI Scripts, Battlecard, Meeting Prep, Handoff Protocol | **sales-enablement** | | Campaign Brief, Messaging & Objections, Targeting & Routing, QA Rubric, Cadence Calendar, Performance Tracker | **campaign-copy-editor** | | Campaign Dashboard | **readiness-expert** | | Landing Pages | **landing-page-designer** | | Security Questionnaires | **security-advisor** | --- ## Inline Field Editing Structured document views — such as Email and LinkedIn — include a Sparkles button next to fields like subject line, opening line, CTA, and body. Clicking a Sparkles button: 1. Sends a focused "rewrite this field" request to Copilot 2. Streams the suggestion directly into the chat 3. Applies the accepted edit instantly — no copy-paste required This allows you to improve specific parts of a document without rewriting everything. --- ## Slash Commands Type `/` in the Copilot chat to open the command menu. - Most commands run instantly - Some commands (like `/edit`) require confirmation - Commands adapt based on the document you are working on ### Editing Structured Documents The `/edit` command works across multiple document types — not just email or LinkedIn. You can: - Select a specific section - Run `/edit` - Let Copilot rewrite or improve that section ## Notifications Copilot can run actions in the background — such as generating documents or completing tasks. When an action is completed, you will see a notification in the bell icon at the top right. Click the notification to review the result or jump to the relevant document. ## Global Search Press **⌘K** (Mac) or **Ctrl+K** (Windows) to open global search. You can search across: - Campaigns - Documents - Contacts - Companies Select any result to navigate directly to it. This helps you quickly access information without switching between pages. --- ## Available Skills | Skill | Best For | |---|---| | **global-advisor** | GTM strategy, campaign ideation, general questions | | **brand-strategist** | Brand voice, tone, guidelines | | **market-analyst** | Competitive analysis, market insights | | **audience-expert** | Buyer psychology, pain points | | **offer-architect** | Value propositions, pricing | | **messaging-expert** | Core messaging and narratives | | **icp-expert** | Ideal customer profiles | | **persona-expert** | Persona development | | **tam-expert** | Market sizing | | **ideas-expert** | Campaign ideation | | **email-writer** | Email copy | | **linkedin-writer** | LinkedIn messaging | | **social-writer** | Social content | | **sales-enablement** | Scripts, battlecards, call prep | | **campaign-copy-editor** | Campaign QA and editing | | **readiness-expert** | Campaign readiness analysis | | **landing-page-designer** | Landing page structure | | **security-advisor** | Security questionnaires | --- ## Actions & Saved Intelligence Copilot can use insights generated from actions run on contacts and companies. When you run an action (such as research or analysis), the result appears in a side panel. You can choose to save this result as intelligence. Saved intelligence: - Is attached to the contact or company - Becomes available to Copilot automatically - Improves future outputs and personalization For example: - Writing outreach → uses saved insights about the contact - Generating campaigns → uses enriched company data - Answering questions → uses stored intelligence You don’t need to manually reference this data — Copilot uses it automatically. > **Tip:** > If Copilot is giving generic answers, check which skill is active in the panel header. Opening the correct document will automatically switch to the right specialist. --- ## Related - [Campaigns →](https://docs.graph8.com/studio/campaigns/) - [Global Context →](https://docs.graph8.com/studio/global/) - [Companies →](https://docs.graph8.com/studio/accounts/) --- # Custom Records > Model custom object types beyond contacts and companies — accounts, opportunities, territories, products, or anything specific to your business. Source: https://docs.graph8.com/studio/custom-records/ Custom Records let you model your own object types in graph8 — beyond the built-in contacts, companies, and deals. If your sales motion tracks things like territories, partners, opportunities, or vertical-specific entities, Custom Records give you a structured place to store them with relationships, attributes, and AI access. ## When to Use Custom Records Use custom records when: - You need to track something that doesn't fit contacts, companies, or deals - You want to reference the object from campaigns, sequences, or skills - You need to sync the object to/from your CRM as a custom object - The object has structured fields and relationships to other records Common examples: | Object Type | Use Case | |-------------|----------| | **Territories** | Sales region with assigned reps and target accounts | | **Partners** | Channel partners with their own contacts and deals | | **Products** | SKUs with pricing tiers and configurations | | **Events** | Conferences, trade shows with attendee contacts | | **Support Tickets** | Customer issues tied to contacts | | **Properties** | Real estate listings with locations and attributes | | **Policies** | Insurance products with contract terms | | **Vehicles** | Auto sales inventory with specs | ## Creating a Custom Record Type 1. Go to **Studio → Custom Records → New Type** 2. Name the record type (singular and plural, e.g., "Territory" / "Territories") 3. Pick an icon for sidebar navigation 4. Define attributes (see below) 5. Define relationships to other records (see below) 6. Set permissions 7. Click **Create** Once created, a new menu entry appears in the sidebar for that record type. ## Attributes (Fields) Each custom record type has its own fields. Add as many as needed. ### Field Types | Type | Example Use | |------|-------------| | **Text (short)** | Name, code, label | | **Text (long)** | Description, notes | | **Number** | Target revenue, rank | | **Currency** | Price, quota | | **Date** | Start date, end date | | **Datetime** | Event start time | | **Boolean** | Active / inactive flag | | **Dropdown** | Single-select from predefined options | | **Multi-select** | Multiple options | | **URL** | External link | | **Email** | Contact email | | **Phone** | Phone number with validation | | **Reference** | Points to another record (contact, company, deal, or custom record) | | **Formula** | Computed from other fields | | **AI Field** | Generated by an AI skill when the record is created or updated | ### Field Configuration For each field: - **Label** — display name - **Key** — internal identifier (auto-generated from label, immutable after creation) - **Required** — must have a value to save the record - **Unique** — values must be unique across all records of this type - **Default value** — optional starting value - **Help text** — shown in the UI when creating/editing records - **Validation rules** — regex patterns, min/max, etc. > **Caution:** > Field **keys** cannot be renamed once created — only labels. If you need a different key, create a new field and migrate data. This keeps formulas, workflows, and API integrations stable. ## Relationships Custom records can reference other records to model how your data connects. ### Relationship Types | Type | Example | |------|---------| | **One-to-one** | A Territory has one assigned Rep (User) | | **Many-to-one** | Many Opportunities belong to one Company | | **One-to-many** | One Territory has many target Companies | | **Many-to-many** | Partners can sell many Products; Products are sold by many Partners | ### Defining a Relationship 1. In the record type editor, click **Add Relationship** 2. Pick the target record type (contact, company, deal, or custom) 3. Set the cardinality (1:1, many:1, etc.) 4. Name the relationship (e.g., "Assigned Rep", "Parent Territory") 5. Choose cascade behavior: - **Cascade delete** — deleting parent deletes children - **Restrict delete** — can't delete parent if children exist - **Nullify** — deleting parent sets children's reference to null ## Views and Filters Each record type gets its own list view with AG Grid — same grid engine as Contacts and Companies. ### Default Columns Configure default columns at **Record Type → Views → Default**. Users can add or hide columns per session. ### Saved Views Create named views with specific filters and column sets: - "Active Territories" — filter by `Status = Active` - "Q1 Opportunities" — filter by close date range - "Enterprise Partners" — filter by tier Share views with the team or keep them private. ### Bulk Operations Like contacts and companies, you can: - Bulk edit fields - Bulk delete - Run skills against every record in a filtered view - Export to CSV or JSON ## AI Integration Custom records are first-class citizens for AI actions. ### Skills Create skills that target a custom record type (see [Skills →](https://docs.graph8.com/studio/skills/)). Variables follow the pattern `{{record.field_key}}`. Example skill for a "Territory" record: ``` Using the territory {{record.name}} with {{record.account_count}} target accounts and revenue goal of {{record.revenue_target | currency}}, draft a Q1 plan including: 1. Top 5 priority accounts 2. Key campaigns to run 3. Expected pipeline contribution ``` ### Agent Access Voice agents, Copilot, and workflows can all read and write custom records. Grant agent access at **Record Type → Permissions → AI Access**. ### Workflow Triggers Workflows can trigger on custom record events: - Created, updated, deleted - Field changed (specific field transitions) - Relationship added or removed ## CRM Sync If your CRM supports custom objects (Salesforce, HubSpot, Pipedrive custom objects), map them to graph8 custom records. 1. Go to **Settings → Integrations → [CRM] → Custom Objects** 2. Select your graph8 record type 3. Map to the CRM's corresponding custom object 4. Map fields (source ↔ destination) 5. Choose sync direction (one-way, two-way) 6. Enable — syncs run on the normal schedule ### Sync Behavior | Scenario | Behavior | |----------|----------| | Record created in graph8 | Pushed to CRM on next sync | | Record updated in graph8 | Pushed to CRM on next sync | | Record created in CRM | Pulled into graph8 | | Simultaneous edits | Conflict resolution per field — configure at the field level | | Record deleted | Configurable (cascade or soft-delete) | ## Permissions | Role | Capabilities | |------|-------------| | **Admin** | Create/edit/delete record types, all records | | **Member** | Create/edit/delete records within types they have access to | | **Viewer** | Read-only access | Per-record-type permissions can further restrict access. For example, only the Sales team sees Opportunities; everyone sees Territories. ## Limits | Limit | Default | Upgradable | |-------|---------|-----------| | **Custom record types** | 10 | Yes, up to 100 on Enterprise | | **Fields per type** | 50 | Yes, up to 500 | | **Records per type** | 100,000 | Yes, up to 10M | | **Relationships per type** | 20 | Yes | Check current limits at **Settings → Billing → Plan**. ## Troubleshooting | Issue | Fix | |-------|-----| | Can't delete a record type | Records still exist — delete all records first, or archive the type | | Field validation failing on import | Check regex patterns and required fields; export sample CSV from the UI for the exact format | | CRM sync not working for custom object | Verify the CRM user has permissions to the custom object; re-auth the integration | | AI skill can't find the record | Check that AI Access is enabled for the record type | | Relationship shows "broken reference" | Target record was deleted with cascade=nullify; re-link manually | ## Related - [Global Context →](https://docs.graph8.com/studio/global/) — Custom records can feed Global Context - [Skills →](https://docs.graph8.com/studio/skills/) — Target custom record types in skills - [Workflows →](https://docs.graph8.com/ai-features/workflows/) — Trigger on custom record events - [CRM Integrations →](https://docs.graph8.com/settings/integrations/) — Sync custom records to your CRM --- # Run Actions on Contacts and Companies > Execute any AI action directly from a contact or company detail page, with that record's data used as context. Source: https://docs.graph8.com/studio/entity-action-execution/ The Run Action button on contact and company detail pages lets you execute any AI skill against a single record. The AI receives the full record data as context, so outputs are specific to that person or company — no copy-paste required. ## How It Works 1. Open any contact or company detail page 2. Click the **lightning bolt** icon in the page header 3. Search or scroll to select an action 4. The action runs with this record's data automatically injected as context 5. Results appear in a panel — you can copy or close when done The panel stays open while the action runs and shows a completion time when finished. ## What Actions Are Available The dropdown shows all **enabled AI actions** configured for your organization. These are the same actions available in the AI Agents section. Actions that appear here: | Type | Description | Example | |------|-------------|---------| | **Research** | Summarize company news, find trigger events, identify pain points | "Summarize recent funding news for this company" | | **Personalization** | Generate an opening line, write a tailored email, draft a connection note | "Write a LinkedIn opener for this contact" | | **Analysis** | Qualification scoring, fit assessment, competitive context | "Score this lead against our ICP" | | **Enablement** | Meeting prep notes, talk track suggestions, objection handling | "Generate meeting prep notes for this account" | Actions must be enabled in **AI Agents** to appear in this list. Only AI-powered (LLM) actions are shown here — automation and webhook actions are not. ## Context Injection When you run an action from a contact page, the action receives: - First name, last name, job title, company name - Email address and phone number - Location, LinkedIn URL - Any custom fields on the record When run from a company page, the action receives: - Company name, domain, industry - Employee count, revenue range, location - Technologies, keywords, description - Any custom fields on the record The AI uses this data for variable substitution in action templates and as background context in its prompt. ### Global Context Included Beyond the record data, every action also receives: - **Brand Brief** — your company positioning - **Value Propositions** — key differentiators - **ICP definition** — who you sell to - **Offer Brief** — pricing and packaging - **Personas** — buyer role profiles This context comes from Studio's Global Context. Update it at **Studio → Global Context** to improve every action at once. ## Result Panel Results render in-line with formatting (headings, lists, links). You can: | Button | Action | |--------|--------| | **Copy** | Copy the full result to clipboard | | **Copy as Markdown** | Copy with markdown formatting preserved | | **Regenerate** | Re-run the action (useful if the first output wasn't quite right) | | **Save to Record** | Store the result as a note on the contact or company | | **Send to Copilot** | Continue iterating in Copilot chat | ### Saving Results When you click **Save to Record**, the result is added to: - The contact or company's **Notes** tab - The CRM (if sync is enabled) as an activity note - The Inbox timeline as an "AI Generated" entry Saved results can be referenced in future actions — they're part of the record context going forward. ## Running Actions at Scale Single-record actions are for ad-hoc work. To run the same action against many records, use one of these: | Method | Best For | Location | |--------|----------|----------| | **List bulk action** | Run against every record in a list | **Data → Lists → Select list → Run Action** | | **Campaign step** | Run as part of a sequence | **Engage → Sequencer → Add AI Action Step** | | **Workflow trigger** | Auto-run when a contact hits a condition | **Agents → Workflows** | | **Enrichment column** | Run as a computed column on every record | **Data → Lists** → open a list → **+** column → **AI** | ## Permissions and Credits ### Who Can Run Actions | Role | Permissions | |------|-------------| | **Admin** | Run any enabled action, edit actions | | **Member** | Run any enabled action | | **Viewer** | Cannot run actions | Disable individual actions for specific roles at **AI Agents → Action settings → Role visibility**. ### Credit Costs Each action consumes credits based on model tier: | Model Tier | Cost per Run | Typical Actions | |------------|--------------|-----------------| | **Fast** | 1 credit | Short responses, summaries | | **Standard** | 3–5 credits | Most personalization and research | | **Deep** | 10–20 credits | Deep research with web access | | **Thinking** | 15–40 credits | Qualification scoring, complex analysis | Running actions from detail pages counts against your monthly credit quota. Check remaining credits in **Settings → Billing**. ## Error Handling If an action fails, the panel shows the error reason: | Error | Cause | Fix | |-------|-------|-----| | **Missing required field** | The action needs a field that's empty on this record (e.g., LinkedIn URL) | Enrich the record first | | **Insufficient credits** | Your credit balance is below the action's cost | Top up in Settings → Billing | | **Model timeout** | The LLM didn't respond in time (rare) | Click Regenerate | | **Action disabled** | An admin disabled this action after you opened the page | Ask your admin to re-enable | | **Rate limited** | Too many concurrent actions from this user | Wait 30 seconds and retry | ## Activity Log Every action run is logged: - **Who** ran it (user email) - **What** action was run - **Which** record it ran against - **When** it ran (timestamp) - **Cost** in credits View the log at **Settings → Activity → AI Actions**. Use this for compliance review, usage auditing, or understanding team behavior. > **Tip:** > Create a "Personalized Opener" action in AI Agents and run it from any contact page to generate a tailored first line for outreach in seconds. Pair it with a "Research Trigger Event" action — run both back-to-back on the same contact for a fully-prepped outreach message in under a minute. ## Related - [AI Agents →](https://docs.graph8.com/ai-features/overview/) — Create and configure actions - [Campaigns →](https://docs.graph8.com/studio/campaigns/) — Run actions at scale across audiences - [Copilot Skills →](https://docs.graph8.com/studio/copilot/) — Context-aware skills in the Studio editor - [Workflows →](https://docs.graph8.com/ai-features/workflows/) — Auto-run actions on triggers - [Global Context →](https://docs.graph8.com/studio/global/) — Shared context every action uses --- # Global Context > Set up your organization's brand, personas, ICPs, and intelligence — the strategic foundation that powers all AI-generated content. Source: https://docs.graph8.com/studio/global/ Global Context is the foundation of Studio. It stores everything the AI needs to know about your organization — your brand, messaging, target audience, and competitive landscape. Every campaign, account document, and piece of content Studio generates draws from this context. Global generates documents organized in three sections: Intelligence, Context Documents, and Research Reports. Improving your context directly improves all AI outputs across Studio. Global generates documents organized in three sections: Intelligence, Context Documents, and Research Reports. ## Company Profile Your company profile provides the basic facts the AI uses in every output. Go to **Studio → Global → Company Profile** to set up: - **Basic info** — company name, website, industry, size, description - **Contact** — headquarters address, phone, support email - **Social media** — LinkedIn, Twitter, YouTube, and other profiles - **Custom fields** — any additional details relevant to your business Complete your profile before generating documents — the AI uses this data as a starting point for all research. ## Intelligence Intelligence is raw data collection. Studio analyzes your website, competitors, and market to gather the source material that feeds into everything else. | Intelligence Item | What It Collects | |-------------------|-----------------| | **Website Content** | Your site's pages, structure, and messaging | | **Company Data** | Firmographic data and company details | | **Products & Features** | Product inventory extracted from your website | | **Organic Keywords** | Keywords you rank for in organic search | | **Paid Keywords** | Keywords you bid on in paid search | | **Competitors** | Competitor companies identified from your market | | **Top Content** | Your highest-performing pages and posts | | **Audience Questions** | Questions your target audience is asking online | | **Search Trends** | Trending searches relevant to your industry | | **Brand News** | Recent news mentions and press coverage | | **Brand Sentiment** | Public sentiment analysis across channels | | **Visual Assets** | Brand imagery, logos, and visual identity elements | | **Social Profiles** | Your social media presence and activity | ### Running Intelligence 1. Go to **Studio → Global → Intelligence** 2. Click **Run All** to collect all data types, or run individually 3. Intelligence runs in the background — each item updates as it completes 4. Rerun periodically to keep your data current Intelligence data feeds directly into context document and research report generation. The more complete your intelligence, the better your generated documents will be. ## Context Documents Context documents are AI-generated strategic assets about your organization. They're organized into five categories. ### Brand | Document | What It Provides | |----------|-----------------| | **Brand Brief** | Your brand identity, positioning, and key differentiators | | **Brand Voice** | Tone, style, vocabulary, and communication guidelines | | **Style Guide** | Visual and content standards | | **Writing Style Guide** | Writing conventions, grammar preferences, and formatting rules | | **Compliance Rules** | Regulatory and compliance considerations for your content | ### Market | Document | What It Provides | |----------|-----------------| | **TAM Research** | Total addressable market size and segmentation | | **Competitors** | Competitor identification and positioning analysis | | **Positioning Matrix** | How you position against alternatives in the market | | **Market Insights** | Industry trends, market dynamics, and growth drivers | ### Audience | Document | What It Provides | |----------|-----------------| | **ICP Research** | Ideal customer profile criteria and firmographic targeting | | **Persona Research** | Buyer persona analysis with roles, motivations, and behaviors | | **Pains & Gains** | Customer pain points and desired outcomes | | **Buying Journey** | Stage-by-stage buyer decision process | ### Offer | Document | What It Provides | |----------|-----------------| | **Offer Brief** | Product/service offerings with features and pricing context | | **Value Propositions** | What you offer, who it's for, and why it matters | | **Proof Catalog** | Case studies, testimonials, and social proof inventory | | **Pricing Matrix** | Pricing strategy and positioning against alternatives | ### Messaging | Document | What It Provides | |----------|-----------------| | **Messaging House** | Core messaging themes, value pillars, and proof points | | **Elevator Pitch** | Concise pitch for different audiences | | **Narrative Templates** | Storytelling frameworks for different scenarios | | **Channel Templates** | Messaging adapted for each outreach channel | ### Generating Context Documents 1. Go to **Studio → Global** 2. Click **Generate All** to create all documents, or generate individually by category 3. The AI analyzes your company profile, website, and intelligence data to produce each document 4. Review and edit any document — your changes are preserved across regenerations Documents are versioned automatically. Each save creates a snapshot you can reference later. ## How It Connects to Campaigns Global Context is used automatically when generating campaigns, messaging, and outreach strategies. - Campaign ideas use your ICP, personas, and positioning - Messaging pulls from your messaging framework and value propositions - Channel strategies align with your audience and market Updating your Global Context improves all future campaigns, messaging, and AI-generated outputs across Studio. > **Tip:** > Generate your context documents early — they improve the quality of everything else in Studio. Campaign copy, account briefs, and content all pull from these documents. ## Research Reports Research reports are deep-dive analyses that synthesize your intelligence data into actionable insights. They use extended AI thinking for more thorough analysis. | Report | What It Covers | |--------|---------------| | **Review & Sentiment** | Customer review analysis across platforms | | **Competitive Teardown** | Deep-dive into a specific competitor | | **Analyst & Media** | Analyst coverage, press analysis, and industry commentary | | **Voice of Customer** | Customer sentiment synthesis across channels | | **GTM Channel Strategy** | Go-to-market strategy insights and channel recommendations | | **Buyer Psychology** | Decision-making factors, objections, and buying process | Research reports can be regenerated as your market evolves. Each report builds on your intelligence data, so rerunning intelligence first produces more up-to-date reports. ## Personas Personas define the buyer roles you're targeting. Studio's AI generates detailed personas based on your intelligence and context documents. Each persona includes: - **Title and priority** — the role and how important it is to your go-to-market - **Why target** — what makes this persona relevant - **Key signals** — indicators this person is a good fit - **Receptivity assessment** — how likely they are to engage - **Campaign approach** — recommended outreach strategy - **Confidence scoring** — data strengths and gaps behind the recommendation ### Creating Personas - **AI-generated** — let Studio analyze your intelligence and suggest personas with confidence scores - **Manual** — create personas by hand with your own criteria - **Hybrid** — start with AI suggestions and refine ## ICPs (Ideal Customer Profiles) ICPs define the companies you want to sell to. They complement personas (who) with firmographic targeting (where). Each ICP includes: | Criteria | Examples | |----------|---------| | **Firmographics** | Company size, revenue, industry, growth stage | | **Technographics** | Tech stack requirements (e.g., uses Salesforce) | | **Business signals** | Buying triggers, growth indicators | | **Tiers** | Priority tiers (Tier 1 = best fit, Tier 2 = good fit, etc.) | | **Market size** | Estimated number of companies matching the criteria | ICPs map to personas — you can see which buyer roles are most relevant for each ICP tier. ## Brand Kit The Brand Kit is your reusable asset library — page layouts, landing-page templates, and the mined design system that the AI applies across every Studio output. Open it from **Studio → Brand Kit**. ### Layouts Layouts are reusable archetype-based page structures (Demo, Lead Magnet, Product Launch, etc.). The catalog shows preview thumbnails and remembers which layouts you've selected. - **Auto-saved picks** — layout selections save automatically as you click; your preferences persist across sessions - **Custom layouts** — open **Create Custom Template** to write a prompt and generate a brand-new layout tailored to your business - **Regenerate** — re-run a custom layout with the same prompt to get a fresh variation when you want options ### Templates Templates are finalized landing pages promoted from the Drafts library. Reps can pull a template into a new campaign without re-doing the design work. | Action | What It Does | |--------|--------------| | **Save as Template** | Promote a polished landing-page draft into the template library | | **Delete** | Remove a template (the original draft is preserved) | | **Fix with AI** | Run [AI Review](https://docs.graph8.com/studio/landing-pages/#ai-review) on the template to surface issues before reps use it | The Templates toolbar groups these actions together so you can audit and curate the library quickly. ### Design System (Mined CSS) graph8 automatically extracts a design system from your website — colors, typography scale, spacing, shadows, gradients, border radii, and effect styles. The mined design system lives in the Brand Kit under **Design System** and is used by: - The AI when generating new landing pages and templates - The landing-page editor (so manual edits match your brand) - Any "match your brand" prompt across Studio The miner runs as part of the website intelligence pass. Re-run intelligence to refresh the mined design system after a website redesign. ## Next Steps - [Campaigns →](https://docs.graph8.com/studio/campaigns/) — Create AI-powered campaigns using your global context - [Landing Pages →](https://docs.graph8.com/studio/landing-pages/) — Build and publish branded pages using your Brand Kit - [Companies →](https://docs.graph8.com/studio/accounts/) — Run company-level intelligence and deal strategy - [Content →](https://docs.graph8.com/studio/content/) — Manage and optimize your website content - [Team →](https://docs.graph8.com/studio/team/) — Set up team profiles for personalized content --- # Intelligence > Research competitors, scrape websites, discover keywords, and enrich company data — all from Studio's Intelligence module. Source: https://docs.graph8.com/studio/intelligence/ Intelligence is Studio's research engine. It pulls together competitor data, website content, keyword rankings, and company enrichment into a structured knowledge base that powers every campaign, document, and AI action in graph8. ## Intelligence Documents Each Intelligence document is an automatically generated artifact stored in your organization. They feed the Campaign Intelligence Brief and are referenced by Copilot, Skills, and every AI action. | Document | What It Contains | When It Refreshes | |----------|-----------------|-------------------| | **Website Scrape** | Structured content from your website (headlines, pricing, product pages) | On demand or weekly | | **Company Enrichment** | Firmographics (size, revenue, industry, tech stack) | On demand or monthly | | **Competitor Discovery** | List of direct and indirect competitors with positioning | On demand | | **Competitor Teardown** | Deep analysis of each competitor (messaging, pricing, positioning) | On demand | | **Company Keywords** | Keywords your brand and content rank for | Monthly | | **Organic Keywords** | SEO keywords driving organic traffic | Monthly | | **Paid Keywords** | Keywords your company runs paid ads on | Monthly | | **Product Inventory** | All products, pricing, and positioning data | On demand | | **Review Sentiment** | Aggregated sentiment from review sites (G2, Capterra, Trustpilot) | Monthly | | **Industry Analyst** | Market size, growth trends, key players in your category | Quarterly | ## How It Works 1. Go to **Studio → Intelligence** 2. Pick a document type from the sidebar 3. Click **Generate** (or **Refresh** if a previous version exists) 4. graph8 runs the research — takes 30 seconds to 5 minutes depending on depth 5. Review the generated document 6. Accept to commit, or edit specific sections before accepting Generated documents become part of your organization's Global Context and are automatically referenced by every downstream AI action. ## Website Scrape The website scrape extracts structured content from your public site. ### What Gets Extracted - Headline copy from the homepage - Product and feature pages - Pricing tables (if publicly available) - Customer logos and case studies - About / team content - Blog taxonomies and post counts ### Configuration 1. Go to **Intelligence → Website Scrape → Configure** 2. Enter the root URL (e.g., `https://yourcompany.com`) 3. Set scrape depth: - **Shallow** — homepage + 1 click (fast, 30s) - **Standard** — 3 clicks deep (default, 2–3 min) - **Deep** — 5 clicks deep + blog (slow, 5–10 min) 4. Click **Save** ### Manual Refresh Website content changes. Refresh monthly, or after any major site update: 1. Open the Website Scrape document 2. Click **Refresh** 3. Review the diff — graph8 highlights what changed since last scrape 4. Accept the new version ## Competitor Discovery Competitor Discovery finds companies that compete with yours based on your website content and ICP. ### How Competitors Are Found graph8 uses three sources: - **SEO data** — who else ranks for your target keywords - **Paid ad intelligence** — who bids on your brand and category terms - **Market data** — companies in the same G2 / Capterra category ### Competitor Types | Type | Definition | |------|-----------| | **Direct** | Solves the same problem for the same audience | | **Indirect** | Solves the same problem with a different approach | | **Status quo** | The internal/manual alternative to your product | | **Adjacent** | Targets the same buyer with a related product | ### Reviewing Competitors 1. Open **Intelligence → Competitor Discovery** 2. Review the suggested list 3. For each competitor: - **Keep** — include in competitive analysis - **Exclude** — remove (not a real competitor) - **Reclassify** — change from Direct to Indirect, etc. 4. Click **Save** to update your competitor set The finalized competitor list powers Competitor Teardown, competitive battlecards, and Copilot's competitive-positioning skills. ## Competitor Teardown Once competitors are identified, generate a deep teardown per competitor. ### What a Teardown Includes | Section | Content | |---------|---------| | **Positioning** | Their homepage messaging and target ICP | | **Value propositions** | Their top 3 stated differentiators | | **Pricing** | Publicly visible pricing tiers and model | | **Key features** | Feature list from their product pages | | **Review sentiment** | G2/Capterra themes (strengths and weaknesses) | | **Social proof** | Customer logos, case studies, stated ARR/users | | **Your advantages** | Auto-generated comparison highlighting your differentiation | ### Using Teardowns - In campaigns — reference competitors directly in outbound copy - In Copilot — ask "how do we compete with [competitor]?" and get a structured answer - In deals — pull up competitive context before any meeting ## Keyword Intelligence Three related documents track keyword data: ### Company Keywords Keywords your brand name + product names appear for. Useful for tracking brand search volume over time. ### Organic Keywords SEO keywords driving organic traffic to your site. Includes: - Keyword, volume, rank position - Ranking URL on your site - Keyword difficulty - Monthly trend (growing/declining) ### Paid Keywords Keywords you bid on in paid search. Includes: - Keyword, monthly cost, CPC - Landing page URL - Quality score (if available) - Month-over-month spend ### Using Keyword Data Keyword documents feed Studio campaigns — when generating landing pages or ad copy, the AI references your ranking and spending data to target terms you're already winning or bidding on. ## Product Inventory Automatically extracted product/SKU data from your website and product pages. | Field | Example | |-------|---------| | **Product name** | "graph8 Growth Plan" | | **Pricing** | "$499/month" | | **Target audience** | "SMB sales teams" | | **Key features** | "5 seats, 1M emails, voice AI" | | **Positioning** | "Full sales stack for scaling teams" | | **CTA URL** | Link to pricing or signup page | The inventory powers: - Campaign ideation (campaigns tied to specific products) - AE Cockpit deal scoring (matching products to prospect size/needs) - Landing page generation (auto-pulls product details) ## Review Sentiment Aggregates review text from G2, Capterra, Trustpilot, and other sources. Not just star counts — extracts the themes. ### What You Get | Output | Description | |--------|-------------| | **Positive themes** | Top 5 things customers love, with frequency counts | | **Negative themes** | Top 5 complaints, with frequency counts | | **Feature requests** | Commonly requested features | | **Churn signals** | Reasons customers gave for switching away | | **Competitor mentions** | Which competitors reviewers compared you to | Use this data to inform positioning, identify product gaps, and prep win/loss narratives. ## Industry Analyst A quarterly-refreshed document covering your market category. | Section | Content | |---------|---------| | **Market size** | TAM, SAM, SOM estimates with source citations | | **Growth rate** | CAGR and recent trend direction | | **Key players** | Leaders, challengers, niche players (think Magic Quadrant) | | **Tailwinds** | Macro trends favoring your category | | **Headwinds** | Risks and macro concerns | | **Regulatory** | Any legislation affecting the category | Executives love this for board decks and strategic planning. ## Credit Costs | Document | Cost per Generation | |----------|---------------------| | Website Scrape | 10 credits | | Company Enrichment | 5 credits | | Competitor Discovery | 15 credits | | Competitor Teardown | 20 credits per competitor | | Keyword Intelligence (any) | 10 credits | | Product Inventory | 10 credits | | Review Sentiment | 15 credits | | Industry Analyst | 50 credits | Refreshes cost the same as generations. Check your credit balance at **Settings → Billing**. ## Permissions | Role | Capabilities | |------|-------------| | **Admin** | Generate, edit, delete any Intelligence document | | **Member** | Generate and refresh; edit if given document-level write access | | **Viewer** | Read-only access to all Intelligence documents | ## Related - [Global Context →](https://docs.graph8.com/studio/global/) — How Intelligence docs flow into Global Context - [Campaigns →](https://docs.graph8.com/studio/campaigns/) — Campaigns reference Intelligence data automatically - [Companies →](https://docs.graph8.com/studio/accounts/) — Company-level research and briefs - [Run Actions →](https://docs.graph8.com/studio/entity-action-execution/) — Actions that use Intelligence as context --- # Organization knowledge > Browse, search, and contribute the knowledge your organization uses. Source: https://docs.graph8.com/studio/knowledge/ Use **Studio → Knowledge** to find the documents and concepts that provide context for your organization. ## Browse and search 1. Open **Studio → Knowledge**. 2. Select a folder to see its contents. On a small screen, use the folder-tree control to choose another folder. 3. Select a document to preview its details. 4. Use search to find relevant knowledge, then inspect the source and content before relying on it. A document preview is useful for confirming that the knowledge actually supports the answer or action you have in mind. ## Add or propose knowledge Choose **Add knowledge** and follow the available contribution options. When authoring is offered, write the information and supporting context in the form. Submission may create a proposal; review its resulting status rather than assuming that the content immediately became authoritative. ## Edit and amend When the detail view offers **Edit** or **Amend**, use the action that matches your intended change. Keep the change specific and preserve the context needed to understand it. Check the resulting proposal or document after submission. ## Troubleshooting - If loading fails, use the retry action and check your organization's access. - If a document is absent, try its subject or source name in search and check the folder selection. - If a generated answer conflicts with a source, review the underlying document and correct or amend it through the available authoring flow. ## Related - [Global context](https://docs.graph8.com/studio/global/) - [Content creation](https://docs.graph8.com/studio/content/) - [Knowledge API](https://docs.graph8.com/developers/gtm-knowledge-base/) --- # Landing Pages > Create, edit, and publish landing pages using AI - clone from your website, start from a template, or build from scratch. Source: https://docs.graph8.com/studio/landing-pages/ Landing Pages let you create branded, conversion-focused pages in minutes — clone your existing website, pick a layout, or generate one with AI. Pages publish to a live URL with one click. ## Lifecycle: Create → Drafts → Publish Landing pages live under **Studio → Content → Landing Pages** and follow a three-stage lifecycle. Each stage is its own tab so you always know where a page is. | Stage | What's there | What you do | |-------|--------------|-------------| | **Create** | Layout catalog, prompt input, clone-URL | Start a new page from a layout, prompt, or URL | | **Drafts** | Pages in progress, fully editable | Edit, iterate, run AI Review, finalize | | **Publish** | Finalized + live pages | Publish, unpublish, swap versions, manage live URLs | A page moves from **Create** → **Drafts** when you start it, and from **Drafts** → **Publish** when you click Publish for the first time. ## Create Open **Studio → Content → Landing Pages → Create** to start a new page. There are three creation paths: ### Pick a Layout 1. Browse the layout catalog — cards show preview thumbnails for each archetype (Demo, Lead Magnet, Product Launch, Event, Free Trial, etc.) 2. Click a layout card — graph8 builds a draft using your brand's design tokens, colors, and fonts 3. Your selection auto-saves so the catalog "remembers" what you most recently picked across sessions Takes 30–60 seconds. Uses credits. ### Generate from a Prompt 1. In the Create tab, write a custom prompt (e.g., "Demo request page for enterprise buyers in fintech") 2. graph8 generates a full page tailored to your prompt and brand 3. Page lands in **Drafts** for editing Takes 30–60 seconds. Uses credits. ### Clone Any URL 1. Enter any web page URL (your homepage or a competitor's page) 2. graph8 captures the layout, styles, and structure 3. The cloned page appears in **Drafts** with your branding overlaid Takes 10–30 seconds. Works with most public websites. Logo hygiene runs automatically — AI-invented logos are stripped and replaced with your brand assets. ## The Editor The landing page editor is a three-column layout: | Column | What It Contains | |--------|------------------| | **Left** | Page library, section navigator, image browser | | **Middle** | Live page canvas - click any element to edit | | **Right** | AI chat panel and editing tools | ### Editing Text Click any text element on the canvas to edit it inline. Type directly to replace content. ### AI-Powered Editing **Highlight-to-chat:** Select any text on the page, then type instructions in the chat panel. AI rewrites the selected text while keeping your brand voice. **Section-level edits:** Click a section header, then describe changes in chat. AI can rewrite entire sections, change layouts, or adjust messaging. ### Managing Images Click the **Images** tab in the left panel to open the image browser: - Browse images extracted from your website - Upload new images - Filter by category (hero, product, team, logo, icon) - Click any image to place it on the page Images automatically match your brand's design tokens. ### Section Management Each page is built from sections (hero, features, testimonials, CTA, footer, etc.). Use the section navigator in the left panel to: - Reorder sections by dragging - Show or hide sections - Add new sections from templates ### Interactive Feature Cards When the AI generates a page with a "Features" or "Why Us" section, repeated card content is automatically upgraded into an **interactive tabs** layout — a tab strip with one tab per feature, content switching on click. This is more compact than stacked static cards and reads better on mobile. You can revert to static cards in the section settings if you prefer the longer scroll layout. ## AI Review AI Review checks a draft for conversion, accessibility, and brand-consistency issues, then offers a one-click fix for each finding. ### Running a Review 1. Open a draft and click **AI Review** in the toolbar 2. The review panel opens on the right with findings grouped by category (conversion, accessibility, brand, content quality) 3. Each finding shows severity (critical / warning / info), what's wrong, and a suggested fix 4. Click **Fix with AI** on a finding to apply the suggested change automatically 5. Or click **Fix with AI** at the panel level to apply all suggestions in one pass AI Review is also available on saved templates from the **Templates** view (under Brand Kit), so you can audit reusable layouts before reps pull them into campaigns. ### What Gets Reviewed | Category | Examples | |----------|----------| | **Conversion** | CTA placement, form length, value-prop clarity | | **Accessibility** | Color contrast, alt text presence, heading order | | **Brand** | Voice consistency, off-brand colors, missing logo | | **Content quality** | Placeholder text left over, broken links, ambiguous copy | The reviewer pipeline includes URL safety guards — `javascript:` URLs and HTML injected into text fields are blocked at the schema layer, so AI fixes can't introduce unsafe content. ## Saving and Versions Pages auto-save on every edit. View the full version history: 1. Click the **History** icon in the toolbar 2. See a timeline of all changes 3. Click any version to preview it 4. Click **Restore** to revert to that version Chat history is preserved alongside versions, so you can see what AI edits were made and why. ## Publishing Publish your page to a live URL: 1. Click **Publish** in the top toolbar 2. graph8 deploys the page to Cloudflare Pages 3. Your live URL appears: `https://g8-lp-{org-id}.pages.dev/{page-slug}` 4. Share the URL or embed it in campaigns **Updating a published page:** Make edits in the editor, then click **Publish** again. Changes go live immediately. **Unpublishing:** Click the publish menu and select **Unpublish** to take the page offline. > **Tip:** > Connect a custom domain for branded URLs. Contact your admin to configure DNS for your Cloudflare Pages deployment. ## Linking to Campaigns Connect landing pages to campaigns for tracking and attribution: 1. Open a campaign in **Studio → Campaigns** 2. In the campaign dashboard, click **Add Landing Page** 3. Select an existing page or create a new one 4. The page is now linked - visits are attributed to that campaign You can also link pages during creation by selecting a campaign from the landing page editor. ## Drafts The **Drafts** tab is your in-progress library. Pages stay here until you publish them. - Search and filter drafts by name or layout - Duplicate a draft to A/B test variations - Delete drafts you no longer need - Promote a polished draft to a reusable **template** in the Brand Kit (see [Brand Kit](https://docs.graph8.com/studio/global/#brand-kit)) ## Publish The **Publish** tab manages your live pages. - See all currently-published pages with their live URLs - Unpublish to take a page offline (the URL stops resolving but the draft is preserved) - Swap versions — bring a different draft live without changing the URL - Search by published URL, slug, or campaign association ## Advanced Features ### Export HTML Download the page as a standalone HTML file for hosting on your own infrastructure. ### Edit Raw HTML Switch to code view to make direct HTML/CSS edits for fine-grained control. ## Best Practices - **Start from your homepage clone** - this ensures perfect brand consistency - **Keep forms above the fold** - visitors should see the CTA without scrolling - **Use one clear CTA per page** - avoid competing actions - **Test before sharing** - preview the published URL on mobile and desktop - **Link to campaigns** - always connect pages to campaigns for attribution tracking ## Troubleshooting | Issue | Fix | |-------|-----| | Clone captures wrong styling | Try cloning again - the capture depends on page load timing | | Published URL not loading | Check that your org has Cloudflare credentials configured (contact admin) | | Images not showing | Verify images are uploaded to the image browser, not just linked externally | | AI edits not matching brand | Ensure your Global Context documents are generated (go to **Studio → Global**) | ## Related - [Campaigns →](https://docs.graph8.com/studio/campaigns/) - Create campaigns and link landing pages - [Global Context →](https://docs.graph8.com/studio/global/) - Generate the brand intelligence that powers AI editing - [Content →](https://docs.graph8.com/studio/content/) - Manage all content including landing pages --- # Security Questionnaires > Upload security questionnaires, let AI draft answers from your approved responses, and ship completed questionnaires faster. Source: https://docs.graph8.com/studio/security-questionnaires/ Security Questionnaires turn painful procurement paperwork into a few minutes of review. Upload a questionnaire (SIG, CAIQ, custom spreadsheet), graph8 extracts the questions, matches them against your approved answer library, and drafts responses. You review and ship. ## Why This Exists A single security questionnaire can have 150–400 questions. Teams spend 8–40 hours per questionnaire manually copying answers from old questionnaires, security policies, and runbooks. This module reduces that to 30–90 minutes of review. ## How It Works 1. Go to **Studio → Security Questionnaires → New Questionnaire** 2. Upload the questionnaire file (XLSX, DOCX, or PDF) 3. graph8 parses it — extracts every question, section, and expected answer format 4. AI matches each question against your approved answer library 5. Review the drafted answers 6. Export the completed questionnaire in the same format you uploaded ## Supported Formats | Format | Notes | |--------|-------| | **XLSX** | Most common — questionnaires with rows of questions. Output preserves original formatting | | **DOCX** | Word docs with numbered questions. Output returns a fresh DOCX | | **PDF** | Read-only input; output must be XLSX or DOCX | | **Google Sheets** | Import via Google Drive connection | | **SIG (Standard Information Gathering)** | Native support — recognizes the standard SIG Lite and SIG Core formats | | **CAIQ (Consensus Assessments Initiative Questionnaire)** | Native support | ## Question Extraction Parsing depends on format: - **Structured (XLSX)** — graph8 identifies the question column, answer column, and any classification columns (category, control family, etc.) - **Semi-structured (DOCX)** — detects numbered questions and preserves hierarchy - **Unstructured (PDF)** — uses AI to identify question text; may require manual review for edge cases After extraction, review the parsed questions. You can: - Merge duplicates - Reclassify category tags - Mark questions as "skip" if they don't apply ## Answer Library The answer library is your single source of truth for approved responses. Every time you review and approve an answer, it's added to the library for reuse on future questionnaires. ### Seeding the Library Seed your library from: - Previous completed questionnaires (bulk upload) - Security policies (SOC 2 report, pen test results, ISMS docs) - Engineering runbooks - Compliance team's canonical answer doc Go to **Security Questionnaires → Answer Library → Import** to bulk-upload. ### Library Organization | Field | Purpose | |-------|---------| | **Question pattern** | The question (or paraphrased variant) this answer addresses | | **Canonical answer** | The approved response | | **Alternate phrasings** | Short, long, or spreadsheet-friendly variants | | **Tags** | Category (access control, data protection, etc.) | | **Confidence level** | High / Medium / Low — affects auto-approval thresholds | | **Last reviewed** | Date the answer was last approved | | **Owner** | Which team/person is accountable | ### Library Governance Answers have a review lifecycle: - **Pending review** — drafted or imported, not yet approved - **Approved** — ready to use - **Needs update** — flagged after N months without re-review - **Deprecated** — no longer applicable Set review cadence at **Library → Settings → Review Cadence** (default: every 6 months). ## Drafting Responses Once a questionnaire is uploaded and parsed, click **Draft Answers**. graph8 iterates through every question and: 1. Finds the best-match answer from your library 2. Rewrites the answer to match the question's format (short answer, long answer, Y/N/NA) 3. Assigns a confidence score (0–100) 4. Flags questions with no good match for manual review ### Confidence Scoring | Confidence | Meaning | Recommended Action | |-----------|---------|-------------------| | **≥90** | High-confidence direct match | Quick approve | | **70–89** | Strong match with minor rewording | Review for accuracy | | **40–69** | Partial match — some relevant library content exists | Deep review or re-answer | | **Below 40** | No good match in library | Write from scratch or reject | ### Bulk Actions From the review screen: - **Approve all ≥90 confidence** — quick-approve obvious matches - **Flag all under 40 confidence** — triage unknowns - **Reassign to owner** — route specific categories to their owner (legal, security, eng) ## Review Workflow 1. Open the questionnaire's **Review** screen 2. Filter by confidence, section, or owner 3. For each question: - Read the drafted answer - Edit inline if needed - Click **Approve** to finalize - Click **Needs Input** to route to someone else with a comment 4. Once all questions are approved, click **Export** ### Collaboration Multiple reviewers can work on the same questionnaire simultaneously: - Assign sections to owners (security, legal, engineering) - Add comments on questions that need discussion - See who's currently viewing a section (presence indicator) - Mentions (`@username`) send email notifications ### Versioning Every edit is tracked. Roll back to any previous version of an answer at **Review → [Question] → History**. ## Exporting When all answers are approved, export the questionnaire: 1. Click **Export** 2. Choose format: - **Original XLSX** — answers inserted into the original file structure - **Formatted DOCX** — answers rendered into a formatted Word doc - **PDF** — static, locked PDF - **CSV** — raw question-answer pairs 3. Click **Download** Exports preserve: - Original question order - Section headings and formatting - Any branding/headers in the original ### Audit Trail The export includes an optional audit trail page listing: - Who approved each answer - When it was approved - Source from the answer library (canonical ID) - Any edits made during review Enable at **Export → Include Audit Trail**. ## Reusing Across Questionnaires After shipping 2–3 questionnaires, your library is strong enough that new questionnaires get 70–90% auto-matched with ≥70 confidence. Review time drops dramatically after the first few. ### Typical Library Growth | Questionnaires Completed | Library Size | Auto-Match Rate | |--------------------------|--------------|-----------------| | 1 | ~200 answers | 20–30% | | 3 | ~500 answers | 50–65% | | 10 | ~1,200 answers | 75–85% | | 25+ | ~2,000+ answers | 85–95% | ## Integrations | Integration | Use | |-------------|-----| | **Google Drive** | Import questionnaires directly from Drive | | **Slack** | Get notifications when questionnaires are assigned to you | | **Jira / Linear** | Create tickets for questions that need engineering input | | **Vanta / Drata** | Pull compliance controls directly into the answer library | | **SharePoint** | Bulk-import historical questionnaires | Configure integrations at **Settings → Integrations**. ## Permissions | Role | Capabilities | |------|-------------| | **Admin** | Upload questionnaires, manage library, approve answers, export | | **Reviewer** | Review and approve drafted answers for assigned sections | | **Contributor** | Add answers to the library (pending admin approval) | | **Viewer** | Read-only access | Set per-section permissions for sensitive questionnaires (legal, financial, HR). ## Security All questionnaire content and answer library data is encrypted at rest and in transit. Questionnaires are scoped to your organization — they're never used as training data or shared across tenants. Access logs track every view, edit, and export. Export logs at **Settings → Audit → Security Questionnaires**. ## Troubleshooting | Issue | Fix | |-------|-----| | Questionnaire parsing missed questions | Re-parse with manual section boundaries; or edit the extraction output directly | | Low confidence on most questions | Library is too sparse — seed from previous questionnaires and policies | | Export format doesn't match original | Use "Original XLSX" mode; if still off, the original used non-standard templates | | Answers contradict each other | Review library for conflicting entries; mark older ones deprecated | | Reviewer can't see a section | Check section-level permissions in the Review screen | > **Tip:** > Seed your library before your first questionnaire by uploading your SOC 2 report, pen test results, and last 1–2 completed questionnaires. First-draft quality improves dramatically with just 100–200 library entries. ## Related - [Global Context →](https://docs.graph8.com/studio/global/) — Brand and company details referenced by answers - [Custom Records →](https://docs.graph8.com/studio/custom-records/) — Model related objects (policies, controls) - [Skills →](https://docs.graph8.com/studio/skills/) — Answer drafting is powered by skills --- # Skills > Create, version, and deploy reusable AI skills — structured prompts that run across contacts, companies, campaigns, and documents. Source: https://docs.graph8.com/studio/skills/ Skills are reusable AI building blocks. Each skill is a named, versioned prompt that can run from any record, campaign, or workflow — giving your team consistent output without re-writing prompts every time. ## What a Skill Is A skill is: - **A named prompt** — so anyone on your team can find and run it - **Templated** — with variables that get filled in from the record it's running against - **Versioned** — edits create new versions; old versions still work for anything referencing them - **Role-aware** — admins control who can edit, run, and publish skills - **Backed by your context** — every skill automatically receives Global Context and the active record's data Think of skills as internal GPTs, but scoped to your company's knowledge and safe to deploy to the whole team. ## Creating a Skill 1. Go to **Studio → Skills** and click **New Skill** 2. Give it a name and description 3. Pick the skill type: | Type | Runs Against | Example | |------|-------------|---------| | **Contact** | A single contact | "Generate a LinkedIn opener" | | **Company** | A single company | "Summarize recent funding news" | | **Deal** | A single deal | "Draft a follow-up email after discovery" | | **List / Audience** | A contact list | "Score every contact against our ICP" | | **Document** | A Studio document | "Rewrite this landing page for enterprise buyers" | | **Ad-hoc** | No record context | "Write a cold email for a persona I'll paste in" | 4. Write the prompt with variables (see below) 5. Click **Save as Draft** 6. Test it against a real record before publishing ## Prompt Templating Skills use `{{variable}}` syntax to reference record data and global context. ### Record Variables Common variables you can use: | Variable | Value | |----------|-------| | `{{contact.first_name}}` | Contact's first name | | `{{contact.last_name}}` | Contact's last name | | `{{contact.job_title}}` | Job title | | `{{contact.email}}` | Email address | | `{{contact.linkedin_url}}` | LinkedIn URL | | `{{company.name}}` | Company name | | `{{company.domain}}` | Primary domain | | `{{company.industry}}` | Industry | | `{{company.employee_count}}` | Employee count | | `{{company.description}}` | Company description | | `{{deal.stage}}` | Deal's current stage | | `{{deal.amount}}` | Deal size | | `{{deal.close_date}}` | Expected close date | ### Global Context Variables | Variable | Value | |----------|-------| | `{{global.brand_brief}}` | Your brand brief from Studio → Global | | `{{global.value_props}}` | Your value propositions | | `{{global.icp}}` | Your ICP definition | | `{{global.personas}}` | All personas | | `{{global.persona.vp_sales}}` | A specific persona | | `{{global.offer_brief}}` | Pricing and packaging | ### Custom Fields Any custom field on a record is accessible via `{{contact.custom.field_name}}` or `{{company.custom.field_name}}`. ## Testing Skills Before publishing a skill, test it on real records: 1. Open the skill draft 2. Click **Test** 3. Select a record (contact, company, or deal) 4. Run — the result appears in the Test panel 5. Iterate on the prompt and re-run until you're happy Tests are logged but don't affect the record (no notes saved, no CRM sync). ### A/B Testing Prompts Compare two versions of the same skill before committing: 1. Click **A/B Test** on a draft 2. Create version A and version B prompts 3. Select 10 records 4. Run both versions 5. Compare outputs side-by-side 6. Pick the winner — it becomes the published version ## Versioning Every edit to a published skill creates a new version. Previous versions remain available for: - Workflows that reference older versions - Audit trails (who ran what version when) - Rolling back if a new version regresses quality | Version State | Meaning | |---------------|---------| | **Draft** | Not yet published, only the author can run it | | **Published** | Live, all eligible users can run it | | **Deprecated** | Hidden from the skill picker but still runs for anything pinned to this version | | **Archived** | Fully retired, no new runs allowed | ### Version Pinning Workflows and sequences can pin to a specific version. When you publish a new version, pinned references don't auto-upgrade — they stay on their pinned version until you explicitly update them. This prevents surprise behavior changes. ## Publishing and Permissions ### Publishing a Skill 1. With a draft selected, click **Publish** 2. Set visibility: - **Private** — only you - **Team** — specified users - **Organization** — everyone in your org 3. Set who can run it: - **All eligible users** - **Specific roles** (Admin, Member, specific custom roles) 4. Click **Publish** ### Role Controls | Capability | Admin | Member | Viewer | |-----------|-------|--------|--------| | Run published skills | ✓ | ✓ | — | | Create drafts | ✓ | ✓ | — | | Publish | ✓ | Requires admin approval | — | | Edit others' skills | ✓ | Only if shared write access | — | | Archive skills | ✓ | — | — | ## Running Skills Skills appear in multiple places: | Location | Behavior | |----------|----------| | **Contact detail page** | Lightning bolt icon opens a skill picker | | **Company detail page** | Same picker, filtered to company skills | | **Deal detail page** | Deal skills only | | **List bulk action** | Run a skill against every contact in a list | | **Campaign step** | Use a skill as a step in a sequence | | **Workflow trigger** | Auto-run a skill when a condition fires | | **Copilot** | "/skill_name" invokes the skill in chat | ## Credit Costs | Model Tier | Cost per Run | |------------|--------------| | Fast | 1 credit | | Standard | 3–5 credits | | Deep (web access) | 10–20 credits | | Thinking | 15–40 credits | Set the model tier per skill. Fast is fine for simple templating; Deep is needed for research-heavy skills; Thinking is for scoring or reasoning tasks. ## Troubleshooting | Issue | Fix | |-------|-----| | Skill produces inconsistent output | Add more structure to the prompt — explicit sections, format templates, or few-shot examples | | Variable shows up as `{{contact.x}}` in output | The field is empty on that record — enrich first or make the variable optional | | Skill times out | Reduce context size or switch to a faster model tier | | Output doesn't match template | Increase the model tier; add "Respond exactly in this format:" to the prompt | | Published skill disappeared | An admin may have archived it; check Archive tab | > **Tip:** > Build a library of small, focused skills instead of one giant skill. "Generate LinkedIn opener" is better than "Handle all outreach" — small skills are easier to test, tune, and compose in workflows. ## Related - [Copilot →](https://docs.graph8.com/studio/copilot/) — Skills are invokable directly in Copilot chat - [Run Actions →](https://docs.graph8.com/studio/entity-action-execution/) — How skills run from detail pages - [Workflows →](https://docs.graph8.com/ai-features/workflows/) — Auto-run skills on triggers - [Global Context →](https://docs.graph8.com/studio/global/) — The shared context every skill uses --- # NPS and CSAT surveys > Create surveys, review responses and compare customer feedback. Source: https://docs.graph8.com/studio/surveys/ Use the Surveys area in Studio to manage customer feedback. The survey list provides **Surveys** and **Overview** views, with list or grid presentation where offered. ## Create and review a survey 1. Open the survey creation form. 2. Choose the survey type and complete the requested content and audience settings. 3. Review the selected recipients and the delivery configuration before sending. 4. After delivery, open the survey's results to inspect responses and response timing. NPS and CSAT measure different questions. Compare like-for-like surveys and time periods rather than combining their scores into one measure. ## Understand the summary Survey summaries can show NPS or CSAT scores, sentiment, cadence and trends. NPS results distinguish promoters, passives and detractors. A survey that has never been sent or has no responses should be treated as missing evidence, not as a zero satisfaction score. ## Follow up and resend Review feedback in context before choosing a follow-up. If you use a resend action, inspect the selected survey and recipient scope so that the action matches your intent. Check delivery and response results after sending. ## Remove a survey Use the survey's delete action only after reviewing the confirmation. Removing a survey is separate from resolving the customer's feedback. ## Related - [Customer records](https://docs.graph8.com/deals/customers/) - [Inbox](https://docs.graph8.com/engage/inbox/) - [Reporting](https://docs.graph8.com/analytics/overview/) --- # Team > Set up team profiles, AI Twins, and thought leadership content — generate personalized content in each team member's voice. Source: https://docs.graph8.com/studio/team/ The Team section manages your team members' profiles and AI Twins. Each team member gets a personalized AI profile that Studio uses to generate content in their voice — LinkedIn posts, blog articles, newsletters, and thought leadership pieces. ## Team Member Profiles Every team member in your organization has a profile in Studio. Profiles are automatically created when users log in, or you can add members manually. Each profile includes: | Field | What It Stores | |-------|---------------| | **Identity** | Name, email, title, department | | **Contact** | Phone, avatar | | **Social** | LinkedIn URL, Twitter URL, personal website | | **Bio** | Short bio (1–2 sentences) and full professional bio | | **Expertise** | Areas of expertise and topics of interest | ### Managing Team Members Go to **Studio → Team** to see all team members. From here you can: - View and edit any member's profile - Filter by status (active, pending, inactive) or source - Search by name or email - Sync from your identity provider to pull in new team members ## AI Twin Every team member can have an AI Twin — a personalized AI profile that captures their voice, style, and expertise. Studio uses the AI Twin to generate content that sounds like it was written by that person. An AI Twin includes: - **Voice tone** — professional, casual, or thought-leader - **Writing samples** — real posts and articles used as style reference - **Job history** — past roles and experience for credibility context - **Expertise areas** — topics the person is qualified to speak on - **Topics of interest** — subjects they want to create content about ### Setting Up an AI Twin The fastest way to set up an AI Twin is to import from LinkedIn: 1. Go to your profile in **Studio → Team** 2. Click **Import from LinkedIn** 3. graph8 pulls in your headline, about section, skills, job history, and recent posts 4. Review and adjust — the imported data populates your bio, expertise areas, and writing samples automatically You can also set up an AI Twin manually by filling in each field. ## Content Generation With an AI Twin configured, Studio can generate personalized content: | Content Type | What It Produces | |-------------|-----------------| | **LinkedIn posts** | Posts matching the team member's voice and topics | | **Blog articles** | Bylined articles with the member's perspective | | **Newsletter content** | Sections or full newsletters in their style | | **Thought leadership** | Long-form pieces establishing expertise | Generated content is stored per team member with status tracking: draft → approved → published → archived. ## Thought Leadership Calendar Plan your team's content output with a thought leadership calendar: - **Themes** — define content themes with key messages and target audiences - **Content mix** — balance across LinkedIn, blog, newsletter, podcast, webinar - **Channels** — assign content to specific publishing channels - **Authors** — assign team members to each piece - **Schedule** — plan weekly content across a quarter (50+ pieces) - **Success metrics** — track reach, speaking invitations, and inbound leads The calendar ensures consistent thought leadership output across your team without everyone writing about the same topics. ## AI Clones For external thought leaders, advisors, or executives who don't have a graph8 account, you can create an AI clone: 1. Go to **Studio → Team** and select a team member 2. Click **Create Clone** 3. The clone inherits the original member's profile data 4. Optionally customize the clone's name and details Clones can generate content "as" that person without needing their direct involvement. This is useful for: - Executive ghostwriting - Content from board members or advisors - Thought leadership from external subject matter experts > **Tip:** > Import your sales team's LinkedIn profiles early. When Studio generates campaign copy, it can personalize outreach to match each rep's professional background and communication style. ## Next Steps - [Global Context →](https://docs.graph8.com/studio/global/) — Set up org-level intelligence - [Campaigns →](https://docs.graph8.com/studio/campaigns/) — Create campaigns with team member attribution - [Companies →](https://docs.graph8.com/studio/accounts/) — Assign company owners from your team - [Content →](https://docs.graph8.com/studio/content/) — Assign authors to SEO content pieces --- # Account Executive Training > Maximize deal velocity through AI-powered company intelligence Source: https://docs.graph8.com/training/account-executives/ Your role: Maximize deal velocity through AI-powered company intelligence and efficient pipeline management. > **Tip: Time Savings** > **45+ minutes per company** saved on research and prep ## Core Capabilities | Capability | What It Does | Time Saved | |------------|--------------|------------| | Company Intelligence | 6-category deep dive on any company | 30-45 min/company | | Copilot Chat | Natural language queries and actions | 15-20 min/interaction | | Deal Pipeline | Stage-based tracking with velocity metrics | 10 min/day | | Stakeholder Mapping | Contact enrichment with buying roles | 20 min/company | | Meeting Transcripts | RAG-indexed conversation history | 15 min/meeting | --- ## Week 1 Workflow: Company Research Mastery ### Day 1: Company Intelligence Setup 1. **Run Company Intelligence** Navigate to **Companies** tab, select target company, click **Run Intelligence** button. The 6-step workflow generates automatically. 2. **Review Intelligence Documents** Company detail page shows generated docs. Each category becomes a searchable document. Copilot can answer questions from this data. 3. **Map Stakeholders** Go to **Contacts** subtab within company. Add key stakeholders manually or via import. Assign **buying_role**: champion, decision_maker, influencer, or blocker. Set **engagement_score** based on interaction level. **Generated intelligence categories:** | Category | What It Contains | |----------|------------------| | Website Scrape | Messaging, products, positioning | | Company Enrichment | Firmographics, market position | | Financial Intelligence | Revenue, funding, growth signals | | Leadership Research | Executive team, decision makers | | News & Events | Press releases, developments | | Tech Stack | Tools, integration opportunities | --- ## Pre-Call Prep Workflow **Time: 5 minutes per call** Use this workflow before every prospect call: ``` Pre-Call Checklist ├── 1. Copilot: "What do we know about [company name]?" ├── 2. Review 6 intelligence categories ├── 3. Check stakeholder buying roles ├── 4. Review meeting transcript summaries (if any) └── 5. Note recent news/events for conversation starters ``` ### Copilot Queries for Pre-Call | Information Needed | Copilot Command | |-------------------|-----------------| | Company overview | "Summarize what we know about Acme Corp" | | Decision makers | "Who are the key decision makers at Acme?" | | Pain points | "What challenges is Acme Corp facing?" | | Recent news | "Any recent news about Acme Corp?" | | Tech stack | "What tools does Acme Corp use?" | | Competitors | "Who are Acme Corp's main competitors?" | --- ## During/After Call Workflow ### Live Updates via Copilot | Action | Copilot Command | |--------|-----------------| | Update deal stage | "Move the Acme deal to Proposal stage" | | Add contact | "Add John Smith as a decision maker at Acme" | | Log notes | "Add note to Acme: Discussed pricing, follow up next week" | | Update deal value | "Set Acme deal value to $75,000" | ### Post-Call Best Practices 1. **Update deal stage immediately** — Keeps pipeline accurate 2. **Log key conversation points** — Auto-indexed for future reference 3. **Add new stakeholders** — Expand your contact map 4. **Set next step** — Close date and follow-up tasks --- ## Pipeline Management ### Daily Pipeline View 1. Navigate to **Companies → Deals** tab 2. Use **My Deals** toggle to filter to your companies 3. Review deals by stage: - Discovery → Qualified → Proposal → Negotiation → Closed Won/Lost ### Deal Velocity Metrics | Metric | What It Shows | |--------|---------------| | Days in Stage | How long deal has been in current stage | | Stage Entry Date | When deal entered current stage | | Total Deal Value | Sum of all deals for company | > **Caution: Warning Signs** > - Deal in same stage >14 days without activity > - Multiple deals stuck at same stage > - No stakeholder engagement movement --- ## Stakeholder Intelligence ### Buying Role Definitions | Role | Description | How to Identify | |------|-------------|-----------------| | **Champion** | Internal advocate who wants you to win | Provides inside information, makes introductions | | **Decision Maker** | Has authority to sign | Controls budget, final say on vendor | | **Influencer** | Shapes the decision | Technical evaluator, end user representative | | **Blocker** | Actively opposes | Incumbent vendor relationship, status quo preference | ### Engagement Scoring Contact engagement_score (0-100) based on: - Meeting attendance - Email responsiveness - Document views - Champion behaviors --- ## Key Metrics to Track ### Personal Performance | Metric | Description | |--------|-------------| | **Pipeline Coverage** | Total deal value / Quota | | **Deal Velocity** | Average days in each stage | | **Win Rate** | Closed Won / Total Closed | | **Average Deal Size** | Total value / Deal count | ### Company Health | Metric | Description | |--------|-------------| | **Intelligence Completeness** | 6/6 categories generated | | **Stakeholder Coverage** | Contacts mapped across buying roles | | **Engagement Trend** | Rising or falling engagement_score | --- ## Troubleshooting | Issue | Solution | |-------|----------| | Company Intelligence stuck | Verify website URL is correct; retry workflow | | Missing stakeholder data | Import contacts via CSV or add manually | | Deal stage not updating | Check you have edit permissions on the deal | | Copilot not finding company | Use exact company name; check org filter | --- ## Best Practices 1. **Run intelligence before first call** — Never go in blind 2. **Update deals in real-time** — Stale pipeline = wrong forecast 3. **Map all stakeholders early** — Identify champions and blockers 4. **Use Copilot for speed** — Natural language is faster than forms 5. **Review days_in_stage weekly** — Catch stuck deals early --- ## What's Next? **Company Intelligence** [Deep dive on company research](https://docs.graph8.com/studio/accounts/) **Deals** [Managing your pipeline](https://docs.graph8.com/studio/accounts/) **Global Context** [Studio setup and intelligence](https://docs.graph8.com/studio/global/) --- # AE Training Missions > Four hands-on missions to master graph8 studio as an Account Executive Source: https://docs.graph8.com/training/ae/ Complete these four missions in order to master the workflows that save you **45+ minutes per company** on research, pipeline management, and deal execution. > **Tip: Before You Start** > Make sure your organization has [Global Context](https://docs.graph8.com/studio/global/) configured. Company Intelligence relies on your company profile and context documents. ## Your Missions **Mission 1: Master Company Research** Run Company Intelligence, map stakeholders, and prep for calls in 5 minutes. [Start Mission 1](https://docs.graph8.com/training/ae/research) **Mission 2: Build Your Pipeline** Create deals, set values, and move opportunities through stages. [Start Mission 2](https://docs.graph8.com/training/ae/pipeline) **Mission 3: Leverage AI for Selling** Use Copilot for research, generate AI emails, and leverage meeting transcripts. [Start Mission 3](https://docs.graph8.com/training/ae/ai-selling) **Mission 4: Close Deals Faster** Track deal velocity, keep pipeline current, and export deal summaries. [Start Mission 4](https://docs.graph8.com/training/ae/closing) ## Skill Progression | Mission | Skill | Time to Complete | |---------|-------|------------------| | 1. Company Research | Run intelligence, map stakeholders, pre-call prep | 15 min | | 2. Pipeline | Create and manage deals across stages | 10 min | | 3. AI Selling | Copilot queries, AI email, transcript review | 15 min | | 4. Closing | Velocity tracking, deal updates, export summaries | 10 min | ## Prerequisites - Access to graph8 studio with the **Companies** module enabled - At least one target company added to your workspace - [Global Context](https://docs.graph8.com/studio/global/) configured for your organization ## Need the full reference? These missions focus on hands-on practice. For the complete AE capability overview, see the [AE Training](https://docs.graph8.com/training/account-executives) reference page. --- # Mission 3: Leverage AI for Selling > Use Copilot for deep research, generate AI-powered emails, and leverage meeting transcripts Source: https://docs.graph8.com/training/ae/ai-selling/ ## What you'll learn - How to use Copilot for deep account research queries - How to generate AI-powered follow-up emails - How to leverage meeting transcripts for deal context --- ## Step 1: Deep Research with Copilot Go beyond the basic pre-call queries from Mission 1. Copilot can cross-reference intelligence across all your accounts. 1. Open **Copilot** from the bottom-right corner of Studio. 2. Run advanced research queries against your account intelligence: - **"Compare the tech stacks of Acme Corp and Beta Inc"** -- find patterns across accounts - **"Which of my accounts have recent funding rounds?"** -- identify accounts with budget - **"What pain points has [contact name] mentioned?"** -- pull from meeting notes and CRM data 3. Use the results to tailor your messaging and prioritize your outreach. > **Tip:** > Copilot searches across all 6 intelligence categories, meeting transcripts, and CRM notes. The more data you feed into graph8, the more powerful your queries become. --- ## Step 2: AI Email Generation Generate contextual follow-up emails that reference real account intelligence. 1. From an account detail page, open **Copilot**. 2. Ask: **"Draft a follow-up email to [contact name] at [company name] about [topic]"**. 3. Review the generated email. Copilot pulls from: - Company intelligence documents - Previous meeting notes - Stakeholder roles and engagement history 4. Edit the draft to match your voice, then copy it into your email client or sequence. | Email Type | Copilot Prompt | |------------|----------------| | Post-demo follow-up | "Draft a follow-up email to Sarah at Acme after our product demo" | | Proposal recap | "Write an email summarizing the proposal we sent to Acme Corp" | | Re-engagement | "Draft a re-engagement email to Tom at Beta Inc -- we haven't spoken in 3 weeks" | | Champion enablement | "Help my champion at Acme build an internal business case" | > **Tip:** > Always review and personalize AI-generated emails before sending. The AI provides a strong starting point, but your personal touch is what builds the relationship. --- ## Step 3: Meeting Transcripts Meeting transcripts are RAG-indexed, meaning Copilot can search and reference them in future queries. 1. After a recorded meeting, navigate to the account detail page. 2. Click the **Meetings** subtab to view transcripts associated with this account. 3. Review the auto-generated summary for key discussion points, action items, and next steps. 4. Use Copilot to query across your meeting history: - **"What did we discuss with Acme in our last meeting?"** - **"Has anyone at Acme mentioned concerns about pricing?"** - **"What action items are outstanding for the Acme deal?"** > **Tip:** > Meeting transcripts compound over time. After 3-4 meetings with an account, Copilot can surface patterns and objections you might have missed -- giving you a major advantage in negotiations. --- ## Mission Complete Checklist - [ ] Ran an advanced Copilot research query against account intelligence - [ ] Generated at least one AI-powered follow-up email - [ ] Reviewed a meeting transcript and queried it with Copilot --- **Next mission:** [Mission 4: Close Deals Faster](https://docs.graph8.com/training/ae/closing) -- Track deal velocity, keep your pipeline current, and export deal summaries. --- # Mission 4: Close Deals Faster > Track deal velocity, keep your pipeline current, and export deal summaries for stakeholders Source: https://docs.graph8.com/training/ae/closing/ ## What you'll learn - How to track deal velocity and spot stalled deals - How to keep deals updated efficiently with Copilot - How to export deal summaries for internal reviews --- ## Step 1: Track Deal Velocity Deal velocity tells you how long each opportunity spends in each pipeline stage. Use it to catch deals before they go cold. 1. Navigate to **Companies** and click the **Deals** subtab to view your full pipeline. 2. Use the **My Deals** toggle to filter to your deals only. 3. Review the key velocity metrics for each deal: - **Days in Stage** -- how long the deal has been in its current stage - **Stage Entry Date** -- when the deal entered the current stage - **Total Deal Value** -- sum of all deals on the account 4. Sort by **Days in Stage** (descending) to surface your most stalled deals first. > **Tip:** > Set a personal rule: any deal in the same stage for more than 14 days without activity needs immediate attention. Either advance it, update the stage, or mark it at risk. --- ## Step 2: Update Deals with Copilot Use natural language to keep your pipeline current without navigating through forms. 1. Open **Copilot** from the bottom-right corner of Studio. 2. Use these commands to update deals in real time: | Action | Copilot Command | |--------|-----------------| | Move a deal | "Move the Acme deal to Negotiation stage" | | Update value | "Set Acme deal value to $85,000" | | Add a note | "Add note to Acme deal: Legal review in progress, expect redlines by Friday" | | Add a contact | "Add Maria Lopez as an influencer at Acme Corp" | | Update close date | "Set Acme expected close date to March 30" | 3. Confirm each update -- Copilot will show you the change before applying it. > **Tip:** > Build a habit of updating deals via Copilot immediately after each call. It takes 30 seconds and keeps your pipeline accurate for forecasting. --- ## Step 3: Export Deal Summaries Pull together everything you know about a deal for internal reviews, QBRs, or manager check-ins. 1. From the account detail page, open **Copilot**. 2. Ask: **"Give me a full summary of the Acme Corp deal"** to get a consolidated view that includes: - Current deal stage and value - Key stakeholders and their roles - Recent meeting notes and action items - Company intelligence highlights 3. Copy the summary for use in pipeline reviews, forecast calls, or manager updates. 4. For a quick pipeline overview, ask: **"Show me all my deals in Proposal stage"** to review deals by stage. > **Tip:** > Run a deal summary before every pipeline review meeting. Walking in with a crisp account narrative -- instead of scrambling through notes -- sets you apart. --- ## Mission Complete Checklist - [ ] Reviewed deal velocity metrics and identified any stalled deals - [ ] Updated at least one deal using Copilot commands - [ ] Exported a deal summary for an account --- ## Training Complete You have finished all four AE training missions. Here is what you covered: | Mission | Key Skill | |---------|-----------| | [Mission 1: Company Research](https://docs.graph8.com/training/ae/research) | Intelligence, stakeholders, pre-call prep | | [Mission 2: Pipeline](https://docs.graph8.com/training/ae/pipeline) | Deal creation, stages, pipeline management | | [Mission 3: AI Selling](https://docs.graph8.com/training/ae/ai-selling) | Copilot research, AI email, transcripts | | [Mission 4: Closing](https://docs.graph8.com/training/ae/closing) | Velocity tracking, real-time updates, summaries | ### Keep going - Review the full [AE Training](https://docs.graph8.com/training/account-executives) reference for deeper coverage - Explore [Company Intelligence](https://docs.graph8.com/studio/accounts/) documentation - Set up [Global Context](https://docs.graph8.com/studio/global/) to power even richer AI results - Return to [All Training Missions](https://docs.graph8.com/training/) to explore other roles --- # Mission 2: Build Your Pipeline > Create deals, set values and close dates, and move opportunities through pipeline stages Source: https://docs.graph8.com/training/ae/pipeline/ ## What you'll learn - How to create a new deal on an account - How to set deal value and expected close date - How to move deals through pipeline stages --- ## Step 1: Create a Deal 1. Navigate to **Companies** in the left sidebar and select the company you researched in Mission 1. 2. Click the **Deals** subtab on the company detail page. 3. Click **+ New Deal** to open the deal creation form. 4. Fill in the required fields: - **Deal Name** -- a short, descriptive name (e.g., "Acme Corp - Enterprise License") - **Deal Value** -- the expected contract amount - **Expected Close Date** -- your target close date - **Stage** -- select **Discovery** as the starting stage > **Tip:** > Use a consistent naming convention for deals across your pipeline. Including the company name and product makes it easy to scan your deal list at a glance. --- ## Step 2: Set Deal Details 1. After creating the deal, click into it to open the deal detail view. 2. Review and update these fields as your deal progresses: - **Deal Value** -- adjust as scope changes during negotiation - **Expected Close Date** -- update when timelines shift - **Owner** -- confirm you are assigned as the deal owner 3. Add any relevant notes by clicking **Add Note** at the bottom of the deal view. > **Tip:** > Keep deal values and close dates current. Stale data leads to inaccurate forecasts -- update after every meaningful conversation. --- ## Step 3: Move Deals Through Stages The default pipeline stages are: | Stage | When to Use | |-------|-------------| | **Discovery** | Initial conversations, qualifying the opportunity | | **Qualified** | Budget, authority, need, and timeline confirmed | | **Proposal** | Proposal or pricing sent to the prospect | | **Negotiation** | Active contract or terms negotiation | | **Closed Won** | Deal signed and closed | | **Closed Lost** | Deal lost or disqualified | 1. From the deal detail page, click the **Stage** dropdown. 2. Select the next stage that matches your deal's current status. 3. Confirm the stage change -- the **Stage Entry Date** updates automatically so you can track how long each deal spends in each stage. > **Tip:** > You can also move deals via Copilot. Try: **"Move the Acme deal to Proposal stage"** for faster updates between calls. --- ## Mission Complete Checklist - [ ] Created at least one deal on a target account - [ ] Set deal value and expected close date - [ ] Moved the deal through at least one stage change - [ ] Verified the stage entry date updated automatically --- **Next mission:** [Mission 3: Leverage AI for Selling](https://docs.graph8.com/training/ae/ai-selling) -- Use Copilot, AI email generation, and meeting transcripts to accelerate your deals. --- # Mission 1: Master Company Research > Run Company Intelligence, map stakeholders, and prepare for calls in under 5 minutes Source: https://docs.graph8.com/training/ae/research/ ## What you'll learn - How to run Company Intelligence on a target company - How to map stakeholders with buying roles - How to use Copilot for rapid pre-call prep --- ## Step 1: Run Company Intelligence 1. Navigate to the **Companies** tab in the left sidebar. 2. Select a target company from your list (or add one by clicking **+ Add Company**). 3. On the company detail page, click the **Run Intelligence** button. 4. Wait for the 6-step intelligence workflow to complete. Each category generates a searchable document: - Website Scrape - Company Enrichment - Financial Intelligence - Leadership Research - News & Events - Tech Stack > **Tip:** > Run intelligence on your top 10 companies first. Each company takes 1-2 minutes to process, and the results are cached so you only need to run it once. --- ## Step 2: Map Stakeholders 1. From the company detail page, click the **Contacts** subtab. 2. Add stakeholders by clicking **+ Add Contact** or importing via CSV. 3. For each contact, set the **buying_role** field to one of: - **Champion** -- your internal advocate - **Decision Maker** -- has budget authority - **Influencer** -- shapes the evaluation - **Blocker** -- favors the status quo 4. Set the **engagement_score** (0-100) based on your interaction history with that contact. > **Tip:** > Aim for at least one Champion and one Decision Maker mapped per company before your first call. Missing a Decision Maker is the most common reason deals stall. --- ## Step 3: Pre-Call Prep with Copilot Use these Copilot queries to prepare for any call in under 5 minutes: 1. Open **Copilot** from the bottom-right corner of Studio. 2. Ask: **"Summarize what we know about [company name]"** to get a full company briefing. 3. Ask: **"Who are the key decision makers at [company name]?"** to review your stakeholder map. 4. Ask: **"Any recent news about [company name]?"** to find conversation starters. | Information You Need | Copilot Query | |---------------------|---------------| | Company overview | "Summarize what we know about Acme Corp" | | Pain points | "What challenges is Acme Corp facing?" | | Tech stack | "What tools does Acme Corp use?" | | Competitors | "Who are Acme Corp's main competitors?" | --- ## Mission Complete Checklist - [ ] Ran Company Intelligence on at least one company - [ ] All 6 intelligence categories generated successfully - [ ] Mapped 2+ stakeholders with buying roles - [ ] Ran a Copilot pre-call prep query --- **Next mission:** [Mission 2: Build Your Pipeline](https://docs.graph8.com/training/ae/pipeline) -- Create deals and manage your pipeline stages. --- # Getting Started Training > A guided walkthrough of graph8 for new users Source: https://docs.graph8.com/training/general/ Welcome to graph8! These three missions will walk you through the platform so you can hit the ground running. ## Missions 1. **[Navigate graph8](https://docs.graph8.com/training/general/navigate/)** — Learn the layout and explore each section 2. **[Get Your Data In](https://docs.graph8.com/training/general/first-data/)** — Import contacts and create your first list 3. **[Connect Your Tools](https://docs.graph8.com/training/general/connect/)** — Link your CRM and email Complete all three and you'll have a solid foundation for everything graph8 can do. --- # Mission 3: Connect Your Tools > Link your CRM and email so graph8 can sync data and send outreach Source: https://docs.graph8.com/training/general/connect/ graph8 works best when it's connected to your existing tools. This mission walks you through linking your CRM and email. ## Step 1: Connect a CRM Connecting your CRM lets graph8 sync contacts, companies, and deals automatically. 1. Navigate to **Settings → Connections** 2. Find your CRM: - **HubSpot** — click Connect, then authorize with your HubSpot account - **Salesforce** — click Connect, then log in with your Salesforce credentials - **Pipedrive** — click Connect, then authorize with your Pipedrive account 3. Once connected, graph8 will start syncing data in the background 4. Check back after a few minutes to see your CRM data in the Contacts and Companies sections > **Tip:** > If your CRM isn't listed, reach out to support — we're adding new integrations regularly. ## Step 2: Connect a mailbox Connecting your email lets graph8 send outreach on your behalf and track replies. 1. Open **Settings** from your profile menu, then go to **Channels → Mailboxes** ([app.graph8.com/studio/settings?tab=mailboxes](https://app.graph8.com/studio/settings?tab=mailboxes)) 2. Click **Add Mailbox** 3. Choose your email provider: - **Sign in with Google**: authorize with your Google account - **New SMTP Connection**: enter your email server details manually 4. Complete the authorization flow 5. Once connected, your mailbox will appear in the list with a green status > **Caution:** > For best deliverability, make sure your email domain has SPF, DKIM, and DMARC records configured. Ask your IT team if you're unsure. Once you've connected a CRM and a mailbox, this mission is complete! --- # Mission 2: Get Your Data In > Import contacts and create your first list Source: https://docs.graph8.com/training/general/first-data/ Before you can do anything in graph8, you need data. This mission walks you through importing contacts and organizing them into a list. ## Step 1: Import contacts 1. Navigate to **Data → Contacts** 2. Click the **Import** button in the top right 3. Select a CSV file from your computer - The file should have columns like `email`, `first_name`, `last_name`, `company` - graph8 will auto-map common column names 4. Review the column mapping and click **Import** 5. Wait for the import to complete — you'll see a success message with the count > **Tip:** > Don't have a CSV ready? Export a small list from your CRM or email tool. Even 10-20 contacts is enough to get started. ## Step 2: Create a list Lists let you group contacts for targeted outreach. 1. Navigate to **Data → Lists** 2. Click **Create List** 3. Give it a name (e.g., "First Outreach List") 4. Add contacts: - Use filters to find specific contacts - Or select contacts manually from the grid 5. Save the list ## Step 3: View a company profile graph8 automatically groups contacts by company. 1. Navigate to **Data → Companies** 2. Click on any company name to see its profile 3. Review the company details: - Basic info (industry, size, location) - Associated contacts - Activity history Once you've imported contacts, created a list, and viewed a company profile, this mission is complete! --- # Mission 1: Navigate graph8 > Learn the layout — data, studio, engage, signals, and settings Source: https://docs.graph8.com/training/general/navigate/ graph8 is organized around five main sections. This mission helps you explore each one. ## Step 1: Explore the Data section The **Data** section is where all your contacts, companies, and lists live. 1. Click **Data** in the top navigation 2. Browse the sub-sections: - **Contacts** — individual people you're reaching out to - **Companies** — organizations those contacts belong to - **Lists** — saved groups of contacts for targeting 3. Click on any contact or company to see its detail view ## Step 2: Explore Studio **Studio** is where graph8's AI does the heavy lifting — company intelligence, campaigns, and content. 1. Click **Studio** in the top navigation 2. Select **Global** to see: - Company Profile — your brand, offer, and positioning - Personas — AI-generated buyer personas - ICPs — ideal customer profiles 3. Switch to **Campaign** to see how campaigns are created and managed ## Step 3: Explore Engage **Engage** is where outreach happens — sequences, inbox, and dialer. 1. Click **Engage** in the top navigation 2. Browse: - **Sequencer** — multi-step automated outreach - **Inbox** — unified view of all replies - **Dialer** — power dialer for calls ## Step 4: Visit Settings **Settings** controls your team, integrations, and account configuration. 1. Click **Settings** (gear icon) in the sidebar or from the user menu 2. Key sections: - **Team** — invite users and manage roles - **Connections** — CRM and third-party integrations - **Mailboxes** — email accounts for sending - **Billing** — plan and usage details Once you've explored all four areas, this mission is complete! --- # Growth Manager Training > Five hands-on missions that take you from first login to optimized campaign delivery Source: https://docs.graph8.com/training/growth-manager/ Your role is to deliver high-quality, AI-powered campaigns for clients while maximizing efficiency. These five missions walk you through the complete workflow -- from onboarding a new client to monitoring live campaigns. > **Tip:** > Complete the missions in order. Each one builds on the output of the previous mission. ## What you'll learn By the end of these five missions you will be able to: - Onboard a new client and run the Intelligence Workflow - Generate and review the full suite of Global Context documents - Create a campaign from an AI-generated idea and refine it in Canvas - Build and launch an email sequence from your campaign - Monitor campaign health, document quality, and sequence performance ## Training Missions **Mission 1: Onboard Your First Client** Set up the Company Profile, run Intelligence, and approve the profile. [Start Mission 1](https://docs.graph8.com/training/growth-manager/onboarding/) **Mission 2: Generate Global Context** Trigger Context Generation and review Personas, ICPs, and Research Reports. [Start Mission 2](https://docs.graph8.com/training/growth-manager/global-context/) **Mission 3: Create Your First Campaign** Generate campaign ideas, push to campaign, and refine in Canvas. [Start Mission 3](https://docs.graph8.com/training/growth-manager/first-campaign/) **Mission 4: Master the Sequencer** Create a sequence from your campaign, configure steps, and launch. [Start Mission 4](https://docs.graph8.com/training/growth-manager/sequencer/) **Mission 5: Monitor and Optimize** Track campaign health, document quality, and sequence performance. [Start Mission 5](https://docs.graph8.com/training/growth-manager/optimize/) ## Time Investment | Mission | Active Time | AI Processing Time | |---------|-------------|--------------------| | 1 -- Onboard Your First Client | 15 minutes | 5-10 minutes | | 2 -- Generate Global Context | 15 minutes | 20-30 minutes | | 3 -- Create Your First Campaign | 30 minutes | 10-15 minutes | | 4 -- Master the Sequencer | 20 minutes | -- | | 5 -- Monitor and Optimize | 15 minutes | -- | **Total active time: ~1.5 hours.** Most of the heavy lifting happens in the background while AI generates your documents. ## Prerequisites - A graph8 account with Growth Manager permissions - At least one client organization to work with - The client's primary website URL Ready? [Start Mission 1: Onboard Your First Client](https://docs.graph8.com/training/growth-manager/onboarding/). --- # Mission 3: Create Your First Campaign > Generate campaign ideas from AI, push the best one to a full campaign, and refine content in Canvas Source: https://docs.graph8.com/training/growth-manager/first-campaign/ ## What you'll learn - How to generate AI-powered campaign ideas - How to push an idea into a full campaign with auto-generated documents - How to check campaign readiness using the Dashboard - How to refine campaign content in the Canvas editor > **Tip:** > Campaign generation pulls from everything you built in Missions 1 and 2 -- Company Profile, context documents, personas, ICPs, and research reports. The higher the quality of those inputs, the better your campaign ideas will be. ## Generate campaign ideas 1. **Navigate to Campaign Ideas** Click **Campaigns** in the left sidebar, then open the **Ideas** tab. 2. **Click Generate Ideas** Click **Generate Ideas**. The AI analyzes your context documents, personas, and ICPs to produce 5-10 campaign concepts. This typically takes 2-3 minutes. 3. **Review each idea** Each idea card shows a **core concept**, **primary hook**, target persona, and suggested campaign type. Read through all ideas before choosing one. 4. **Evaluate fit** Ask yourself: Does this idea align with the client's immediate goals? Does the primary hook address a real pain point from the persona research? Is the campaign type appropriate for the audience? > **Tip:** > You are not limited to one round. If none of the ideas feel right, click **Generate Ideas** again to get a fresh batch. Each round draws from the same context but produces different creative angles. ## Push idea to campaign 1. **Select your best idea** Click on the idea card you want to develop into a full campaign. 2. **Click Push to Campaign** Click the **Push to Campaign** button. Enter a campaign name when prompted. 3. **Wait for document generation** The platform automatically generates 10+ campaign-specific documents: email copy, landing page concepts, ad variations, social posts, and more. This takes approximately 10-15 minutes. 4. **Navigate to the campaign** Once generation starts, you can find your new campaign under **Campaigns** in the left sidebar. Click into it to see the document list populating in real time. ## Check campaign readiness 1. **Open the Dashboard tab** Inside your campaign, click the **Dashboard** tab. 2. **Review the Readiness Score** The **Readiness Score** (0-100) summarizes overall campaign quality. Aim for **75 or higher** before considering the campaign ready for launch. 3. **Check the risk assessment** The **Risk Assessment** section highlights specific blockers -- missing documents, low-quality content, incomplete persona coverage. Address these before proceeding. 4. **Review launch readiness status** The dashboard shows one of three statuses: - **ready** -- all documents meet quality thresholds - **needs_work** -- some documents need attention - **not_ready** -- critical gaps remain ## Refine content in Canvas 1. **Open a document** From the campaign's document list, click any document to open it in the **Canvas** editor. 2. **Review the AI-generated content** Read through the document. AI-generated content is a strong starting point but almost always benefits from client-specific refinements. 3. **Edit directly** Click into any text block to edit. Adjust tone, swap in client-specific terminology, and tighten the messaging. 4. **Use Copilot for suggestions** Open the Copilot panel and try prompts like: - "Make this more conversational" - "Rewrite the headline to focus on ROI" - "Shorten this to 50 words" - "Add a stronger call to action" 5. **Repeat for key documents** Prioritize the documents most critical to your campaign type -- typically email subject lines, primary body copy, and landing page headlines. > **Tip:** > Focus your editing time on the first email and the landing page headline. These two pieces have the highest impact on campaign performance and are worth the extra polish. ## Mission complete You have a campaign with AI-generated documents, a readiness score, and refined content. The next step is turning this campaign into a live email sequence. **Next mission:** [Mission 4: Master the Sequencer](https://docs.graph8.com/training/growth-manager/sequencer/) --- # Mission 2: Generate Global Context > Trigger Context Generation and review the Personas, ICPs, and Research Reports that power every campaign Source: https://docs.graph8.com/training/growth-manager/global-context/ ## What you'll learn - How to trigger the 21-document Context Generation suite - How to review and refine auto-generated Personas - How to interpret ICP scoring (fit, opportunity, readiness) - How to access and use the 6 Research Reports > **Tip:** > Context Generation runs in the background. Kick it off, then use the 20-30 minutes of processing time to review the Company Profile or prep client notes. ## Trigger Context Generation 1. **Navigate to Context & Grounding** Click **Studio -> Global** in the left sidebar, then open the **Context & Grounding** section. 2. **Click Generate** Click the **Generate** button to start the 21-document creation process. Documents include messaging frameworks, positioning statements, brand voice guidelines, and more. 3. **Monitor generation progress** Each document shows a status indicator as it generates. The full suite takes approximately 20-30 minutes. You do not need to stay on this page -- generation continues in the background. ## Trigger Research Reports 1. **Navigate to Research** Click **Studio -> Global** in the left sidebar, then open the **Research** section. 2. **Click Generate All Reports** Click **Generate All Reports** to start all 6 reports in parallel. Reports cover Voice of Customer, Buyer Psychology, Competitive Intelligence, Market Trends, Win/Loss Themes, and Industry Analysis. 3. **Wait for completion** Reports generate concurrently and typically finish within 10-15 minutes. ## Review Personas 1. **Open the Personas tab** Navigate to **Audiences -> Personas**. You will see 3-5 auto-generated buyer personas based on the Company Profile and context documents. 2. **Review each persona** Click into each persona to review: **title/role patterns**, **key pain points**, **motivations and goals**, **preferred communication style**, and **common objections**. 3. **Edit as needed** Update persona names, descriptions, or pain points to match your client's actual buying committee. The more accurate these are, the better your campaign messaging will be. 4. **Verify coverage** Ensure the personas cover the key roles in the buying committee -- typically a decision maker, a champion, a technical evaluator, and an end user. > **Tip:** > If a critical buyer role is missing, you can manually add a persona. But first check whether the Company Profile accurately reflects the client's target audience -- re-running generation with better inputs often produces better results than manual creation. ## Review ICPs 1. **Open the ICPs tab** Navigate to **Audiences -> ICPs**. Each ICP represents an ideal customer segment with scored criteria. 2. **Understand the three scores** Each ICP has three component scores: | Score | Range | What It Measures | |-------|-------|------------------| | **Fit Score** | 0-40 | Firmographic match -- industry, size, tech stack | | **Opportunity Score** | 0-30 | Growth signals -- funding, expansion, hiring | | **Readiness Score** | 0-30 | Intent signals -- timing, urgency indicators | 3. **Review firmographic criteria** Check that the industry, company size, revenue range, geography, and tech stack filters align with your client's actual target market. 4. **Adjust if needed** Edit any criteria that do not match. Accurate ICPs drive better contact targeting in campaigns and sequences. ## Review Research Reports 1. **Open the Research section** Navigate to **Studio -> Global -> Research**. You will see 6 completed reports. 2. **Read each report** Open each report and review the key findings. Pay special attention to: - **Voice of Customer** -- language and terminology to use in campaigns - **Competitive Intelligence** -- differentiators to highlight - **Buyer Psychology** -- objection patterns to address 3. **Note insights for campaigns** These reports feed directly into campaign generation. Any gaps or inaccuracies here will cascade into campaign content, so flag anything that needs correction. > **Tip:** > Bookmark the Voice of Customer and Competitive Intelligence reports. You will reference them frequently when refining campaign copy in Canvas. ## Mission complete You now have 21 context documents, 3-5 personas, scored ICPs, and 6 research reports. This foundation powers every campaign the platform generates. **Next mission:** [Mission 3: Create Your First Campaign](https://docs.graph8.com/training/growth-manager/first-campaign/) --- # Mission 1: Onboard Your First Client > Set up the Company Profile, run the Intelligence Workflow, and approve the profile to unlock downstream features Source: https://docs.graph8.com/training/growth-manager/onboarding/ ## What you'll learn - How to create and populate a Company Profile - How to run the Intelligence Workflow to auto-extract 11 data points - How to review, edit, and approve the profile > **Tip:** > Accuracy at this stage matters. Every document generated downstream -- personas, ICPs, campaign copy -- pulls from the Company Profile. Spend the extra minute verifying the data. ## Set up the Company Profile 1. **Navigate to Settings** Click **Settings** in the left sidebar, then select **Company Profile**. 2. **Enter the client's website URL** Paste the client's primary domain (e.g., `https://acmecorp.com`) into the **Website URL** field. This is the seed for the Intelligence Workflow. 3. **Fill in basic company details** Complete the required fields: **Company Name**, **Industry**, and **Headquarters**. Leave optional fields blank for now -- the Intelligence Workflow will fill many of them automatically. ## Run the Intelligence Workflow 1. **Open the Workflows tab** Navigate to **Workflows** from the top navigation. You will see the **Intelligence Workflow** card. 2. **Start Intelligence** Click **Start Intelligence**. The workflow analyzes the client's website and extracts 11 structured data points including value propositions, target markets, product offerings, and competitive positioning. 3. **Monitor progress** Watch the status indicators update as each data point completes. The full workflow typically takes 5-10 minutes. 4. **Review extracted data** Once complete, return to **Settings -> Company Profile**. The Intelligence Workflow populates fields like **Value Propositions**, **Target Markets**, **Key Differentiators**, and more. > **Tip:** > If the workflow stalls on a particular data point, check that the website URL is correct and publicly accessible. You can retry individual steps without rerunning the entire workflow. ## Approve the Company Profile 1. **Review every section** Scroll through the full Company Profile. Pay special attention to **Value Propositions** and **Target Markets** -- these heavily influence persona and campaign generation. 2. **Edit any inaccuracies** Click into any field to edit. Common corrections include refining industry classification, adjusting company size, and sharpening value proposition language. 3. **Click Approve** Once you are satisfied with the profile, click the **Approve** button at the top of the page. This locks the profile and unlocks downstream workflows: Context Generation, Research Reports, and Campaign Ideas. > **Tip:** > You can return and edit the Company Profile later, but changes after approval may trigger re-generation of dependent documents. Get it right the first time when possible. ## Mission complete You now have an approved Company Profile backed by 11 intelligence data points. The platform is ready to generate context documents. **Next mission:** [Mission 2: Generate Global Context](https://docs.graph8.com/training/growth-manager/global-context/) --- # Mission 5: Monitor and Optimize > Use the Campaign Dashboard, document health scores, export features, and sequence analytics to optimize performance Source: https://docs.graph8.com/training/growth-manager/optimize/ ## What you'll learn - How to read the Campaign Dashboard and act on readiness scores - How to monitor document health and fix warnings - How to export deliverables for client review - How to track sequence performance and optimize email steps > **Tip:** > Build a daily habit of checking the Campaign Dashboard and sequence metrics. Ten minutes a day prevents small issues from becoming campaign-level problems. ## Campaign Dashboard 1. **Open the Dashboard** Navigate to your campaign and click the **Dashboard** tab. This is your single-pane view of campaign health. 2. **Review the Readiness Score** The **Readiness Score** (0-100) reflects the overall quality of your campaign. A score below 75 means there are gaps to address. The score updates automatically as you improve documents. 3. **Check the risk assessment** Scroll to the **Risk Assessment** section. Each risk item tells you exactly what is wrong and what to fix -- missing documents, weak proof points, incomplete persona coverage, or low content quality scores. 4. **Act on the status** | Status | Action | |--------|--------| | **ready** | Campaign is launch-ready. Proceed to delivery. | | **needs_work** | Address the flagged risks. Re-check after edits. | | **not_ready** | Critical gaps exist. Do not proceed until resolved. | ## Monitor document health 1. **View document health scores** Inside the campaign, each document shows a **Health Score**: **good**, **warning**, or **poor**. Filter the document list to surface warnings and poor scores first. 2. **Investigate warnings** Click a document with a warning status to open it. Common causes include thin content, missing sections, or messaging that drifts from the Company Profile. 3. **Fix the issue** Edit directly in Canvas or use Copilot to regenerate weak sections. For example: "Expand the proof points section with specific metrics" or "Align this with our value proposition." 4. **Track usage and citation counts** Each document shows a **Usage Count** (how often it was referenced during generation) and **Citation Count** (Copilot references). Low-usage documents may need improvement or may not be relevant to the campaign. > **Tip:** > Fix "warning" documents before they cascade. Other documents and campaign content pull from your context documents -- a weak source creates weak downstream output. ## Export deliverables 1. **Select documents to export** Inside the campaign, select the documents you want to share with the client. For a standard client delivery, include the Campaign Passport (summary), key email copy, and landing page content. 2. **Click Export** Click the **Export** button. Choose your format -- typically PDF for client presentations or editable format for collaborative review. 3. **Include the Campaign Passport** The Campaign Passport is an auto-generated summary PDF that captures the campaign strategy, target personas, messaging themes, and key deliverables in a single document. Always include it in client-facing packages. 4. **Schedule the client walkthrough** Use the exported materials to structure a client walkthrough. Walk through the Campaign Passport first for strategic context, then drill into specific deliverables. ## Sequence performance 1. **Open your active sequence** Navigate to **Engage -> Sequences** and click into your active sequence. 2. **Review step-level metrics** Each email step shows key performance indicators: | Metric | What It Tells You | |--------|--------------------| | **Open Rate** | Subject line effectiveness | | **Click Rate** | Content and CTA relevance | | **Reply Rate** | Message resonance and personalization | | **Bounce Rate** | Contact data quality | | **Unsubscribe Rate** | Audience-message fit | 3. **Identify underperforming steps** Look for steps with significantly lower open or reply rates compared to others. These are your optimization targets. 4. **Optimize weak steps** Common fixes: - **Low open rate** -- rewrite the subject line. Test a shorter, curiosity-driven alternative. - **Low click rate** -- strengthen the call to action. Make the next step obvious. - **Low reply rate** -- add more personalization or change the angle entirely. - **High bounce rate** -- clean your contact list. Verify email addresses before re-sending. 5. **Monitor over time** Check sequence metrics daily for the first week, then weekly once performance stabilizes. Look for trends -- improving reply rates across steps means your messaging is resonating. > **Tip:** > Do not change more than one variable at a time. If you rewrite a subject line, wait for enough data before also changing the body copy. Isolated changes give you clear signal on what is working. ## Daily optimization checklist Use this quick checklist each morning to stay on top of campaign health: - [ ] Campaign Dashboard Readiness Score is 75+ - [ ] No documents with "poor" health status - [ ] Active sequences are sending without errors - [ ] Bounce rate is below 5% - [ ] No unaddressed risk items in the Dashboard ## Mission complete You have completed all five Growth Manager training missions. You can now onboard clients, generate context, create campaigns, launch sequences, and optimize performance using graph8. **Back to overview:** [Growth Manager Training](https://docs.graph8.com/training/growth-manager/) --- # Mission 4: Master the Sequencer > Create an email sequence from your campaign, configure steps with timing and templates, add contacts, and launch Source: https://docs.graph8.com/training/growth-manager/sequencer/ ## What you'll learn - How to create a sequence linked to your campaign - How to configure email steps with subject lines, body content, and send delays - How to add contacts to the sequence - How to review settings and launch > **Tip:** > A sequence is the execution layer of your campaign. The campaign provides the strategy and content -- the sequence delivers it to real inboxes on a schedule. ## Create a sequence from your campaign 1. **Open your campaign** Navigate to **Campaigns** in the left sidebar and click into the campaign you created in Mission 3. 2. **Click Create Sequence** Look for the **Create Sequence** action (or navigate to **Engage -> Sequences** and click **New Sequence**). If creating from the campaign, the sequence is automatically linked and pre-populated with campaign content. 3. **Name your sequence** Enter a descriptive name, for example: "Acme Corp -- Q1 Outbound -- Decision Makers". A clear name helps you find it later when managing multiple active sequences. ## Configure email steps 1. **Add the first email step** Click **Add Step** and select **Email**. The step editor opens with fields for subject line, body content, and sender. 2. **Set the subject line and body** If the sequence was created from a campaign, the first email may already be pre-filled from your campaign documents. Review and adjust the subject line and body copy. Use merge fields like `{{first_name}}` and `{{company_name}}` for personalization. 3. **Set the send delay** For the first step, the delay is typically **0 days** (send immediately on enrollment). Configure the send window -- the time range during which emails are allowed to go out (e.g., 8:00 AM - 6:00 PM in the contact's timezone). 4. **Add follow-up steps** Click **Add Step** again to add subsequent emails. A typical sequence has 3-5 email steps. Set delays between steps -- common patterns are: - Step 2: 3 days after Step 1 - Step 3: 4 days after Step 2 - Step 4: 5 days after Step 3 5. **Write follow-up content** Each follow-up should build on the previous message. Reference your campaign documents for messaging consistency. Vary the angle -- if Step 1 focuses on a pain point, Step 2 might share a case study, and Step 3 could offer a meeting. > **Tip:** > Keep your first email under 150 words. Shorter first touches consistently outperform longer ones. Save the detail for follow-ups. ## Add contacts to the sequence 1. **Open the Contacts tab** Inside your sequence, click the **Contacts** tab. 2. **Add contacts** Click **Add Contacts**. You can add contacts from: - **A list** -- select a pre-built contact list - **Manual selection** -- search and add individual contacts - **CSV import** -- upload a file with contact data 3. **Review contact details** Verify that each contact has the required fields: **email**, **first name**, **last name**, and **company name**. Contacts missing required fields will be flagged and skipped during execution. 4. **Check for duplicates** The platform automatically checks for contacts already enrolled in this or other active sequences. Review any flagged duplicates and decide whether to proceed or exclude them. ## Review and launch 1. **Preview the full sequence** Step back and review all steps in order. Click the **Preview** option on each email to see exactly what the contact will receive, with merge fields populated from sample data. 2. **Check sequence settings** Verify: - **Sender email** is correct and authenticated - **Send window** matches the target audience's working hours - **Timezone** handling is set (contact timezone or a fixed timezone) - **Unsubscribe handling** is enabled 3. **Activate the sequence** Click **Activate** (or **Launch**) to start the sequence. Contacts will begin receiving emails according to the schedule you configured. 4. **Confirm activation** Check that the sequence status changes to **Active**. The first batch of emails will process at the next available send window. > **Tip:** > Send a test email to yourself before activating. Open it on both desktop and mobile to verify formatting, merge fields, and links. ## Mission complete You have a live email sequence delivering your campaign content to real contacts on a defined schedule. The final mission covers how to monitor performance and optimize. **Next mission:** [Mission 5: Monitor and Optimize](https://docs.graph8.com/training/growth-manager/optimize/) --- # Growth Manager Training > Deliver high-quality, AI-powered campaigns while maximizing efficiency Source: https://docs.graph8.com/training/growth-managers/ Your role: Deliver high-quality, AI-powered campaigns for clients while maximizing efficiency. > **Tip: Time Savings** > **20+ hours per week per client** when fully leveraging the platform ## Core Capabilities | Capability | What It Does | Time Saved | |------------|--------------|------------| | Intelligence Workflow | 11 automated data points from website analysis | 4-6 hours/client | | Global Context (21 docs) | Foundational strategy documents | 8-12 hours/client | | Research Reports (6) | Market research and competitive intel | 6-8 hours/client | | Campaign Ideas AI | Generate 5-10 campaign concepts instantly | 2-3 hours/campaign | | Campaign Dashboard | AI readiness scoring and gap analysis | 1-2 hours/campaign | --- ## Week 1 Workflow: New Client Onboarding ### Day 0: Intelligence Foundation **Active time: 30 minutes** 1. **Run Intelligence Workflow** Navigate to **Workflows** tab, enter client's primary website URL, and click **Start Intelligence**. Monitor progress via workflow status indicators. *Expected output: Company Profile, 11 intelligence data points* 2. **Review Company Profile** Go to **Settings → Company Profile**, verify extracted information is accurate, edit any incorrect fields, and click **Approve** to unlock downstream workflows. 3. **Trigger Context Generation** Navigate to **Context & Grounding**, click **Generate** to start 21-document creation. Generation runs in background (~20-30 minutes total). 4. **Trigger Research Reports** Navigate to **Research**, click **Generate All Reports**. 6 reports generate in parallel. ### Day 1: Strategy Development **Active time: 1 hour** 1. **Review Generated Personas** Go to **Audiences → Personas** tab. Review auto-generated buyer personas, edit names, descriptions, or pain points as needed. Ensure 3-5 personas cover the buying committee. 2. **Review ICPs** Go to **Audiences → ICPs** tab. Check fit_score, opportunity_score, readiness_score. Adjust firmographic criteria if needed. 3. **Generate Campaign Ideas** Navigate to **Campaigns → Ideas** tab, click **Generate Ideas**. 5-10 AI-generated concepts appear in 2-3 minutes. Review each idea's core_concept and primary_hook. 4. **Push Idea to Campaign** Select best idea from grid, click **Push to Campaign**, enter campaign name. Document generation begins automatically (10+ docs). ### Day 2: Client Delivery **Active time: 45 minutes** 1. **Check Campaign Dashboard** Open the campaign, click **Dashboard** tab. Review **Readiness Score** (target: 75+) and check risk_assessment for blockers. 2. **Refine Campaign Documents** Click any document to open Canvas editor. Make client-specific refinements. Use Copilot to suggest improvements: "Make this more conversational" 3. **Export for Client** Use **Export** function for client-facing deliverables. Include Campaign Passport (summary PDF). Schedule client walkthrough. --- ## Daily Execution Pattern ``` Morning (30 min) ├── Check in-progress campaign generation status ├── Review any failed workflows (retry as needed) └── Prioritize client work based on deadlines Midday (1-2 hours) ├── Active content refinement using Canvas ├── Copilot queries for messaging improvements └── Client communications End of Day (15 min) ├── Queue overnight generation tasks ├── Verify document health scores └── Update client status notes ``` --- ## Copilot Commands for Growth Managers Use natural language with the Copilot for rapid execution: | Task | Example Command | |------|-----------------| | Status check | "What's the status of GlobalTech's campaign?" | | Content edit | "Rewrite this headline to focus on ROI" | | Research query | "What are GlobalTech's main competitors?" | | Generate ideas | "Give me 3 more campaign ideas for enterprise SaaS" | | Document search | "Find our messaging house document" | --- ## Key Metrics to Track ### Campaign Health | Metric | Target | Description | |--------|--------|-------------| | **Readiness Score** | 75+ | Overall campaign quality (0-100) | | **Documents Complete** | 31+ | X/21 context docs + Y/10 campaign docs | | **Launch Readiness** | "ready" | "ready" / "needs_work" / "not_ready" | ### Document Quality | Metric | Description | |--------|-------------| | **Health Score** | good / warning / poor | | **Usage Count** | How often referenced in generation | | **Citation Count** | Copilot references | ### Time Efficiency - **Generation Time**: Track generation_started_at to completion - **Active Time vs. AI Time**: Measure your hands-on time vs. automated --- ## Troubleshooting | Issue | Solution | |-------|----------| | Intelligence workflow stuck | Check company profile is approved; retry workflow | | Context docs show warnings | Review low health_score docs; update source content | | Campaign generation incomplete | Verify all 21 context docs exist; check Celery worker status | | Low readiness score | Review risk_assessment; address weak proof_analysis | --- ## Best Practices > **Tip: Quality Over Speed** > Spend time on Company Profile accuracy — garbage in = garbage out. AI-generated content is a starting point, not final. 1. **Batch client onboarding** — Run intelligence for multiple clients simultaneously 2. **Quality over speed** — Spend time on Company Profile accuracy 3. **Use Canvas editing** — AI-generated content is a starting point, not final 4. **Monitor health scores** — Fix "warning" documents before they cascade issues 5. **Leverage Copilot** — Natural language is faster than clicking through UI --- ## What's Next? **Global Context** [Company profile and intelligence](https://docs.graph8.com/studio/global/) **Creating Campaigns** [Campaign creation guide](https://docs.graph8.com/studio/campaigns/) **Content** [SEO and content management](https://docs.graph8.com/studio/content/) --- # Sales Manager Training > Pipeline visibility, deal velocity, and campaign health metrics Source: https://docs.graph8.com/training/sales-managers/ Your role: Maintain visibility into team pipeline health, deal velocity, and campaign effectiveness without micromanaging. > **Tip: Key Value** > **Real-time aggregate metrics** across all AEs with actionable insights ## Core Capabilities | Capability | What It Provides | Access | |------------|------------------|--------| | Team Dashboard | Aggregate metrics across AEs | Companies → Team Dashboard | | Deal Pipeline | Stage-based tracking with velocity | Companies → Deals | | Deal Velocity Metrics | Days in stage, conversion rates | Team Dashboard | | Campaign Readiness | AI-scored campaign health (0-100) | Campaigns → Dashboard | | Activity Log | Chronological team activity | Team Dashboard | --- ## Team Dashboard Overview Navigate to **Companies → Team Dashboard** to access: ### Pipeline Summary | Metric | Description | |--------|-------------| | **Total Pipeline Value** | Sum of all active deals | | **Deal Count by Stage** | Distribution across pipeline | | **Average Deal Size** | Pipeline value / deal count | | **Deals Requiring Attention** | Stuck deals, stale opportunities | ### Deal Velocity | Metric | Description | |--------|-------------| | **Average Days in Stage** | Time in each pipeline stage | | **Stage Conversion Rate** | % moving to next stage | | **Stuck Deals** | >14 days in same stage | | **Velocity Trend** | Improving or degrading | ### Campaign Health | Metric | Description | |--------|-------------| | **Active Campaigns** | Currently running campaigns | | **Avg Readiness Score** | Mean of campaign scores | | **Campaigns Needing Work** | Readiness <75 | --- ## Weekly Pipeline Review Workflow ### Monday Morning (30 minutes) 1. **Review Pipeline Summary** Check total value vs. quota coverage. Stage distribution should show more in Qualified/Proposal than Discovery (healthy). 2. **Check Deal Velocity** Review average days in each stage. Compare to team benchmarks. Flag deals significantly above average. 3. **Identify Stuck Deals** Filter deals where days_in_stage > 14. Cross-reference with activity log. Schedule 1:1 with AE if no recent activity. 4. **Review Activity Trends** Check activity log for team engagement. Identify AEs with declining activity. Note high performers for recognition. --- ## Campaign Oversight Workflow ### Campaign Health Review For each active campaign, check: | Section | What to Review | |---------|----------------| | **Readiness Score** | Target: 75+ | | **Launch Readiness** | "ready" / "needs_work" / "not_ready" | | **Strategic Assessment** | Goal alignment, market positioning | | **Audience Profile** | Persona and ICP targeting | | **Messaging Summary** | Core hooks and value props | | **Channel Strategy** | Distribution plan | | **Proof Analysis** | Evidence strength (strong/adequate/weak) | | **Risk Assessment** | Blockers and mitigation | ### Campaign Quality Checkpoints - [ ] Readiness score ≥75 - [ ] All 21 context documents generated - [ ] Proof analysis shows "strong" or "adequate" - [ ] Risk assessment has no "critical" items - [ ] Target personas aligned with ICPs --- ## Deal Velocity Metrics Deep Dive ### Healthy Benchmarks | Stage | Target Days | Warning Threshold | |-------|-------------|-------------------| | Discovery | 7-10 | >14 | | Qualified | 10-14 | >21 | | Proposal | 7-14 | >21 | | Negotiation | 7-14 | >28 | ### Warning Signs | Pattern | Likely Issue | |---------|--------------| | Many deals stuck in Discovery | Weak qualification criteria | | Long Proposal stage | Pricing/objection issues | | Negotiation stalls | Legal/procurement blockers | | Uniform stuck stage | Process bottleneck | --- ## Activity Log Analysis ### Activity Types Tracked | Type | What It Indicates | |------|-------------------| | `deal_created` | New opportunity | | `deal_updated` | Pipeline movement | | `contact_added` | Stakeholder mapping | | `account_viewed` | Research activity | | `intelligence_run` | Company prep | ### Patterns to Watch | Healthy Pattern | Concerning Pattern | |-----------------|-------------------| | Regular deal updates | No updates for days | | New contacts added | Same contacts only | | Intelligence runs | No research before calls | | Consistent daily activity | Sporadic bursts only | --- ## Coaching Conversations Using Data ### For Stuck Deals 1. Pull deal details from pipeline 2. Check days_in_stage 3. Review activity log for that deal 4. Ask AE: "What's blocking [deal name]?" 5. Identify if issue is: - Stakeholder access - Objection handling - Pricing/commercial - Technical validation ### For Low Activity 1. Review activity counts in Team Dashboard 2. Compare to team averages 3. Check if pipeline is healthy despite low activity 4. If pipeline also weak, address prospecting habits ### For Poor Campaign Performance 1. Review campaign readiness scores 2. Check document health scores 3. Identify weak proof_analysis areas 4. Coach on content improvement --- ## Key Metrics for 1:1s ### Per AE Review | Metric | Target | |--------|--------| | Pipeline coverage | 3x quota | | Deal velocity | Within benchmarks | | Win rate | >25% | | Activity level | Daily engagement | | Stakeholder depth | 3+ per opportunity | ### Team Aggregate | Metric | Target | |--------|--------| | Total pipeline | Quota × team size × 3 | | Stage conversion | >50% per stage | | Campaign readiness | Avg >75 | --- ## Weekly Manager Checklist - [ ] Review Team Dashboard metrics - [ ] Identify stuck deals (>14 days in stage) - [ ] Check campaign readiness scores - [ ] Review activity log for engagement - [ ] Prepare coaching points for 1:1s - [ ] Update forecast based on velocity data - [ ] Flag at-risk deals for intervention --- ## Troubleshooting | Issue | Solution | |-------|----------| | Dashboard not loading | Check org filter; refresh page | | Missing deal velocity | Verify deals have stage_entry_date | | Activity log empty | Check date range filter | | Campaign scores not showing | Ensure dashboard has been generated | --- ## Best Practices 1. **Review dashboard daily** — Catch issues early 2. **Use data in 1:1s** — Let metrics guide coaching 3. **Set velocity benchmarks** — Team needs targets 4. **Monitor activity patterns** — Leading indicator of results 5. **Address stuck deals weekly** — Don't let them age --- ## What's Next? **Deals** [Pipeline management](https://docs.graph8.com/studio/accounts/) **Campaigns** [Campaign overview](https://docs.graph8.com/studio/campaigns/) **Global Context** [Studio setup](https://docs.graph8.com/studio/global/) --- # SDR Training Missions > Five hands-on missions to get you fully productive in graph8 as an SDR Source: https://docs.graph8.com/training/sdr/ Complete these five missions in order and you will be fully set up, trained, and running a repeatable daily workflow inside graph8. > **Tip:** > Each mission takes 15-30 minutes. You can complete all five in a single afternoon or spread them across your first week. ## Your Missions **Mission 1: Set Up Your Foundation** Import contacts, create lists, enrich data, and review ICP scores. [Start Mission 1](https://docs.graph8.com/training/sdr/foundation/) **Mission 2: Launch Your First Sequence** Create a sequence, add contacts, and activate outreach. [Start Mission 2](https://docs.graph8.com/training/sdr/first-sequence/) **Mission 3: Master Company Intelligence** Run intelligence, review research categories, and use Copilot. [Start Mission 3](https://docs.graph8.com/training/sdr/intelligence/) **Mission 4: Connect Your Tools** Link your CRM, mailbox, and calendar to graph8. [Start Mission 4](https://docs.graph8.com/training/sdr/tools/) **Mission 5: Build Your Daily Workflow** Establish your morning prep, account working, and activity logging routine. [Start Mission 5](https://docs.graph8.com/training/sdr/daily-workflow/) ## Mission Progress | Mission | Focus | Time | |---------|-------|------| | **1. Foundation** | Contacts, lists, enrichment, ICP scores | 20 min | | **2. First Sequence** | Sequence creation and activation | 15 min | | **3. Intelligence** | Company research and Copilot | 20 min | | **4. Tools** | CRM, mailbox, calendar connections | 15 min | | **5. Daily Workflow** | Repeatable daily routine | 20 min | ## Before You Begin Make sure you have: - A graph8 account with SDR permissions - A CSV file with at least 10 contacts (email, first name, last name, company, title) - Access to your CRM credentials (Salesforce or HubSpot) - Your work email mailbox credentials Ready? [Start Mission 1](https://docs.graph8.com/training/sdr/foundation/). --- # SDR Manager Training > Conversion funnel visibility and team performance metrics Source: https://docs.graph8.com/training/sdr-managers/ Your role: Ensure consistent, high-quality outreach across the team while optimizing territory coverage and conversion. > **Tip: Key Value** > **Conversion funnel visibility** with team performance metrics and territory management ## Core Capabilities | Capability | What It Provides | Access | |------------|------------------|--------| | Team Dashboard | Aggregate SDR metrics | Companies → Team Dashboard | | Conversion Funnel | MQL → SQL → Opportunity rates | Team Dashboard | | Territory View | Company/contact distribution | Companies tab | | Activity Log | Team engagement tracking | Team Dashboard | | ICP Coverage | Scoring distribution | Audiences → ICPs | --- ## Team Dashboard Overview Navigate to **Companies → Team Dashboard** to access: ### Conversion Funnel Metrics | Metric | Description | |--------|-------------| | **MQL Count** | Marketing qualified leads | | **SQL Count** | Sales qualified leads | | **Opportunity Count** | Deals created | | **MQL → SQL Rate** | Qualification conversion | | **SQL → Opp Rate** | Pipeline conversion | ### Team Activity | Metric | Description | |--------|-------------| | **Company Count** | Total companies in territory | | **Active Campaigns** | Campaigns with SDR activity | | **Activity Volume** | Actions per day/week | --- ## Conversion Funnel Deep Dive ### Understanding the Funnel ``` ┌─────────────────┐ │ MQLs │ ← Companies meeting ICP criteria │ (Marketing │ │ Qualified) │ └────────┬────────┘ │ MQL → SQL Rate ▼ ┌─────────────────┐ │ SQLs │ ← Companies with validated interest │ (Sales │ │ Qualified) │ └────────┬────────┘ │ SQL → Opp Rate ▼ ┌─────────────────┐ │ Opportunities │ ← Deals in pipeline │ │ └─────────────────┘ ``` ### Healthy Benchmarks | Conversion | Target Rate | Warning Threshold | |------------|-------------|-------------------| | MQL → SQL | >25% | <15% | | SQL → Opp | >40% | <25% | | Overall (MQL → Opp) | >10% | <5% | ### Diagnosing Funnel Issues | Pattern | Likely Cause | Action | |---------|--------------|--------| | Low MQL → SQL | Poor ICP targeting | Review ICP criteria | | Low SQL → Opp | Weak discovery/qualification | Train on BANT | | High volume, low conversion | Quantity over quality | Focus on personalization | | High conversion, low volume | Capacity issue | Expand territory/team | --- ## Territory Management ### Company Distribution View 1. Navigate to **Companies** tab 2. Use **Owner** filter to view by SDR 3. Check distribution metrics: - Company count per SDR - ICP score distribution - Status breakdown (prospect/active) ### Balancing Territories | Factor | How to Balance | |--------|----------------| | Company count | Even distribution ±20% | | ICP score mix | Each SDR gets high/med/low | | Industry coverage | Specialists vs. generalists | | Geographic spread | Time zone considerations | ### Import with Owner Assignment When importing accounts: 1. Include `company_owner` field in CSV 2. Maps to `owner_name` in system 3. Auto-assigns to SDR territories 4. Webhook sync maintains assignments --- ## Weekly Review Workflow ### Monday: Funnel Review (20 minutes) 1. **Check MQL Volume** Are we generating enough qualified leads? Compare to historical averages. Check ICP score distribution. 2. **Review SQL Conversion** MQL → SQL rate by SDR. Identify top/bottom performers. Look for qualification patterns. 3. **Analyze Opp Creation** SQL → Opp rate overall. Pipeline quality indicators. Deal value averages. ### Wednesday: Activity Review (15 minutes) 1. **Check Activity Log** - Filter by date range (past week) - Review volume per SDR - Note engagement patterns 2. **Compare Activity to Results** - High activity, low conversion = quality issue - Low activity, low results = effort issue - High activity, high conversion = best practices ### Friday: Territory Health (15 minutes) 1. **ICP Coverage Check** - Are high-score accounts being worked? - Any territories with gaps? - New accounts needing assignment? 2. **Intelligence Completion** - What % of accounts have full intelligence? - Flag accounts needing research --- ## Activity Log Analysis ### Activity Types for SDRs | Type | What It Shows | |------|---------------| | `account_viewed` | Research engagement | | `contact_added` | Stakeholder mapping | | `intelligence_run` | Company prep | | `deal_created` | Opportunity generation | ### Patterns to Monitor | Healthy | Concerning | |---------|------------| | Consistent daily activity | Sporadic/burst patterns | | Intelligence before outreach | No research activity | | Regular contact additions | Stale contact lists | | Steady opp creation | Long gaps between deals | --- ## ICP Utilization Review ### Checking ICP Coverage Navigate to **Audiences → ICPs**. Review score distributions: - **Fit Score** (0-40): Firmographic match - **Opportunity Score** (0-30): Growth signals - **Readiness Score** (0-30): Intent indicators - **Total Score** (0-100): Combined priority ### Ensuring Quality Targeting | Check | Target | |-------|--------| | % accounts with ICP match | >80% | | Avg total_score worked | >60 | | High-score accounts contacted | 100% | ### ICP Refinement Triggers - Conversion dropping despite high scores - High scores not generating opps - Low scores outperforming high scores --- ## Quality Assurance Checks ### Weekly QA Checklist - [ ] Intelligence complete on priority accounts - [ ] Personas aligned with campaign messaging - [ ] Stakeholder roles properly assigned - [ ] Recent contacts have buying_role set - [ ] Campaign messaging being used ### Spot Checks to Perform | Area | What to Verify | |------|----------------| | Research depth | Intelligence has 6/6 categories | | Contact quality | Buying roles assigned | | Messaging alignment | Using campaign talking points | | Personalization | Evidence of account-specific outreach | --- ## Coaching Conversations Using Data ### For Low Conversion 1. Pull funnel metrics for SDR 2. Compare to team averages 3. Identify which stage is dropping 4. Review activity patterns 5. Coach on specific gap: - MQL → SQL: Qualification technique - SQL → Opp: Discovery/BANT execution ### For Low Activity 1. Check activity log volume 2. Compare to peers 3. Verify territory capacity 4. Address: - Time management issues - Tool/process confusion - Motivation/engagement ### For Quality Issues 1. Spot check recent activities 2. Review account research depth 3. Check contact role assignments 4. Coach on: - Research best practices - Persona utilization - Messaging consistency --- ## Key Metrics for 1:1s ### Per SDR Review | Metric | Target | |--------|--------| | MQL → SQL rate | >25% | | SQL → Opp rate | >40% | | Activity volume | Consistent daily | | Companies worked | Based on capacity | | Intelligence complete | 100% priority | ### Team Aggregate | Metric | Target | |--------|--------| | Total MQLs | Based on pipeline needs | | Overall conversion | >10% MQL → Opp | | Avg activity/day | Benchmark per SDR | | High-score coverage | 100% worked | --- ## Weekly Manager Checklist - [ ] Review conversion funnel metrics - [ ] Check activity log for engagement - [ ] Analyze territory distribution - [ ] Verify ICP coverage and usage - [ ] Spot check research quality - [ ] Prepare coaching points for 1:1s - [ ] Identify training needs - [ ] Update team on campaign changes --- ## Troubleshooting | Issue | Solution | |-------|----------| | Funnel metrics not showing | Check accounts have proper status | | Activity log empty | Verify date range; check org filter | | Territory imbalanced | Re-assign via import with owner_name | | ICP scores missing | Regenerate ICPs; verify firmographics | --- ## Best Practices 1. **Review funnel weekly** — Catch drops early 2. **Compare SDR-to-SDR** — Identify best practices 3. **Use data in coaching** — Specific over general 4. **Balance territories regularly** — Prevent imbalance 5. **Verify research quality** — Intelligence drives personalization --- ## What's Next? **ICPs & Personas** [ICP scoring and targeting](https://docs.graph8.com/studio/global/) **Companies** [Company intelligence](https://docs.graph8.com/studio/accounts/) **Campaigns** [Campaign management](https://docs.graph8.com/studio/campaigns/) --- # Mission 5: Build Your Daily Workflow > Establish a repeatable daily routine for morning prep, working accounts, and logging activities Source: https://docs.graph8.com/training/sdr/daily-workflow/ ## What you'll learn - How to structure your morning prep in graph8 - How to work through accounts efficiently during the day - How to log activities and keep your pipeline clean - How to use end-of-day review to stay on track --- ## Morning Prep (15 minutes) 1. **Check your Inbox** Go to **Engage --> Inbox** first thing. Reply to any overnight responses within the first 30 minutes of your day. Hot replies go cold fast. 2. **Review today's tasks** Go to **Engage --> Tasks** (or check your task panel). These are follow-ups, scheduled calls, and reminders that are due today. Sort by **Due Date** to see what needs attention first. 3. **Check sequence activity** Go to **Engage --> Sequences** and review your active sequences. Look at: - **Replies received** since yesterday - **Bounces** that need cleanup - **Contacts completing** sequences without a reply (candidates for a different approach) 4. **Identify priority companies** Go to **Audiences --> Companies**, sort by **Total Score**, and pick 3-5 companies to focus on today. If any are missing intelligence, click **Run Intelligence** now so it is ready when you need it. > **Tip:** > Do your morning prep before checking Slack or email. The first 15 minutes sets the direction for your entire day. --- ## Working Companies (core of your day) 1. **Open your first priority company** Click into the company from your Companies view. Review the intelligence categories for talking points (especially **Recent News** and **Pain Points**). 2. **Check the contact map** Click the **Contacts** subtab within the company. Review buying roles and engagement scores. Identify who you have not reached yet and who has shown engagement. 3. **Execute outreach** Based on what the account needs: - **No prior contact**: Add key contacts to your active sequence - **Sequence completed, no reply**: Try a different channel (phone via **Engage --> Dialer**, or LinkedIn) - **Replied but not booked**: Send a personalized follow-up with your calendar link - **Meeting booked**: Prep for the meeting using Copilot ("Summarize what we know about [company]") 4. **Log the activity** After each meaningful interaction (call, email reply, meeting), log it on the contact record. Click into the contact, go to the **Activity** tab, and add a note. This keeps your CRM synced and your manager informed. 5. **Move to the next account** Repeat for each of your 3-5 priority accounts. Then move to lower-priority accounts or new contacts from your lists. > **Tip:** > Block 2-3 hours of uninterrupted time for account work. Context switching between outreach and meetings kills momentum. --- ## Logging Activities 1. **Log calls after every dial** Whether someone answers or not, log the attempt. Go to the contact record, click **Log Activity**, and select **Call**. Add a disposition (Connected, Voicemail, No Answer) and a brief note. 2. **Log email replies** Replies from sequences are tracked automatically. For manual emails or LinkedIn messages, log them on the contact record so the full picture is in one place. 3. **Update contact statuses** As you work contacts, update their status: | Status | When to use | |--------|-------------| | **New** | Just imported, not yet contacted | | **Working** | Actively in a sequence or being contacted | | **Replied** | Responded to outreach | | **Meeting Booked** | Demo or intro scheduled | | **Disqualified** | Not a fit, wrong contact, or opted out | --- ## End-of-Day Review (10 minutes) 1. **Review what you accomplished** Go to **Engage --> Tasks** and mark completed tasks as done. Note any tasks that need to carry over to tomorrow. 2. **Check your numbers** Review your daily activity counts: - Emails sent (via sequences + manual) - Calls made - Replies received - Meetings booked 3. **Queue tomorrow's work** Based on what you learned today, identify which accounts need follow-up tomorrow. Create tasks for any specific actions (e.g., "Call back Sarah at Acme - she asked to reconnect Thursday"). > **Tip:** > Spending 10 minutes on end-of-day review saves 30 minutes of confusion the next morning. Write down your top 3 priorities for tomorrow before you sign off. --- ## Your Daily Routine at a Glance | Time Block | Activity | Where in graph8 | |------------|----------|-----------------| | **First 15 min** | Inbox, tasks, sequence review | **Engage --> Inbox**, **Tasks**, **Sequences** | | **Morning block** | Work priority companies (research, outreach) | **Audiences --> Companies**, **Engage --> Dialer** | | **Midday** | Follow up on morning replies, log activities | **Engage --> Inbox**, Contact records | | **Afternoon block** | Work remaining companies, add new contacts | **Data --> Contacts**, **Engage --> Sequences** | | **Last 10 min** | Review numbers, queue tomorrow | **Engage --> Tasks** | --- ## Mission 5 Complete You now have a structured, repeatable daily workflow that keeps you focused on the highest-value activities. The key is consistency: follow this routine every day and the results compound. --- ## All Missions Complete Congratulations. You have finished all five SDR training missions: 1. **Foundation** - Contacts, lists, enrichment, ICP scores 2. **First Sequence** - Outreach creation and activation 3. **Intelligence** - Company research and Copilot 4. **Tools** - CRM, mailbox, calendar connections 5. **Daily Workflow** - Repeatable daily routine For deeper reference on any topic, visit the [full SDR training guide](https://docs.graph8.com/training/sdrs/) or explore the feature-specific documentation in the sidebar. --- # Mission 2: Launch Your First Sequence > Create a multi-step outreach sequence, add contacts, and activate it Source: https://docs.graph8.com/training/sdr/first-sequence/ ## What you'll learn - How to create a new sequence with steps - How to add contacts to a sequence - How to review and activate your sequence - How to monitor sequence performance --- ## Create a Sequence 1. **Open the Sequencer** Go to **Engage --> Sequences** and click **Create Sequence** in the top-right corner. 2. **Name your sequence** Enter a clear name, for example "Q2 VP Engineering - Cold Outreach". Select the **mailbox** you want to send from. 3. **Add your first step** Click **Add Step**. Choose **Email** as the step type. Write your subject line and email body. Use merge fields like `{{first_name}}` and `{{company_name}}` to personalize automatically. 4. **Add follow-up steps** Click **Add Step** again to add follow-ups. A typical first sequence has 3-5 steps: | Step | Type | Delay | Purpose | |------|------|-------|---------| | 1 | Email | Day 0 | Initial outreach | | 2 | Email | Day 3 | Follow-up with new angle | | 3 | Phone | Day 5 | Call attempt | | 4 | Email | Day 7 | Value-add content | | 5 | Email | Day 14 | Breakup / final touch | Set the **delay** between steps using the delay selector on each step. > **Tip:** > Keep your first sequence simple: 3 email steps over 10 days. You can always add more steps later once you see what works. --- ## Add Contacts to Your Sequence 1. **Add from the sequence page** From your sequence, click **Add Contacts**. You can search for individual contacts or select an entire list. 2. **Or add from the Contacts table** Go to **Data --> Contacts**, select contacts using the checkboxes, and click **Add to Sequence** in the bulk actions bar. Choose your sequence from the dropdown. 3. **Review the contact list** Back on your sequence page, click the **Contacts** tab to see everyone enrolled. Verify the count matches what you expect. > **Tip:** > Start with a small batch (10-20 contacts) for your first sequence. This lets you test messaging before scaling up. --- ## Activate Your Sequence 1. **Review before activating** Click through each step to proofread your emails. Check that merge fields render correctly in the preview. Verify your sending schedule and delays. 2. **Activate** Click the **Activate** button at the top of the sequence page. Confirm the activation prompt. Your first step sends according to the schedule you set. 3. **Verify sends are going out** After activation, check the **Activity** tab on your sequence. You should see emails queuing and sending within the first scheduled window. --- ## Monitor Performance 1. **Check sequence metrics** On the sequence page, review the summary stats at the top: **Sent**, **Opened**, **Replied**, and **Bounced** counts. 2. **Review individual contact status** Click the **Contacts** tab to see where each contact is in the sequence. Statuses include: **Active**, **Replied**, **Completed**, **Paused**, and **Bounced**. 3. **Handle replies** When a contact replies, they are automatically removed from the sequence. Check your **Inbox** (under **Engage --> Inbox**) to respond to replies promptly. > **Tip:** > Check your sequence metrics daily for the first week. If open rates are below 30%, revisit your subject lines. If reply rates are below 3%, revisit your messaging. --- ## Mission 2 Complete You have created, populated, and launched your first outreach sequence. Contacts are receiving personalized emails on autopilot. **Next mission:** [Master Company Intelligence](https://docs.graph8.com/training/sdr/intelligence/) --- # Mission 1: Set Up Your Foundation > Import contacts, create lists, enrich data, and review ICP scores Source: https://docs.graph8.com/training/sdr/foundation/ ## What you'll learn - How to import contacts from a CSV file - How to organize contacts into lists - How to enrich contact data automatically - How to read and use ICP scores for prioritization --- ## Import Your First Contacts 1. **Prepare your CSV** Your file needs these columns: **email**, **first name**, **last name**, **company name**, **title**, and **owner name**. One row per contact. Save as `.csv`. 2. **Upload the file** Go to **Data --> Contacts** and click the **Import** button in the top-right corner. Drag your CSV into the upload area or click to browse. 3. **Map your fields** graph8 auto-maps columns that match standard names. Review the mapping screen and correct any fields shown as **Unmapped**. Click **Next** when all required fields are green. 4. **Confirm and import** Review the summary (total rows, mapped fields, skipped rows). Click **Start Import**. You will see a progress bar. When it finishes, your contacts appear in the **Contacts** table. > **Tip:** > Importing automatically queues each new contact for enrichment. You do not need to trigger it manually. --- ## Create a List 1. **Open the Lists page** Go to **Data --> Lists** and click **Create List**. 2. **Name your list** Give it a descriptive name, for example "Q2 Target Accounts - VP Engineering". Add an optional description. 3. **Add contacts to the list** You can add contacts in two ways: - **From the list page**: Click **Add Contacts**, search or filter, and select the ones you want. - **From the Contacts table**: Select rows using the checkboxes, then click **Add to List** in the bulk actions bar. --- ## Review Enrichment Results 1. **Check enrichment status** Go to **Data --> Contacts** and look at the **Enrichment** column. A green checkmark means enrichment is complete. A clock icon means it is still processing. 2. **Open a contact profile** Click any contact row to open their detail page. The **Overview** tab shows enriched fields: verified email, phone number, LinkedIn URL, company data, and more. 3. **Spot gaps** If key fields like phone or LinkedIn are blank after enrichment, the data was not available. You can manually add it or re-run enrichment from the contact's action menu. --- ## Review ICP Scores 1. **Navigate to Audiences** Go to **Audiences --> ICPs** tab to see your organization's ideal customer profiles. 2. **Understand the scoring** Each account is scored on three dimensions: | Score | Range | What it measures | |-------|-------|------------------| | **Fit** | 0-40 | Industry, size, tech stack match | | **Opportunity** | 0-30 | Growth signals, funding, expansion | | **Readiness** | 0-30 | Intent signals, timing indicators | | **Total** | 0-100 | Combined prioritization score | 3. **Sort your companies** Go to the **Companies** tab and sort by **Total Score** (highest first). Companies scoring 80+ are your top priority. Focus your outreach here first. > **Tip:** > Bookmark the Companies view sorted by Total Score. This becomes your daily prioritization dashboard. --- ## Mission 1 Complete You now have contacts imported, organized into lists, enriched with data, and prioritized by ICP score. This is the foundation everything else builds on. **Next mission:** [Launch Your First Sequence](https://docs.graph8.com/training/sdr/first-sequence/) --- # Mission 3: Master Company Intelligence > Run intelligence on companies, review research categories, and use Copilot for quick insights Source: https://docs.graph8.com/training/sdr/intelligence/ ## What you'll learn - How to run Company Intelligence on a target company - How to read and use the six research categories - How to use Copilot to get quick answers about any company - How to turn intelligence into outreach talking points --- ## Run Company Intelligence 1. **Pick a high-priority company** Go to **Audiences --> Companies** and sort by **Total Score** (highest first). Click on a company with a score of 60 or above. 2. **Trigger intelligence** On the company detail page, click the **Run Intelligence** button. graph8 researches the company across multiple sources. This takes 1-2 minutes. 3. **Wait for completion** A progress indicator shows the research running. When all six categories show a green checkmark, intelligence is complete. > **Tip:** > Run intelligence on your top 10 companies first. This gives you a strong research foundation for your highest-value outreach. --- ## Review the Six Research Categories Once intelligence completes, you see six research categories on the company page: | Category | What it contains | How to use it | |----------|-----------------|---------------| | **Company Overview** | Business summary, founding, headquarters, size | Opening context for cold outreach | | **Products & Services** | What they sell, key offerings | Align your pitch to their business model | | **Recent News** | Funding, leadership changes, product launches | Timely hooks for personalization | | **Technology Stack** | Tools and platforms they use | Identify integration opportunities | | **Competitors** | Key competitors and market position | Differentiation talking points | | **Pain Points** | Industry challenges and company-specific issues | Lead with relevance in your messaging | 1. **Read each category** Click through the six tabs on the company detail page. Each category contains a summary with key bullet points. 2. **Note outreach hooks** Look for items you can reference in your emails or calls: - A recent funding round (Recent News) - A tool they use that your product integrates with (Technology Stack) - A specific challenge mentioned (Pain Points) 3. **Check stakeholder mapping** Within the company, click the **Contacts** subtab. Review the **Buying Role** assignments (Champion, Decision Maker, Influencer, End User) and **Engagement Score** for each contact. --- ## Use Copilot for Quick Insights 1. **Open Copilot** Click the **Copilot** icon in the bottom-right corner of any page (or press the keyboard shortcut). 2. **Ask about a company** Try these commands: | What you need | Example prompt | |---------------|----------------| | Company summary | "What do we know about Acme Corp?" | | Key contacts | "Who are the decision makers at Acme Corp?" | | Pain points | "What challenges does Acme Corp face?" | | Competitors | "Who competes with Acme Corp?" | | Recent activity | "What's happened recently at Acme Corp?" | 3. **Use the answers** Copilot pulls from the intelligence data you generated. Copy relevant insights directly into your email drafts or call notes. > **Tip:** > Before every call, open Copilot and ask "Summarize what we know about [company]." You get a 30-second briefing that would take 15 minutes to research manually. --- ## Turn Intelligence into Talking Points 1. **Pick 2-3 hooks per company** From the intelligence categories, select the most relevant data points. A strong cold email uses one company-specific hook in the opening line. 2. **Match hooks to personas** Go to **Audiences --> Personas** to see buyer persona profiles. Match your talking points to the persona's pain points and motivations. 3. **Draft personalized messaging** Use your hooks and persona insights to craft outreach that feels researched, not generic. Reference specifics: "I saw your Series B announcement" is stronger than "I noticed your company is growing." --- ## Mission 3 Complete You can now generate deep company research on demand, navigate the six intelligence categories, and use Copilot to get instant insights before any outreach. **Next mission:** [Connect Your Tools](https://docs.graph8.com/training/sdr/tools/) --- # Mission 4: Connect Your Tools > Link your CRM, mailbox, and calendar to graph8 for a seamless workflow Source: https://docs.graph8.com/training/sdr/tools/ ## What you'll learn - How to connect your CRM (Salesforce or HubSpot) - How to connect your email mailbox for sending sequences - How to connect your calendar for booking meetings - How to verify that everything is syncing --- ## Connect Your CRM 1. **Open Integrations** Go to **Settings --> Integrations**. You will see available integration cards for Salesforce, HubSpot, and other tools. 2. **Select your CRM** Click **Connect** on either **Salesforce** or **HubSpot**. You will be redirected to your CRM's authorization page. 3. **Authorize access** Log in to your CRM and approve the permissions graph8 requests. These permissions allow graph8 to sync contacts, companies, and activities bidirectionally. 4. **Configure sync settings** After authorization, you return to graph8. Review the sync settings: - **Contact sync direction**: Bidirectional is recommended - **Activity logging**: Enable so calls and emails appear in CRM - **Field mapping**: Review which graph8 fields map to your CRM fields 5. **Trigger initial sync** Click **Sync Now** to start the first sync. Depending on your CRM data size, this takes 5-30 minutes. > **Tip:** > Enable bidirectional sync so you never have to manually update records in two places. Changes in either system flow to the other automatically. --- ## Connect Your Mailbox 1. **Open Mailbox settings** Go to **Settings --> Mailboxes** and click **Add Mailbox**. 2. **Choose your provider** Select **Google Workspace**, **Microsoft 365**, or **Other SMTP** depending on your email provider. 3. **Authenticate** For Google or Microsoft, click the provider button and authorize via OAuth. For SMTP, enter your server details, email, and app password. 4. **Set sending limits** Configure your daily send limit. Start conservative: | Account age | Recommended daily limit | |-------------|------------------------| | New mailbox (< 2 weeks) | 20-30 emails/day | | Warming (2-4 weeks) | 30-50 emails/day | | Established (4+ weeks) | 50-100 emails/day | 5. **Send a test email** Click **Send Test Email** to verify delivery. Check that it lands in the inbox (not spam) of the test address. > **Tip:** > If you are using a new sending domain, ask your admin about the domain warmup schedule. Sending too many emails too fast from a cold domain hurts deliverability. --- ## Connect Your Calendar 1. **Open Calendar settings** Go to **Settings --> Integrations** and find the **Calendar** section. Click **Connect Calendar**. 2. **Authorize access** Select **Google Calendar** or **Microsoft Outlook** and complete the OAuth flow. graph8 reads your availability and can create events on your behalf. 3. **Set your availability** Configure your meeting availability: - **Working hours**: Set the hours you are available for meetings - **Buffer time**: Add buffer between meetings (15 minutes recommended) - **Meeting types**: Create templates for different meeting lengths (15 min intro, 30 min demo) 4. **Test a booking link** graph8 generates a booking link you can include in outreach. Click **Preview** to test the booking flow and confirm your availability appears correctly. --- ## Verify Everything Is Connected 1. **Check the Integrations dashboard** Go to **Settings --> Integrations**. Each connected tool shows a green **Connected** status badge. If any show a warning or error, click into the integration to troubleshoot. 2. **Verify CRM sync** Go to **Data --> Contacts** and spot-check a few contacts. Open a contact that exists in your CRM and confirm the CRM fields (like CRM ID or last activity date) are populated. 3. **Verify mailbox** Go to **Settings --> Mailboxes**. Your mailbox should show **Active** status with a green indicator. If it shows **Disconnected**, re-authenticate. 4. **Verify calendar** Go to **Engage --> Appointments** and confirm your calendar is listed with available time slots showing. > **Tip:** > If any integration shows errors, check that your organization admin has granted the necessary permissions. Some CRM connections require admin-level approval. --- ## Mission 4 Complete Your CRM, mailbox, and calendar are connected. Activities sync automatically, emails send from your real address, and prospects can book meetings directly. **Next mission:** [Build Your Daily Workflow](https://docs.graph8.com/training/sdr/daily-workflow/) --- # SDR Training > Maximize outreach efficiency through AI-powered prioritization and research Source: https://docs.graph8.com/training/sdrs/ Your role: Maximize outreach efficiency through AI-powered prioritization and personalized research at scale. > **Tip: Time Savings** > **2+ hours per segment** saved on ICP/persona research ## Core Capabilities | Capability | What It Does | Time Saved | |------------|--------------|------------| | Auto-generated Personas | Buyer personas with pain points and messaging | 1-2 hours/segment | | Auto-generated ICPs | Scored ideal customer profiles | 1-2 hours/segment | | TAM Definition | Firmographic boundaries for targeting | 30 min setup | | Contact Import | CSV/webhook sync with enrichment | 30 min/batch | | Company Intelligence | Deep research on priority companies | 30-45 min/company | --- ## Week 1 Workflow: Company Prioritization Setup ### Day 1: Understanding Your ICPs 1. **Review Existing ICPs** Navigate to **Audiences → ICPs** tab. Each ICP has three scores that combine into a Total Score for prioritization. 2. **Understand ICP Criteria** Review the firmographic criteria: industry, company size, revenue range, geography, tech stack, and pain points. 3. **Filter Companies by ICP** Go to **Companies** tab, use filters to match ICP criteria, sort by **total_score** (highest first). These are your priority companies. **ICP Scoring Breakdown:** | Score | Range | What It Measures | |-------|-------|------------------| | **Fit Score** | 0-40 | Firmographic match (industry, size, tech) | | **Opportunity Score** | 0-30 | Growth signals, funding, expansion | | **Readiness Score** | 0-30 | Intent signals, timing indicators | | **Total Score** | 0-100 | Combined prioritization | ### Day 2: Contact Research Workflow 1. **Import Contacts** Prepare CSV with required fields (email, first name, last name, company name, title, owner name). Navigate to **Companies → Import**, upload CSV, map fields. Auto-enrichment queues for new contacts. 2. **Run Company Intelligence** For top priority companies: select company, click **Run Intelligence**, review 6 generated categories, note key talking points for outreach. 3. **Review Stakeholders** Within company, go to **Contacts** subtab. Check **buying_role** assignments and **engagement_score** (0-100). --- ## Daily Outreach Prep Workflow ``` Morning Prep (15 min) ├── 1. Check today's priority companies ├── 2. Run any missing intelligence ├── 3. Review research docs for key insights └── 4. Queue personalization notes Research per Company (5 min each) ├── Copilot: "What do we know about [company]?" ├── Check recent news/events ├── Identify key stakeholders └── Note specific pain points for messaging Outreach Execution ├── Reference campaign messaging docs ├── Personalize using intelligence data └── Log activities for tracking ``` --- ## Copilot Commands for SDRs | Task | Example Command | |------|-----------------| | Company research | "Summarize what we know about TechCorp" | | Find contacts | "Who are the VPs in our companies?" | | Pain points | "What challenges does TechCorp face?" | | Competitors | "Who competes with TechCorp?" | | Add contact | "Add sarah@techcorp.com as champion" | | News search | "Recent news about TechCorp" | --- ## Using Personas for Messaging ### Access Buyer Personas Navigate to **Audiences → Personas** tab. Each persona includes: - Title/role patterns - Key pain points - Motivations and goals - Preferred communication style - Common objections ### Matching Contacts to Personas When researching a contact: 1. Check their title 2. Match to closest persona 3. Use persona's pain points in messaging 4. Address persona-specific objections --- ## Research Reports for Outreach Access these reports for messaging insights: | Report | Use For | |--------|---------| | **Voice of Customer** | Language and terminology to mirror | | **Buyer Psychology** | Objection handling patterns | | **Competitive Intel** | Differentiators vs. competitors | | **Market Trends** | Industry context for relevance | | **Win/Loss Themes** | What resonates with similar prospects | Navigate to **Research** tab to access all reports. --- ## Prioritization Matrix | Total Score | Priority | Action | |-------------|----------|--------| | 80-100 | Highest | Immediate, personalized outreach | | 60-79 | High | Multi-touch sequence | | 40-59 | Medium | Nurture sequence | | 0-39 | Low | Batch campaigns only | --- ## Conversion Metrics to Track | Metric | Definition | |--------|------------| | **Companies Worked** | Companies with logged activity | | **Contacts Added** | New stakeholders mapped | | **Intelligence Complete** | Companies with full research | | **MQLs Generated** | Marketing qualified leads | | **SQLs Generated** | Sales qualified leads | | **Opportunities Created** | Deals in pipeline | The Team Dashboard shows conversion rates at each stage. --- ## Outreach Checklist Before reaching out to any company: - [ ] ICP score reviewed (≥60 for personalized outreach) - [ ] Company Intelligence complete (6/6 categories) - [ ] Stakeholders mapped with buying roles - [ ] Persona matched for primary contact - [ ] Recent news/events reviewed - [ ] Talking points noted from research - [ ] Campaign messaging referenced for consistency --- ## Troubleshooting | Issue | Solution | |-------|----------| | Low ICP scores | Verify firmographic data is correct | | Missing contact enrichment | Check email format; retry enrichment | | No intelligence data | Verify website URL; run workflow manually | | Company not appearing | Check org filter; confirm import completed | --- ## Best Practices > **Tip: Let AI Prioritize** > Work highest ICP scores first — let AI do the heavy lifting on prioritization so you can focus on personalization. 1. **Work highest ICP scores first** — Let AI prioritize for you 2. **Run intelligence before outreach** — Personalization requires data 3. **Use persona pain points** — Generic messaging fails 4. **Check news before calling** — Recent events = relevance 5. **Log everything** — Your activity data informs team strategy --- ## What's Next? **ICPs & Personas** [Understanding ICP scoring](https://docs.graph8.com/studio/global/) **Company Intelligence** [Deep research workflows](https://docs.graph8.com/studio/accounts/) **Campaigns** [Campaign creation](https://docs.graph8.com/studio/campaigns/)