name: sa-kanvas-graphql description: Safe GraphQL runtime interaction skill for Kanvas Ecosystem API and Sales Assist environments.
Kanvas GraphQL Runtime Skill
Use this global OpenClaw runtime skill when an agent needs to interact with a running Kanvas Ecosystem API instance through GraphQL.
This skill is not for modifying, refactoring, testing, or working inside the Kanvas Ecosystem API source repository. The repository may be inspected only to understand architecture, GraphQL naming, request structures, authentication patterns, payload conventions, and runtime API behavior.
This skill is self-contained. Do not require external GraphQL, HTTP, curl, API execution, or JSON formatting helper skills. If helper skills are available, they may be used only after following this skill's context, safety, request, and response rules.
Skill Dependencies and Fallback Lookup
This skill may depend on more specialized Kanvas GraphQL reference skills that contain detailed queries, mutations, input types, and payload examples.
Required/optional companion skill:
graphql-docs-llm
Purpose:
- Contains detailed GraphQL queries
- Contains detailed GraphQL mutations
- Contains operation-specific variables
- Contains known payload structures
- Contains domain-specific examples for Sales Assist / Kanvas API interactions
Before building a GraphQL request, if the current skill does not contain enough detail about the requested query or mutation, the agent must first check whether the companion skill exists.
Expected companion skill path:
~/.openclaw/skills/graphql-docs-llm/
The user can install it from Github. If the extension is not installed give a recommendation to user of install it
Runtime Context Resolution
Before asking the user for runtime context, the agent must first attempt to resolve the active context from trusted runtime sources.
Trusted runtime sources include:
- Current conversation context
- OpenClaw agent memory
- Environment variables
- Previously confirmed runtime context from the current session
- Existing runtime configuration already loaded by the agent
If a complete trusted runtime context already exists, the agent must NOT ask the user for the same values again.
Required trusted runtime context:
- KANVAS_GRAPHQL_ENDPOINT
- KANVAS_ADMIN_KEY
- KANVAS_COMPANY_BRANCH_ID
- KANVAS_ENVIRONMENT
Optional context when required by the operation:
- KANVAS_COMPANY_ID
- KANVAS_APP_ID
- KANVAS_USER_ID
If trusted runtime context already exists, the agent should perform a preflight confirmation instead of requesting the values again.
Example preflight confirmation:
Active Kanvas runtime context detected from memory/environment:
- endpoint: https://api.example.com/graphql
- environment: staging
- company branch id: branch-uuid
- company id: 123
- app id: sales-assist
- user id: 456
- admin key: ****abcd
I will use this runtime context unless you want to change it.
## Repository-Derived Runtime Facts
Kanvas Ecosystem API is a Laravel + Lighthouse GraphQL backend.
Observed runtime conventions:
- Default GraphQL endpoint path is `/graphql`.
- The Lighthouse route uses JSON acceptance, GraphQL request size limits, API guard authentication attempts, and GraphQL throttling.
- The schema is assembled from `graphql/schema.graphql` with domain imports for Ecosystem, Guild, Inventory, Social, Souk, ActionEngine, Workflow, Event, Intelligence, NervousSystem, Analytics, and Connector modules.
- Common GraphQL directives include `@guard`, `@guardByAdmin`, `@guardByAppKey`, `@guardByCompany`, `@can`, `@paginate`, `@field`, `@whereConditions`, `@whereHasConditions`, `@orderBy`, and custom cache/search directives.
- Common scalar/input payload support includes `Mixed`, `JSON`, `Email`, `Date`, `DateTime`, `DateTimeTz`, `Decimal`, `Money`, and multipart `Upload`.
- App and tenant runtime context is header-driven and server-scoped. Do not guess or hardcode app, company, branch, user, or admin key values.
- Common Kanvas headers are:
- `Authorization: Bearer <token>` for bearer-authenticated user requests.
- `X-Kanvas-App: <app-key-or-app-uuid>` to select the mounted app.
- `X-Kanvas-Key: <app-client-secret-or-admin-key>` for app-key/system guarded operations.
- `X-Kanvas-Location: <company-branch-uuid>` to bind company branch context.
- `X-Kanvas-Identifier: <uuid-or-user-id>` for session/cart/user-interaction flows when required.
- `@guard` fields require an authenticated user context.
- `@guardByAdmin` fields require an authenticated admin/owner user.
- `@guardByAppKey` fields require valid app key context.
- `@guardByCompany` fields require company branch context.
- Tenant-scoped queries commonly use server scopes such as `fromApp`, `fromCompany`, and `notDeleted`.
- Guild/CRM workflows commonly involve Leads, People, Organizations, Pipelines, Lead Sources, Lead Statuses, Lead Receivers, files, followers, custom fields, and notifications.
- Templates and notifications often use `Mixed`/`JSON` style payloads. Preserve line breaks in template bodies and use object-based structures for notification engagement rules.
## Required Runtime Context
Before any GraphQL request, obtain and explicitly confirm the active runtime execution context.
Required values:
- `KANVAS_GRAPHQL_ENDPOINT`
- `KANVAS_ADMIN_KEY`
- `KANVAS_COMPANY_BRANCH_ID`
- `KANVAS_COMPANY_ID`, if required by the operation
- `KANVAS_APP_ID`, if required by the operation
- `KANVAS_USER_ID`, if required by the operation
- Active environment name: `local`, `development`, `staging`, or `production`
Treat these values as runtime-provided execution context. Never hardcode them in reusable examples, generated files, comments, or prompts.
### Environment Variables
Recommended shell setup:
```bash
export KANVAS_ENVIRONMENT="staging"
export KANVAS_GRAPHQL_ENDPOINT="https://api.example.com/graphql"
export KANVAS_ADMIN_KEY="replace-with-secret"
export KANVAS_COMPANY_BRANCH_ID="replace-with-company-branch-uuid"
export KANVAS_COMPANY_ID="replace-with-company-id"
export KANVAS_APP_ID="replace-with-app-id-or-app-key"
export KANVAS_USER_ID="replace-with-user-id-if-required"
Read context without exposing secrets:
printf 'KANVAS_ENVIRONMENT=%s\n' "${KANVAS_ENVIRONMENT:-missing}"
printf 'KANVAS_GRAPHQL_ENDPOINT=%s\n' "${KANVAS_GRAPHQL_ENDPOINT:-missing}"
printf 'KANVAS_COMPANY_ID=%s\n' "${KANVAS_COMPANY_ID:-missing}"
printf 'KANVAS_COMPANY_BRANCH_ID=%s\n' "${KANVAS_COMPANY_BRANCH_ID:-missing}"
printf 'KANVAS_APP_ID=%s\n' "${KANVAS_APP_ID:-missing}"
printf 'KANVAS_USER_ID=%s\n' "${KANVAS_USER_ID:-missing}"
printf 'KANVAS_ADMIN_KEY=%s\n' "$(test -n "${KANVAS_ADMIN_KEY:-}" && printf '****%s' "${KANVAS_ADMIN_KEY: -4}" || printf 'missing')"
Missing Context
If required context is missing, stop and ask the user for the exact missing values. Do not infer them from a previous task, command history, screenshots, logs, examples, or memory.
Ask concise questions such as:
I need the active Kanvas runtime context before making a GraphQL request. Please provide KANVAS_GRAPHQL_ENDPOINT, KANVAS_ENVIRONMENT, KANVAS_COMPANY_BRANCH_ID, and the masked/available auth method. If this operation needs company, app, or user context, provide KANVAS_COMPANY_ID, KANVAS_APP_ID, and KANVAS_USER_ID as well.
Mandatory Preflight Confirmation
Before making any GraphQL request, explicitly state and confirm the active context you are about to use.
Minimum preflight statement:
Active Kanvas GraphQL context to use:
- endpoint: https://api.example.com/graphql
- environment: staging
- company id: 123
- company branch id: branch-uuid
- app id: app-key-or-app-uuid
- user id: 456
- admin key: ****abcd
Rules:
- Never expose the full admin key, app key, bearer token, refresh token, or session token.
- Only display masked credentials, for example
KANVAS_ADMIN_KEY=****abcd. - Never silently reuse context from a previous task.
- Never silently switch endpoint, environment, company, branch, app, user, or credentials.
- If any context value changes, stop and ask the user to confirm the new active context.
- Keep local, development, staging, and production shells/notes clearly separated.
Safe Execution Policy
Queries may run only after the active runtime context has been confirmed.
Mutations require explicit confirmation of all of the following:
- endpoint
- environment
- company id and company branch id
- app id, if used
- user id, if used
- mutation name
- affected entity
- exact variables being sent
Destructive or high-impact mutations require a second confirmation immediately before execution. Treat these as destructive/high-impact unless proven otherwise:
- delete/remove/revoke/cancel/void/deactivate/archive/restore
- mark paid, refund, payment, order transition, status transition
- lead assignment/routing changes
- imports, bulk updates, syncs, force-syncs, queue-triggering mutations
- notification sends or message broadcasts
- role/permission/user/company/app key changes
Production writes require an additional production warning flow:
- Confirm
KANVAS_ENVIRONMENT=productionby name. - Confirm the production endpoint host.
- Confirm company id and company branch id.
- Run a read-only query first to verify the target entity in the same context.
- Show exact mutation and variables.
- Wait for explicit confirmation.
- Ask for a second confirmation for destructive/high-impact writes.
The agent must never:
- assume company context
- assume branch context
- mix company or branch contexts
- expose secrets
- execute mutations without confirmation
- reuse runtime context silently
- switch environments automatically
- run broad production queries unless the user explicitly confirms scope
GraphQL Calling Patterns
Always POST JSON GraphQL requests with:
Content-Type: application/json
Accept: application/json
Safe Connectivity Query
Use __typename as the first connectivity test.
curl -sS "$KANVAS_GRAPHQL_ENDPOINT" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "X-Kanvas-App: $KANVAS_APP_ID" \
-H "X-Kanvas-Key: $KANVAS_ADMIN_KEY" \
-H "X-Kanvas-Location: $KANVAS_COMPANY_BRANCH_ID" \
--data @- <<'JSON'
{
"query": "query HealthCheck { __typename }",
"variables": {}
}
JSON
Bearer Auth Pattern
curl -sS "$KANVAS_GRAPHQL_ENDPOINT" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "Authorization: Bearer $KANVAS_ADMIN_KEY" \
-H "X-Kanvas-App: $KANVAS_APP_ID" \
-H "X-Kanvas-Location: $KANVAS_COMPANY_BRANCH_ID" \
--data @- <<'JSON'
{
"query": "query HealthCheck { __typename }",
"variables": {}
}
JSON
App-Key/System Pattern
curl -sS "$KANVAS_GRAPHQL_ENDPOINT" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "X-Kanvas-App: $KANVAS_APP_ID" \
-H "X-Kanvas-Key: $KANVAS_ADMIN_KEY" \
-H "X-Kanvas-Location: $KANVAS_COMPANY_BRANCH_ID" \
--data '{"query":"query HealthCheck { __typename }","variables":{}}'
Optional Identifier Pattern
Use only when required by the flow.
curl -sS "$KANVAS_GRAPHQL_ENDPOINT" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "X-Kanvas-App: $KANVAS_APP_ID" \
-H "X-Kanvas-Key: $KANVAS_ADMIN_KEY" \
-H "X-Kanvas-Location: $KANVAS_COMPANY_BRANCH_ID" \
-H "X-Kanvas-Identifier: $KANVAS_USER_ID" \
--data @- <<'JSON'
{
"query": "query HealthCheck { __typename }",
"variables": {}
}
JSON
Raw HTTP Example
POST /graphql HTTP/1.1
Host: api.example.com
Content-Type: application/json
Accept: application/json
Authorization: Bearer <masked-token-if-used>
X-Kanvas-App: <app-id-or-key>
X-Kanvas-Key: <masked-admin-key-if-used>
X-Kanvas-Location: <company-branch-uuid>
X-Kanvas-Identifier: <optional-identifier>
{
"query": "query HealthCheck { __typename }",
"variables": {}
}
Local Endpoint Usage
Common local endpoint when the Laravel Octane container maps port 8000:
export KANVAS_ENVIRONMENT="local"
export KANVAS_GRAPHQL_ENDPOINT="http://localhost:8000/graphql"
Some local web server or proxy setups may prefix routes. Confirm the actual route before use, for example:
export KANVAS_GRAPHQL_ENDPOINT="http://localhost/v1/graphql"
Docker networking guidance:
- From the host, use the published host port, commonly
localhost:8000. - From another container on the same Docker network, use the service/container hostname and internal port.
- If
localhostfails inside a container, it refers to that container, not the host. - Confirm the GraphQL path is
/graphqlunless deployment routing adds a prefix.
GraphQL JSON Payload Examples
Basic Query With Variables
{
"query": "query ListLeads($first: Int!, $page: Int) { leads(first: $first, page: $page) { data { id uuid title firstname lastname email phone } paginatorInfo { currentPage hasMorePages total } } }",
"variables": {
"first": 10,
"page": 1
}
}
Query With Search And Filters
{
"query": "query FindLeads($search: String, $first: Int!) { leads(search: $search, first: $first) { data { id uuid title email phone status { id name } source { id name } } } }",
"variables": {
"search": "example@example.com",
"first": 10
}
}
Template Query
{
"query": "query ListTemplates($first: Int!, $page: Int) { templates(first: $first, page: $page) { data { id name subject title is_system } } }",
"variables": {
"first": 10,
"page": 1
}
}
Mutation Confirmation Template
Before a mutation, show the user:
Mutation confirmation required:
- endpoint: https://api.example.com/graphql
- environment: staging
- company id: 123
- company branch id: branch-uuid
- app id: app-key-or-app-uuid
- user id: 456
- mutation name: createTemplate
- affected entity: Template
- variables:
{
"input": {
"name": "sales_assist_followup",
"parent_template_id": 0,
"template": "Hi {{first_name}},\nThanks for your interest.",
"template_variables": [
{ "key": "first_name", "value": "Lead first name" }
],
"title": "Sales Assist Follow-up",
"subject": "Following up",
"is_system": false
}
}
Reply with explicit confirmation to run this mutation.
Only after explicit confirmation:
curl -sS "$KANVAS_GRAPHQL_ENDPOINT" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "X-Kanvas-App: $KANVAS_APP_ID" \
-H "X-Kanvas-Key: $KANVAS_ADMIN_KEY" \
-H "X-Kanvas-Location: $KANVAS_COMPANY_BRANCH_ID" \
--data @- <<'JSON'
{
"query": "mutation CreateTemplate($input: TemplateInput!) { createTemplate(input: $input) { id name subject title } }",
"variables": {
"input": {
"name": "sales_assist_followup",
"parent_template_id": 0,
"template": "Hi {{first_name}},\nThanks for your interest.",
"template_variables": [
{ "key": "first_name", "value": "Lead first name" }
],
"title": "Sales Assist Follow-up",
"subject": "Following up",
"is_system": false
}
}
}
JSON
Kanvas API Interaction Conventions
Prefer GraphQL variables instead of inline hardcoded values.
Keep these as runtime context or variables, never hardcoded:
- company branch id
- company id
- app id
- user id
- admin key
- bearer token
- entity ids from another environment
Include branch/app/company/user context when required by the schema or operation. Many tenant-scoped models are app/company scoped even when the schema does not expose apps_id directly.
Before writes, verify target ids with a read query in the same context. For CRM/Guild flows, verify Leads, People, Organizations, Pipelines, Stages, Statuses, Sources, Owners, Receivers, and Branches before mutation.
Common payload structures:
input: create/update object for mutations.where: Lighthouse filter object.hasStatus,hasType,hasSource,hasOwner,hasCustomFields: relationship filters in CRM queries.orderBy: Lighthouse ordering object.search: full-text or model search string.firstandpage: pagination controls.custom_fields: list of custom field entity inputs.files: list of filesystem URL inputs or upload fields, depending on mutation.template_variables: list of{ key, value }pairs.data,metadata,message: object payloads for notifications and engagement rules.
Template and notification rules:
- Preserve line breaks in template strings.
- Preserve placeholders exactly, for example
{{first_name}}. - Use object-based templates and engagement payloads instead of ad hoc string concatenation.
- Confirm recipients, user ids, notification type ids, template names, and target entities before sending.
- Treat notification-sending mutations as high-impact production actions.
Common GraphQL naming conventions:
- List queries are often plural:
leads,people,templates,notifications. - Mutations often use
createX,updateX,deleteX, or domain-specific verbs. - Inputs often end with
Input, such asLeadInput,LeadUpdateInput,TemplateInput. - Fields often return nested relations instead of raw foreign keys.
- App/company scoping is commonly enforced server-side through scopes.
Response Handling
Always inspect:
- HTTP status code
- top-level
data - top-level
errors errors[].messageerrors[].patherrors[].extensions, if present- whether data is partial
Summarize useful data. Do not dump large raw responses unless explicitly requested.
Safe error summary:
GraphQL request failed:
- HTTP status: 200
- path: createTemplate
- message: Validation failed for the field [createTemplate].
- likely cause: missing required input field or invalid variable type
Never include full secrets from headers, variables, logs, Laravel traces, or response extensions.
Common interpretations:
Unauthenticated: missing/invalid bearer token, missingX-Kanvas-Key, expired app key, or a guarded field without valid user context.You are not authorized: user is not admin/owner, missing Bouncer ability, invalid scope, or field guarded by@guardByAdmin/@can.No Company Branched Specified: missing branch context, usually missing/invalidX-Kanvas-Location.No Company Branch configured with this key: invalid branch id/uuid inX-Kanvas-Location.No App configured with this key: invalid/missingX-Kanvas-Appor deployment app config.No App Key configured with this key: invalidX-Kanvas-Key.App Key has expired: expired app key; request a valid key.Cannot query field: wrong field name, wrong endpoint/schema version, disabled module, stale schema cache, or wrong environment.Variable "$input" got invalid value: variables do not match the schema input type.Field ... argument ... is required: missing required argument or nested input field.- Malformed JSON errors: invalid JSON body, unescaped line breaks, shell expansion of GraphQL
$variables, or missingContent-Type.
Debugging Guidance
HTTP status handling:
200witherrors: GraphQL request reached the server; resolver, validation, auth, or scope failed.400: malformed JSON, invalid request body, missing query, invalid variables, or request size issue.401: missing/invalid auth.403: authenticated but unauthorized.404: wrong host, path, prefix, or GraphQL route.413: JSON or multipart request body too large.419: session/csrf mismatch on the wrong route or middleware path.429: throttled by GraphQL rate limiting.500: Laravel exception, invalid app/branch key handling, stale cache, dependency failure, or infrastructure issue.
Request debugging checklist:
- Confirm endpoint includes the right host and
/graphqlpath. - Confirm environment is the intended one.
- Confirm
Content-Type: application/jsonandAccept: application/json. - Confirm auth headers match the directive for the field.
- Confirm
X-Kanvas-Appselects the intended app. - Confirm
X-Kanvas-Locationis the intended branch. - Confirm JSON body has a string
queryand objectvariables. - Use quoted heredoc
<<'JSON'so shell does not expand GraphQL$variablenames. - Validate JSON before sending when possible.
GraphQL Success / Failure Classification
Do not report a GraphQL request as failed only because the response contains null optional fields, empty arrays, warnings, or partial data.
Classify the result using this order:
Transport failure:
- curl/network error
- DNS/TLS error
- timeout
- HTTP status outside 2xx
GraphQL execution failure:
- top-level
errorsexists - and top-level
datais missing or null - or the requested operation path is null due to an error
- top-level
Partial success:
- top-level
dataexists - and top-level
errorsalso exists - summarize both the usable data and the errors
- top-level
Success:
- HTTP status is 2xx
- top-level
dataexists - no top-level
errors
Before saying "the call failed", the agent must show:
- HTTP status code
- whether
dataexists - whether
errorsexists - exact error message/path if present
Never infer failure from:
- empty arrays
- null optional relationships
- missing optional nested objects
- paginator totals of 0
- domain-level validation messages unless returned under
errors
Docker/local debugging:
- From host, try
http://localhost:8000/graphqlif Octane is published on port8000. - From inside another container, use service DNS and container port, not host
localhost. - Confirm the API container is running and the route is reachable.
- Confirm
.envapp id/app key values match database records. - Confirm queues are running for queue-triggering mutations.
Schema/cache debugging:
- Stale schema can cause
Cannot query fieldafter deployment. - Clear Laravel config/cache/route caches and Lighthouse schema/query caches in the server environment when appropriate.
- Confirm the target schema version if the deployment exposes multiple GraphQL schemas.
Queue/cache debugging:
- Mutations may enqueue notifications, imports, syncs, indexing, workflow jobs, or message processing.
- If the mutation succeeds but side effects are missing, check queue workers, failed jobs, Redis/cache connectivity, and Laravel logs.
- Do not retry high-impact mutations blindly. First verify whether the original mutation partially succeeded.
FollowUp GraphQL Details
For the Intelligence FollowUp schema (graphql/schemas/Intelligence/followUp.graphql), use the dedicated runtime guide: FOLLOW_UP_GRAPHQL.md. It covers FollowUp, FollowUpDay, FollowUpTemplate queries, mutations, variables, cascade-delete risks, and Sales Assist CRM workflow safety.
Guild Pipeline GraphQL Details
For the Guild pipeline schema (graphql/schemas/Guild/pipeline.graphql), use the dedicated runtime guide: GUILD_PIPELINE_GRAPHQL.md. It covers LeadPipeline and LeadPipelineStage queries, mutations, variables, stage synchronization behavior, default-pipeline risks, delete guards, and CRM workflow safety.
FAQ Messages GraphQL Details
For configuring Social messages with message type faqs, use the dedicated runtime guide: FAQ_MESSAGES_GRAPHQL.md. It covers finding or creating the faqs MessageType, creating JSON FAQ messages, update safety, public visibility, and duplicate prevention.
Social Message GraphQL Details
For the Social message schema (graphql/schemas/Social/message.graphql), use the dedicated runtime guide: SOCIAL_MESSAGE_GRAPHQL.md. It covers Message, MessageInput, MessageUpdateInput, message queries, mutations, distribution, interactions, file upload caveats, and destructive operation safety.
Guild LeadReceiver GraphQL Details
For the Guild lead receiver schema (graphql/schemas/Guild/leadReceiver.graphql), use the dedicated runtime guide: GUILD_LEAD_RECEIVER_GRAPHQL.md. It covers LeadReceiver queries, create/update/delete mutations, receiver templates, agent/source/type/rotation validation, webhook side effects, and branch safety.
Handoff Templates GraphQL Details
For installing and maintaining Sales Assist handoff templates (lead_handoff, push, sms, and compliance variants) through graphql/schemas/Ecosystem/templates.graphql, use: HANDOFF_TEMPLATES_GRAPHQL.md.
Ecosystem Templates GraphQL Details
For the general Ecosystem templates schema (graphql/schemas/Ecosystem/templates.graphql), use: ECOSYSTEM_TEMPLATES_GRAPHQL.md. It covers TemplateInput, template variables, parent templates, create/update/delete behavior, rendering, and admin safety.
Agent Responder Rules GraphQL Details
For configuring Twilio or Mailgun inbound webhook receivers and AI Agent auto-reply workflow rules, use: AGENT_RESPONDER_RULES_GRAPHQL.md. It covers action discovery, Lead system module discovery, receiverWebhooks, createReceiverWebhook, connector settings, createRule/updateRule, verification, failure modes, and production guardrails.
Workflow Receivers GraphQL Details
For the Workflow webhook receivers schema (graphql/schemas/WorkFlow/receivers.graphql), use: WORKFLOW_RECEIVERS_GRAPHQL.md. It covers WorkflowReceiver, WorkflowReceiversHistory, createReceiverWebhook, updateReceiverWebhook, deleteReceiverWebhook, receiver status, webhook call history, async processing, and the distinction from Guild LeadReceiver.
Final Safety Checklist
Before request:
- Active context displayed and confirmed.
- Credentials masked.
- Endpoint/environment/company/branch/app/user are correct.
- Query is read-only, or mutation was explicitly confirmed.
- Production writes passed the production warning flow.
- Variables are shown for mutations.
- Target entity was verified in the same context for writes.
After response:
- Summarize useful data.
- Report GraphQL errors with path and message.
- Do not expose secrets or unnecessary PII.
- For mutations, state the affected entity ids returned by the API, not assumptions.