{
  "openapi": "3.1.0",
  "info": {
    "title": "Wetel API: operation catalogue",
    "version": "1.0.0",
    "summary": "The Wetel GraphQL operations, described one operation at a time for REST tools.",
    "description": "Wetel's API is GraphQL: every operation is an HTTP `POST` of a JSON body `{ query, variables }` to one URL, `/graphql`.\nThis document lists the curated operations one by one so REST tools (Postman, Insomnia, code generators, reference viewers) can show and call them.\n\n**Path convention:** each operation has the path `/graphql#<operationName>`. The part after `#` is a URL fragment that HTTP clients never send, so every request goes to `/graphql`.\n\n**Every request needs the `x-huat-platform: customer` header** as well as its credential.\n\n**Servers:** the sandbox is the only server listed. Your production base URL is issued when you go live. Use it in place of the sandbox server, with a production key.\n\nHuman-readable guide: https://wetel.dev/docs/developer-portal/quickstart/"
  },
  "servers": [
    {
      "url": "https://api.wetel.dev",
      "description": "Sandbox. Test data, no uptime promise. Keys minted here only work here. GraphQL endpoint: https://api.wetel.dev/graphql"
    }
  ],
  "tags": [
    {
      "name": "Sessions and messages",
      "description": "Start a conversation with an agent, send messages, read the replies and end it."
    },
    {
      "name": "Agents",
      "description": "Create, read and update agents, and attach the workflow an agent runs."
    },
    {
      "name": "Workflows",
      "description": "Build a workflow graph, check it, publish it, run it headlessly and read its runs."
    },
    {
      "name": "Scheduled workflows",
      "description": "Run an agent's published workflow on a cron schedule, with no chat message, and list the schedules."
    },
    {
      "name": "Knowledge bases (RAG)",
      "description": "Create knowledge bases, add documents (pasted text or uploaded files), check their status and delete them."
    },
    {
      "name": "MCP connectors",
      "description": "Register your own MCP tool server, list the tools it offers, and remove it."
    },
    {
      "name": "Custom actions",
      "description": "Register a REST call a workflow can make (URL, method, credential), test it for real, list and delete it."
    },
    {
      "name": "Approvals",
      "description": "Read the approvals inbox and approve, reject or cancel requests a workflow raised."
    },
    {
      "name": "API keys",
      "description": "Create, list and revoke named API keys, one per integration or environment."
    },
    {
      "name": "Workspace and usage",
      "description": "Read your workspace, its plan and this month's model spend, and the usage of one end customer."
    }
  ],
  "paths": {
    "/graphql#sdkStart": {
      "post": {
        "operationId": "sdkStart",
        "tags": ["Sessions and messages"],
        "summary": "Start a session against an agent and receive its session token.",
        "description": "Start a session against an agent and receive its session token.\n\n**Credential:** API key. `X-Api-Key: sk_...`, kept on your server only.\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- Call it from your server only. The API key must never reach a browser or a mobile app.\n\n- `clientId` is your own identifier for the end user or conversation. Give each conversation its own value (for example the ticket number) if you plan to poll with `sessionsByExternalCustomerId`.\n\n- `context` is an optional flat JSON object (at most 32 keys, scalar values, 8 KB) that every workflow turn can read as `session.context`. Never put secrets in it.\n\n- Starting a session can send the agent's opening greeting, which is a metered model call.\n\n- Limited to 30 calls per minute per client IP.\n\n**From the schema:**\n\nEntry point for the <vai-avatar> embed SDK. Auth is via an X-Api-Key header (see ApiKeyGuard), NOT a JWT. Starts a Session and returns everything the SDK web component needs as attributes: sessionId, avatarToken (a separate HMAC token for apps/avatar-service, not this API), and voice/avatar config. Must be called server-side — the API key must never reach the browser. avatarBackend indicates which rendering path to use — CLIENT_3D (default, an in-browser 3D avatar) or HOSTED_API (a hosted video avatar, pilot); avatarBackendConfig carries the backend-specific fields HOSTED_API needs when negotiating a session against avatar-service. Optional `context` (flat JSON, pass via `variables`; see SdkStartInput.context) is validated, stored on the session and exposed to every workflow turn as ctx.session.context.\n\n**Allowed values:**\n\n- `AgentUseCase`: `CUSTOM`, `EDUCATION`, `INTERVIEW`, `ONBOARDING`, `SALES`, `SUPPORT`\n- `VoiceTier`: `CHIRP3_HD`, `NEURAL2`, `STANDARD`, `STUDIO`, `WAVENET`",
        "security": [{ "apiKey": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "mutation StartSession($input: SdkStartInput!) {\n  sdkStart(input: $input) {\n    sessionId\n    avatarToken\n    agentId\n    graphqlEndpoint\n  }\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["StartSession"]
                  },
                  "variables": {
                    "type": "object",
                    "properties": {
                      "input": { "$ref": "#/components/schemas/SdkStartInput" }
                    },
                    "required": ["input"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "Start a session against an agent and receive its session token.",
                  "value": {
                    "query": "mutation StartSession($input: SdkStartInput!) {\n  sdkStart(input: $input) {\n    sessionId\n    avatarToken\n    agentId\n    graphqlEndpoint\n  }\n}\n",
                    "operationName": "StartSession",
                    "variables": {
                      "input": {
                        "agentId": 7,
                        "clientId": "ticket-1001",
                        "context": { "ticketId": "T-1001", "product": "laptop" }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "sdkStart": {
                          "type": "object",
                          "properties": {
                            "sessionId": {
                              "type": "integer",
                              "description": "Id of the new session. Pass it to sdkSendMessage and sdkEndSession and to the sessionEvents subscription."
                            },
                            "avatarToken": {
                              "type": "string",
                              "description": "Session credential, bound to this one session. Send it as Authorization: Bearer on sdkSendMessage, sdkEndSession and the sessionEvents subscription, and on the speech service calls. Valid about one hour; renew it with refreshAvatarToken. Safe to hand to a browser, unlike the API key."
                            },
                            "agentId": {
                              "type": "integer",
                              "description": "Id of the agent serving the session (the agentId you passed, or the default agent that was chosen or created)."
                            },
                            "graphqlEndpoint": {
                              "type": "string",
                              "description": "Absolute URL of this GraphQL API (ending in /graphql), for HTTP calls and for the sessionEvents WebSocket subscription."
                            }
                          },
                          "required": [
                            "sessionId",
                            "avatarToken",
                            "agentId",
                            "graphqlEndpoint"
                          ],
                          "description": "Everything a client needs to talk to a new session: its ids, the session credential (avatarToken) and the agent voice and avatar settings."
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": {
                      "data": {
                        "sdkStart": {
                          "sessionId": 4211,
                          "avatarToken": "example-avatar-token-not-real",
                          "agentId": 7,
                          "graphqlEndpoint": "https://api.wetel.dev/graphql"
                        }
                      }
                    }
                  },
                  "200-2": {
                    "summary": "UNAUTHENTICATED: Missing API key",
                    "value": {
                      "errors": [
                        {
                          "message": "Missing X-Api-Key header",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["sdkStart"],
                          "extensions": {
                            "code": "UNAUTHENTICATED",
                            "originalError": {
                              "message": "Missing X-Api-Key header",
                              "error": "Unauthorized",
                              "statusCode": 401
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  },
                  "200-3": {
                    "summary": "UNAUTHENTICATED: Unknown or revoked API key",
                    "value": {
                      "errors": [
                        {
                          "message": "Invalid API key",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["sdkStart"],
                          "extensions": {
                            "code": "UNAUTHENTICATED",
                            "originalError": {
                              "message": "Invalid API key",
                              "error": "Unauthorized",
                              "statusCode": 401
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  },
                  "200-4": {
                    "summary": "NOT_FOUND: Agent not found in your workspace",
                    "value": {
                      "errors": [
                        {
                          "message": "Agent #7 not found",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["sdkStart"],
                          "extensions": {
                            "code": "NOT_FOUND",
                            "status": 404,
                            "originalError": {
                              "message": "Agent #7 not found",
                              "error": "Not Found",
                              "statusCode": 404
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  },
                  "200-5": {
                    "summary": "FORBIDDEN: Platform header missing",
                    "value": {
                      "errors": [
                        {
                          "message": "Platform is not specified!",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["sdkStart"],
                          "extensions": {
                            "code": "FORBIDDEN",
                            "originalError": {
                              "message": "Platform is not specified!",
                              "error": "Forbidden",
                              "statusCode": 403
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request itself was refused before running: variables that do not match their types (`BAD_USER_INPUT`) or an invalid document (`GRAPHQL_VALIDATION_FAILED`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "400-1": {
                    "summary": "BAD_USER_INPUT: Unknown field in the input",
                    "value": {
                      "errors": [
                        {
                          "message": "Variable \"$input\" got invalid value { agentId: 7, customerId: \"ticket-1001\" }; Field \"customerId\" is not defined by type \"SdkStartInput\".",
                          "locations": [{ "line": 1, "column": 23 }],
                          "extensions": { "code": "BAD_USER_INPUT" }
                        }
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "mutation",
          "field": "sdkStart",
          "capability": "sessions"
        }
      }
    },
    "/graphql#sdkStartVoiceCall": {
      "post": {
        "operationId": "sdkStartVoiceCall",
        "tags": ["Sessions and messages"],
        "summary": "Start a live voice call with an agent and receive the LiveKit URL and join token for the caller.",
        "description": "Start a live voice call with an agent and receive the LiveKit URL and join token for the caller.\n\n**Credential:** API key. `X-Api-Key: sk_...`, kept on your server only.\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- Staging only, on request. Answers FORBIDDEN where voice calls are not enabled; not enabled in production.\n\n- Call it from your server only. The API key must never reach a browser or a mobile app.\n\n- The join token is valid to join for about two minutes and carries a session credential that the caller's browser can read. Treat it as belonging to one caller; never log or share it.\n\n- `context` is an optional flat JSON object exposed to the workflow as `ctx.session.context`, with the same rules as `sdkStart`.\n\n- Limited to 60 calls per minute per client address and 30 call starts per minute per workspace. The over-limit error may carry no specific code.\n\n- Capped at 1,000 call requests per API key per UTC day by default. Past it the call is refused with `extensions.status` 429 and the message `Daily voice call limit reached for this API key; try again tomorrow (UTC)`. A call nobody joins is closed as `EXPIRED` within about 15 minutes.\n\n**From the schema:**\n\nStarts a voice call with one of your agents. Auth is an X-Api-Key header (like sdkStart), call it server-side: the API key must never reach the browser. Creates a session for the agent exactly as sdkStart does (so tenant scoping, the agent's published workflow and usage metering all apply) and returns a LiveKit URL plus a short-lived token that can join one fresh room. Give both to the caller's browser or app and connect with the LiveKit client (a WebRTC voice call): the agent is dispatched into the room as soon as the caller joins, speaks first (it says it is an AI and offers a person) and can be interrupted. Replies are not recorded as audio. Refused with FORBIDDEN when voice calls are not enabled on this environment. Rate limited per client address (default 60 a minute, shared by every caller behind one server) and per workspace (30 call starts a minute), and capped per API key per UTC day (default 1000, set by the environment; once reached the call is refused with a 'Daily voice call limit reached' error). A call nobody joins is closed by the platform (session status EXPIRED) within about 15 minutes of the request (a 10-minute window, set by the environment, checked every 5 minutes).\n\n**Allowed values:**\n\n- `VoiceEngine`: `LIVE`, `WORKFLOW`",
        "security": [{ "apiKey": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "mutation StartVoiceCall($input: SdkStartVoiceCallInput!) {\n  sdkStartVoiceCall(input: $input) {\n    sessionId\n    agentId\n    livekitUrl\n    livekitToken\n    roomName\n    expiresAt\n  }\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["StartVoiceCall"]
                  },
                  "variables": {
                    "type": "object",
                    "properties": {
                      "input": {
                        "$ref": "#/components/schemas/SdkStartVoiceCallInput"
                      }
                    },
                    "required": ["input"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "Start a live voice call with an agent and receive the LiveKit URL and join token for the caller.",
                  "value": {
                    "query": "mutation StartVoiceCall($input: SdkStartVoiceCallInput!) {\n  sdkStartVoiceCall(input: $input) {\n    sessionId\n    agentId\n    livekitUrl\n    livekitToken\n    roomName\n    expiresAt\n  }\n}\n",
                    "operationName": "StartVoiceCall",
                    "variables": {
                      "input": { "agentId": 7, "clientId": "call-0042" }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "sdkStartVoiceCall": {
                          "type": "object",
                          "properties": {
                            "sessionId": {
                              "type": "integer",
                              "description": "Id of the session created for this call (the conversation record)."
                            },
                            "agentId": {
                              "type": "integer",
                              "description": "Id of the agent that will answer."
                            },
                            "livekitUrl": {
                              "type": "string",
                              "description": "The LiveKit server URL (wss://...) to connect the LiveKit client to."
                            },
                            "livekitToken": {
                              "type": "string",
                              "description": "A LiveKit join token for the one room of this call. Give it to the caller's browser or app and pass it to the LiveKit client with livekitUrl; it expires at expiresAt (about two minutes: join immediately) and cannot join any other room. The agent is dispatched into the room as soon as the caller joins. The token's contents are readable by the caller's browser: it carries the session credential the voice worker uses, so treat the token as belonging to that one caller and never share it."
                            },
                            "roomName": {
                              "type": "string",
                              "description": "Name of the room created for this call."
                            },
                            "expiresAt": {
                              "type": "string",
                              "format": "date-time",
                              "description": "When livekitToken stops being accepted for joining (about two minutes after the call is requested). A call already joined continues."
                            }
                          },
                          "required": [
                            "sessionId",
                            "agentId",
                            "livekitUrl",
                            "livekitToken",
                            "roomName",
                            "expiresAt"
                          ],
                          "description": "Everything a browser or app needs to join one voice call: the LiveKit URL, a short-lived token that can join exactly one room, and the session behind the call."
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": {
                      "data": {
                        "sdkStartVoiceCall": {
                          "sessionId": 4212,
                          "agentId": 7,
                          "livekitUrl": "wss://voice.example.com",
                          "livekitToken": "example-join-token-not-real",
                          "roomName": "call-4212",
                          "expiresAt": "2026-10-19T09:02:00.000Z"
                        }
                      }
                    }
                  },
                  "200-2": {
                    "summary": "UNAUTHENTICATED: Missing API key",
                    "value": {
                      "errors": [
                        {
                          "message": "Missing X-Api-Key header",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["sdkStartVoiceCall"],
                          "extensions": {
                            "code": "UNAUTHENTICATED",
                            "originalError": {
                              "message": "Missing X-Api-Key header",
                              "error": "Unauthorized",
                              "statusCode": 401
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  },
                  "200-3": {
                    "summary": "UNAUTHENTICATED: Unknown or revoked API key",
                    "value": {
                      "errors": [
                        {
                          "message": "Invalid API key",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["sdkStartVoiceCall"],
                          "extensions": {
                            "code": "UNAUTHENTICATED",
                            "originalError": {
                              "message": "Invalid API key",
                              "error": "Unauthorized",
                              "statusCode": 401
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  },
                  "200-4": {
                    "summary": "FORBIDDEN: Voice calls not enabled on this environment",
                    "value": {
                      "errors": [
                        {
                          "message": "Voice calls are not enabled on this environment",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["sdkStartVoiceCall"],
                          "extensions": {
                            "code": "FORBIDDEN",
                            "originalError": {
                              "message": "Voice calls are not enabled on this environment",
                              "error": "Forbidden",
                              "statusCode": 403
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  },
                  "200-5": {
                    "summary": "NOT_FOUND: Agent not found in your workspace",
                    "value": {
                      "errors": [
                        {
                          "message": "Agent #7 not found",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["sdkStartVoiceCall"],
                          "extensions": {
                            "code": "NOT_FOUND",
                            "status": 404,
                            "originalError": {
                              "message": "Agent #7 not found",
                              "error": "Not Found",
                              "statusCode": 404
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "mutation",
          "field": "sdkStartVoiceCall",
          "capability": "sessions"
        }
      }
    },
    "/graphql#sdkSendMessage": {
      "post": {
        "operationId": "sdkSendMessage",
        "tags": ["Sessions and messages"],
        "summary": "Send one user message into the session. Returns true at once; the reply follows asynchronously.",
        "description": "Send one user message into the session. Returns true at once; the reply follows asynchronously.\n\n**Credential:** Session token (avatarToken). `Authorization: Bearer <avatarToken>`, returned by `sdkStart` for one session.\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- `true` is an acknowledgement, not the reply. Read the reply by polling the session's messages or from the `sessionEvents` subscription.\n\n- `text` holds up to 100,000 characters. It may be empty only when `attachments` has at least one entry.\n\n- `turnContext` is an optional flat JSON object for this turn only (workflows read it as `turn.context`).\n\n- The token must belong to the same session as `input.sessionId`.\n\n**From the schema:**\n\nThe <vai-avatar> SDK's equivalent of sendMessage — takes the avatarToken from sdkStart (via AvatarTokenGuard) rather than a JWT. Same async-reply semantics as the dashboard's sendMessage: this returns immediately, the AI's reply arrives via the sessionEvents subscription. clientTurnId is an optional, opaque pass-through correlation token (not persisted, not interpreted) — if supplied, it is echoed back verbatim on the AiResponseEvent(s) published for this turn's reply, so a proxy/integration in front of this API can correlate a specific reply back to the sdkSendMessage call that triggered it. Optional `attachments` (at most 10 files, validated but not fetched; `text` may then be empty) and `turnContext` (flat JSON, pass via `variables`) reach the workflow as ctx.attachments / ctx.attachmentImageUrls / ctx.attachment* and ctx.turn.context, replacing the need to write [[...]] markers into the text; both are optional and existing callers are unaffected.\n\n**Allowed values:**\n\n- `AttachmentKind`: `AUDIO`, `DOCUMENT`, `IMAGE`, `OTHER`, `VIDEO`",
        "security": [{ "sessionToken": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "mutation SendMessage($input: SdkSendMessageInput!) {\n  sdkSendMessage(input: $input)\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["SendMessage"]
                  },
                  "variables": {
                    "type": "object",
                    "properties": {
                      "input": {
                        "$ref": "#/components/schemas/SdkSendMessageInput"
                      }
                    },
                    "required": ["input"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "Send one user message into the session. Returns true at once; the reply follows asynchronously.",
                  "value": {
                    "query": "mutation SendMessage($input: SdkSendMessageInput!) {\n  sdkSendMessage(input: $input)\n}\n",
                    "operationName": "SendMessage",
                    "variables": {
                      "input": {
                        "sessionId": 4211,
                        "text": "My laptop cannot connect to the office VPN since this morning."
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": { "sdkSendMessage": { "type": "boolean" } }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success (acknowledgement only)",
                    "value": { "data": { "sdkSendMessage": true } }
                  },
                  "200-2": {
                    "summary": "FORBIDDEN: Token for a different session",
                    "value": {
                      "errors": [
                        {
                          "message": "This avatar token is not valid for the requested session",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["sdkSendMessage"],
                          "extensions": {
                            "code": "FORBIDDEN",
                            "originalError": {
                              "message": "This avatar token is not valid for the requested session",
                              "error": "Forbidden",
                              "statusCode": 403
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  },
                  "200-3": {
                    "summary": "UNAUTHENTICATED: Expired or invalid session token",
                    "value": {
                      "errors": [
                        {
                          "message": "Invalid or expired avatar token",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["sdkSendMessage"],
                          "extensions": {
                            "code": "UNAUTHENTICATED",
                            "originalError": {
                              "message": "Invalid or expired avatar token",
                              "error": "Unauthorized",
                              "statusCode": 401
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  },
                  "200-4": {
                    "summary": "BAD_REQUEST: Session already ended",
                    "value": {
                      "errors": [
                        {
                          "message": "Session 4211 is not active (status: ended)",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["sdkSendMessage"],
                          "extensions": {
                            "code": "BAD_REQUEST",
                            "originalError": {
                              "message": "Session 4211 is not active (status: ended)",
                              "error": "Bad Request",
                              "statusCode": 400
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  },
                  "200-5": {
                    "summary": "UNAUTHENTICATED: No Authorization header",
                    "value": {
                      "errors": [
                        {
                          "message": "Missing or malformed Authorization header",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["sdkSendMessage"],
                          "extensions": {
                            "code": "UNAUTHENTICATED",
                            "originalError": {
                              "message": "Missing or malformed Authorization header",
                              "error": "Unauthorized",
                              "statusCode": 401
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "mutation",
          "field": "sdkSendMessage",
          "capability": "sessions"
        }
      }
    },
    "/graphql#sessionsByExternalCustomerId": {
      "post": {
        "operationId": "sessionsByExternalCustomerId",
        "tags": ["Sessions and messages"],
        "summary": "List the sessions started with one clientId, with their messages. The API-key way to poll for a reply.",
        "description": "List the sessions started with one clientId, with their messages. The API-key way to poll for a reply.\n\n**Credential:** API key. `X-Api-Key: sk_...`, kept on your server only.\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- `externalCustomerId` is the `clientId` you passed to `sdkStart`. Sessions come oldest first; `first` defaults to 20.\n\n- Messages come oldest first. A reply is a `MODEL` message that follows your `USER` message. Poll every one to two seconds with a timeout; a reply usually takes a few seconds and can take longer when the workflow calls tools.\n\n- Do not select every field of every message: the API refuses a query whose complexity is above 50 fields.\n\n**From the schema:**\n\nRelay-paginated list of Sessions matching the caller's own `clientExternalId` (the same identifier passed to sdkStart), scoped to the API key's tenant. Use `first` (default 20, clamped to 1-100)/`after` for forward pagination — `pageInfo.endCursor` feeds the next page's `after`. Oldest first. Intended as a backup/re-sync path for API-key integrations that want to re-pull a specific end-customer's session history.\n\n**Allowed values:**\n\n- `SessionStatus`: `ACTIVE`, `ENDED`, `EXPIRED`, `PENDING`\n- `MessageRole`: `MODEL`, `USER`",
        "security": [{ "apiKey": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "query PollSession($externalCustomerId: String!, $first: Int) {\n  sessionsByExternalCustomerId(externalCustomerId: $externalCustomerId, first: $first) {\n    totalCount\n    edges {\n      node {\n        id\n        status\n        messages {\n          id\n          role\n          text\n          turnIndex\n          createdAt\n        }\n      }\n    }\n  }\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["PollSession"]
                  },
                  "variables": {
                    "type": "object",
                    "properties": {
                      "externalCustomerId": { "type": "string" },
                      "first": { "type": ["integer", "null"] }
                    },
                    "required": ["externalCustomerId"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "List the sessions started with one clientId, with their messages. The API-key way to poll for a reply.",
                  "value": {
                    "query": "query PollSession($externalCustomerId: String!, $first: Int) {\n  sessionsByExternalCustomerId(externalCustomerId: $externalCustomerId, first: $first) {\n    totalCount\n    edges {\n      node {\n        id\n        status\n        messages {\n          id\n          role\n          text\n          turnIndex\n          createdAt\n        }\n      }\n    }\n  }\n}\n",
                    "operationName": "PollSession",
                    "variables": {
                      "externalCustomerId": "ticket-1001",
                      "first": 1
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "sessionsByExternalCustomerId": {
                          "type": "object",
                          "properties": {
                            "totalCount": {
                              "type": "integer",
                              "description": "Number of sessions matching the filter in total, not just on this page."
                            },
                            "edges": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "node": {
                                    "type": "object",
                                    "properties": {
                                      "id": {
                                        "type": "integer",
                                        "description": "Server-generated integer id of the session."
                                      },
                                      "status": {
                                        "$ref": "#/components/schemas/SessionStatus"
                                      },
                                      "messages": {
                                        "type": "array",
                                        "items": {
                                          "type": "object",
                                          "properties": {
                                            "id": {
                                              "type": "integer",
                                              "description": "Server-generated integer id of the message."
                                            },
                                            "role": {
                                              "$ref": "#/components/schemas/MessageRole"
                                            },
                                            "text": {
                                              "type": "string",
                                              "description": "The message text. The URL of a file written into the text as an attachment marker is blanked when read back."
                                            },
                                            "turnIndex": {
                                              "type": "number",
                                              "description": "Position of the message in the session, starting at 0 (an integer, exposed as Float for historical reasons). Order by it to replay the conversation."
                                            },
                                            "createdAt": {
                                              "type": "string",
                                              "format": "date-time",
                                              "description": "When the message was stored (ISO 8601, server-set)."
                                            }
                                          },
                                          "required": [
                                            "id",
                                            "role",
                                            "text",
                                            "turnIndex",
                                            "createdAt"
                                          ],
                                          "description": "One message of a session transcript."
                                        },
                                        "description": "Full chat history for this Session, oldest first. There is no standalone chat-history query — always fetch via session(id) { messages { ... } }."
                                      }
                                    },
                                    "required": ["id", "status", "messages"],
                                    "description": "The session."
                                  }
                                },
                                "required": ["node"],
                                "description": "One session and its cursor."
                              },
                              "description": "The sessions on this page, oldest first."
                            }
                          },
                          "required": ["totalCount", "edges"],
                          "description": "A page of sessions, oldest first."
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Reply has arrived",
                    "value": {
                      "data": {
                        "sessionsByExternalCustomerId": {
                          "totalCount": 1,
                          "edges": [
                            {
                              "node": {
                                "id": 4211,
                                "status": "ACTIVE",
                                "messages": [
                                  {
                                    "id": 90311,
                                    "role": "USER",
                                    "text": "My laptop cannot connect to the office VPN since this morning.",
                                    "turnIndex": 0,
                                    "createdAt": "2026-10-12T03:15:02.114Z"
                                  },
                                  {
                                    "id": 90312,
                                    "role": "MODEL",
                                    "text": "Sorry to hear that. Which operating system is the laptop running, and do you see an error message?",
                                    "turnIndex": 1,
                                    "createdAt": "2026-10-12T03:15:05.871Z"
                                  }
                                ]
                              }
                            }
                          ]
                        }
                      }
                    }
                  },
                  "200-2": {
                    "summary": "UNAUTHENTICATED: API key missing",
                    "value": {
                      "errors": [
                        {
                          "message": "Missing X-Api-Key header",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["sessionsByExternalCustomerId"],
                          "extensions": {
                            "code": "UNAUTHENTICATED",
                            "originalError": {
                              "message": "Missing X-Api-Key header",
                              "error": "Unauthorized",
                              "statusCode": 401
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "query",
          "field": "sessionsByExternalCustomerId",
          "capability": "sessions"
        }
      }
    },
    "/graphql#session": {
      "post": {
        "operationId": "session",
        "tags": ["Sessions and messages"],
        "summary": "Read one session and its full message history by id (dashboard credential).",
        "description": "Read one session and its full message history by id (dashboard credential).\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- Takes a dashboard JWT. An API key or a session token is refused with `Unauthorized`; with an API key, poll with `sessionsByExternalCustomerId` instead.\n\n- Returns `null` (not an error) for an id that does not exist in your workspace.\n\n**From the schema:**\n\nFetches a single Session by id, scoped to the caller's tenant. This is also the ONLY way to retrieve chat history — select the nested `messages` field; there is no standalone getChatHistory/messages(sessionId) query.\n\n**Allowed values:**\n\n- `SessionStatus`: `ACTIVE`, `ENDED`, `EXPIRED`, `PENDING`\n- `MessageRole`: `MODEL`, `USER`",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "query ReadSession($id: ID!) {\n  session(id: $id) {\n    id\n    status\n    startedAt\n    endedAt\n    messages {\n      id\n      role\n      text\n      turnIndex\n    }\n  }\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["ReadSession"]
                  },
                  "variables": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": ["string", "integer"],
                        "description": "GraphQL ID: send a string or an integer."
                      }
                    },
                    "required": ["id"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "Read one session and its full message history by id (dashboard credential).",
                  "value": {
                    "query": "query ReadSession($id: ID!) {\n  session(id: $id) {\n    id\n    status\n    startedAt\n    endedAt\n    messages {\n      id\n      role\n      text\n      turnIndex\n    }\n  }\n}\n",
                    "operationName": "ReadSession",
                    "variables": { "id": "4211" }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "session": {
                          "type": ["object", "null"],
                          "properties": {
                            "id": {
                              "type": "integer",
                              "description": "Server-generated integer id of the session."
                            },
                            "status": {
                              "$ref": "#/components/schemas/SessionStatus"
                            },
                            "startedAt": {
                              "type": ["string", "null"],
                              "format": "date-time",
                              "description": "When the session started (ISO 8601)."
                            },
                            "endedAt": {
                              "type": ["string", "null"],
                              "format": "date-time",
                              "description": "When the session ended or expired (ISO 8601); null while it is ACTIVE."
                            },
                            "messages": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "id": {
                                    "type": "integer",
                                    "description": "Server-generated integer id of the message."
                                  },
                                  "role": {
                                    "$ref": "#/components/schemas/MessageRole"
                                  },
                                  "text": {
                                    "type": "string",
                                    "description": "The message text. The URL of a file written into the text as an attachment marker is blanked when read back."
                                  },
                                  "turnIndex": {
                                    "type": "number",
                                    "description": "Position of the message in the session, starting at 0 (an integer, exposed as Float for historical reasons). Order by it to replay the conversation."
                                  }
                                },
                                "required": ["id", "role", "text", "turnIndex"],
                                "description": "One message of a session transcript."
                              },
                              "description": "Full chat history for this Session, oldest first. There is no standalone chat-history query — always fetch via session(id) { messages { ... } }."
                            }
                          },
                          "required": [
                            "id",
                            "status",
                            "startedAt",
                            "endedAt",
                            "messages"
                          ],
                          "description": "One conversation between an end user and an agent, with its status, timing and transcript (messages)."
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": {
                      "data": {
                        "session": {
                          "id": 4211,
                          "status": "ENDED",
                          "startedAt": "2026-10-12T03:15:01.502Z",
                          "endedAt": "2026-10-12T03:18:40.020Z",
                          "messages": [
                            {
                              "id": 90311,
                              "role": "USER",
                              "text": "My laptop cannot connect to the office VPN since this morning.",
                              "turnIndex": 0
                            },
                            {
                              "id": 90312,
                              "role": "MODEL",
                              "text": "Sorry to hear that. Which operating system is the laptop running, and do you see an error message?",
                              "turnIndex": 1
                            }
                          ]
                        }
                      }
                    }
                  },
                  "200-2": {
                    "summary": "Not in your workspace",
                    "value": { "data": { "session": null } }
                  },
                  "200-3": {
                    "summary": "UNAUTHENTICATED: Called with an API key instead of a JWT",
                    "value": {
                      "errors": [
                        {
                          "message": "Unauthorized",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["session"],
                          "extensions": {
                            "code": "UNAUTHENTICATED",
                            "originalError": {
                              "message": "Unauthorized",
                              "statusCode": 401
                            }
                          }
                        }
                      ],
                      "data": { "session": null }
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "query",
          "field": "session",
          "capability": "sessions"
        }
      }
    },
    "/graphql#sdkEndSession": {
      "post": {
        "operationId": "sdkEndSession",
        "tags": ["Sessions and messages"],
        "summary": "End the session. The session token stops working for new messages.",
        "description": "End the session. The session token stops working for new messages.\n\n**Credential:** Session token (avatarToken). `Authorization: Bearer <avatarToken>`, returned by `sdkStart` for one session.\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- `evaluation` is generated asynchronously and only for conversations with at least two turns, so it is often `null` in this response.\n\n**From the schema:**\n\nThe <vai-avatar> SDK's equivalent of endSession — takes the avatarToken from sdkStart rather than a JWT. Ends the session (status ENDED) and, unlike the dashboard's endSession, evaluates the conversation BEFORE it returns: the result carries the evaluation directly (null when the conversation has fewer than two messages or the evaluation failed). Safe to repeat: calling it again on an ended session does not fail and returns the stored evaluation when one exists.",
        "security": [{ "sessionToken": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "mutation EndSession($input: SdkEndSessionInput!) {\n  sdkEndSession(input: $input) {\n    sessionId\n    durationSeconds\n    evaluation {\n      score\n      summary\n    }\n  }\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["EndSession"]
                  },
                  "variables": {
                    "type": "object",
                    "properties": {
                      "input": {
                        "$ref": "#/components/schemas/SdkEndSessionInput"
                      }
                    },
                    "required": ["input"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "End the session. The session token stops working for new messages.",
                  "value": {
                    "query": "mutation EndSession($input: SdkEndSessionInput!) {\n  sdkEndSession(input: $input) {\n    sessionId\n    durationSeconds\n    evaluation {\n      score\n      summary\n    }\n  }\n}\n",
                    "operationName": "EndSession",
                    "variables": { "input": { "sessionId": 4211 } }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "sdkEndSession": {
                          "type": "object",
                          "properties": {
                            "sessionId": {
                              "type": "integer",
                              "description": "Id of the session that was ended."
                            },
                            "durationSeconds": {
                              "type": "integer",
                              "description": "Whole seconds between the session start and its end (rounded). Calling sdkEndSession again on an ended session returns the same value."
                            },
                            "evaluation": {
                              "type": ["object", "null"],
                              "properties": {
                                "score": {
                                  "type": "integer",
                                  "description": "Overall score from 0 to 100, as rated by the evaluator model (what it measures depends on the agent useCase, for example customer satisfaction for SUPPORT)."
                                },
                                "summary": {
                                  "type": "string",
                                  "description": "Two or three sentence summary of the conversation."
                                }
                              },
                              "required": ["score", "summary"],
                              "description": "The evaluation, computed before this call returns. null when the conversation has fewer than two messages or the evaluation failed (the session is still ended). A repeated call returns the stored evaluation when one exists."
                            }
                          },
                          "required": [
                            "sessionId",
                            "durationSeconds",
                            "evaluation"
                          ],
                          "description": "Result of sdkEndSession."
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": {
                      "data": {
                        "sdkEndSession": {
                          "sessionId": 4211,
                          "durationSeconds": 219,
                          "evaluation": null
                        }
                      }
                    }
                  },
                  "200-2": {
                    "summary": "FORBIDDEN: Token for a different session",
                    "value": {
                      "errors": [
                        {
                          "message": "This avatar token is not valid for the requested session",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["sdkEndSession"],
                          "extensions": {
                            "code": "FORBIDDEN",
                            "originalError": {
                              "message": "This avatar token is not valid for the requested session",
                              "error": "Forbidden",
                              "statusCode": 403
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "mutation",
          "field": "sdkEndSession",
          "capability": "sessions"
        }
      }
    },
    "/graphql#evaluation": {
      "post": {
        "operationId": "evaluation",
        "tags": ["Sessions and messages"],
        "summary": "Read the scored evaluation of an ended session (score, recommendation, summary, strengths, weaknesses).",
        "description": "Read the scored evaluation of an ended session (score, recommendation, summary, strengths, weaknesses).\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- Returns `null` until the session has ended and the background evaluation has finished, and stays `null` for a session that was not evaluated (fewer than two turns, or evaluation not enabled for it). Poll with a back-off after `sdkEndSession`.\n\n- `score` is 0 to 100. For a support agent `recommendation` is one of `resolved`, `escalate` or `follow-up` (`n/a` when none fits); an agent with its own evaluation labels returns those.\n\n- `transcript` is also available on this type; it holds the whole conversation, so select it only if you need it.\n\n**From the schema:**\n\nFetches the async Evaluation (sentiment/outcome/score) for a Session, scoped to the caller's tenant. Returns null until the Session has ended AND the background evaluation job has completed — poll after calling endSession, don't expect it immediately.",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "query SessionEvaluation($sessionId: Int!) {\n  evaluation(sessionId: $sessionId) {\n    id\n    sessionId\n    score\n    recommendation\n    summary\n    strengths\n    weaknesses\n    generatedAt\n  }\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["SessionEvaluation"]
                  },
                  "variables": {
                    "type": "object",
                    "properties": { "sessionId": { "type": "integer" } },
                    "required": ["sessionId"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "Read the scored evaluation of an ended session (score, recommendation, summary, strengths, weaknesses).",
                  "value": {
                    "query": "query SessionEvaluation($sessionId: Int!) {\n  evaluation(sessionId: $sessionId) {\n    id\n    sessionId\n    score\n    recommendation\n    summary\n    strengths\n    weaknesses\n    generatedAt\n  }\n}\n",
                    "operationName": "SessionEvaluation",
                    "variables": { "sessionId": 4211 }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "evaluation": {
                          "type": ["object", "null"],
                          "properties": {
                            "id": { "type": "integer" },
                            "sessionId": { "type": "integer" },
                            "score": { "type": "integer" },
                            "recommendation": { "type": "string" },
                            "summary": { "type": "string" },
                            "strengths": {
                              "type": "array",
                              "items": { "type": "string" }
                            },
                            "weaknesses": {
                              "type": "array",
                              "items": { "type": "string" }
                            },
                            "generatedAt": {
                              "type": ["string", "null"],
                              "format": "date-time"
                            }
                          },
                          "required": [
                            "id",
                            "sessionId",
                            "score",
                            "recommendation",
                            "summary",
                            "strengths",
                            "weaknesses",
                            "generatedAt"
                          ]
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": {
                      "data": {
                        "evaluation": {
                          "id": 1532,
                          "sessionId": 4211,
                          "score": 82,
                          "recommendation": "resolved",
                          "summary": "The user could not connect to the VPN. The agent identified an expired client certificate and walked the user through renewing it; the user confirmed it works.",
                          "strengths": [
                            "Asked for the operating system and error before suggesting a fix"
                          ],
                          "weaknesses": [
                            "Did not offer to open a ticket in case the fix failed"
                          ],
                          "generatedAt": "2026-10-12T03:18:52.114Z"
                        }
                      }
                    }
                  },
                  "200-2": {
                    "summary": "Not evaluated yet (or never)",
                    "value": { "data": { "evaluation": null } }
                  },
                  "200-3": {
                    "summary": "UNAUTHENTICATED: Called with an API key instead of a JWT",
                    "value": {
                      "errors": [
                        {
                          "message": "Unauthorized",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["evaluation"],
                          "extensions": {
                            "code": "UNAUTHENTICATED",
                            "originalError": {
                              "message": "Unauthorized",
                              "statusCode": 401
                            }
                          }
                        }
                      ],
                      "data": { "evaluation": null }
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "query",
          "field": "evaluation",
          "capability": "sessions"
        }
      }
    },
    "/graphql#refreshAvatarToken": {
      "post": {
        "operationId": "refreshAvatarToken",
        "tags": ["Sessions and messages"],
        "summary": "Exchange a session token that is about to expire (or expired in the last 10 minutes) for a fresh one.",
        "description": "Exchange a session token that is about to expire (or expired in the last 10 minutes) for a fresh one.\n\n**Credential:** No credential header. The credential travels inside the input (see the operation).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- A session token lives about one hour. The token goes in `input.avatarToken`, not in an Authorization header, because an expired token must be accepted here.\n\n- The session must still be active. Limited to 10 calls per minute per client IP.\n\n- An open `sessionEvents` WebSocket keeps the old token; close it and reopen it with the new one.\n\n**From the schema:**\n\nExchanges an avatarToken that is about to expire — or expired within the last AVATAR_TOKEN_REFRESH_GRACE_MS (default 10 minutes) — for a fresh one, so a conversation can outlive the ~1h AVATAR_TOKEN_TTL_MS without restarting the session. Pass the CURRENT token as input.avatarToken; it is not a Bearer header here, because this is the one avatarToken-authenticated operation that must accept an already-expired token (AvatarTokenGuard rejects one outright). The token's HMAC signature is still verified in full — only the expiry bound is relaxed, and only within the grace window — and the session it is bound to must still exist and be ACTIVE. The replacement carries the same sessionId, tenant, and avatar backend config; it never widens a token's authority. IMPORTANT for clients: a refreshed token CANNOT be swapped into a live sessionEvents WebSocket, because graphql-ws reads connectionParams only once at connection open — close and reopen the subscription with the new token. Plain HTTP mutations (sdkSendMessage, sdkEndSession) use it immediately on the next call. Rate-limited per client IP.",
        "security": [],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "mutation RefreshToken($input: RefreshAvatarTokenInput!) {\n  refreshAvatarToken(input: $input) {\n    sessionId\n    avatarToken\n    expiresInMs\n  }\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["RefreshToken"]
                  },
                  "variables": {
                    "type": "object",
                    "properties": {
                      "input": {
                        "$ref": "#/components/schemas/RefreshAvatarTokenInput"
                      }
                    },
                    "required": ["input"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "Exchange a session token that is about to expire (or expired in the last 10 minutes) for a fresh one.",
                  "value": {
                    "query": "mutation RefreshToken($input: RefreshAvatarTokenInput!) {\n  refreshAvatarToken(input: $input) {\n    sessionId\n    avatarToken\n    expiresInMs\n  }\n}\n",
                    "operationName": "RefreshToken",
                    "variables": {
                      "input": {
                        "avatarToken": "example-avatar-token-not-real"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "refreshAvatarToken": {
                          "type": "object",
                          "properties": {
                            "sessionId": {
                              "type": "integer",
                              "description": "Id of the session the token is bound to, taken from the old token itself (never from input)."
                            },
                            "avatarToken": {
                              "type": "string",
                              "description": "The new avatarToken for the same session. Use it on the next call; reopen any sessionEvents subscription with it."
                            },
                            "expiresInMs": {
                              "type": "integer",
                              "description": "Lifetime of the new token in milliseconds (3600000, one hour, by default); schedule the next refresh before it runs out."
                            }
                          },
                          "required": [
                            "sessionId",
                            "avatarToken",
                            "expiresInMs"
                          ],
                          "description": "A renewed session credential."
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": {
                      "data": {
                        "refreshAvatarToken": {
                          "sessionId": 4211,
                          "avatarToken": "example-refreshed-token-not-real",
                          "expiresInMs": 3600000
                        }
                      }
                    }
                  },
                  "200-2": {
                    "summary": "UNAUTHENTICATED: Expired beyond the grace window, or invalid",
                    "value": {
                      "errors": [
                        {
                          "message": "Invalid or expired avatar token",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["refreshAvatarToken"],
                          "extensions": {
                            "code": "UNAUTHENTICATED",
                            "originalError": {
                              "message": "Invalid or expired avatar token",
                              "error": "Unauthorized",
                              "statusCode": 401
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  },
                  "200-3": {
                    "summary": "BAD_REQUEST: Session already ended",
                    "value": {
                      "errors": [
                        {
                          "message": "Session 4211 is not active (status: ended)",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["refreshAvatarToken"],
                          "extensions": {
                            "code": "BAD_REQUEST",
                            "originalError": {
                              "message": "Session 4211 is not active (status: ended)",
                              "error": "Bad Request",
                              "statusCode": 400
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "mutation",
          "field": "refreshAvatarToken",
          "capability": "sessions"
        }
      }
    },
    "/graphql#agents": {
      "post": {
        "operationId": "agents",
        "tags": ["Agents"],
        "summary": "List every agent in your workspace (a plain list, not paginated).",
        "description": "List every agent in your workspace (a plain list, not paginated).\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n**From the schema:**\n\nLists every Agent belonging to the caller's tenant. Returns a plain array, not a Relay connection. While the workspace has member-built drafts on, a member sees only released (approved), own and author-less agents.\n\n**Allowed values:**\n\n- `AgentStatus`: `ACTIVE`, `INACTIVE`\n- `AgentUseCase`: `CUSTOM`, `EDUCATION`, `INTERVIEW`, `ONBOARDING`, `SALES`, `SUPPORT`",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "query ListAgents {\n  agents {\n    id\n    name\n    status\n    useCase\n    workflowId\n  }\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["ListAgents"]
                  },
                  "variables": { "type": "object", "properties": {} }
                }
              },
              "examples": {
                "default": {
                  "summary": "List every agent in your workspace (a plain list, not paginated).",
                  "value": {
                    "query": "query ListAgents {\n  agents {\n    id\n    name\n    status\n    useCase\n    workflowId\n  }\n}\n",
                    "operationName": "ListAgents",
                    "variables": {}
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "agents": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "integer",
                                "description": "Server-generated integer id of the agent."
                              },
                              "name": {
                                "type": "string",
                                "description": "Display name of the agent (at most 100 characters)."
                              },
                              "status": {
                                "$ref": "#/components/schemas/AgentStatus"
                              },
                              "useCase": {
                                "$ref": "#/components/schemas/AgentUseCase"
                              },
                              "workflowId": {
                                "type": ["integer", "null"],
                                "description": "Id of the workflow that drives this agent, set with updateAgent. null means no workflow: each turn is answered directly by the model using personaPrompt."
                              }
                            },
                            "required": [
                              "id",
                              "name",
                              "status",
                              "useCase",
                              "workflowId"
                            ],
                            "description": "An AI agent of your workspace: its persona, the workflow that drives it, and its voice and avatar settings."
                          }
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": {
                      "data": {
                        "agents": [
                          {
                            "id": 7,
                            "name": "Helpdesk triage",
                            "status": "ACTIVE",
                            "useCase": "SUPPORT",
                            "workflowId": 12
                          },
                          {
                            "id": 9,
                            "name": "Onboarding guide",
                            "status": "INACTIVE",
                            "useCase": "ONBOARDING",
                            "workflowId": null
                          }
                        ]
                      }
                    }
                  },
                  "200-2": {
                    "summary": "UNAUTHENTICATED: Called with an API key instead of a JWT",
                    "value": {
                      "errors": [
                        {
                          "message": "Unauthorized",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["agents"],
                          "extensions": {
                            "code": "UNAUTHENTICATED",
                            "originalError": {
                              "message": "Unauthorized",
                              "statusCode": 401
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "query",
          "field": "agents",
          "capability": "agents"
        }
      }
    },
    "/graphql#agent": {
      "post": {
        "operationId": "agent",
        "tags": ["Agents"],
        "summary": "Read one agent by id.",
        "description": "Read one agent by id.\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- Returns `null` (not an error) when the id does not exist in your workspace.\n\n**From the schema:**\n\nFetches a single Agent (persona, prompt, voice, avatar config) by id, scoped to the caller's tenant. Returns null if not found or not owned by this tenant, and for a member who may not see it while member-built drafts are on (same as a missing one).\n\n**Allowed values:**\n\n- `AgentStatus`: `ACTIVE`, `INACTIVE`\n- `AgentUseCase`: `CUSTOM`, `EDUCATION`, `INTERVIEW`, `ONBOARDING`, `SALES`, `SUPPORT`\n- `VoiceTier`: `CHIRP3_HD`, `NEURAL2`, `STANDARD`, `STUDIO`, `WAVENET`",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "query OneAgent($id: ID!) {\n  agent(id: $id) {\n    id\n    name\n    position\n    personaPrompt\n    openingGreeting\n    status\n    useCase\n    voiceTier\n    timezone\n    workflowId\n  }\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["OneAgent"]
                  },
                  "variables": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": ["string", "integer"],
                        "description": "GraphQL ID: send a string or an integer."
                      }
                    },
                    "required": ["id"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "Read one agent by id.",
                  "value": {
                    "query": "query OneAgent($id: ID!) {\n  agent(id: $id) {\n    id\n    name\n    position\n    personaPrompt\n    openingGreeting\n    status\n    useCase\n    voiceTier\n    timezone\n    workflowId\n  }\n}\n",
                    "operationName": "OneAgent",
                    "variables": { "id": "7" }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "agent": {
                          "type": ["object", "null"],
                          "properties": {
                            "id": {
                              "type": "integer",
                              "description": "Server-generated integer id of the agent."
                            },
                            "name": {
                              "type": "string",
                              "description": "Display name of the agent (at most 100 characters)."
                            },
                            "position": {
                              "type": "string",
                              "description": "Role title of the agent, for example \"Support Specialist\" (at most 200 characters). Also the evaluation subject when subject is not set."
                            },
                            "personaPrompt": {
                              "type": "string",
                              "description": "The agent's system prompt: identity, tone and boundaries (at most 10000 characters)."
                            },
                            "openingGreeting": {
                              "type": ["string", "null"],
                              "description": "Fixed opening line, a template that may use {{clientExternalId}} (at most 2000 characters). Sent when a session starts on an agent without a workflow; null means the model writes a greeting instead."
                            },
                            "status": {
                              "$ref": "#/components/schemas/AgentStatus"
                            },
                            "useCase": {
                              "$ref": "#/components/schemas/AgentUseCase"
                            },
                            "voiceTier": {
                              "$ref": "#/components/schemas/VoiceTier"
                            },
                            "timezone": {
                              "type": "string",
                              "description": "IANA timezone of this agent (e.g. \"Asia/Kuala_Lumpur\"). Used by the {{today}} and {{now}} workflow template helpers so dates resolve in the agent's local day. Defaults to Asia/Kuala_Lumpur."
                            },
                            "workflowId": {
                              "type": ["integer", "null"],
                              "description": "Id of the workflow that drives this agent, set with updateAgent. null means no workflow: each turn is answered directly by the model using personaPrompt."
                            }
                          },
                          "required": [
                            "id",
                            "name",
                            "position",
                            "personaPrompt",
                            "openingGreeting",
                            "status",
                            "useCase",
                            "voiceTier",
                            "timezone",
                            "workflowId"
                          ],
                          "description": "An AI agent of your workspace: its persona, the workflow that drives it, and its voice and avatar settings."
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": {
                      "data": {
                        "agent": {
                          "id": 7,
                          "name": "Helpdesk triage",
                          "position": "IT helpdesk assistant",
                          "personaPrompt": "You are a calm, precise IT helpdesk assistant. Ask one question at a time and never promise a fix you cannot make.",
                          "openingGreeting": "Hi, I can log an IT ticket for you. What is the problem?",
                          "status": "ACTIVE",
                          "useCase": "SUPPORT",
                          "voiceTier": "STANDARD",
                          "timezone": "Asia/Kuala_Lumpur",
                          "workflowId": 12
                        }
                      }
                    }
                  },
                  "200-2": {
                    "summary": "Not in your workspace",
                    "value": { "data": { "agent": null } }
                  },
                  "200-3": {
                    "summary": "UNAUTHENTICATED: Called with an API key instead of a JWT",
                    "value": {
                      "errors": [
                        {
                          "message": "Unauthorized",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["agent"],
                          "extensions": {
                            "code": "UNAUTHENTICATED",
                            "originalError": {
                              "message": "Unauthorized",
                              "statusCode": 401
                            }
                          }
                        }
                      ],
                      "data": { "agent": null }
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "query",
          "field": "agent",
          "capability": "agents"
        }
      }
    },
    "/graphql#createAgent": {
      "post": {
        "operationId": "createAgent",
        "tags": ["Agents"],
        "summary": "Create an agent. It has no workflow until you attach one with updateAgent.",
        "description": "Create an agent. It has no workflow until you attach one with updateAgent.\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- [object Object]\n\n- `useCase: CUSTOM` also needs `evaluationPromptTemplate` (with a `{transcript}` placeholder) and `evaluationRecommendationLabels`.\n\n- `openingGreeting` is a fixed first message; without it the opening line is generated by a model on every session.\n\n**From the schema:**\n\nCreates a new Agent (persona) for the caller's tenant. While the workspace has member-built drafts on, a member's agent starts INACTIVE (the owner releases it and switches it on) and a member cannot set workflowId or external sync fields (BAD_REQUEST). Does not create a Session — call startSession with the returned id to begin a conversation.\n\n**Allowed values:**\n\n- `AvatarBackendType`: `CLIENT_3D`, `HOSTED_API`, `SELF_HOSTED_VIDEO_STREAM`\n- `AvatarGenderCode`: `F`, `M`\n- `LlmProviderType`: `BEDROCK_MANTLE`, `GEMINI_DIRECT` (plus 1 internal value, not listed)\n- `AgentUseCase`: `CUSTOM`, `EDUCATION`, `INTERVIEW`, `ONBOARDING`, `SALES`, `SUPPORT`\n- `VoiceTier`: `CHIRP3_HD`, `NEURAL2`, `STANDARD`, `STUDIO`, `WAVENET`\n- `AgentStatus`: `ACTIVE`, `INACTIVE`",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "mutation CreateAgent($input: CreateAgentInput!) {\n  createAgent(input: $input) {\n    id\n    name\n    status\n    workflowId\n  }\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["CreateAgent"]
                  },
                  "variables": {
                    "type": "object",
                    "properties": {
                      "input": {
                        "$ref": "#/components/schemas/CreateAgentInput"
                      }
                    },
                    "required": ["input"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "Create an agent. It has no workflow until you attach one with updateAgent.",
                  "value": {
                    "query": "mutation CreateAgent($input: CreateAgentInput!) {\n  createAgent(input: $input) {\n    id\n    name\n    status\n    workflowId\n  }\n}\n",
                    "operationName": "CreateAgent",
                    "variables": {
                      "input": {
                        "name": "Helpdesk triage",
                        "position": "IT helpdesk assistant",
                        "personaPrompt": "You are a calm, precise IT helpdesk assistant. Ask one question at a time and never promise a fix you cannot make.",
                        "openingGreeting": "Hi, I can log an IT ticket for you. What is the problem?",
                        "useCase": "SUPPORT",
                        "voiceTier": "STANDARD",
                        "avatarBackend": "CLIENT_3D"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "createAgent": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "integer",
                              "description": "Server-generated integer id of the agent."
                            },
                            "name": {
                              "type": "string",
                              "description": "Display name of the agent (at most 100 characters)."
                            },
                            "status": {
                              "$ref": "#/components/schemas/AgentStatus"
                            },
                            "workflowId": {
                              "type": ["integer", "null"],
                              "description": "Id of the workflow that drives this agent, set with updateAgent. null means no workflow: each turn is answered directly by the model using personaPrompt."
                            }
                          },
                          "required": ["id", "name", "status", "workflowId"],
                          "description": "An AI agent of your workspace: its persona, the workflow that drives it, and its voice and avatar settings."
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": {
                      "data": {
                        "createAgent": {
                          "id": 7,
                          "name": "Helpdesk triage",
                          "status": "ACTIVE",
                          "workflowId": null
                        }
                      }
                    }
                  },
                  "200-2": {
                    "summary": "BAD_REQUEST: CUSTOM use case without an evaluation prompt",
                    "value": {
                      "errors": [
                        {
                          "message": "CUSTOM use case requires evaluationPromptTemplate",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["createAgent"],
                          "extensions": {
                            "code": "BAD_REQUEST",
                            "originalError": {
                              "message": "CUSTOM use case requires evaluationPromptTemplate",
                              "error": "Bad Request",
                              "statusCode": 400
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request itself was refused before running: variables that do not match their types (`BAD_USER_INPUT`) or an invalid document (`GRAPHQL_VALIDATION_FAILED`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "400-1": {
                    "summary": "BAD_USER_INPUT: A required field is missing",
                    "value": {
                      "errors": [
                        {
                          "message": "Variable \"$input\" got invalid value { name: \"Helpdesk triage\", personaPrompt: \"You are a calm, precise IT helpdesk assistant.\", useCase: \"SUPPORT\", voiceTier: \"STANDARD\", avatarBackend: \"CLIENT_3D\" }; Field \"position\" of required type \"String!\" was not provided.",
                          "locations": [{ "line": 1, "column": 22 }],
                          "extensions": { "code": "BAD_USER_INPUT" }
                        }
                      ]
                    }
                  },
                  "400-2": {
                    "summary": "BAD_USER_INPUT: Unknown enum value",
                    "value": {
                      "errors": [
                        {
                          "message": "Variable \"$input\" got invalid value \"HELPDESK\" at \"input.useCase\"; Value \"HELPDESK\" does not exist in \"AgentUseCase\" enum.",
                          "locations": [{ "line": 1, "column": 22 }],
                          "extensions": { "code": "BAD_USER_INPUT" }
                        }
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "mutation",
          "field": "createAgent",
          "capability": "agents"
        }
      }
    },
    "/graphql#updateAgent": {
      "post": {
        "operationId": "updateAgent",
        "tags": ["Agents"],
        "summary": "Change an agent. Send only the fields that change; attach a workflow with workflowId.",
        "description": "Change an agent. Send only the fields that change; attach a workflow with workflowId.\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- `id` is a separate argument, not a field of `input`.\n\n- The workflow you attach must belong to your workspace. Sessions run its **published** version, so publish it first (see Workflows).\n\n- Set `status` to `INACTIVE` to stop new tasks from running against the agent.\n\n**From the schema:**\n\n`id` is a separate argument, not a field inside `input`. Every field on UpdateAgentInput is optional — send only what is changing. While the workspace has member-built drafts on, a member can change only their own agent that is not pending or approved (anything else answers NOT_FOUND) and cannot set status ACTIVE, workflowId or external sync fields (BAD_REQUEST); owners are never restricted.\n\n**Allowed values:**\n\n- `AvatarBackendType`: `CLIENT_3D`, `HOSTED_API`, `SELF_HOSTED_VIDEO_STREAM`\n- `AvatarGenderCode`: `F`, `M`\n- `LlmProviderType`: `BEDROCK_MANTLE`, `GEMINI_DIRECT` (plus 1 internal value, not listed)\n- `AgentStatus`: `ACTIVE`, `INACTIVE`\n- `AgentUseCase`: `CUSTOM`, `EDUCATION`, `INTERVIEW`, `ONBOARDING`, `SALES`, `SUPPORT`\n- `VoiceTier`: `CHIRP3_HD`, `NEURAL2`, `STANDARD`, `STUDIO`, `WAVENET`",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "mutation UpdateAgent($id: ID!, $input: UpdateAgentInput!) {\n  updateAgent(id: $id, input: $input) {\n    id\n    workflowId\n    status\n    updatedAt\n  }\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["UpdateAgent"]
                  },
                  "variables": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": ["string", "integer"],
                        "description": "GraphQL ID: send a string or an integer."
                      },
                      "input": {
                        "$ref": "#/components/schemas/UpdateAgentInput"
                      }
                    },
                    "required": ["id", "input"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "Change an agent. Send only the fields that change; attach a workflow with workflowId.",
                  "value": {
                    "query": "mutation UpdateAgent($id: ID!, $input: UpdateAgentInput!) {\n  updateAgent(id: $id, input: $input) {\n    id\n    workflowId\n    status\n    updatedAt\n  }\n}\n",
                    "operationName": "UpdateAgent",
                    "variables": { "id": "7", "input": { "workflowId": 12 } }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "updateAgent": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "integer",
                              "description": "Server-generated integer id of the agent."
                            },
                            "workflowId": {
                              "type": ["integer", "null"],
                              "description": "Id of the workflow that drives this agent, set with updateAgent. null means no workflow: each turn is answered directly by the model using personaPrompt."
                            },
                            "status": {
                              "$ref": "#/components/schemas/AgentStatus"
                            },
                            "updatedAt": {
                              "type": "string",
                              "format": "date-time",
                              "description": "When the agent was last changed (ISO 8601, server-set)."
                            }
                          },
                          "required": [
                            "id",
                            "workflowId",
                            "status",
                            "updatedAt"
                          ],
                          "description": "An AI agent of your workspace: its persona, the workflow that drives it, and its voice and avatar settings."
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": {
                      "data": {
                        "updateAgent": {
                          "id": 7,
                          "workflowId": 12,
                          "status": "ACTIVE",
                          "updatedAt": "2026-10-12T04:02:19.311Z"
                        }
                      }
                    }
                  },
                  "200-2": {
                    "summary": "NOT_FOUND: Agent not in your workspace",
                    "value": {
                      "errors": [
                        {
                          "message": "Agent 7 not found",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["updateAgent"],
                          "extensions": {
                            "code": "NOT_FOUND",
                            "status": 404,
                            "originalError": {
                              "message": "Agent 7 not found",
                              "error": "Not Found",
                              "statusCode": 404
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  },
                  "200-3": {
                    "summary": "BAD_REQUEST: Workflow not in your workspace",
                    "value": {
                      "errors": [
                        {
                          "message": "Workflow 12 not found for this tenant",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["updateAgent"],
                          "extensions": {
                            "code": "BAD_REQUEST",
                            "originalError": {
                              "message": "Workflow 12 not found for this tenant",
                              "error": "Bad Request",
                              "statusCode": 400
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "mutation",
          "field": "updateAgent",
          "capability": "agents"
        }
      }
    },
    "/graphql#createWorkflow": {
      "post": {
        "operationId": "createWorkflow",
        "tags": ["Workflows"],
        "summary": "Create an empty workflow (name and description only).",
        "description": "Create an empty workflow (name and description only).\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- `CreateWorkflowInput` has no `nodes` or `edges`: add the graph with `updateWorkflow`.\n\n**From the schema:**\n\nCreates a new, empty Workflow with just a name/description — CreateWorkflowInput has NO nodes/edges fields. Add the graph itself in a second call to updateWorkflow using the id returned here. While the workspace has member-built drafts on, a member can build and test the draft freely but cannot publish it until the owner releases it.",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "mutation CreateWorkflow($input: CreateWorkflowInput!) {\n  createWorkflow(input: $input) {\n    id\n    name\n    version\n    isPublished\n  }\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["CreateWorkflow"]
                  },
                  "variables": {
                    "type": "object",
                    "properties": {
                      "input": {
                        "$ref": "#/components/schemas/CreateWorkflowInput"
                      }
                    },
                    "required": ["input"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "Create an empty workflow (name and description only).",
                  "value": {
                    "query": "mutation CreateWorkflow($input: CreateWorkflowInput!) {\n  createWorkflow(input: $input) {\n    id\n    name\n    version\n    isPublished\n  }\n}\n",
                    "operationName": "CreateWorkflow",
                    "variables": {
                      "input": {
                        "name": "Ticket routing",
                        "description": "Classifies a ticket as L1, L2 or L3 and tells the user where it goes."
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "createWorkflow": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "integer",
                              "description": "Server-generated integer id of the workflow."
                            },
                            "name": {
                              "type": "string",
                              "description": "Display name (at most 120 characters)."
                            },
                            "version": {
                              "type": "integer",
                              "description": "Row version, starting at 1 and increased by every save of the workflow (each updateWorkflow and each publishWorkflow); a change counter, not a count of publishes."
                            },
                            "isPublished": {
                              "type": "boolean",
                              "description": "True once publishWorkflow has succeeded at least once. Editing the draft afterwards does not change it; only an unpublished workflow runs nothing for its agent."
                            }
                          },
                          "required": ["id", "name", "version", "isPublished"],
                          "description": "A workflow: the graph of nodes and edges that drives an agent. nodes and edges are the editable DRAFT; sessions run the version last published with publishWorkflow."
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": {
                      "data": {
                        "createWorkflow": {
                          "id": 12,
                          "name": "Ticket routing",
                          "version": 1,
                          "isPublished": false
                        }
                      }
                    }
                  },
                  "200-2": {
                    "summary": "UNAUTHENTICATED: Called with an API key instead of a JWT",
                    "value": {
                      "errors": [
                        {
                          "message": "Unauthorized",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["createWorkflow"],
                          "extensions": {
                            "code": "UNAUTHENTICATED",
                            "originalError": {
                              "message": "Unauthorized",
                              "statusCode": 401
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "mutation",
          "field": "createWorkflow",
          "capability": "workflows"
        }
      }
    },
    "/graphql#updateWorkflow": {
      "post": {
        "operationId": "updateWorkflow",
        "tags": ["Workflows"],
        "summary": "Save the workflow's draft graph (nodes and edges) and optionally its name.",
        "description": "Save the workflow's draft graph (nodes and edges) and optionally its name.\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- `nodes` and `edges` are JSON: always send them through variables, never inline in the query.\n\n- The draft is what you edit; sessions keep running the last published version until you call `publishWorkflow`.\n\n- A node config the platform cannot accept (a missing field, an out-of-range number, an unknown value) is refused with `BAD_USER_INPUT` and a message naming the node and the field. A draft may still be structurally incomplete; `publishWorkflow` checks the whole graph.\n\n- `input.settings.failureResponse` sets plain text (1 to 500 characters) sent instead of the standard error reply when a run fails; `null` clears it. Effective on save, not on publish.\n\n**From the schema:**\n\nUpdates a Workflow, including its nodes/edges graph. nodes/edges are GraphQLJSON scalars — always send them via a `variables` block, never as an inline literal in the query string (inline JSON objects are not valid GraphQL syntax and fail with a parse error). While the workspace has member-built drafts on, a member can change only their own workflow that is not pending, approved or published (anything else answers NOT_FOUND); owners are never restricted.\n\n**Allowed values:**\n\n- `WorkflowFailureNotify`: `NONE`, `OWNERS`",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "mutation SaveGraph($input: UpdateWorkflowInput!) {\n  updateWorkflow(input: $input) {\n    id\n    version\n    isPublished\n    updatedAt\n  }\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["SaveGraph"]
                  },
                  "variables": {
                    "type": "object",
                    "properties": {
                      "input": {
                        "$ref": "#/components/schemas/UpdateWorkflowInput"
                      }
                    },
                    "required": ["input"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "Save the workflow's draft graph (nodes and edges) and optionally its name.",
                  "value": {
                    "query": "mutation SaveGraph($input: UpdateWorkflowInput!) {\n  updateWorkflow(input: $input) {\n    id\n    version\n    isPublished\n    updatedAt\n  }\n}\n",
                    "operationName": "SaveGraph",
                    "variables": {
                      "input": {
                        "id": 12,
                        "nodes": [
                          {
                            "id": "start",
                            "type": "start",
                            "label": "Start",
                            "config": {},
                            "position": { "x": 0, "y": 0 }
                          },
                          {
                            "id": "classify",
                            "type": "llm",
                            "label": "Classify level",
                            "config": {
                              "promptTemplate": "You route IT support tickets. Read the ticket and answer with one word: L1 for password resets, account unlocks and how-to questions; L2 for a broken device, application or connection that needs a technician; L3 for an outage, a security incident or anything affecting many people.\n\nTicket: \"{{userMessage}}\"",
                              "outputVar": "level",
                              "matchAnyOf": ["L1", "L2", "L3"],
                              "streamOutput": false
                            },
                            "position": { "x": 0, "y": 100 }
                          },
                          {
                            "id": "reply",
                            "type": "response",
                            "label": "Tell the user",
                            "config": {
                              "messageTemplate": "Thanks. Your ticket goes to the {{level}} queue; we will reply here."
                            },
                            "position": { "x": 0, "y": 200 }
                          }
                        ],
                        "edges": [
                          {
                            "id": "e1",
                            "source": "start",
                            "target": "classify"
                          },
                          {
                            "id": "e2",
                            "source": "classify",
                            "target": "reply"
                          }
                        ]
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "updateWorkflow": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "integer",
                              "description": "Server-generated integer id of the workflow."
                            },
                            "version": {
                              "type": "integer",
                              "description": "Row version, starting at 1 and increased by every save of the workflow (each updateWorkflow and each publishWorkflow); a change counter, not a count of publishes."
                            },
                            "isPublished": {
                              "type": "boolean",
                              "description": "True once publishWorkflow has succeeded at least once. Editing the draft afterwards does not change it; only an unpublished workflow runs nothing for its agent."
                            },
                            "updatedAt": {
                              "type": "string",
                              "format": "date-time",
                              "description": "When the workflow was last saved or published (ISO 8601, server-set)."
                            }
                          },
                          "required": [
                            "id",
                            "version",
                            "isPublished",
                            "updatedAt"
                          ],
                          "description": "A workflow: the graph of nodes and edges that drives an agent. nodes and edges are the editable DRAFT; sessions run the version last published with publishWorkflow."
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": {
                      "data": {
                        "updateWorkflow": {
                          "id": 12,
                          "version": 1,
                          "isPublished": false,
                          "updatedAt": "2026-10-12T04:05:41.020Z"
                        }
                      }
                    }
                  },
                  "200-2": {
                    "summary": "NOT_FOUND: Workflow not in your workspace",
                    "value": {
                      "errors": [
                        {
                          "message": "Workflow 12 not found",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["updateWorkflow"],
                          "extensions": {
                            "code": "NOT_FOUND",
                            "status": 404,
                            "originalError": {
                              "message": "Workflow 12 not found",
                              "error": "Not Found",
                              "statusCode": 404
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request itself was refused before running: variables that do not match their types (`BAD_USER_INPUT`) or an invalid document (`GRAPHQL_VALIDATION_FAILED`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "400-1": {
                    "summary": "BAD_USER_INPUT: name sent as a number",
                    "value": {
                      "errors": [
                        {
                          "message": "Variable \"$input\" got invalid value 42 at \"input.name\"; String cannot represent a non string value: 42",
                          "locations": [{ "line": 1, "column": 20 }],
                          "extensions": { "code": "BAD_USER_INPUT" }
                        }
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "mutation",
          "field": "updateWorkflow",
          "capability": "workflows"
        }
      }
    },
    "/graphql#workflowDiagnostics": {
      "post": {
        "operationId": "workflowDiagnostics",
        "tags": ["Workflows"],
        "summary": "Lint a workflow graph. Lists findings that will probably misbehave at run time; never blocks.",
        "description": "Lint a workflow graph. Lists findings that will probably misbehave at run time; never blocks.\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- `published: false` checks the draft (what `updateWorkflow` wrote); `published: true` checks the version sessions run.\n\n- Switch on `code`, not on `message`. An empty list means nothing was found. Publishing has its own blocking checks.\n\n**From the schema:**\n\nNon-blocking checks (\"lints\") for one Workflow graph: things that will probably not behave as the author meant at run time, each naming the node and what to do. Read-only, and it NEVER blocks saving or publishing (publishWorkflow has its own blocking errors). Checks the DRAFT graph (the one updateWorkflow writes) by default; pass published: true to check the published snapshot real sessions run instead (a 404 if the workflow was never published). An empty list means nothing was found. `code` is a stable identifier a client can switch on; new codes may be added later. Codes: FAN_IN_WITHOUT_JOIN (warning), UNREACHABLE_NODE (warning), ROUTER_NO_DEFAULT (warning), ROUTER_DEFAULT_TARGET_MISSING_NODE (warning), ROUTER_CIRCUIT_BREAKER_TARGET_MISSING_NODE (warning), ROUTER_DOTTED_VARIABLE_OF_TEXT (warning), ROUTER_DUPLICATE_OR_UNLABELLED_EDGE (warning), CONDITION_MISSING_BRANCH (warning), LLM_HELPER_IGNORED_WHEN_STREAMING (warning), FAILURE_WITHOUT_SUCCESS (info), SUCCESS_WITHOUT_FAILURE (info), FAILURE_EDGE_ON_UNROUTABLE_NODE (info), DEAD_END_NON_TERMINAL (info), EXPLICIT_MAXVISITS_NO_ESCAPE (info), RETRY_NOT_IDEMPOTENT (warning), LLM_STRUCTURED_WITHOUT_FAILURE_EDGE (info), MOCK_RESPONSE_SET (info), TIMEOUT_CLAMPED (warning), TIMEOUT_IGNORED (info), PLAINTEXT_SECRET_IN_CONFIG (warning).\n\n**Allowed values:**\n\n- `WorkflowDiagnosticSeverity`: `INFO`, `WARNING`",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "query Lint($id: Int!, $published: Boolean!) {\n  workflowDiagnostics(id: $id, published: $published) {\n    code\n    severity\n    nodeId\n    edgeId\n    message\n  }\n}\n"
                    ]
                  },
                  "operationName": { "type": "string", "examples": ["Lint"] },
                  "variables": {
                    "type": "object",
                    "properties": {
                      "id": { "type": "integer" },
                      "published": { "type": "boolean" }
                    },
                    "required": ["id", "published"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "Lint a workflow graph. Lists findings that will probably misbehave at run time; never blocks.",
                  "value": {
                    "query": "query Lint($id: Int!, $published: Boolean!) {\n  workflowDiagnostics(id: $id, published: $published) {\n    code\n    severity\n    nodeId\n    edgeId\n    message\n  }\n}\n",
                    "operationName": "Lint",
                    "variables": { "id": 12, "published": false }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "workflowDiagnostics": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "code": {
                                "type": "string",
                                "description": "Stable identifier for the rule (e.g. ROUTER_NO_DEFAULT). Safe to switch on: codes are never renamed; new codes may be added, so treat an unknown code as a generic finding."
                              },
                              "severity": {
                                "$ref": "#/components/schemas/WorkflowDiagnosticSeverity"
                              },
                              "nodeId": {
                                "type": ["string", "null"],
                                "description": "The node the finding is about, when it is about one node."
                              },
                              "edgeId": {
                                "type": ["string", "null"],
                                "description": "The edge the finding is about, when it is about one edge (and that edge has an id)."
                              },
                              "message": {
                                "type": "string",
                                "description": "Plain-language explanation that names the node and says what to do. Wording may change; switch on `code`, not on this text."
                              }
                            },
                            "required": [
                              "code",
                              "severity",
                              "nodeId",
                              "edgeId",
                              "message"
                            ],
                            "description": "One non-blocking finding about a workflow graph, from workflowDiagnostics."
                          }
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "One finding",
                    "value": {
                      "data": {
                        "workflowDiagnostics": [
                          {
                            "code": "UNREACHABLE_NODE",
                            "severity": "WARNING",
                            "nodeId": "notes",
                            "edgeId": null,
                            "message": "Node \"notes\" (\"Internal notes\") cannot be reached from the start node, so it never runs, and publish will refuse the workflow. Connect it to the graph or delete it."
                          }
                        ]
                      }
                    }
                  },
                  "200-2": {
                    "summary": "Nothing found",
                    "value": { "data": { "workflowDiagnostics": [] } }
                  },
                  "200-3": {
                    "summary": "NOT_FOUND: Never published, asked for the published version",
                    "value": {
                      "errors": [
                        {
                          "message": "Workflow 12 has no published version; omit published to check the draft",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["workflowDiagnostics"],
                          "extensions": {
                            "code": "NOT_FOUND",
                            "status": 404,
                            "originalError": {
                              "message": "Workflow 12 has no published version; omit published to check the draft",
                              "error": "Not Found",
                              "statusCode": 404
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "query",
          "field": "workflowDiagnostics",
          "capability": "workflows"
        }
      }
    },
    "/graphql#publishWorkflow": {
      "post": {
        "operationId": "publishWorkflow",
        "tags": ["Workflows"],
        "summary": "Publish the current draft. New turns of every session on the agent run this version.",
        "description": "Publish the current draft. New turns of every session on the agent run this version.\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- Refused when the graph is not runnable, for example with no `start` node or with a node that cannot be reached.\n\n**From the schema:**\n\nWhen the platform enforces email verification, an account whose email is not verified is refused with FORBIDDEN 'Verify your email address first' (code EMAIL_NOT_VERIFIED). Validates the current draft of a Workflow and, when it passes, snapshots it as the published graph (sets isPublished true; version goes up as on any save). Every session turn of an agent attached to this workflow runs the published snapshot from then on; later draft edits change nothing until the next publish. Each publish that changes the published graph is also kept as a numbered entry in the version history (see workflowVersions) and moves currentVersionId to it; republishing an unchanged graph adds no entry. A conversation paused at an AWAIT_REPLY or APPROVAL node keeps running the version it paused on; its next fresh turn uses the new one. Refused with a BAD_REQUEST naming the problem when the graph shape or a node config is invalid. While the workspace has member-built drafts on, a member can publish only a workflow the owner has released (approved), and every workflow, the owner’s too, is refused while it uses a knowledge base, custom action or sub-agent that is not released.",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "mutation Publish($id: Int!) {\n  publishWorkflow(id: $id) {\n    id\n    version\n    isPublished\n  }\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["Publish"]
                  },
                  "variables": {
                    "type": "object",
                    "properties": { "id": { "type": "integer" } },
                    "required": ["id"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "Publish the current draft. New turns of every session on the agent run this version.",
                  "value": {
                    "query": "mutation Publish($id: Int!) {\n  publishWorkflow(id: $id) {\n    id\n    version\n    isPublished\n  }\n}\n",
                    "operationName": "Publish",
                    "variables": { "id": 12 }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "publishWorkflow": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "integer",
                              "description": "Server-generated integer id of the workflow."
                            },
                            "version": {
                              "type": "integer",
                              "description": "Row version, starting at 1 and increased by every save of the workflow (each updateWorkflow and each publishWorkflow); a change counter, not a count of publishes."
                            },
                            "isPublished": {
                              "type": "boolean",
                              "description": "True once publishWorkflow has succeeded at least once. Editing the draft afterwards does not change it; only an unpublished workflow runs nothing for its agent."
                            }
                          },
                          "required": ["id", "version", "isPublished"],
                          "description": "A workflow: the graph of nodes and edges that drives an agent. nodes and edges are the editable DRAFT; sessions run the version last published with publishWorkflow."
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": {
                      "data": {
                        "publishWorkflow": {
                          "id": 12,
                          "version": 2,
                          "isPublished": true
                        }
                      }
                    }
                  },
                  "200-2": {
                    "summary": "BAD_USER_INPUT: Node type written in capitals",
                    "value": {
                      "errors": [
                        {
                          "message": "node classify: type \"LLM\" must be lowercase: \"llm\"",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["publishWorkflow"],
                          "extensions": { "code": "BAD_USER_INPUT" }
                        }
                      ],
                      "data": null
                    }
                  },
                  "200-3": {
                    "summary": "BAD_REQUEST: No start node",
                    "value": {
                      "errors": [
                        {
                          "message": "Workflow must have a START node before publishing (node types seen: llm, response; the start type is \"start\", lowercase)",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["publishWorkflow"],
                          "extensions": {
                            "code": "BAD_REQUEST",
                            "originalError": {
                              "message": "Workflow must have a START node before publishing (node types seen: llm, response; the start type is \"start\", lowercase)",
                              "error": "Bad Request",
                              "statusCode": 400
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "mutation",
          "field": "publishWorkflow",
          "capability": "workflows"
        }
      }
    },
    "/graphql#workflowVersions": {
      "post": {
        "operationId": "workflowVersions",
        "tags": ["Workflows"],
        "summary": "List a workflow's published versions, newest first (summaries only, no graph).",
        "description": "List a workflow's published versions, newest first (summaries only, no graph).\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- One entry for every publish, rollback or template hire that changed the published graph; republishing an unchanged graph adds none.\n\n- `versionNumber` (1, 2, 3 ...) is what the other version operations take. `id` is the value `currentVersionId` and a run's `workflowVersionId` hold. `isCurrent` marks the version new turns run now.\n\n- Page with `first` (default 20, at most 100) and `after` (a previous page's `pageInfo.endCursor`, older versions). The newest 100 versions are kept, so numbers can have gaps.\n\n**From the schema:**\n\nA workflow's published-version history, newest first: one entry for every publish, rollback or template hire that changed the published graph (republishing an unchanged graph adds none). Summaries only, no graph; read one with workflowVersion. isCurrent marks the version new session turns run now. Relay forward pagination: `first` (default 20, capped at 100) and `after` (a previous page's pageInfo.endCursor, which walks to OLDER versions). Old versions are pruned past the newest 100, except the current one and any a still-waiting conversation runs on, so version numbers can have gaps. Every workflow that was published (and not deleted) when version history was rolled out has at least one entry: when no version matched its published graph at that point, a BACKFILL entry holds that graph (version 1 when it had none, otherwise the next number) and became the current version. A workflow not published or deleted at that point gets its first entry at its next publish. NotFound for a workflow that does not exist in your workspace, and for a member who may not see it while member-built drafts are on.\n\n**Allowed values:**\n\n- `WorkflowVersionSource`: `BACKFILL`, `HIRE`, `PUBLISH`, `ROLLBACK`, `SEED`",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "query History($workflowId: Int!, $first: Int, $after: String) {\n  workflowVersions(workflowId: $workflowId, first: $first, after: $after) {\n    pageInfo {\n      hasNextPage\n      endCursor\n    }\n    edges {\n      node {\n        id\n        versionNumber\n        isCurrent\n        source\n        restoredFromVersionNumber\n        note\n        createdAt\n      }\n    }\n  }\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["History"]
                  },
                  "variables": {
                    "type": "object",
                    "properties": {
                      "workflowId": { "type": "integer" },
                      "first": { "type": ["integer", "null"] },
                      "after": { "type": ["string", "null"] }
                    },
                    "required": ["workflowId"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "List a workflow's published versions, newest first (summaries only, no graph).",
                  "value": {
                    "query": "query History($workflowId: Int!, $first: Int, $after: String) {\n  workflowVersions(workflowId: $workflowId, first: $first, after: $after) {\n    pageInfo {\n      hasNextPage\n      endCursor\n    }\n    edges {\n      node {\n        id\n        versionNumber\n        isCurrent\n        source\n        restoredFromVersionNumber\n        note\n        createdAt\n      }\n    }\n  }\n}\n",
                    "operationName": "History",
                    "variables": { "workflowId": 12, "first": 20 }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "workflowVersions": {
                          "type": "object",
                          "properties": {
                            "pageInfo": {
                              "type": "object",
                              "properties": {
                                "hasNextPage": {
                                  "type": "boolean",
                                  "description": "True when an older page exists."
                                },
                                "endCursor": {
                                  "type": ["string", "null"],
                                  "description": "Pass as `after` to read the next (older) page; null when the page is empty."
                                }
                              },
                              "required": ["hasNextPage", "endCursor"],
                              "description": "Forward paging info."
                            },
                            "edges": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "node": {
                                    "type": "object",
                                    "properties": {
                                      "id": {
                                        "type": "integer",
                                        "description": "Server-generated integer id of the version (the value WorkflowDto.currentVersionId and WorkflowRunDto.workflowVersionId refer to)."
                                      },
                                      "versionNumber": {
                                        "type": "integer",
                                        "description": "1, 2, 3 ... per workflow, never reused. Gaps appear once old versions are pruned (the newest 100 are kept, plus the current one and any a still-waiting conversation runs on)."
                                      },
                                      "isCurrent": {
                                        "type": "boolean",
                                        "description": "True for the version WorkflowDto.currentVersionId points to: the one new session turns run now."
                                      },
                                      "source": {
                                        "$ref": "#/components/schemas/WorkflowVersionSource"
                                      },
                                      "restoredFromVersionNumber": {
                                        "type": ["integer", "null"],
                                        "description": "For a ROLLBACK version: the version number whose graph it republished. Null otherwise."
                                      },
                                      "note": {
                                        "type": ["string", "null"],
                                        "description": "The note given to publishWorkflow or rollbackWorkflow; null when none. A BACKFILL version shows 'Imported from the published graph'."
                                      },
                                      "createdAt": {
                                        "type": "string",
                                        "format": "date-time",
                                        "description": "When the version was recorded (ISO 8601)."
                                      }
                                    },
                                    "required": [
                                      "id",
                                      "versionNumber",
                                      "isCurrent",
                                      "source",
                                      "restoredFromVersionNumber",
                                      "note",
                                      "createdAt"
                                    ],
                                    "description": "The version."
                                  }
                                },
                                "required": ["node"],
                                "description": "One version summary and its cursor."
                              },
                              "description": "This page, newest version first."
                            }
                          },
                          "required": ["pageInfo", "edges"],
                          "description": "A page of a workflow's version history, newest first."
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": {
                      "data": {
                        "workflowVersions": {
                          "pageInfo": {
                            "hasNextPage": false,
                            "endCursor": "djox"
                          },
                          "edges": [
                            {
                              "node": {
                                "id": 305,
                                "versionNumber": 2,
                                "isCurrent": true,
                                "source": "PUBLISH",
                                "restoredFromVersionNumber": null,
                                "note": "Stricter classifier prompt",
                                "createdAt": "2026-10-15T08:40:10.000Z"
                              }
                            },
                            {
                              "node": {
                                "id": 291,
                                "versionNumber": 1,
                                "isCurrent": false,
                                "source": "PUBLISH",
                                "restoredFromVersionNumber": null,
                                "note": null,
                                "createdAt": "2026-10-14T09:02:31.000Z"
                              }
                            }
                          ]
                        }
                      }
                    }
                  },
                  "200-2": {
                    "summary": "NOT_FOUND: Workflow not in your workspace",
                    "value": {
                      "errors": [
                        {
                          "message": "Workflow 12 not found",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["workflowVersions"],
                          "extensions": {
                            "code": "NOT_FOUND",
                            "status": 404,
                            "originalError": {
                              "message": "Workflow 12 not found",
                              "error": "Not Found",
                              "statusCode": 404
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  },
                  "200-3": {
                    "summary": "BAD_REQUEST: Cursor not issued by this query",
                    "value": {
                      "errors": [
                        {
                          "message": "Invalid `after` cursor",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["workflowVersions"],
                          "extensions": {
                            "code": "BAD_REQUEST",
                            "originalError": {
                              "message": "Invalid `after` cursor",
                              "error": "Bad Request",
                              "statusCode": 400
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "query",
          "field": "workflowVersions",
          "capability": "workflows"
        }
      }
    },
    "/graphql#workflowVersionDiff": {
      "post": {
        "operationId": "workflowVersionDiff",
        "tags": ["Workflows"],
        "summary": "What changed between two versions, or between a version and the current draft. Ids and key names only, never values.",
        "description": "What changed between two versions, or between a version and the current draft. Ids and key names only, never values.\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- Omit `toVersion` to compare against the draft. With `fromVersion` set to the current version, that is exactly what the next `publishWorkflow` would change.\n\n- `changedKeys` names top-level keys (`label`, `position`, `type`) and config keys as `config.<key>`. Read both graphs with `workflowVersion` to see the values.\n\n**From the schema:**\n\nWhat changed from one version of a workflow to another: added, removed and changed node and edge ids, and for a changed node the NAMES of the keys that differ (label, position, config.promptTemplate ...). Key names only, never values, because configs can hold secrets; read both graphs with workflowVersion to see values. Omit toVersion to compare against the current DRAFT: with fromVersion = the current version, that is exactly what publishWorkflow would change. NotFound when the workflow or either version does not exist (or was pruned).",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "query Diff($workflowId: Int!, $fromVersion: Int!, $toVersion: Int) {\n  workflowVersionDiff(workflowId: $workflowId, fromVersion: $fromVersion, toVersion: $toVersion) {\n    addedNodeIds\n    removedNodeIds\n    changedNodes {\n      id\n      type\n      changedKeys\n    }\n    addedEdgeIds\n    removedEdgeIds\n    changedEdgeIds\n  }\n}\n"
                    ]
                  },
                  "operationName": { "type": "string", "examples": ["Diff"] },
                  "variables": {
                    "type": "object",
                    "properties": {
                      "workflowId": { "type": "integer" },
                      "fromVersion": { "type": "integer" },
                      "toVersion": { "type": ["integer", "null"] }
                    },
                    "required": ["workflowId", "fromVersion"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "What changed between two versions, or between a version and the current draft. Ids and key names only, never values.",
                  "value": {
                    "query": "query Diff($workflowId: Int!, $fromVersion: Int!, $toVersion: Int) {\n  workflowVersionDiff(workflowId: $workflowId, fromVersion: $fromVersion, toVersion: $toVersion) {\n    addedNodeIds\n    removedNodeIds\n    changedNodes {\n      id\n      type\n      changedKeys\n    }\n    addedEdgeIds\n    removedEdgeIds\n    changedEdgeIds\n  }\n}\n",
                    "operationName": "Diff",
                    "variables": { "workflowId": 12, "fromVersion": 2 }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "workflowVersionDiff": {
                          "type": "object",
                          "properties": {
                            "addedNodeIds": {
                              "type": "array",
                              "items": { "type": "string" },
                              "description": "Node ids only in the newer graph."
                            },
                            "removedNodeIds": {
                              "type": "array",
                              "items": { "type": "string" },
                              "description": "Node ids only in the older graph."
                            },
                            "changedNodes": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "id": {
                                    "type": "string",
                                    "description": "Node id."
                                  },
                                  "type": {
                                    "type": "string",
                                    "description": "Node type (lower case) in the newer graph."
                                  },
                                  "changedKeys": {
                                    "type": "array",
                                    "items": { "type": "string" },
                                    "description": "Sorted names of what changed: top-level keys such as label, position or type, and config keys as config.<key> (for example config.promptTemplate)."
                                  }
                                },
                                "required": ["id", "type", "changedKeys"],
                                "description": "A node present in both graphs whose content differs. Names the keys only, never their values."
                              },
                              "description": "Nodes in both graphs whose content differs."
                            },
                            "addedEdgeIds": {
                              "type": "array",
                              "items": { "type": "string" },
                              "description": "Edge ids only in the newer graph."
                            },
                            "removedEdgeIds": {
                              "type": "array",
                              "items": { "type": "string" },
                              "description": "Edge ids only in the older graph."
                            },
                            "changedEdgeIds": {
                              "type": "array",
                              "items": { "type": "string" },
                              "description": "Edge ids in both graphs whose source, target or label differs."
                            }
                          },
                          "required": [
                            "addedNodeIds",
                            "removedNodeIds",
                            "changedNodes",
                            "addedEdgeIds",
                            "removedEdgeIds",
                            "changedEdgeIds"
                          ],
                          "description": "What differs between two workflow graphs, by node and edge id and key name only (never values, since configs can hold secrets). Every list is sorted."
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Draft compared with version 2",
                    "value": {
                      "data": {
                        "workflowVersionDiff": {
                          "addedNodeIds": [],
                          "removedNodeIds": [],
                          "changedNodes": [
                            {
                              "id": "classify",
                              "type": "llm",
                              "changedKeys": ["config.promptTemplate"]
                            }
                          ],
                          "addedEdgeIds": [],
                          "removedEdgeIds": [],
                          "changedEdgeIds": []
                        }
                      }
                    }
                  },
                  "200-2": {
                    "summary": "NOT_FOUND: Version does not exist or was pruned",
                    "value": {
                      "errors": [
                        {
                          "message": "Version 2 of workflow 12 not found (it may have been pruned)",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["workflowVersionDiff"],
                          "extensions": {
                            "code": "NOT_FOUND",
                            "status": 404,
                            "originalError": {
                              "message": "Version 2 of workflow 12 not found (it may have been pruned)",
                              "error": "Not Found",
                              "statusCode": 404
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "query",
          "field": "workflowVersionDiff",
          "capability": "workflows"
        }
      }
    },
    "/graphql#rollbackWorkflow": {
      "post": {
        "operationId": "rollbackWorkflow",
        "tags": ["Workflows"],
        "summary": "Publish an earlier version's graph again, as a new version. The draft is not changed.",
        "description": "Publish an earlier version's graph again, as a new version. The draft is not changed.\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- The old graph is checked against today's rules, as a publish is, and refused with the same errors as `publishWorkflow` when it no longer passes.\n\n- Refused when that graph is already the one published now. `note` is optional, at most 500 characters.\n\n- Takes effect for the next session turn; a conversation paused at `await_reply` or `approval` keeps the version it paused on.\n\n**From the schema:**\n\nPublishes an earlier version's graph again, as a NEW version (source ROLLBACK, restoredFromVersionNumber set); history is never rewritten. The DRAFT is not changed (use restoreWorkflowVersionToDraft to edit from an old version). The graph is re-validated against today's rules (node types, node configs, outbound URL checks, the workspace's model allowlist) and refused with the same BAD_REQUEST / BAD_USER_INPUT errors as publishWorkflow when it no longer passes; refused too when that graph is already the published one. Takes effect for the next session turn; a conversation paused at an AWAIT_REPLY or APPROVAL node keeps running the version it paused on, and finished runs keep the version they ran. Returns the workflow with its new currentVersionId. NotFound when the workflow or version does not exist in your workspace. While member-built drafts are on, a member can roll back only a workflow the owner has released (approved).",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "mutation Rollback($workflowId: Int!, $versionNumber: Int!, $note: String) {\n  rollbackWorkflow(workflowId: $workflowId, versionNumber: $versionNumber, note: $note) {\n    id\n    currentVersionId\n    isPublished\n  }\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["Rollback"]
                  },
                  "variables": {
                    "type": "object",
                    "properties": {
                      "workflowId": { "type": "integer" },
                      "versionNumber": { "type": "integer" },
                      "note": { "type": ["string", "null"] }
                    },
                    "required": ["workflowId", "versionNumber"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "Publish an earlier version's graph again, as a new version. The draft is not changed.",
                  "value": {
                    "query": "mutation Rollback($workflowId: Int!, $versionNumber: Int!, $note: String) {\n  rollbackWorkflow(workflowId: $workflowId, versionNumber: $versionNumber, note: $note) {\n    id\n    currentVersionId\n    isPublished\n  }\n}\n",
                    "operationName": "Rollback",
                    "variables": {
                      "workflowId": 12,
                      "versionNumber": 1,
                      "note": "Back to the first prompt while the classifier is fixed"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "rollbackWorkflow": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "integer",
                              "description": "Server-generated integer id of the workflow."
                            },
                            "currentVersionId": {
                              "type": ["integer", "null"],
                              "description": "Id of the published version (an entry of workflowVersions) that new session turns run now. Moves on every publish or rollback that changes the published graph. Null for a workflow never published, and for one whose last publish happened before version history existed."
                            },
                            "isPublished": {
                              "type": "boolean",
                              "description": "True once publishWorkflow has succeeded at least once. Editing the draft afterwards does not change it; only an unpublished workflow runs nothing for its agent."
                            }
                          },
                          "required": ["id", "currentVersionId", "isPublished"],
                          "description": "A workflow: the graph of nodes and edges that drives an agent. nodes and edges are the editable DRAFT; sessions run the version last published with publishWorkflow."
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": {
                      "data": {
                        "rollbackWorkflow": {
                          "id": 12,
                          "currentVersionId": 318,
                          "isPublished": true
                        }
                      }
                    }
                  },
                  "200-2": {
                    "summary": "BAD_USER_INPUT: That graph is already published",
                    "value": {
                      "errors": [
                        {
                          "message": "Version 1 has the same graph as the version published now; nothing to roll back.",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["rollbackWorkflow"],
                          "extensions": { "code": "BAD_USER_INPUT" }
                        }
                      ],
                      "data": null
                    }
                  },
                  "200-3": {
                    "summary": "NOT_FOUND: Version does not exist or was pruned",
                    "value": {
                      "errors": [
                        {
                          "message": "Version 1 of workflow 12 not found (it may have been pruned)",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["rollbackWorkflow"],
                          "extensions": {
                            "code": "NOT_FOUND",
                            "status": 404,
                            "originalError": {
                              "message": "Version 1 of workflow 12 not found (it may have been pruned)",
                              "error": "Not Found",
                              "statusCode": 404
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "mutation",
          "field": "rollbackWorkflow",
          "capability": "workflows"
        }
      }
    },
    "/graphql#restoreWorkflowVersionToDraft": {
      "post": {
        "operationId": "restoreWorkflowVersionToDraft",
        "tags": ["Workflows"],
        "summary": "Copy a version's graph into the draft, replacing it. Nothing is published.",
        "description": "Copy a version's graph into the draft, replacing it. Nothing is published.\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- Unpublished edits in the draft are replaced. Edit the restored draft, then call `publishWorkflow`.\n\n- The graph is checked like an `updateWorkflow` save, so a node config that no longer passes today's rules is refused with `BAD_USER_INPUT`.\n\n**From the schema:**\n\nCopies a version's graph into the workflow's DRAFT (its nodes and edges), replacing the current draft, including any unpublished edits. Nothing is published and no version is created: edit it, then publishWorkflow. The graph is checked like an updateWorkflow save, so a version whose node configs no longer pass today's rules is refused with the same BAD_USER_INPUT error. NotFound when the workflow or version does not exist in your workspace. A member needs the same right as for updateWorkflow (own, not pending, approved or published) while member-built drafts are on.",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "mutation RestoreToDraft($workflowId: Int!, $versionNumber: Int!) {\n  restoreWorkflowVersionToDraft(workflowId: $workflowId, versionNumber: $versionNumber) {\n    id\n    version\n    updatedAt\n  }\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["RestoreToDraft"]
                  },
                  "variables": {
                    "type": "object",
                    "properties": {
                      "workflowId": { "type": "integer" },
                      "versionNumber": { "type": "integer" }
                    },
                    "required": ["workflowId", "versionNumber"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "Copy a version's graph into the draft, replacing it. Nothing is published.",
                  "value": {
                    "query": "mutation RestoreToDraft($workflowId: Int!, $versionNumber: Int!) {\n  restoreWorkflowVersionToDraft(workflowId: $workflowId, versionNumber: $versionNumber) {\n    id\n    version\n    updatedAt\n  }\n}\n",
                    "operationName": "RestoreToDraft",
                    "variables": { "workflowId": 12, "versionNumber": 1 }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "restoreWorkflowVersionToDraft": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "integer",
                              "description": "Server-generated integer id of the workflow."
                            },
                            "version": {
                              "type": "integer",
                              "description": "Row version, starting at 1 and increased by every save of the workflow (each updateWorkflow and each publishWorkflow); a change counter, not a count of publishes."
                            },
                            "updatedAt": {
                              "type": "string",
                              "format": "date-time",
                              "description": "When the workflow was last saved or published (ISO 8601, server-set)."
                            }
                          },
                          "required": ["id", "version", "updatedAt"],
                          "description": "A workflow: the graph of nodes and edges that drives an agent. nodes and edges are the editable DRAFT; sessions run the version last published with publishWorkflow."
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": {
                      "data": {
                        "restoreWorkflowVersionToDraft": {
                          "id": 12,
                          "version": 5,
                          "updatedAt": "2026-10-16T03:20:11.400Z"
                        }
                      }
                    }
                  },
                  "200-2": {
                    "summary": "NOT_FOUND: Version does not exist or was pruned",
                    "value": {
                      "errors": [
                        {
                          "message": "Version 1 of workflow 12 not found (it may have been pruned)",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["restoreWorkflowVersionToDraft"],
                          "extensions": {
                            "code": "NOT_FOUND",
                            "status": 404,
                            "originalError": {
                              "message": "Version 1 of workflow 12 not found (it may have been pruned)",
                              "error": "Not Found",
                              "statusCode": 404
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request itself was refused before running: variables that do not match their types (`BAD_USER_INPUT`) or an invalid document (`GRAPHQL_VALIDATION_FAILED`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "400-1": {
                    "summary": "BAD_USER_INPUT: versionNumber sent as a string",
                    "value": {
                      "errors": [
                        {
                          "message": "Variable \"$versionNumber\" got invalid value \"one\"; Int cannot represent non-integer value: \"one\"",
                          "locations": [{ "line": 1, "column": 44 }],
                          "extensions": { "code": "BAD_USER_INPUT" }
                        }
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "mutation",
          "field": "restoreWorkflowVersionToDraft",
          "capability": "workflows"
        }
      }
    },
    "/graphql#nodeConfigSchemas": {
      "post": {
        "operationId": "nodeConfigSchemas",
        "tags": ["Workflows"],
        "summary": "The JSON Schema of every node type's config, for building or validating graphs in your own tools.",
        "description": "The JSON Schema of every node type's config, for building or validating graphs in your own tools.\n\n**Credential:** API key. `X-Api-Key: sk_...`, kept on your server only.\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- Accepts an API key or a dashboard JWT. The answer is the same for every workspace.\n\n- `schema` is a standard JSON Schema (draft 2020-12). One entry per node type that has a config; `START` has none.\n\n**From the schema:**\n\nBatch variant of nodeConfigSchema — returns the JSON Schema document for every node type that HAS a config schema (one entry per node type except NodeType.START, which has no config fields at all; 14 today). Useful for an OEM partner building their own form-generating workflow editor, which typically needs all shapes up front rather than one round trip per node type a user might pick. Callable with either a dashboard JWT or an X-Api-Key header.\n\n**Allowed values:**\n\n- `NodeType`: `ACTION`, `APPROVAL`, `AWAIT_REPLY`, `CONDITION`, `END_SESSION`, `GENERATE_ARTIFACT`, `KNOWLEDGE_SEARCH`, `LLM`, `PDF_EXPORT`, `RESPONSE`, `ROUTER`, `SET_VARIABLE`, `START`, `SUB_AGENT`, `SWITCH`, `TOOL`, `WEBHOOK`",
        "security": [{ "apiKey": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "query NodeSchemas {\n  nodeConfigSchemas {\n    type\n    schema\n  }\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["NodeSchemas"]
                  },
                  "variables": { "type": "object", "properties": {} }
                }
              },
              "examples": {
                "default": {
                  "summary": "The JSON Schema of every node type's config, for building or validating graphs in your own tools.",
                  "value": {
                    "query": "query NodeSchemas {\n  nodeConfigSchemas {\n    type\n    schema\n  }\n}\n",
                    "operationName": "NodeSchemas",
                    "variables": {}
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "nodeConfigSchemas": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "type": {
                                "$ref": "#/components/schemas/NodeType"
                              },
                              "schema": {
                                "description": "A real JSON Schema (draft 2020-12) document describing this node type's `config` field shape — generated from this backend's own Zod validation schema, not a Wetel-specific format. Every field is optional and the object allows additional properties (`additionalProperties: {}`): a node's config is an intentionally open bag, so this schema documents the KNOWN, named fields without rejecting ones it doesn't list."
                              }
                            },
                            "required": ["type", "schema"],
                            "description": "The JSON Schema of one node type config, from nodeConfigSchemas."
                          }
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success (one entry of the list shown)",
                    "value": {
                      "data": {
                        "nodeConfigSchemas": [
                          {
                            "type": "RESPONSE",
                            "schema": {
                              "$schema": "https://json-schema.org/draft/2020-12/schema",
                              "type": "object",
                              "properties": {
                                "maxVisits": { "type": "number" },
                                "messageTemplate": { "type": "string" },
                                "mood": { "type": "string" },
                                "structuredContentTemplate": {
                                  "type": "string"
                                }
                              },
                              "additionalProperties": {}
                            }
                          }
                        ]
                      }
                    }
                  },
                  "200-2": {
                    "summary": "UNAUTHENTICATED: No credential",
                    "value": {
                      "errors": [
                        {
                          "message": "Unauthorized",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["nodeConfigSchemas"],
                          "extensions": {
                            "code": "UNAUTHENTICATED",
                            "originalError": {
                              "message": "Unauthorized",
                              "statusCode": 401
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "query",
          "field": "nodeConfigSchemas",
          "capability": "workflows"
        }
      }
    },
    "/graphql#workflowRuns": {
      "post": {
        "operationId": "workflowRuns",
        "tags": ["Workflows"],
        "summary": "List the runs of one session, newest first, to debug what a workflow did.",
        "description": "List the runs of one session, newest first, to debug what a workflow did.\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- A run is one execution of the graph for one turn. Pass a run's id to `workflowRun(id)` for the full node trace.\n\n**From the schema:**\n\nDebugging aid: lists every WorkflowRun for a given Session in your workspace (empty for an unknown session), newest first — the way to discover a run's id when you only have the sessionId (there is no other lookup path; the alternative is capturing workflowRunId live off a NodeExecutingEvent/NodeFailedEvent subscription payload while that turn is happening). For a MEMBER, approval nodes are left out of each run's nodeTrace (see workflowRun). Each entry's id can then be passed to workflowRun(id) for the full nodeTrace. While member-built drafts are on, a member sees only runs of workflows they may see and of agents they may see.\n\n**Allowed values:**\n\n- `WorkflowRunStatus`: `AWAITING_INPUT`, `COMPLETED`, `FAILED`, `INTERRUPTED`, `PENDING`, `RUNNING`",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "query Runs($sessionId: Int!) {\n  workflowRuns(sessionId: $sessionId) {\n    id\n    status\n    workflowId\n    errorMessage\n    totalTokensUsed\n    createdAt\n    completedAt\n  }\n}\n"
                    ]
                  },
                  "operationName": { "type": "string", "examples": ["Runs"] },
                  "variables": {
                    "type": "object",
                    "properties": { "sessionId": { "type": "integer" } },
                    "required": ["sessionId"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "List the runs of one session, newest first, to debug what a workflow did.",
                  "value": {
                    "query": "query Runs($sessionId: Int!) {\n  workflowRuns(sessionId: $sessionId) {\n    id\n    status\n    workflowId\n    errorMessage\n    totalTokensUsed\n    createdAt\n    completedAt\n  }\n}\n",
                    "operationName": "Runs",
                    "variables": { "sessionId": 4211 }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "workflowRuns": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "integer",
                                "description": "Server-generated integer id of the run."
                              },
                              "status": {
                                "$ref": "#/components/schemas/WorkflowRunStatus"
                              },
                              "workflowId": {
                                "type": "integer",
                                "description": "Id of the workflow that ran."
                              },
                              "errorMessage": {
                                "type": ["string", "null"],
                                "description": "Why the run failed; null unless status is FAILED."
                              },
                              "totalTokensUsed": {
                                "type": "integer",
                                "description": "Tokens used by the model calls of this run, an estimate of about 4 characters per token; 0 when no model node ran."
                              },
                              "createdAt": {
                                "type": "string",
                                "format": "date-time",
                                "description": "When the run was created (ISO 8601, server-set)."
                              },
                              "completedAt": {
                                "type": ["string", "null"],
                                "format": "date-time",
                                "description": "When the run stopped (completed, failed, was interrupted or paused) (ISO 8601); null while PENDING or RUNNING."
                              }
                            },
                            "required": [
                              "id",
                              "status",
                              "workflowId",
                              "errorMessage",
                              "totalTokensUsed",
                              "createdAt",
                              "completedAt"
                            ],
                            "description": "One execution of a workflow, normally one conversation turn: which nodes ran, in order, and the outcome."
                          }
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": {
                      "data": {
                        "workflowRuns": [
                          {
                            "id": 5531,
                            "status": "COMPLETED",
                            "workflowId": 12,
                            "errorMessage": null,
                            "totalTokensUsed": 412,
                            "createdAt": "2026-10-12T03:15:02.300Z",
                            "completedAt": "2026-10-12T03:15:05.902Z"
                          }
                        ]
                      }
                    }
                  },
                  "200-2": {
                    "summary": "UNAUTHENTICATED: Called with an API key instead of a JWT",
                    "value": {
                      "errors": [
                        {
                          "message": "Unauthorized",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["workflowRuns"],
                          "extensions": {
                            "code": "UNAUTHENTICATED",
                            "originalError": {
                              "message": "Unauthorized",
                              "statusCode": 401
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "query",
          "field": "workflowRuns",
          "capability": "workflows"
        }
      }
    },
    "/graphql#workflowRun": {
      "post": {
        "operationId": "workflowRun",
        "tags": ["Workflows"],
        "summary": "Read one run's node trace and context, to see exactly which nodes ran, in order, and why.",
        "description": "Read one run's node trace and context, to see exactly which nodes ran, in order, and why.\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- `nodeTrace` is a JSON array with one entry per finished node (`nodeId`, `nodeType`, `startedAt`, `finishedAt` and, depending on the node, `outputVar`, `outputValue`, `tokensUsed`, a status and error details). `context` is the run's variables. Secrets and signed URLs are masked on read.\n\n- A run that stopped at an `await_reply` or `approval` node is `AWAITING_INPUT`; the run that continued it carries its id in `resumedFromRunId`, and the paused run points forward with `continuedByRunId`.\n\n- Returns `null` (not an error) for an id that does not exist in your workspace.\n\n**From the schema:**\n\nDebugging aid: fetches a single WorkflowRun by id, showing exactly which nodes a Session's run visited, in order, and why. Approvals are owner-only: for a MEMBER the approval node is left out of nodeTrace and its approval keys out of context (a run paused for an approval shows AWAITING_INPUT, the same as an await-reply pause). Null for a member who may not see the run's workflow or agent while member-built drafts are on.\n\n**Allowed values:**\n\n- `WorkflowRunStatus`: `AWAITING_INPUT`, `COMPLETED`, `FAILED`, `INTERRUPTED`, `PENDING`, `RUNNING`",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "query Run($id: Int!) {\n  workflowRun(id: $id) {\n    id\n    status\n    sessionId\n    workflowId\n    errorMessage\n    nodeTrace\n    context\n    resumedFromRunId\n    continuedByRunId\n    totalTokensUsed\n    createdAt\n    completedAt\n  }\n}\n"
                    ]
                  },
                  "operationName": { "type": "string", "examples": ["Run"] },
                  "variables": {
                    "type": "object",
                    "properties": { "id": { "type": "integer" } },
                    "required": ["id"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "Read one run's node trace and context, to see exactly which nodes ran, in order, and why.",
                  "value": {
                    "query": "query Run($id: Int!) {\n  workflowRun(id: $id) {\n    id\n    status\n    sessionId\n    workflowId\n    errorMessage\n    nodeTrace\n    context\n    resumedFromRunId\n    continuedByRunId\n    totalTokensUsed\n    createdAt\n    completedAt\n  }\n}\n",
                    "operationName": "Run",
                    "variables": { "id": 5531 }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "workflowRun": {
                          "type": ["object", "null"],
                          "properties": {
                            "id": {
                              "type": "integer",
                              "description": "Server-generated integer id of the run."
                            },
                            "status": {
                              "$ref": "#/components/schemas/WorkflowRunStatus"
                            },
                            "sessionId": {
                              "type": ["integer", "null"],
                              "description": "Id of the session (conversation) the run belongs to; null for a run without one."
                            },
                            "workflowId": {
                              "type": "integer",
                              "description": "Id of the workflow that ran."
                            },
                            "errorMessage": {
                              "type": ["string", "null"],
                              "description": "Why the run failed; null unless status is FAILED."
                            },
                            "nodeTrace": {
                              "description": "The nodes that ran, in order: a JSON array with one entry per finished node, carrying nodeId, nodeType, startedAt and finishedAt (ISO 8601) and, depending on the node, outputVar, outputValue, tokensUsed, a status and error details. Secrets are masked on read."
                            },
                            "context": {
                              "description": "The run variables (ctx) as a JSON object: inputs such as userMessage and the outputVar of each node. Secrets and signed URLs are masked on read, and inline base64 images are not kept."
                            },
                            "resumedFromRunId": {
                              "type": ["integer", "null"],
                              "description": "Set only when this run CONTINUED a run that had paused at an AWAIT_REPLY or APPROVAL node: the id of that paused (AWAITING_INPUT) run. Null for an ordinary turn. Follow it to see the first half of the same logical step."
                            },
                            "continuedByRunId": {
                              "type": ["integer", "null"],
                              "description": "The inverse of resumedFromRunId: set on a paused (AWAITING_INPUT) run once another run has CONTINUED it, with that run's id. A paused run that has a continuedByRunId is no longer waiting: its status stays AWAITING_INPUT as a record of where it stopped, and the real state of the conversation step is on the continuing run. Null while the run is genuinely still waiting, and null for every run that never paused. Filled by workflowRun and workflowRuns only."
                            },
                            "totalTokensUsed": {
                              "type": "integer",
                              "description": "Tokens used by the model calls of this run, an estimate of about 4 characters per token; 0 when no model node ran."
                            },
                            "createdAt": {
                              "type": "string",
                              "format": "date-time",
                              "description": "When the run was created (ISO 8601, server-set)."
                            },
                            "completedAt": {
                              "type": ["string", "null"],
                              "format": "date-time",
                              "description": "When the run stopped (completed, failed, was interrupted or paused) (ISO 8601); null while PENDING or RUNNING."
                            }
                          },
                          "required": [
                            "id",
                            "status",
                            "sessionId",
                            "workflowId",
                            "errorMessage",
                            "nodeTrace",
                            "context",
                            "resumedFromRunId",
                            "continuedByRunId",
                            "totalTokensUsed",
                            "createdAt",
                            "completedAt"
                          ],
                          "description": "One execution of a workflow, normally one conversation turn: which nodes ran, in order, and the outcome."
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": {
                      "data": {
                        "workflowRun": {
                          "id": 5531,
                          "status": "COMPLETED",
                          "sessionId": 4211,
                          "workflowId": 12,
                          "errorMessage": null,
                          "nodeTrace": [
                            {
                              "nodeId": "start",
                              "nodeType": "start",
                              "startedAt": "2026-10-12T03:15:02.310Z",
                              "finishedAt": "2026-10-12T03:15:02.311Z"
                            },
                            {
                              "nodeId": "classify",
                              "nodeType": "llm",
                              "startedAt": "2026-10-12T03:15:02.312Z",
                              "finishedAt": "2026-10-12T03:15:04.870Z",
                              "outputVar": "category",
                              "outputValue": "network",
                              "tokensUsed": 412
                            },
                            {
                              "nodeId": "reply",
                              "nodeType": "response",
                              "startedAt": "2026-10-12T03:15:04.871Z",
                              "finishedAt": "2026-10-12T03:15:05.902Z"
                            }
                          ],
                          "context": {
                            "userMessage": "My laptop cannot connect to the office VPN since this morning.",
                            "category": "network"
                          },
                          "resumedFromRunId": null,
                          "continuedByRunId": null,
                          "totalTokensUsed": 412,
                          "createdAt": "2026-10-12T03:15:02.300Z",
                          "completedAt": "2026-10-12T03:15:05.902Z"
                        }
                      }
                    }
                  },
                  "200-2": {
                    "summary": "Not in your workspace",
                    "value": { "data": { "workflowRun": null } }
                  },
                  "200-3": {
                    "summary": "UNAUTHENTICATED: Called with an API key instead of a JWT",
                    "value": {
                      "errors": [
                        {
                          "message": "Unauthorized",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["workflowRun"],
                          "extensions": {
                            "code": "UNAUTHENTICATED",
                            "originalError": {
                              "message": "Unauthorized",
                              "statusCode": 401
                            }
                          }
                        }
                      ],
                      "data": { "workflowRun": null }
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "query",
          "field": "workflowRun",
          "capability": "workflows"
        }
      }
    },
    "/graphql#pendingAwaitReplies": {
      "post": {
        "operationId": "pendingAwaitReplies",
        "tags": ["Workflows"],
        "summary": "List the conversations waiting at an await_reply node for the user's answer, newest first.",
        "description": "List the conversations waiting at an await_reply node for the user's answer, newest first.\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- Pass `sessionId` to check one conversation (is it stuck, or waiting for the user?); omit it for every open wait in the workspace. Only `WAITING` rows are listed.\n\n- [object Object]\n\n- `options` is the numbered list the user was shown (empty in `FREE_TEXT` mode). The stored run context is never returned here.\n\n**From the schema:**\n\nLists conversations currently paused at an AWAIT_REPLY node — a workflow run that asked the user a question, ended with status AWAITING_INPUT, and will continue from that node's successor when the next message on that session arrives. Newest first, scoped to the caller's tenant. Pass `sessionId` to check one conversation (the answer to \"is my testDraft run hung, or is it waiting on me?\"); omit it for every open wait, which is the operational view of what is waiting and for how long. `expiresAt` is when the wait stops being resumable — after that the next message simply starts a fresh run from START. The paused run's own node trace is at workflowRun(id: workflowRunId). Deliberately does NOT expose the stored workflow context the run will resume with. While member-built drafts are on, a member sees only the waits of agents they may see.\n\n**Allowed values:**\n\n- `WorkflowRunWaitStatus`: `CANCELLED`, `EXPIRED`, `RESUMED`, `WAITING`\n- `AwaitReplyMatchMode`: `FREE_TEXT`, `OPTIONS`",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "query Waiting($sessionId: Int) {\n  pendingAwaitReplies(sessionId: $sessionId) {\n    id\n    sessionId\n    workflowRunId\n    nodeId\n    status\n    matchMode\n    saveAs\n    options {\n      slot\n      id\n      label\n    }\n    expiresAt\n  }\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["Waiting"]
                  },
                  "variables": {
                    "type": "object",
                    "properties": {
                      "sessionId": { "type": ["integer", "null"] }
                    }
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "List the conversations waiting at an await_reply node for the user's answer, newest first.",
                  "value": {
                    "query": "query Waiting($sessionId: Int) {\n  pendingAwaitReplies(sessionId: $sessionId) {\n    id\n    sessionId\n    workflowRunId\n    nodeId\n    status\n    matchMode\n    saveAs\n    options {\n      slot\n      id\n      label\n    }\n    expiresAt\n  }\n}\n",
                    "operationName": "Waiting",
                    "variables": { "sessionId": 4211 }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "pendingAwaitReplies": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": { "type": "integer" },
                              "sessionId": { "type": "integer" },
                              "workflowRunId": {
                                "type": "integer",
                                "description": "The WorkflowRun that asked the question — the one persisted as AWAITING_INPUT. Pass it to workflowRun(id) for the nodeTrace up to the pause."
                              },
                              "nodeId": {
                                "type": "string",
                                "description": "Graph node id of the AWAIT_REPLY node that is waiting."
                              },
                              "status": {
                                "$ref": "#/components/schemas/WorkflowRunWaitStatus"
                              },
                              "matchMode": {
                                "$ref": "#/components/schemas/AwaitReplyMatchMode"
                              },
                              "saveAs": {
                                "type": "string",
                                "description": "Context variable the answer will be written to on resume."
                              },
                              "options": {
                                "type": "array",
                                "items": {
                                  "type": "object",
                                  "properties": {
                                    "slot": {
                                      "type": "integer",
                                      "description": "1-based position shown to the user, so a reply of \"2\" is matchable without exposing an internal id."
                                    },
                                    "id": {
                                      "type": "string",
                                      "description": "The option's own identifier, from optionIdField."
                                    },
                                    "label": {
                                      "type": "string",
                                      "description": "Display label, from optionLabelField, truncated to 80 characters at render time."
                                    }
                                  },
                                  "required": ["slot", "id", "label"],
                                  "description": "One choice offered by an AWAIT_REPLY node, exactly as it was rendered when the question was asked."
                                },
                                "description": "The option page fixed at ask time. Empty in FREE_TEXT mode. Matching a reply reads only this — never the live workflow context."
                              },
                              "expiresAt": {
                                "type": "string",
                                "format": "date-time",
                                "description": "After this instant the wait is no longer resumable and the next message starts a fresh run from START."
                              }
                            },
                            "required": [
                              "id",
                              "sessionId",
                              "workflowRunId",
                              "nodeId",
                              "status",
                              "matchMode",
                              "saveAs",
                              "options",
                              "expiresAt"
                            ]
                          }
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": {
                      "data": {
                        "pendingAwaitReplies": [
                          {
                            "id": 207,
                            "sessionId": 4211,
                            "workflowRunId": 5534,
                            "nodeId": "ask_device",
                            "status": "WAITING",
                            "matchMode": "OPTIONS",
                            "saveAs": "device",
                            "options": [
                              { "slot": 1, "id": "laptop", "label": "Laptop" },
                              { "slot": 2, "id": "desktop", "label": "Desktop" }
                            ],
                            "expiresAt": "2026-10-13T03:16:10.000Z"
                          }
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request itself was refused before running: variables that do not match their types (`BAD_USER_INPUT`) or an invalid document (`GRAPHQL_VALIDATION_FAILED`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "400-1": {
                    "summary": "BAD_USER_INPUT: Session id sent as a string",
                    "value": {
                      "errors": [
                        {
                          "message": "Variable \"$sessionId\" got invalid value \"four-two-one-one\"; Int cannot represent non-integer value: \"four-two-one-one\"",
                          "locations": [{ "line": 1, "column": 15 }],
                          "extensions": { "code": "BAD_USER_INPUT" }
                        }
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "query",
          "field": "pendingAwaitReplies",
          "capability": "workflows"
        }
      }
    },
    "/graphql#runWorkflowTask": {
      "post": {
        "operationId": "runWorkflowTask",
        "tags": ["Workflows"],
        "summary": "Run an agent's published workflow headlessly (no chat message). Asynchronous; returns once accepted.",
        "description": "Run an agent's published workflow headlessly (no chat message). Asynchronous; returns once accepted.\n\n**Credential:** API key. `X-Api-Key: sk_...`, kept on your server only.\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- A successful answer means the task was accepted and queued, never that it succeeded. Read the result with `workflowTaskOutcome` and the returned `sessionId`.\n\n- `variables` is a flat JSON object merged into the run's context; `externalConversationId` groups repeat calls for one conversation into one session.\n\n- The agent must be `ACTIVE` and have a workflow attached. Limited to 60 calls per minute per client IP by default.\n\n**From the schema:**\n\nCredentialed, headless entry point for running one of your own agents' published workflows from an external orchestrator (a webhook step in another product, a channel gateway, your backend, a cURL) with NO chat message involved. Auth is an X-Api-Key header (ApiKeyGuard), NOT a JWT, and the tenant is resolved from the key itself — never from input. Structured facts travel in `variables` (a flat map of scalars merged verbatim into the run's initial context) and `attachmentUrl`/`attachmentFilename`/`attachmentMime` (seeded into exactly the same ctx shape a chat attachment produces, so one graph serves both callers) — this is the deterministic replacement for smuggling the same values through message text as `[[...]]` markers and re-extracting them with LLM nodes. `externalConversationId` becomes the Session's clientExternalId, so repeat calls for one conversation share a session. IMPORTANT: this is ASYNCHRONOUS and fire-and-forget — a successful return means the run was ACCEPTED and enqueued, never that it succeeded. Read the real outcome with workflowTaskOutcome(sessionId) (same API key) using the sessionId returned here; do not infer success from this mutation returning, nor from any reply text. Every accepted or refused call against one of your agents is recorded for Wetel support (agent, session, time, your externalConversationId, variable NAMES, whether an attachment was passed, outcome); variable values, the attachment URL and error text are never stored. Any value that feeds an outbound write must be gated inside the graph by a CONDITION node before the ACTION node (write `someId > 0`, never `someId != 0` — expr-eval's equality operators do not coerce types). Rate-limited per client IP.\n\n**Allowed values:**\n\n- `AttachmentKind`: `AUDIO`, `DOCUMENT`, `IMAGE`, `OTHER`, `VIDEO`",
        "security": [{ "apiKey": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "mutation RunTask($input: RunWorkflowTaskInput!) {\n  runWorkflowTask(input: $input) {\n    accepted\n    sessionId\n    workflowId\n    reusedSession\n    turnCount\n  }\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["RunTask"]
                  },
                  "variables": {
                    "type": "object",
                    "properties": {
                      "input": {
                        "$ref": "#/components/schemas/RunWorkflowTaskInput"
                      }
                    },
                    "required": ["input"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "Run an agent's published workflow headlessly (no chat message). Asynchronous; returns once accepted.",
                  "value": {
                    "query": "mutation RunTask($input: RunWorkflowTaskInput!) {\n  runWorkflowTask(input: $input) {\n    accepted\n    sessionId\n    workflowId\n    reusedSession\n    turnCount\n  }\n}\n",
                    "operationName": "RunTask",
                    "variables": {
                      "input": {
                        "agentId": 7,
                        "externalConversationId": "ticket-1002",
                        "variables": {
                          "ticketId": "T-1002",
                          "subject": "Cannot print to the 3rd floor printer",
                          "reporterRole": "staff"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "runWorkflowTask": {
                          "type": "object",
                          "properties": {
                            "accepted": {
                              "type": "boolean",
                              "description": "Always true when the call returns: the task was validated and queued (errors are returned as GraphQL errors instead)."
                            },
                            "sessionId": {
                              "type": "integer",
                              "description": "Id of the session the task runs on; pass it to workflowTaskOutcome."
                            },
                            "workflowId": {
                              "type": "integer",
                              "description": "Id of the workflow that will run (the agent attached workflow)."
                            },
                            "reusedSession": {
                              "type": "boolean",
                              "description": "True when an open session for this externalConversationId was reused; false when a new one was created."
                            },
                            "turnCount": {
                              "type": "integer",
                              "description": "Turn number this task took in the session, starting at 1 and increasing by one per accepted task or message."
                            }
                          },
                          "required": [
                            "accepted",
                            "sessionId",
                            "workflowId",
                            "reusedSession",
                            "turnCount"
                          ],
                          "description": "Acknowledgement that a task was accepted and queued. It says nothing about success: read workflowTaskOutcome(sessionId)."
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Accepted",
                    "value": {
                      "data": {
                        "runWorkflowTask": {
                          "accepted": true,
                          "sessionId": 4290,
                          "workflowId": 12,
                          "reusedSession": false,
                          "turnCount": 1
                        }
                      }
                    }
                  },
                  "200-2": {
                    "summary": "BAD_REQUEST: Agent has no workflow",
                    "value": {
                      "errors": [
                        {
                          "message": "Agent 7 has no workflow attached — runWorkflowTask requires one",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["runWorkflowTask"],
                          "extensions": {
                            "code": "BAD_REQUEST",
                            "originalError": {
                              "message": "Agent 7 has no workflow attached — runWorkflowTask requires one",
                              "error": "Bad Request",
                              "statusCode": 400
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  },
                  "200-3": {
                    "summary": "NOT_FOUND: Agent not in the key's workspace",
                    "value": {
                      "errors": [
                        {
                          "message": "Agent 7 not found",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["runWorkflowTask"],
                          "extensions": {
                            "code": "NOT_FOUND",
                            "status": 404,
                            "originalError": {
                              "message": "Agent 7 not found",
                              "error": "Not Found",
                              "statusCode": 404
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "mutation",
          "field": "runWorkflowTask",
          "capability": "workflows"
        }
      }
    },
    "/graphql#workflowTaskOutcome": {
      "post": {
        "operationId": "workflowTaskOutcome",
        "tags": ["Workflows"],
        "summary": "Read the outcome of a task started with runWorkflowTask, with the same API key.",
        "description": "Read the outcome of a task started with runWorkflowTask, with the same API key.\n\n**Credential:** API key. `X-Api-Key: sk_...`, kept on your server only.\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- `status` is the newest run's status: `PENDING` and `RUNNING` mean poll again (with a back-off); `COMPLETED`, `FAILED`, `INTERRUPTED` and `AWAITING_INPUT` are final for now. `null` means the task is still queued.\n\n- `replies` are what the agent said, oldest first. The run context and node trace are never returned here.\n\n**From the schema:**\n\nReads back the outcome of a task started with runWorkflowTask, with the SAME X-Api-Key (no dashboard login needed). Pass the `sessionId` that runWorkflowTask returned. Returns the newest run's `status` (null while the accepted task is still queued and has not started a run), the session's runs newest first (status, timing, and for a failed run a masked, 300-character error), and `replies`: what the agent said, oldest first. `status` COMPLETED means the graph reached its end; FAILED, INTERRUPTED and AWAITING_INPUT are the other terminal-ish states; PENDING and RUNNING mean keep polling. It never returns the run context or node trace. Only sessions that a runWorkflowTask call was accepted for are readable: any other session id, or one in another tenant, returns the same not-found error. Poll with a back-off rather than in a tight loop. Rate-limited per client IP.\n\n**Allowed values:**\n\n- `WorkflowRunStatus`: `AWAITING_INPUT`, `COMPLETED`, `FAILED`, `INTERRUPTED`, `PENDING`, `RUNNING`",
        "security": [{ "apiKey": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "query TaskOutcome($sessionId: Int!) {\n  workflowTaskOutcome(sessionId: $sessionId) {\n    sessionId\n    status\n    runs {\n      id\n      status\n      errorMessage\n    }\n    replies {\n      text\n      createdAt\n    }\n  }\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["TaskOutcome"]
                  },
                  "variables": {
                    "type": "object",
                    "properties": { "sessionId": { "type": "integer" } },
                    "required": ["sessionId"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "Read the outcome of a task started with runWorkflowTask, with the same API key.",
                  "value": {
                    "query": "query TaskOutcome($sessionId: Int!) {\n  workflowTaskOutcome(sessionId: $sessionId) {\n    sessionId\n    status\n    runs {\n      id\n      status\n      errorMessage\n    }\n    replies {\n      text\n      createdAt\n    }\n  }\n}\n",
                    "operationName": "TaskOutcome",
                    "variables": { "sessionId": 4290 }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "workflowTaskOutcome": {
                          "type": "object",
                          "properties": {
                            "sessionId": {
                              "type": "integer",
                              "description": "Id of the task session, as passed."
                            },
                            "status": {
                              "anyOf": [
                                {
                                  "$ref": "#/components/schemas/WorkflowRunStatus"
                                },
                                { "type": "null" }
                              ],
                              "description": "Status of the NEWEST run. Null while the accepted task has not started a run yet (still queued)."
                            },
                            "runs": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "id": {
                                    "type": "integer",
                                    "description": "Id of the workflow run."
                                  },
                                  "status": {
                                    "$ref": "#/components/schemas/WorkflowRunStatus"
                                  },
                                  "errorMessage": {
                                    "type": ["string", "null"],
                                    "description": "Why the run failed, with anything that looks like a credential masked and the text cut to 300 characters. Null for a run that did not fail."
                                  }
                                },
                                "required": ["id", "status", "errorMessage"],
                                "description": "One engine execution of the task session. A pause and resume at an await_reply node is two runs."
                              },
                              "description": "Runs of this session, newest first, at most 20."
                            },
                            "replies": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "text": {
                                    "type": "string",
                                    "description": "The message text."
                                  },
                                  "createdAt": {
                                    "type": "string",
                                    "format": "date-time",
                                    "description": "When the message was stored (ISO 8601)."
                                  }
                                },
                                "required": ["text", "createdAt"],
                                "description": "A message the agent produced in the task session."
                              },
                              "description": "What the agent said in this session (the workflow's response nodes), oldest first, at most the latest 10."
                            }
                          },
                          "required": [
                            "sessionId",
                            "status",
                            "runs",
                            "replies"
                          ],
                          "description": "The outcome of a task started with runWorkflowTask, readable with the same API key."
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Completed",
                    "value": {
                      "data": {
                        "workflowTaskOutcome": {
                          "sessionId": 4290,
                          "status": "COMPLETED",
                          "runs": [
                            {
                              "id": 5602,
                              "status": "COMPLETED",
                              "errorMessage": null
                            }
                          ],
                          "replies": [
                            {
                              "text": "Thanks. Your ticket goes to the L2 queue; we will reply here.",
                              "createdAt": "2026-10-12T04:10:13.540Z"
                            }
                          ]
                        }
                      }
                    }
                  },
                  "200-2": {
                    "summary": "NOT_FOUND: Not a task session of this key's workspace",
                    "value": {
                      "errors": [
                        {
                          "message": "No workflow task found for session 4290",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["workflowTaskOutcome"],
                          "extensions": {
                            "code": "NOT_FOUND",
                            "status": 404,
                            "originalError": {
                              "message": "No workflow task found for session 4290",
                              "error": "Not Found",
                              "statusCode": 404
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "query",
          "field": "workflowTaskOutcome",
          "capability": "workflows"
        }
      }
    },
    "/graphql#createScheduledWorkflow": {
      "post": {
        "operationId": "createScheduledWorkflow",
        "tags": ["Scheduled workflows"],
        "summary": "Create an ACTIVE schedule that runs an agent's published workflow on a cron expression.",
        "description": "Create an ACTIVE schedule that runs an agent's published workflow on a cron expression.\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- `cronExpression` has exactly 5 fields (minute hour day-of-month month day-of-week); seconds and `@daily`-style aliases are refused. `timezone` defaults to `UTC`.\n\n- `variables` is a flat JSON object of scalars merged into every run's context, with the same rules as `runWorkflowTask`. Send it through variables, never as an inline literal.\n\n- `externalConversationIdTemplate` names each fire's session. The default `scheduled:{{scheduleId}}:{{firedAt}}` gives every fire a fresh session; drop `{{firedAt}}` to reuse one session. Only `{{scheduleId}}`, `{{date}}` and `{{firedAt}}` are allowed.\n\n- After `maxConsecutiveFailures` failed runs in a row (1 to 20, default 3) the schedule turns `AUTO_DISABLED`.\n\n**From the schema:**\n\nCreates an ACTIVE schedule that runs `agentId`'s published workflow headlessly (no user message) on a 5-field cron expression in the given IANA timezone. Invalid cron/timezone/variables are rejected up front. Counts against the plan's maxScheduledWorkflows quota (PAUSED/AUTO_DISABLED schedules still hold a slot). Each fire runs asynchronously — observe outcomes via lastRunAt/lastRunStatus and workflowRuns(sessionId), never from this mutation's return.\n\n**Allowed values:**\n\n- `ScheduledWorkflowStatus`: `ACTIVE`, `AUTO_DISABLED`, `PAUSED`",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "mutation CreateSchedule($input: CreateScheduledWorkflowInput!) {\n  createScheduledWorkflow(input: $input) {\n    id\n    name\n    agentId\n    cronExpression\n    timezone\n    status\n    nextRunAt\n    variables\n  }\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["CreateSchedule"]
                  },
                  "variables": {
                    "type": "object",
                    "properties": {
                      "input": {
                        "$ref": "#/components/schemas/CreateScheduledWorkflowInput"
                      }
                    },
                    "required": ["input"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "Create an ACTIVE schedule that runs an agent's published workflow on a cron expression.",
                  "value": {
                    "query": "mutation CreateSchedule($input: CreateScheduledWorkflowInput!) {\n  createScheduledWorkflow(input: $input) {\n    id\n    name\n    agentId\n    cronExpression\n    timezone\n    status\n    nextRunAt\n    variables\n  }\n}\n",
                    "operationName": "CreateSchedule",
                    "variables": {
                      "input": {
                        "agentId": 7,
                        "name": "Weekday stale-ticket check",
                        "cronExpression": "0 9 * * 1-5",
                        "timezone": "Asia/Kuala_Lumpur",
                        "variables": { "queue": "l2", "staleAfterHours": 48 }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "createScheduledWorkflow": {
                          "type": "object",
                          "properties": {
                            "id": { "type": "integer" },
                            "name": { "type": "string" },
                            "agentId": { "type": "integer" },
                            "cronExpression": { "type": "string" },
                            "timezone": { "type": "string" },
                            "status": {
                              "$ref": "#/components/schemas/ScheduledWorkflowStatus"
                            },
                            "nextRunAt": {
                              "type": ["string", "null"],
                              "format": "date-time",
                              "description": "Next scheduled fire time, for display only (Bull is the actual trigger). Null unless status is ACTIVE."
                            },
                            "variables": {
                              "description": "Any JSON value. Send it through variables, never inline in the query."
                            }
                          },
                          "required": [
                            "id",
                            "name",
                            "agentId",
                            "cronExpression",
                            "timezone",
                            "status",
                            "nextRunAt",
                            "variables"
                          ]
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": {
                      "data": {
                        "createScheduledWorkflow": {
                          "id": 14,
                          "name": "Weekday stale-ticket check",
                          "agentId": 7,
                          "cronExpression": "0 9 * * 1-5",
                          "timezone": "Asia/Kuala_Lumpur",
                          "status": "ACTIVE",
                          "nextRunAt": "2026-10-19T01:00:00.000Z",
                          "variables": { "queue": "l2", "staleAfterHours": 48 }
                        }
                      }
                    }
                  },
                  "200-2": {
                    "summary": "BAD_REQUEST: Cron expression with six fields",
                    "value": {
                      "errors": [
                        {
                          "message": "cronExpression must have exactly 5 fields (minute hour day-of-month month day-of-week) — received 6. Seconds-level and @alias schedules are not supported.",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["createScheduledWorkflow"],
                          "extensions": {
                            "code": "BAD_REQUEST",
                            "originalError": {
                              "message": "cronExpression must have exactly 5 fields (minute hour day-of-month month day-of-week) — received 6. Seconds-level and @alias schedules are not supported.",
                              "error": "Bad Request",
                              "statusCode": 400
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  },
                  "200-3": {
                    "summary": "BAD_REQUEST: Unknown timezone",
                    "value": {
                      "errors": [
                        {
                          "message": "timezone \"Asia/Kuala Lumpur\" is not a valid IANA timezone (e.g. \"UTC\", \"Asia/Kuala_Lumpur\")",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["createScheduledWorkflow"],
                          "extensions": {
                            "code": "BAD_REQUEST",
                            "originalError": {
                              "message": "timezone \"Asia/Kuala Lumpur\" is not a valid IANA timezone (e.g. \"UTC\", \"Asia/Kuala_Lumpur\")",
                              "error": "Bad Request",
                              "statusCode": 400
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  },
                  "200-4": {
                    "summary": "BAD_REQUEST: Plan limit reached",
                    "value": {
                      "errors": [
                        {
                          "message": "Creating another scheduled workflow would exceed your plan's limit of 25. Upgrade your plan or delete an existing scheduled workflow.",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["createScheduledWorkflow"],
                          "extensions": {
                            "code": "BAD_REQUEST",
                            "originalError": {
                              "message": "Creating another scheduled workflow would exceed your plan's limit of 25. Upgrade your plan or delete an existing scheduled workflow.",
                              "error": "Bad Request",
                              "statusCode": 400
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  },
                  "200-5": {
                    "summary": "NOT_FOUND: Agent not in your workspace",
                    "value": {
                      "errors": [
                        {
                          "message": "Agent 7 not found",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["createScheduledWorkflow"],
                          "extensions": {
                            "code": "NOT_FOUND",
                            "status": 404,
                            "originalError": {
                              "message": "Agent 7 not found",
                              "error": "Not Found",
                              "statusCode": 404
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "mutation",
          "field": "createScheduledWorkflow",
          "capability": "schedules"
        }
      }
    },
    "/graphql#scheduledWorkflows": {
      "post": {
        "operationId": "scheduledWorkflows",
        "tags": ["Scheduled workflows"],
        "summary": "List the workspace's schedules, every status included, ordered by id.",
        "description": "List the workspace's schedules, every status included, ordered by id.\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- Forward pagination: `first` (1 to 100, default 20) and `after` (a previous page's `pageInfo.endCursor`).\n\n- `lastRunStatus` is the last fire's run status in lower case (`running`, `completed`, `failed` and so on), or `null` before the first fire. `nextRunAt` is `null` unless the schedule is `ACTIVE`.\n\n**From the schema:**\n\nRelay-paginated list of the caller's tenant's scheduled (cron) workflows, every status included (ACTIVE, PAUSED, AUTO_DISABLED), ordered by id. Use `first` (1-100, default 20) and pass `pageInfo.endCursor` as `after` for the next page. While member-built drafts are on, a member sees (and may create, change, pause, resume or delete) only schedules of agents they may see.\n\n**Allowed values:**\n\n- `ScheduledWorkflowStatus`: `ACTIVE`, `AUTO_DISABLED`, `PAUSED`",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "query Schedules($first: Int, $after: String) {\n  scheduledWorkflows(first: $first, after: $after) {\n    totalCount\n    edges {\n      node {\n        id\n        name\n        agentId\n        cronExpression\n        timezone\n        status\n        lastRunAt\n        lastRunStatus\n        consecutiveFailureCount\n        nextRunAt\n      }\n    }\n    pageInfo {\n      hasNextPage\n      endCursor\n    }\n  }\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["Schedules"]
                  },
                  "variables": {
                    "type": "object",
                    "properties": {
                      "first": { "type": ["integer", "null"] },
                      "after": { "type": ["string", "null"] }
                    }
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "List the workspace's schedules, every status included, ordered by id.",
                  "value": {
                    "query": "query Schedules($first: Int, $after: String) {\n  scheduledWorkflows(first: $first, after: $after) {\n    totalCount\n    edges {\n      node {\n        id\n        name\n        agentId\n        cronExpression\n        timezone\n        status\n        lastRunAt\n        lastRunStatus\n        consecutiveFailureCount\n        nextRunAt\n      }\n    }\n    pageInfo {\n      hasNextPage\n      endCursor\n    }\n  }\n}\n",
                    "operationName": "Schedules",
                    "variables": { "first": 20 }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "scheduledWorkflows": {
                          "type": "object",
                          "properties": {
                            "totalCount": { "type": "integer" },
                            "edges": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "node": {
                                    "type": "object",
                                    "properties": {
                                      "id": { "type": "integer" },
                                      "name": { "type": "string" },
                                      "agentId": { "type": "integer" },
                                      "cronExpression": { "type": "string" },
                                      "timezone": { "type": "string" },
                                      "status": {
                                        "$ref": "#/components/schemas/ScheduledWorkflowStatus"
                                      },
                                      "lastRunAt": {
                                        "type": ["string", "null"],
                                        "format": "date-time"
                                      },
                                      "lastRunStatus": {
                                        "type": ["string", "null"]
                                      },
                                      "consecutiveFailureCount": {
                                        "type": "integer"
                                      },
                                      "nextRunAt": {
                                        "type": ["string", "null"],
                                        "format": "date-time",
                                        "description": "Next scheduled fire time, for display only (Bull is the actual trigger). Null unless status is ACTIVE."
                                      }
                                    },
                                    "required": [
                                      "id",
                                      "name",
                                      "agentId",
                                      "cronExpression",
                                      "timezone",
                                      "status",
                                      "lastRunAt",
                                      "lastRunStatus",
                                      "consecutiveFailureCount",
                                      "nextRunAt"
                                    ]
                                  }
                                },
                                "required": ["node"]
                              }
                            },
                            "pageInfo": {
                              "type": "object",
                              "properties": {
                                "hasNextPage": { "type": "boolean" },
                                "endCursor": { "type": ["string", "null"] }
                              },
                              "required": ["hasNextPage", "endCursor"]
                            }
                          },
                          "required": ["totalCount", "edges", "pageInfo"]
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": {
                      "data": {
                        "scheduledWorkflows": {
                          "totalCount": 1,
                          "edges": [
                            {
                              "node": {
                                "id": 14,
                                "name": "Weekday stale-ticket check",
                                "agentId": 7,
                                "cronExpression": "0 9 * * 1-5",
                                "timezone": "Asia/Kuala_Lumpur",
                                "status": "ACTIVE",
                                "lastRunAt": "2026-10-16T01:00:00.412Z",
                                "lastRunStatus": "completed",
                                "consecutiveFailureCount": 0,
                                "nextRunAt": "2026-10-19T01:00:00.000Z"
                              }
                            }
                          ],
                          "pageInfo": {
                            "hasNextPage": false,
                            "endCursor": "MTQ="
                          }
                        }
                      }
                    }
                  },
                  "200-2": {
                    "summary": "BAD_REQUEST: Page size out of range",
                    "value": {
                      "errors": [
                        {
                          "message": "first must be between 1 and 100",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["scheduledWorkflows"],
                          "extensions": {
                            "code": "BAD_REQUEST",
                            "originalError": {
                              "message": "first must be between 1 and 100",
                              "error": "Bad Request",
                              "statusCode": 400
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  },
                  "200-3": {
                    "summary": "UNAUTHENTICATED: Called with an API key instead of a JWT",
                    "value": {
                      "errors": [
                        {
                          "message": "Unauthorized",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["scheduledWorkflows"],
                          "extensions": {
                            "code": "UNAUTHENTICATED",
                            "originalError": {
                              "message": "Unauthorized",
                              "statusCode": 401
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "query",
          "field": "scheduledWorkflows",
          "capability": "schedules"
        }
      }
    },
    "/graphql#createKnowledgeBase": {
      "post": {
        "operationId": "createKnowledgeBase",
        "tags": ["Knowledge bases (RAG)"],
        "summary": "Create an empty knowledge base.",
        "description": "Create an empty knowledge base.\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n**From the schema:**\n\nCreate a new named knowledge base container for this tenant. Subject to the plan's max-knowledge-base quota.",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "mutation CreateKb($input: CreateKnowledgeBaseInput!) {\n  createKnowledgeBase(input: $input) {\n    id\n    name\n    description\n  }\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["CreateKb"]
                  },
                  "variables": {
                    "type": "object",
                    "properties": {
                      "input": {
                        "$ref": "#/components/schemas/CreateKnowledgeBaseInput"
                      }
                    },
                    "required": ["input"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "Create an empty knowledge base.",
                  "value": {
                    "query": "mutation CreateKb($input: CreateKnowledgeBaseInput!) {\n  createKnowledgeBase(input: $input) {\n    id\n    name\n    description\n  }\n}\n",
                    "operationName": "CreateKb",
                    "variables": {
                      "input": {
                        "name": "IT helpdesk articles",
                        "description": "How-to articles the helpdesk agent may quote."
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "createKnowledgeBase": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "integer",
                              "description": "Server-generated integer id of the knowledge base."
                            },
                            "name": {
                              "type": "string",
                              "description": "Display name (at most 120 characters)."
                            },
                            "description": {
                              "type": ["string", "null"],
                              "description": "Free-text description; null when none was given."
                            }
                          },
                          "required": ["id", "name", "description"],
                          "description": "A named collection of documents an agent can search (retrieval-augmented generation)."
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": {
                      "data": {
                        "createKnowledgeBase": {
                          "id": 5,
                          "name": "IT helpdesk articles",
                          "description": "How-to articles the helpdesk agent may quote."
                        }
                      }
                    }
                  },
                  "200-2": {
                    "summary": "BAD_REQUEST: Plan limit reached",
                    "value": {
                      "errors": [
                        {
                          "message": "Creating another knowledge base would exceed your plan's limit of 3. Upgrade your plan or delete an existing knowledge base.",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["createKnowledgeBase"],
                          "extensions": {
                            "code": "BAD_REQUEST",
                            "originalError": {
                              "message": "Creating another knowledge base would exceed your plan's limit of 3. Upgrade your plan or delete an existing knowledge base.",
                              "error": "Bad Request",
                              "statusCode": 400
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "mutation",
          "field": "createKnowledgeBase",
          "capability": "knowledge"
        }
      }
    },
    "/graphql#knowledgeBases": {
      "post": {
        "operationId": "knowledgeBases",
        "tags": ["Knowledge bases (RAG)"],
        "summary": "List the workspace's knowledge bases.",
        "description": "List the workspace's knowledge bases.\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n**From the schema:**\n\nAll knowledge bases owned by the current tenant.",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "query ListKbs {\n  knowledgeBases {\n    id\n    name\n    description\n    createdAt\n  }\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["ListKbs"]
                  },
                  "variables": { "type": "object", "properties": {} }
                }
              },
              "examples": {
                "default": {
                  "summary": "List the workspace's knowledge bases.",
                  "value": {
                    "query": "query ListKbs {\n  knowledgeBases {\n    id\n    name\n    description\n    createdAt\n  }\n}\n",
                    "operationName": "ListKbs",
                    "variables": {}
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "knowledgeBases": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "integer",
                                "description": "Server-generated integer id of the knowledge base."
                              },
                              "name": {
                                "type": "string",
                                "description": "Display name (at most 120 characters)."
                              },
                              "description": {
                                "type": ["string", "null"],
                                "description": "Free-text description; null when none was given."
                              },
                              "createdAt": {
                                "type": "string",
                                "format": "date-time",
                                "description": "When the knowledge base was created (ISO 8601, server-set)."
                              }
                            },
                            "required": [
                              "id",
                              "name",
                              "description",
                              "createdAt"
                            ],
                            "description": "A named collection of documents an agent can search (retrieval-augmented generation)."
                          }
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": {
                      "data": {
                        "knowledgeBases": [
                          {
                            "id": 5,
                            "name": "IT helpdesk articles",
                            "description": "How-to articles the helpdesk agent may quote.",
                            "createdAt": "2026-10-12T04:20:00.000Z"
                          }
                        ]
                      }
                    }
                  },
                  "200-2": {
                    "summary": "UNAUTHENTICATED: Called with an API key instead of a JWT",
                    "value": {
                      "errors": [
                        {
                          "message": "Unauthorized",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["knowledgeBases"],
                          "extensions": {
                            "code": "UNAUTHENTICATED",
                            "originalError": {
                              "message": "Unauthorized",
                              "statusCode": 401
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "query",
          "field": "knowledgeBases",
          "capability": "knowledge"
        }
      }
    },
    "/graphql#ingestDocument": {
      "post": {
        "operationId": "ingestDocument",
        "tags": ["Knowledge bases (RAG)"],
        "summary": "Add pasted text as a document. Chunked and embedded at once; returns the number of chunks.",
        "description": "Add pasted text as a document. Chunked and embedded at once; returns the number of chunks.\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- Text only, at most 500 KB. The document is `READY` as soon as this returns.\n\n**From the schema:**\n\nIngest inline pasted text into a knowledge base — chunks, embeds, and persists it synchronously, subject to plan quotas (chunk count and storage MB). Text only (max 500KB). For a real file (PDF, DOCX, Markdown, CSV, or XLSX), use requestKnowledgeDocumentUpload + confirmKnowledgeDocumentUpload instead — that path parses asynchronously. Returns the number of chunks created.",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "mutation AddText($input: IngestDocumentInput!) {\n  ingestDocument(input: $input)\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["AddText"]
                  },
                  "variables": {
                    "type": "object",
                    "properties": {
                      "input": {
                        "$ref": "#/components/schemas/IngestDocumentInput"
                      }
                    },
                    "required": ["input"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "Add pasted text as a document. Chunked and embedded at once; returns the number of chunks.",
                  "value": {
                    "query": "mutation AddText($input: IngestDocumentInput!) {\n  ingestDocument(input: $input)\n}\n",
                    "operationName": "AddText",
                    "variables": {
                      "input": {
                        "knowledgeBaseId": 5,
                        "fileName": "reset-password.md",
                        "text": "To reset your password, open the self-service portal, choose Forgot password and follow the code sent to your work phone. Codes expire after 10 minutes."
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": { "ingestDocument": { "type": "integer" } }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success (number of chunks created)",
                    "value": { "data": { "ingestDocument": 1 } }
                  },
                  "200-2": {
                    "summary": "NOT_FOUND: Knowledge base not in your workspace",
                    "value": {
                      "errors": [
                        {
                          "message": "Knowledge base 5 not found",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["ingestDocument"],
                          "extensions": {
                            "code": "NOT_FOUND",
                            "status": 404,
                            "originalError": {
                              "message": "Knowledge base 5 not found",
                              "error": "Not Found",
                              "statusCode": 404
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "mutation",
          "field": "ingestDocument",
          "capability": "knowledge"
        }
      }
    },
    "/graphql#requestKnowledgeDocumentUpload": {
      "post": {
        "operationId": "requestKnowledgeDocumentUpload",
        "tags": ["Knowledge bases (RAG)"],
        "summary": "File upload, step 1: get a short-lived URL to PUT the file to.",
        "description": "File upload, step 1: get a short-lived URL to PUT the file to.\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- Then upload the bytes yourself, with the same `Content-Type` as `mimeType`: `curl -X PUT -H \"Content-Type: application/pdf\" --data-binary @vpn-guide.pdf \"$UPLOAD_URL\"`. The URL expires after 15 minutes.\n\n- Allowed `mimeType` values: `application/pdf`, `application/vnd.openxmlformats-officedocument.wordprocessingml.document` (DOCX), `text/markdown`, `text/csv`, `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` (XLSX). At most 20 MB.\n\n- Keep `objectStorageKey`; step 2 needs it. Limited to 10 calls per minute per client IP.\n\n**From the schema:**\n\nStep 1 of file upload: validates the knowledge base ownership and a soft (non-authoritative) quota pre-check, then returns a presigned object-storage PUT URL (15 min expiry) plus the objectStorageKey to use when confirming. The browser uploads the file bytes directly to uploadUrl — they never pass through this server. PDF, DOCX, Markdown, CSV, or XLSX only, max 20MB. Rate-limited (10/min per client).",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "mutation RequestUpload($input: RequestKnowledgeDocumentUploadInput!) {\n  requestKnowledgeDocumentUpload(input: $input) {\n    uploadUrl\n    objectStorageKey\n  }\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["RequestUpload"]
                  },
                  "variables": {
                    "type": "object",
                    "properties": {
                      "input": {
                        "$ref": "#/components/schemas/RequestKnowledgeDocumentUploadInput"
                      }
                    },
                    "required": ["input"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "File upload, step 1: get a short-lived URL to PUT the file to.",
                  "value": {
                    "query": "mutation RequestUpload($input: RequestKnowledgeDocumentUploadInput!) {\n  requestKnowledgeDocumentUpload(input: $input) {\n    uploadUrl\n    objectStorageKey\n  }\n}\n",
                    "operationName": "RequestUpload",
                    "variables": {
                      "input": {
                        "knowledgeBaseId": 5,
                        "fileName": "vpn-guide.pdf",
                        "mimeType": "application/pdf",
                        "declaredSizeBytes": 482113
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "requestKnowledgeDocumentUpload": {
                          "type": "object",
                          "properties": {
                            "uploadUrl": {
                              "type": "string",
                              "description": "Presigned URL to PUT the file bytes to directly, with Content-Type equal to the requested mimeType. Valid for 15 minutes; treat it as a secret."
                            },
                            "objectStorageKey": {
                              "type": "string",
                              "description": "Server-generated storage key of the upload; pass it unchanged to confirmKnowledgeDocumentUpload."
                            }
                          },
                          "required": ["uploadUrl", "objectStorageKey"],
                          "description": "Where to upload the file, and the key to confirm it with."
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": {
                      "data": {
                        "requestKnowledgeDocumentUpload": {
                          "uploadUrl": "https://storage.example.com/uploads/tenant-3/kb-5/0b7c2f9e-vpn-guide.pdf?X-Signature=example-not-real",
                          "objectStorageKey": "tenant-3/kb-5/0b7c2f9e-vpn-guide.pdf"
                        }
                      }
                    }
                  },
                  "200-2": {
                    "summary": "BAD_REQUEST: File type not supported",
                    "value": {
                      "errors": [
                        {
                          "message": "mimeType must be one of the following values: application/pdf, application/vnd.openxmlformats-officedocument.wordprocessingml.document, text/markdown, text/csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["requestKnowledgeDocumentUpload"],
                          "extensions": {
                            "code": "BAD_REQUEST",
                            "originalError": {
                              "message": "mimeType must be one of the following values: application/pdf, application/vnd.openxmlformats-officedocument.wordprocessingml.document, text/markdown, text/csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
                              "error": "Bad Request",
                              "statusCode": 400
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  },
                  "200-3": {
                    "summary": "BAD_REQUEST: File too large",
                    "value": {
                      "errors": [
                        {
                          "message": "File size 31457280 bytes exceeds the maximum upload size of 20971520 bytes.",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["requestKnowledgeDocumentUpload"],
                          "extensions": {
                            "code": "BAD_REQUEST",
                            "originalError": {
                              "message": "File size 31457280 bytes exceeds the maximum upload size of 20971520 bytes.",
                              "error": "Bad Request",
                              "statusCode": 400
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "mutation",
          "field": "requestKnowledgeDocumentUpload",
          "capability": "knowledge"
        }
      }
    },
    "/graphql#confirmKnowledgeDocumentUpload": {
      "post": {
        "operationId": "confirmKnowledgeDocumentUpload",
        "tags": ["Knowledge bases (RAG)"],
        "summary": "File upload, step 2: confirm the upload finished. Parsing starts in the background.",
        "description": "File upload, step 2: confirm the upload finished. Parsing starts in the background.\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- Call it after your `PUT` succeeded. The document starts `PENDING`, then `PARSING`, then `READY` or `FAILED`.\n\n- Limited to 10 calls per minute per client IP.\n\n**From the schema:**\n\nStep 2 of file upload: call this after the browser finishes PUTting the file to the URL from requestKnowledgeDocumentUpload. Verifies the upload actually completed, reserves storage quota against the real file size, creates the document row (parseStatus: PENDING), and enqueues async parsing. Poll the knowledgeDocuments query to see parseStatus transition to READY (or FAILED with a parseError). Rate-limited (10/min per client).",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "mutation ConfirmUpload($input: ConfirmKnowledgeDocumentUploadInput!) {\n  confirmKnowledgeDocumentUpload(input: $input) {\n    documentId\n    parseStatus\n  }\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["ConfirmUpload"]
                  },
                  "variables": {
                    "type": "object",
                    "properties": {
                      "input": {
                        "$ref": "#/components/schemas/ConfirmKnowledgeDocumentUploadInput"
                      }
                    },
                    "required": ["input"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "File upload, step 2: confirm the upload finished. Parsing starts in the background.",
                  "value": {
                    "query": "mutation ConfirmUpload($input: ConfirmKnowledgeDocumentUploadInput!) {\n  confirmKnowledgeDocumentUpload(input: $input) {\n    documentId\n    parseStatus\n  }\n}\n",
                    "operationName": "ConfirmUpload",
                    "variables": {
                      "input": {
                        "knowledgeBaseId": 5,
                        "objectStorageKey": "tenant-3/kb-5/0b7c2f9e-vpn-guide.pdf",
                        "fileName": "vpn-guide.pdf",
                        "mimeType": "application/pdf"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "confirmKnowledgeDocumentUpload": {
                          "type": "object",
                          "properties": {
                            "documentId": {
                              "type": "integer",
                              "description": "Id of the new document; find it in knowledgeDocuments."
                            },
                            "parseStatus": {
                              "type": "string",
                              "description": "PENDING: parsing is queued. Poll knowledgeDocuments until it is READY or FAILED."
                            }
                          },
                          "required": ["documentId", "parseStatus"],
                          "description": "The document created for a confirmed upload."
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": {
                      "data": {
                        "confirmKnowledgeDocumentUpload": {
                          "documentId": 61,
                          "parseStatus": "PENDING"
                        }
                      }
                    }
                  },
                  "200-2": {
                    "summary": "BAD_REQUEST: The PUT did not complete",
                    "value": {
                      "errors": [
                        {
                          "message": "No file was found at this upload location — the upload may not have completed. Request a new upload URL and try again.",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["confirmKnowledgeDocumentUpload"],
                          "extensions": {
                            "code": "BAD_REQUEST",
                            "originalError": {
                              "message": "No file was found at this upload location — the upload may not have completed. Request a new upload URL and try again.",
                              "error": "Bad Request",
                              "statusCode": 400
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "mutation",
          "field": "confirmKnowledgeDocumentUpload",
          "capability": "knowledge"
        }
      }
    },
    "/graphql#knowledgeDocuments": {
      "post": {
        "operationId": "knowledgeDocuments",
        "tags": ["Knowledge bases (RAG)"],
        "summary": "List the documents of a knowledge base with their parse status (poll this after an upload).",
        "description": "List the documents of a knowledge base with their parse status (poll this after an upload).\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- `parseStatus` is `PENDING`, `PARSING`, `READY` or `FAILED` (then `parseError` says why). Pasted-text documents are `READY` at once.\n\n- Do not select `rawText` here; read one document's full text with `knowledgeDocument(id)`.\n\n**From the schema:**\n\nAll documents in a knowledge base owned by this tenant, most recent first. Includes parseStatus — poll or refetch this after confirmKnowledgeDocumentUpload to see PENDING → PARSING → READY (or FAILED, with parseError set) for an uploaded file. Inline-text documents (via ingestDocument) are READY immediately.",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "query Documents($knowledgeBaseId: Int!) {\n  knowledgeDocuments(knowledgeBaseId: $knowledgeBaseId) {\n    id\n    fileName\n    mimeType\n    fileSizeBytes\n    parseStatus\n    parseError\n  }\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["Documents"]
                  },
                  "variables": {
                    "type": "object",
                    "properties": { "knowledgeBaseId": { "type": "integer" } },
                    "required": ["knowledgeBaseId"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "List the documents of a knowledge base with their parse status (poll this after an upload).",
                  "value": {
                    "query": "query Documents($knowledgeBaseId: Int!) {\n  knowledgeDocuments(knowledgeBaseId: $knowledgeBaseId) {\n    id\n    fileName\n    mimeType\n    fileSizeBytes\n    parseStatus\n    parseError\n  }\n}\n",
                    "operationName": "Documents",
                    "variables": { "knowledgeBaseId": 5 }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "knowledgeDocuments": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "integer",
                                "description": "Server-generated integer id of the document."
                              },
                              "fileName": {
                                "type": "string",
                                "description": "Name given at ingestion or upload (at most 255 characters)."
                              },
                              "mimeType": {
                                "type": "string",
                                "description": "Type of the source: \"text/plain\" for pasted text, otherwise the MIME type the file was uploaded with."
                              },
                              "fileSizeBytes": {
                                "type": "integer",
                                "description": "Size in bytes that counts against the storage quota: the uploaded file size, or the UTF-8 size of pasted text."
                              },
                              "parseStatus": {
                                "type": "string",
                                "description": "One of PENDING, PARSING, READY or FAILED. Pasted text is READY at once; an uploaded file goes PENDING, then PARSING, then READY or FAILED."
                              },
                              "parseError": {
                                "type": ["string", "null"],
                                "description": "Why parsing failed; null unless parseStatus is FAILED."
                              }
                            },
                            "required": [
                              "id",
                              "fileName",
                              "mimeType",
                              "fileSizeBytes",
                              "parseStatus",
                              "parseError"
                            ],
                            "description": "One document of a knowledge base: pasted text or an uploaded file, with its parse state."
                          }
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": {
                      "data": {
                        "knowledgeDocuments": [
                          {
                            "id": 61,
                            "fileName": "vpn-guide.pdf",
                            "mimeType": "application/pdf",
                            "fileSizeBytes": 482113,
                            "parseStatus": "READY",
                            "parseError": null
                          },
                          {
                            "id": 60,
                            "fileName": "reset-password.md",
                            "mimeType": "text/plain",
                            "fileSizeBytes": 160,
                            "parseStatus": "READY",
                            "parseError": null
                          }
                        ]
                      }
                    }
                  },
                  "200-2": {
                    "summary": "UNAUTHENTICATED: Called with an API key instead of a JWT",
                    "value": {
                      "errors": [
                        {
                          "message": "Unauthorized",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["knowledgeDocuments"],
                          "extensions": {
                            "code": "UNAUTHENTICATED",
                            "originalError": {
                              "message": "Unauthorized",
                              "statusCode": 401
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "query",
          "field": "knowledgeDocuments",
          "capability": "knowledge"
        }
      }
    },
    "/graphql#knowledgeDocument": {
      "post": {
        "operationId": "knowledgeDocument",
        "tags": ["Knowledge bases (RAG)"],
        "summary": "Read one document with its status and full text.",
        "description": "Read one document with its status and full text.\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- `rawText` is the pasted text or the text extracted from the file. It is empty while the document is `PENDING` or `PARSING`.\n\n- Use it to check what the agent can actually quote after a file was parsed (scanned PDFs, for example, can come out empty).\n\n**From the schema:**\n\nA single document owned by this tenant, including its full rawText — unlike knowledgeDocuments (the list query), which never selects this field. Use to preview/edit a document's content.",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "query Document($id: Int!) {\n  knowledgeDocument(id: $id) {\n    id\n    fileName\n    mimeType\n    parseStatus\n    parseError\n    rawText\n    updatedAt\n  }\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["Document"]
                  },
                  "variables": {
                    "type": "object",
                    "properties": { "id": { "type": "integer" } },
                    "required": ["id"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "Read one document with its status and full text.",
                  "value": {
                    "query": "query Document($id: Int!) {\n  knowledgeDocument(id: $id) {\n    id\n    fileName\n    mimeType\n    parseStatus\n    parseError\n    rawText\n    updatedAt\n  }\n}\n",
                    "operationName": "Document",
                    "variables": { "id": 61 }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "knowledgeDocument": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "integer",
                              "description": "Server-generated integer id of the document."
                            },
                            "fileName": {
                              "type": "string",
                              "description": "Name given at ingestion or upload (at most 255 characters)."
                            },
                            "mimeType": {
                              "type": "string",
                              "description": "Type of the source: \"text/plain\" for pasted text, otherwise the MIME type the file was uploaded with."
                            },
                            "parseStatus": {
                              "type": "string",
                              "description": "One of PENDING, PARSING, READY or FAILED. Pasted text is READY at once; an uploaded file goes PENDING, then PARSING, then READY or FAILED."
                            },
                            "parseError": {
                              "type": ["string", "null"],
                              "description": "Why parsing failed; null unless parseStatus is FAILED."
                            },
                            "rawText": {
                              "type": "string",
                              "description": "The document text: what was pasted, or the text extracted from the file. Empty while PENDING or PARSING. Select it on knowledgeDocument(id) rather than on the list."
                            },
                            "updatedAt": {
                              "type": "string",
                              "format": "date-time",
                              "description": "When the document last changed (ISO 8601, server-set)."
                            }
                          },
                          "required": [
                            "id",
                            "fileName",
                            "mimeType",
                            "parseStatus",
                            "parseError",
                            "rawText",
                            "updatedAt"
                          ],
                          "description": "One document of a knowledge base: pasted text or an uploaded file, with its parse state."
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": {
                      "data": {
                        "knowledgeDocument": {
                          "id": 61,
                          "fileName": "vpn-guide.pdf",
                          "mimeType": "application/pdf",
                          "parseStatus": "READY",
                          "parseError": null,
                          "rawText": "Connecting to the VPN. Install the VPN client from the self-service portal, sign in with your work account and approve the prompt on your phone.",
                          "updatedAt": "2026-10-12T04:31:09.000Z"
                        }
                      }
                    }
                  },
                  "200-2": {
                    "summary": "NOT_FOUND: Not in your workspace",
                    "value": {
                      "errors": [
                        {
                          "message": "Knowledge document 61 not found",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["knowledgeDocument"],
                          "extensions": {
                            "code": "NOT_FOUND",
                            "status": 404,
                            "originalError": {
                              "message": "Knowledge document 61 not found",
                              "error": "Not Found",
                              "statusCode": 404
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "query",
          "field": "knowledgeDocument",
          "capability": "knowledge"
        }
      }
    },
    "/graphql#resyncKnowledgeDocument": {
      "post": {
        "operationId": "resyncKnowledgeDocument",
        "tags": ["Knowledge bases (RAG)"],
        "summary": "Retry a FAILED file upload, or re-chunk and re-embed a READY document from its stored text.",
        "description": "Retry a FAILED file upload, or re-chunk and re-embed a READY document from its stored text.\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- A `FAILED` upload goes back to `PENDING` and is parsed again in the background; poll `knowledgeDocument` or `knowledgeDocuments`. A `READY` document is re-embedded before this returns.\n\n- Refused while the document is `PENDING` or `PARSING`. Re-embedding is a metered model call and counts against your plan's chunk quota.\n\n**From the schema:**\n\nRe-run ingestion for an existing document owned by this tenant. A FAILED file upload is re-queued for a fresh parse+embed attempt (parseStatus goes back to PENDING). A READY document is re-chunked and re-embedded in place from its already-stored text — useful after a chunking/embedding change, without re-uploading anything. Rejected if the document is currently PENDING or PARSING.",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "mutation Resync($id: Int!) {\n  resyncKnowledgeDocument(id: $id)\n}\n"
                    ]
                  },
                  "operationName": { "type": "string", "examples": ["Resync"] },
                  "variables": {
                    "type": "object",
                    "properties": { "id": { "type": "integer" } },
                    "required": ["id"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "Retry a FAILED file upload, or re-chunk and re-embed a READY document from its stored text.",
                  "value": {
                    "query": "mutation Resync($id: Int!) {\n  resyncKnowledgeDocument(id: $id)\n}\n",
                    "operationName": "Resync",
                    "variables": { "id": 61 }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "resyncKnowledgeDocument": { "type": "boolean" }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": { "data": { "resyncKnowledgeDocument": true } }
                  },
                  "200-2": {
                    "summary": "BAD_REQUEST: Still being parsed",
                    "value": {
                      "errors": [
                        {
                          "message": "Document 61 is already being parsed — wait for it to finish before resyncing.",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["resyncKnowledgeDocument"],
                          "extensions": {
                            "code": "BAD_REQUEST",
                            "originalError": {
                              "message": "Document 61 is already being parsed — wait for it to finish before resyncing.",
                              "error": "Bad Request",
                              "statusCode": 400
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  },
                  "200-3": {
                    "summary": "NOT_FOUND: Not in your workspace",
                    "value": {
                      "errors": [
                        {
                          "message": "Knowledge document 61 not found",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["resyncKnowledgeDocument"],
                          "extensions": {
                            "code": "NOT_FOUND",
                            "status": 404,
                            "originalError": {
                              "message": "Knowledge document 61 not found",
                              "error": "Not Found",
                              "statusCode": 404
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "mutation",
          "field": "resyncKnowledgeDocument",
          "capability": "knowledge"
        }
      }
    },
    "/graphql#retrieveChunks": {
      "post": {
        "operationId": "retrieveChunks",
        "tags": ["Knowledge bases (RAG)"],
        "summary": "Search a knowledge base the way an llm node does, and see which chunks come back.",
        "description": "Search a knowledge base the way an llm node does, and see which chunks come back.\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- Returns the `topK` chunks (default 5, at most 20) most similar to `query`, most relevant first. `score` is the cosine similarity (higher is more relevant).\n\n- Use it to test whether an article will be found for a typical customer question before you rely on it in a workflow. Embedding the query is a metered model call.\n\n- An unknown knowledge base, or one of another workspace, returns an empty list.\n\n**From the schema:**\n\nDiagnostic/test query: embed `query` and return the topK (max 20) most semantically similar chunks from a knowledge base owned by this tenant, ordered by cosine similarity (most relevant first). Same retrieval path an LLM workflow node with knowledgeBaseId set uses internally.",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "query Search($knowledgeBaseId: Int!, $query: String!, $topK: Int) {\n  retrieveChunks(knowledgeBaseId: $knowledgeBaseId, query: $query, topK: $topK) {\n    chunkId\n    documentId\n    score\n    content\n  }\n}\n"
                    ]
                  },
                  "operationName": { "type": "string", "examples": ["Search"] },
                  "variables": {
                    "type": "object",
                    "properties": {
                      "knowledgeBaseId": { "type": "integer" },
                      "query": { "type": "string" },
                      "topK": { "type": ["integer", "null"] }
                    },
                    "required": ["knowledgeBaseId", "query"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "Search a knowledge base the way an llm node does, and see which chunks come back.",
                  "value": {
                    "query": "query Search($knowledgeBaseId: Int!, $query: String!, $topK: Int) {\n  retrieveChunks(knowledgeBaseId: $knowledgeBaseId, query: $query, topK: $topK) {\n    chunkId\n    documentId\n    score\n    content\n  }\n}\n",
                    "operationName": "Search",
                    "variables": {
                      "knowledgeBaseId": 5,
                      "query": "I forgot my password and the code never arrives",
                      "topK": 3
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "retrieveChunks": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "chunkId": { "type": "integer" },
                              "documentId": { "type": "integer" },
                              "score": { "type": "number" },
                              "content": { "type": "string" }
                            },
                            "required": [
                              "chunkId",
                              "documentId",
                              "score",
                              "content"
                            ]
                          }
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": {
                      "data": {
                        "retrieveChunks": [
                          {
                            "chunkId": 812,
                            "documentId": 60,
                            "score": 0.8132,
                            "content": "To reset your password, open the self-service portal, choose Forgot password and follow the code sent to your work phone. Codes expire after 10 minutes."
                          },
                          {
                            "chunkId": 845,
                            "documentId": 61,
                            "score": 0.6017,
                            "content": "Connecting to the VPN. Install the VPN client from the self-service portal, sign in with your work account and approve the prompt on your phone."
                          }
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request itself was refused before running: variables that do not match their types (`BAD_USER_INPUT`) or an invalid document (`GRAPHQL_VALIDATION_FAILED`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "400-1": {
                    "summary": "BAD_USER_INPUT: Query missing",
                    "value": {
                      "errors": [
                        {
                          "message": "Variable \"$query\" of required type \"String!\" was not provided.",
                          "locations": [{ "line": 1, "column": 38 }],
                          "extensions": { "code": "BAD_USER_INPUT" }
                        }
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "query",
          "field": "retrieveChunks",
          "capability": "knowledge"
        }
      }
    },
    "/graphql#deleteKnowledgeDocument": {
      "post": {
        "operationId": "deleteKnowledgeDocument",
        "tags": ["Knowledge bases (RAG)"],
        "summary": "Delete one document and its chunks, releasing its quota. The knowledge base stays.",
        "description": "Delete one document and its chunks, releasing its quota. The knowledge base stays.\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n**From the schema:**\n\nSoft-delete a single document (and its chunks) from a knowledge base owned by this tenant, releasing its chunk-count and storage quota reservation. Does not delete the knowledge base itself.",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "mutation DeleteDocument($id: Int!) {\n  deleteKnowledgeDocument(id: $id)\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["DeleteDocument"]
                  },
                  "variables": {
                    "type": "object",
                    "properties": { "id": { "type": "integer" } },
                    "required": ["id"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "Delete one document and its chunks, releasing its quota. The knowledge base stays.",
                  "value": {
                    "query": "mutation DeleteDocument($id: Int!) {\n  deleteKnowledgeDocument(id: $id)\n}\n",
                    "operationName": "DeleteDocument",
                    "variables": { "id": 61 }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "deleteKnowledgeDocument": { "type": "boolean" }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": { "data": { "deleteKnowledgeDocument": true } }
                  },
                  "200-2": {
                    "summary": "NOT_FOUND: Not in your workspace",
                    "value": {
                      "errors": [
                        {
                          "message": "Knowledge document 61 not found",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["deleteKnowledgeDocument"],
                          "extensions": {
                            "code": "NOT_FOUND",
                            "status": 404,
                            "originalError": {
                              "message": "Knowledge document 61 not found",
                              "error": "Not Found",
                              "statusCode": 404
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "mutation",
          "field": "deleteKnowledgeDocument",
          "capability": "knowledge"
        }
      }
    },
    "/graphql#deleteKnowledgeBase": {
      "post": {
        "operationId": "deleteKnowledgeBase",
        "tags": ["Knowledge bases (RAG)"],
        "summary": "Delete a knowledge base.",
        "description": "Delete a knowledge base.\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- Delete its documents first with `deleteKnowledgeDocument` if you want their quota back; deleting the knowledge base does not delete them.\n\n**From the schema:**\n\nSoft-delete a knowledge base for this tenant. Its documents and chunks are not deleted with it.",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "mutation DeleteKb($id: Int!) {\n  deleteKnowledgeBase(id: $id)\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["DeleteKb"]
                  },
                  "variables": {
                    "type": "object",
                    "properties": { "id": { "type": "integer" } },
                    "required": ["id"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "Delete a knowledge base.",
                  "value": {
                    "query": "mutation DeleteKb($id: Int!) {\n  deleteKnowledgeBase(id: $id)\n}\n",
                    "operationName": "DeleteKb",
                    "variables": { "id": 5 }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "deleteKnowledgeBase": { "type": "boolean" }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": { "data": { "deleteKnowledgeBase": true } }
                  },
                  "200-2": {
                    "summary": "NOT_FOUND: Not in your workspace",
                    "value": {
                      "errors": [
                        {
                          "message": "Knowledge base 5 not found",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["deleteKnowledgeBase"],
                          "extensions": {
                            "code": "NOT_FOUND",
                            "status": 404,
                            "originalError": {
                              "message": "Knowledge base 5 not found",
                              "error": "Not Found",
                              "statusCode": 404
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "mutation",
          "field": "deleteKnowledgeBase",
          "capability": "knowledge"
        }
      }
    },
    "/graphql#createMcpConnector": {
      "post": {
        "operationId": "createMcpConnector",
        "tags": ["MCP connectors"],
        "summary": "Register an MCP tool server for your workspace.",
        "description": "Register an MCP tool server for your workspace.\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- `creditsPerCall` is optional: the credits charged for one successful call of any tool on this connector. Omit it for the platform default.\n\n**From the schema:**\n\nRegisters a new MCP connector (third-party tool server credential/endpoint) for the caller's tenant.",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "mutation CreateConnector($input: CreateMcpConnectorInput!) {\n  createMcpConnector(input: $input) {\n    id\n    name\n    serverUrl\n    creditsPerCall\n  }\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["CreateConnector"]
                  },
                  "variables": {
                    "type": "object",
                    "properties": {
                      "input": {
                        "$ref": "#/components/schemas/CreateMcpConnectorInput"
                      }
                    },
                    "required": ["input"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "Register an MCP tool server for your workspace.",
                  "value": {
                    "query": "mutation CreateConnector($input: CreateMcpConnectorInput!) {\n  createMcpConnector(input: $input) {\n    id\n    name\n    serverUrl\n    creditsPerCall\n  }\n}\n",
                    "operationName": "CreateConnector",
                    "variables": {
                      "input": {
                        "name": "Ticketing tools",
                        "serverUrl": "https://tools.example.com/mcp",
                        "apiKey": "example-tool-server-key-not-real"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "createMcpConnector": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "integer",
                              "description": "Server-generated integer id of the connector."
                            },
                            "name": {
                              "type": "string",
                              "description": "Display name (at most 100 characters)."
                            },
                            "serverUrl": {
                              "type": "string",
                              "description": "URL of the MCP server, reached over streamable HTTP."
                            },
                            "creditsPerCall": {
                              "type": ["integer", "null"],
                              "description": "Credits charged for one successful call of any tool on this connector; null means the platform default (2 credits)."
                            }
                          },
                          "required": [
                            "id",
                            "name",
                            "serverUrl",
                            "creditsPerCall"
                          ],
                          "description": "A remote MCP (Model Context Protocol) server registered for your workspace, whose tools TOOL workflow nodes can call. Its credential is never returned."
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": {
                      "data": {
                        "createMcpConnector": {
                          "id": 4,
                          "name": "Ticketing tools",
                          "serverUrl": "https://tools.example.com/mcp",
                          "creditsPerCall": null
                        }
                      }
                    }
                  },
                  "200-2": {
                    "summary": "UNAUTHENTICATED: Called with an API key instead of a JWT",
                    "value": {
                      "errors": [
                        {
                          "message": "Unauthorized",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["createMcpConnector"],
                          "extensions": {
                            "code": "UNAUTHENTICATED",
                            "originalError": {
                              "message": "Unauthorized",
                              "statusCode": 401
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request itself was refused before running: variables that do not match their types (`BAD_USER_INPUT`) or an invalid document (`GRAPHQL_VALIDATION_FAILED`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "400-1": {
                    "summary": "BAD_USER_INPUT: A required field is missing",
                    "value": {
                      "errors": [
                        {
                          "message": "Variable \"$input\" got invalid value { name: \"Ticketing tools\", serverUrl: \"https://tools.example.com/mcp\" }; Field \"apiKey\" of required type \"String!\" was not provided.",
                          "locations": [{ "line": 1, "column": 26 }],
                          "extensions": { "code": "BAD_USER_INPUT" }
                        }
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "mutation",
          "field": "createMcpConnector",
          "capability": "mcp-connectors"
        }
      }
    },
    "/graphql#mcpConnectors": {
      "post": {
        "operationId": "mcpConnectors",
        "tags": ["MCP connectors"],
        "summary": "List the workspace's MCP connectors, newest first.",
        "description": "List the workspace's MCP connectors, newest first.\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n**From the schema:**\n\nLists every MCP connector registered for the caller's tenant.",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "query ListConnectors {\n  mcpConnectors {\n    id\n    name\n    serverUrl\n    creditsPerCall\n    createdAt\n  }\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["ListConnectors"]
                  },
                  "variables": { "type": "object", "properties": {} }
                }
              },
              "examples": {
                "default": {
                  "summary": "List the workspace's MCP connectors, newest first.",
                  "value": {
                    "query": "query ListConnectors {\n  mcpConnectors {\n    id\n    name\n    serverUrl\n    creditsPerCall\n    createdAt\n  }\n}\n",
                    "operationName": "ListConnectors",
                    "variables": {}
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "mcpConnectors": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "integer",
                                "description": "Server-generated integer id of the connector."
                              },
                              "name": {
                                "type": "string",
                                "description": "Display name (at most 100 characters)."
                              },
                              "serverUrl": {
                                "type": "string",
                                "description": "URL of the MCP server, reached over streamable HTTP."
                              },
                              "creditsPerCall": {
                                "type": ["integer", "null"],
                                "description": "Credits charged for one successful call of any tool on this connector; null means the platform default (2 credits)."
                              },
                              "createdAt": {
                                "type": "string",
                                "format": "date-time",
                                "description": "When the connector was created (ISO 8601, server-set)."
                              }
                            },
                            "required": [
                              "id",
                              "name",
                              "serverUrl",
                              "creditsPerCall",
                              "createdAt"
                            ],
                            "description": "A remote MCP (Model Context Protocol) server registered for your workspace, whose tools TOOL workflow nodes can call. Its credential is never returned."
                          }
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": {
                      "data": {
                        "mcpConnectors": [
                          {
                            "id": 4,
                            "name": "Ticketing tools",
                            "serverUrl": "https://tools.example.com/mcp",
                            "creditsPerCall": null,
                            "createdAt": "2026-10-12T04:30:00.000Z"
                          }
                        ]
                      }
                    }
                  },
                  "200-2": {
                    "summary": "UNAUTHENTICATED: Missing credential",
                    "value": {
                      "errors": [
                        {
                          "message": "Unauthorized",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["mcpConnectors"],
                          "extensions": {
                            "code": "UNAUTHENTICATED",
                            "originalError": {
                              "message": "Unauthorized",
                              "statusCode": 401
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "query",
          "field": "mcpConnectors",
          "capability": "mcp-connectors"
        }
      }
    },
    "/graphql#listMcpTools": {
      "post": {
        "operationId": "listMcpTools",
        "tags": ["MCP connectors"],
        "summary": "Connect to the server and list its tools. Use it to test a connector before a workflow relies on it.",
        "description": "Connect to the server and list its tools. Use it to test a connector before a workflow relies on it.\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- This calls your server live with the stored key. A server that cannot be reached, or that refuses the key, answers `INTERNAL_SERVER_ERROR` with a message describing the connection problem.\n\n- `inputSchema` is the tool's own JSON Schema for its arguments, as your server declares it.\n\n**From the schema:**\n\nLists the tool schemas exposed by a given MCP connector — what an Agent/workflow using this connector can actually call.",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "query Tools($connectorId: Int!) {\n  listMcpTools(connectorId: $connectorId) {\n    name\n    description\n    inputSchema\n  }\n}\n"
                    ]
                  },
                  "operationName": { "type": "string", "examples": ["Tools"] },
                  "variables": {
                    "type": "object",
                    "properties": { "connectorId": { "type": "integer" } },
                    "required": ["connectorId"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "Connect to the server and list its tools. Use it to test a connector before a workflow relies on it.",
                  "value": {
                    "query": "query Tools($connectorId: Int!) {\n  listMcpTools(connectorId: $connectorId) {\n    name\n    description\n    inputSchema\n  }\n}\n",
                    "operationName": "Tools",
                    "variables": { "connectorId": 4 }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "listMcpTools": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "name": {
                                "type": "string",
                                "description": "Tool name; use it as toolName in a TOOL node."
                              },
                              "description": {
                                "type": ["string", "null"],
                                "description": "The server's own description of the tool; null when it gives none."
                              },
                              "inputSchema": {
                                "description": "The server's JSON Schema for the tool arguments, passed through unchanged; null when it gives none."
                              }
                            },
                            "required": ["name", "description", "inputSchema"],
                            "description": "One tool an MCP server offers, read live from the server (tools/list)."
                          }
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": {
                      "data": {
                        "listMcpTools": [
                          {
                            "name": "create_ticket",
                            "description": "Create a ticket in the ticketing system and return its number.",
                            "inputSchema": {
                              "type": "object",
                              "properties": {
                                "subject": { "type": "string" },
                                "level": {
                                  "type": "string",
                                  "enum": ["L1", "L2", "L3"]
                                }
                              },
                              "required": ["subject", "level"]
                            }
                          }
                        ]
                      }
                    }
                  },
                  "200-2": {
                    "summary": "NOT_FOUND: Connector not in your workspace",
                    "value": {
                      "errors": [
                        {
                          "message": "Connector #4 not found",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["listMcpTools"],
                          "extensions": {
                            "code": "NOT_FOUND",
                            "status": 404,
                            "originalError": {
                              "message": "Connector #4 not found",
                              "error": "Not Found",
                              "statusCode": 404
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "query",
          "field": "listMcpTools",
          "capability": "mcp-connectors"
        }
      }
    },
    "/graphql#deleteMcpConnector": {
      "post": {
        "operationId": "deleteMcpConnector",
        "tags": ["MCP connectors"],
        "summary": "Remove an MCP connector. Workflow nodes that use it stop working.",
        "description": "Remove an MCP connector. Workflow nodes that use it stop working.\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n**From the schema:**\n\nDeletes an MCP connector owned by the caller's tenant.",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "mutation DeleteConnector($id: Int!) {\n  deleteMcpConnector(id: $id)\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["DeleteConnector"]
                  },
                  "variables": {
                    "type": "object",
                    "properties": { "id": { "type": "integer" } },
                    "required": ["id"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "Remove an MCP connector. Workflow nodes that use it stop working.",
                  "value": {
                    "query": "mutation DeleteConnector($id: Int!) {\n  deleteMcpConnector(id: $id)\n}\n",
                    "operationName": "DeleteConnector",
                    "variables": { "id": 4 }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "deleteMcpConnector": { "type": "boolean" }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": { "data": { "deleteMcpConnector": true } }
                  },
                  "200-2": {
                    "summary": "NOT_FOUND: Not in your workspace",
                    "value": {
                      "errors": [
                        {
                          "message": "Connector #4 not found",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["deleteMcpConnector"],
                          "extensions": {
                            "code": "NOT_FOUND",
                            "status": 404,
                            "originalError": {
                              "message": "Connector #4 not found",
                              "error": "Not Found",
                              "statusCode": 404
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "mutation",
          "field": "deleteMcpConnector",
          "capability": "mcp-connectors"
        }
      }
    },
    "/graphql#createCustomAction": {
      "post": {
        "operationId": "createCustomAction",
        "tags": ["Custom actions"],
        "summary": "Register a REST call for workflow action nodes.",
        "description": "Register a REST call for workflow action nodes.\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- `method` is one of `GET`, `POST`, `PUT`, `PATCH`, `DELETE` (default `POST`).\n\n- `argsSchema` documents the arguments the call expects; it is not enforced at run time.\n\n- Limited to 10 calls per minute per client IP.\n\n**From the schema:**\n\nRegisters a new custom action (a named, reusable REST call definition — URL, HTTP method, and optional credential) for the caller's tenant, for use by ACTION workflow nodes. Credential options: bearerToken (static secret), larkAppId+larkAppSecret (tenant's own Lark app), or credentialSource set to a SESSION_ value (resolved live from the end user signed in to the conversation through single sign-on) — supply at most one.",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "mutation CreateAction($input: CreateCustomActionInput!) {\n  createCustomAction(input: $input) {\n    id\n    name\n    method\n    url\n    credentialSecretHeaderName\n  }\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["CreateAction"]
                  },
                  "variables": {
                    "type": "object",
                    "properties": {
                      "input": {
                        "$ref": "#/components/schemas/CreateCustomActionInput"
                      }
                    },
                    "required": ["input"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "Register a REST call for workflow action nodes.",
                  "value": {
                    "query": "mutation CreateAction($input: CreateCustomActionInput!) {\n  createCustomAction(input: $input) {\n    id\n    name\n    method\n    url\n    credentialSecretHeaderName\n  }\n}\n",
                    "operationName": "CreateAction",
                    "variables": {
                      "input": {
                        "name": "create_ticket",
                        "description": "Create a ticket in the ticketing backend and return its number.",
                        "url": "https://tickets.example.com/api/tickets",
                        "method": "POST",
                        "bearerToken": "example-backend-token-not-real",
                        "argsSchema": {
                          "type": "object",
                          "properties": {
                            "subject": { "type": "string" },
                            "level": {
                              "type": "string",
                              "enum": ["L1", "L2", "L3"]
                            }
                          },
                          "required": ["subject", "level"]
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "createCustomAction": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "integer",
                              "description": "Server-generated integer id of the custom action."
                            },
                            "name": {
                              "type": "string",
                              "description": "Display name (at most 100 characters)."
                            },
                            "method": {
                              "type": "string",
                              "description": "HTTP method: GET, POST, PUT, PATCH or DELETE (default POST). GET and DELETE send the arguments as the query string, the others as a JSON body."
                            },
                            "url": {
                              "type": "string",
                              "description": "The URL called."
                            },
                            "credentialSecretHeaderName": {
                              "type": ["string", "null"],
                              "description": "Header the stored bearerToken is sent under, raw and without \"Bearer \"; null means the default Authorization: Bearer header."
                            }
                          },
                          "required": [
                            "id",
                            "name",
                            "method",
                            "url",
                            "credentialSecretHeaderName"
                          ],
                          "description": "A reusable REST call (URL, method, credential) of your workspace that ACTION workflow nodes call. The stored credential is never returned."
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": {
                      "data": {
                        "createCustomAction": {
                          "id": 9,
                          "name": "create_ticket",
                          "method": "POST",
                          "url": "https://tickets.example.com/api/tickets",
                          "credentialSecretHeaderName": null
                        }
                      }
                    }
                  },
                  "200-2": {
                    "summary": "BAD_REQUEST: Header name without a secret",
                    "value": {
                      "errors": [
                        {
                          "message": "credentialSecretHeaderName requires bearerToken in the same call — a header name with no secret to send under it has no effect.",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["createCustomAction"],
                          "extensions": {
                            "code": "BAD_REQUEST",
                            "originalError": {
                              "message": "credentialSecretHeaderName requires bearerToken in the same call — a header name with no secret to send under it has no effect.",
                              "error": "Bad Request",
                              "statusCode": 400
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  },
                  "200-3": {
                    "summary": "BAD_REQUEST: Unsupported method",
                    "value": {
                      "errors": [
                        {
                          "message": "method must be one of the following values: GET, POST, PUT, PATCH, DELETE",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["createCustomAction"],
                          "extensions": {
                            "code": "BAD_REQUEST",
                            "originalError": {
                              "message": "method must be one of the following values: GET, POST, PUT, PATCH, DELETE",
                              "error": "Bad Request",
                              "statusCode": 400
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "mutation",
          "field": "createCustomAction",
          "capability": "custom-actions"
        }
      }
    },
    "/graphql#customActions": {
      "post": {
        "operationId": "customActions",
        "tags": ["Custom actions"],
        "summary": "List the workspace's custom actions.",
        "description": "List the workspace's custom actions.\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n**From the schema:**\n\nLists every custom action (plain-REST structured call definition, used by ACTION workflow nodes) registered for the caller's tenant.",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "query ListActions {\n  customActions {\n    id\n    name\n    method\n    url\n    description\n    creditsPerCall\n  }\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["ListActions"]
                  },
                  "variables": { "type": "object", "properties": {} }
                }
              },
              "examples": {
                "default": {
                  "summary": "List the workspace's custom actions.",
                  "value": {
                    "query": "query ListActions {\n  customActions {\n    id\n    name\n    method\n    url\n    description\n    creditsPerCall\n  }\n}\n",
                    "operationName": "ListActions",
                    "variables": {}
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "customActions": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "integer",
                                "description": "Server-generated integer id of the custom action."
                              },
                              "name": {
                                "type": "string",
                                "description": "Display name (at most 100 characters)."
                              },
                              "method": {
                                "type": "string",
                                "description": "HTTP method: GET, POST, PUT, PATCH or DELETE (default POST). GET and DELETE send the arguments as the query string, the others as a JSON body."
                              },
                              "url": {
                                "type": "string",
                                "description": "The URL called."
                              },
                              "description": {
                                "type": ["string", "null"],
                                "description": "Free-text description; null when none was given."
                              },
                              "creditsPerCall": {
                                "type": ["integer", "null"],
                                "description": "Credits charged for one successful call; null means the platform default (2 credits)."
                              }
                            },
                            "required": [
                              "id",
                              "name",
                              "method",
                              "url",
                              "description",
                              "creditsPerCall"
                            ],
                            "description": "A reusable REST call (URL, method, credential) of your workspace that ACTION workflow nodes call. The stored credential is never returned."
                          }
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": {
                      "data": {
                        "customActions": [
                          {
                            "id": 9,
                            "name": "create_ticket",
                            "method": "POST",
                            "url": "https://tickets.example.com/api/tickets",
                            "description": "Create a ticket in the ticketing backend and return its number.",
                            "creditsPerCall": null
                          }
                        ]
                      }
                    }
                  },
                  "200-2": {
                    "summary": "UNAUTHENTICATED: Called with an API key instead of a JWT",
                    "value": {
                      "errors": [
                        {
                          "message": "Unauthorized",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["customActions"],
                          "extensions": {
                            "code": "UNAUTHENTICATED",
                            "originalError": {
                              "message": "Unauthorized",
                              "statusCode": 401
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "query",
          "field": "customActions",
          "capability": "custom-actions"
        }
      }
    },
    "/graphql#testCustomAction": {
      "post": {
        "operationId": "testCustomAction",
        "tags": ["Custom actions"],
        "summary": "Make one real call of a custom action and classify the answer, before a conversation depends on it.",
        "description": "Make one real call of a custom action and classify the answer, before a conversation depends on it.\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- The call really happens, through the same path a workflow uses. An action whose method is not `GET` needs `confirmWrite: true` and may change data on the target.\n\n- `sampleArgs` (a flat JSON object) is sent as the body, or as the query string for `GET` and `DELETE`, exactly as written: template syntax such as `{{x}}` is refused.\n\n- `outcome` tells you what to fix: `CREDENTIAL_REJECTED` (401/403: the stored token or its header is wrong), `REQUEST_REJECTED` (other 4xx), `SERVER_ERROR` (5xx), `TIMEOUT`, `UNREACHABLE`, `BLOCKED` (a private or reserved address), `WRITE_NOT_CONFIRMED`. The response snippet is masked and cut to 500 characters.\n\n- A successful test is billed like the same call in a workflow (`creditsBilled`); a failed or refused test is free. Limited to 10 calls per minute per client IP.\n\n**From the schema:**\n\nMakes ONE real call of one of your custom actions, through the same path a workflow ACTION node uses (outbound-address safety check, stored-credential resolution, header injection, redirects refused), and classifies the answer so you can fix a wrong URL or credential BEFORE a live conversation hits it: a 401/403 comes back as CREDENTIAL_REJECTED, other 4xx as REQUEST_REJECTED, 5xx as SERVER_ERROR, then TIMEOUT, UNREACHABLE, BLOCKED and so on. This is a mutation because the call really happens: an action whose method is not GET needs `confirmWrite: true` and may change data on the target. `sampleArgs` (a flat JSON object) is sent verbatim as the body, or the query string for GET/DELETE; template syntax is rejected. Actions that use a per-end-user credential (SESSION_*) cannot be tested here (NOT_TESTABLE). A successful test is billed exactly like the same call in a workflow (the action's creditsPerCall, or the platform default) and `creditsBilled` says how much; a failed or refused test is free. The response snippet is masked and cut to 500 characters. Rate-limited to 10 per minute per client IP.\n\n**Allowed values:**\n\n- `CustomActionTestOutcome`: `BLOCKED`, `CREDENTIAL_REJECTED`, `CREDENTIAL_UNAVAILABLE`, `NOT_TESTABLE`, `OK`, `REQUEST_REJECTED`, `SERVER_ERROR`, `TIMEOUT`, `UNKNOWN_ERROR`, `UNREACHABLE`, `WRITE_NOT_CONFIRMED`",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "mutation TestAction($input: TestCustomActionInput!) {\n  testCustomAction(input: $input) {\n    outcome\n    httpStatus\n    message\n    responseSnippet\n    durationMs\n    creditsBilled\n  }\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["TestAction"]
                  },
                  "variables": {
                    "type": "object",
                    "properties": {
                      "input": {
                        "$ref": "#/components/schemas/TestCustomActionInput"
                      }
                    },
                    "required": ["input"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "Make one real call of a custom action and classify the answer, before a conversation depends on it.",
                  "value": {
                    "query": "mutation TestAction($input: TestCustomActionInput!) {\n  testCustomAction(input: $input) {\n    outcome\n    httpStatus\n    message\n    responseSnippet\n    durationMs\n    creditsBilled\n  }\n}\n",
                    "operationName": "TestAction",
                    "variables": {
                      "input": {
                        "id": 9,
                        "confirmWrite": true,
                        "sampleArgs": {
                          "subject": "Test ticket from the developer portal",
                          "level": "L1"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "testCustomAction": {
                          "type": "object",
                          "properties": {
                            "outcome": {
                              "$ref": "#/components/schemas/CustomActionTestOutcome"
                            },
                            "httpStatus": {
                              "type": ["integer", "null"],
                              "description": "HTTP status the target answered with, when it answered."
                            },
                            "message": {
                              "type": "string",
                              "description": "A short human-readable explanation of the outcome, with anything credential-shaped masked."
                            },
                            "responseSnippet": {
                              "type": ["string", "null"],
                              "description": "The start of the target's response (masked, at most 500 characters) so you can see what it said. Null when nothing came back."
                            },
                            "durationMs": {
                              "type": "integer",
                              "description": "How long the call took in milliseconds; 0 when it was refused before any call."
                            },
                            "creditsBilled": {
                              "type": "integer",
                              "description": "Credits charged for this test. A successful call is billed exactly like the same call in a workflow (the action's creditsPerCall, or the platform default); a failed or refused test is free."
                            }
                          },
                          "required": [
                            "outcome",
                            "httpStatus",
                            "message",
                            "responseSnippet",
                            "durationMs",
                            "creditsBilled"
                          ],
                          "description": "Outcome of one real test call of a custom action, made through the same path a workflow node uses."
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "OK",
                    "value": {
                      "data": {
                        "testCustomAction": {
                          "outcome": "OK",
                          "httpStatus": 200,
                          "message": "The target answered successfully.",
                          "responseSnippet": "{\"ticketNumber\":\"T-1003\"}",
                          "durationMs": 284,
                          "creditsBilled": 2
                        }
                      }
                    }
                  },
                  "200-2": {
                    "summary": "Write not confirmed (a classified outcome, not an error)",
                    "value": {
                      "data": {
                        "testCustomAction": {
                          "outcome": "WRITE_NOT_CONFIRMED",
                          "httpStatus": null,
                          "message": "This action uses POST, so the test call would really be made and may change data on the target. Send confirmWrite: true to run it.",
                          "responseSnippet": null,
                          "durationMs": 0,
                          "creditsBilled": 0
                        }
                      }
                    }
                  },
                  "200-3": {
                    "summary": "BAD_REQUEST: Template syntax in sampleArgs",
                    "value": {
                      "errors": [
                        {
                          "message": "sampleArgs are sent verbatim and may not contain template syntax ({{ }}).",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["testCustomAction"],
                          "extensions": {
                            "code": "BAD_REQUEST",
                            "originalError": {
                              "message": "sampleArgs are sent verbatim and may not contain template syntax ({{ }}).",
                              "error": "Bad Request",
                              "statusCode": 400
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  },
                  "200-4": {
                    "summary": "NOT_FOUND: Not in your workspace",
                    "value": {
                      "errors": [
                        {
                          "message": "Custom action #9 not found",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["testCustomAction"],
                          "extensions": {
                            "code": "NOT_FOUND",
                            "status": 404,
                            "originalError": {
                              "message": "Custom action #9 not found",
                              "error": "Not Found",
                              "statusCode": 404
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "mutation",
          "field": "testCustomAction",
          "capability": "custom-actions"
        }
      }
    },
    "/graphql#deleteCustomAction": {
      "post": {
        "operationId": "deleteCustomAction",
        "tags": ["Custom actions"],
        "summary": "Delete a custom action. Action nodes that use it fail when they run.",
        "description": "Delete a custom action. Action nodes that use it fail when they run.\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- Limited to 10 calls per minute per client IP.\n\n**From the schema:**\n\nDeletes a custom action owned by the caller's tenant.",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "mutation DeleteAction($id: Int!) {\n  deleteCustomAction(id: $id)\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["DeleteAction"]
                  },
                  "variables": {
                    "type": "object",
                    "properties": { "id": { "type": "integer" } },
                    "required": ["id"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "Delete a custom action. Action nodes that use it fail when they run.",
                  "value": {
                    "query": "mutation DeleteAction($id: Int!) {\n  deleteCustomAction(id: $id)\n}\n",
                    "operationName": "DeleteAction",
                    "variables": { "id": 9 }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "deleteCustomAction": { "type": "boolean" }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": { "data": { "deleteCustomAction": true } }
                  },
                  "200-2": {
                    "summary": "NOT_FOUND: Not in your workspace",
                    "value": {
                      "errors": [
                        {
                          "message": "Custom action #9 not found",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["deleteCustomAction"],
                          "extensions": {
                            "code": "NOT_FOUND",
                            "status": 404,
                            "originalError": {
                              "message": "Custom action #9 not found",
                              "error": "Not Found",
                              "statusCode": 404
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "mutation",
          "field": "deleteCustomAction",
          "capability": "custom-actions"
        }
      }
    },
    "/graphql#approvals": {
      "post": {
        "operationId": "approvals",
        "tags": ["Approvals"],
        "summary": "List approval requests, newest first. Without a filter it lists only PENDING requests.",
        "description": "List approval requests, newest first. Without a filter it lists only PENDING requests.\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\n**Workspace owner only.** A member is refused with `FORBIDDEN`.\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- Pass `status` for one state or `statuses` for several in one list (for example every decided request). Passing both, or an empty `statuses` list, is a `BAD_REQUEST`.\n\n- Forward pagination: `first` (default 20, at most 100) and `after` (a previous page's `pageInfo.endCursor`, which walks to older rows). Optional `workflowId` and `agentId` narrow the list.\n\n- A request past `expiresAt` that has not been swept yet still reads `PENDING` but can no longer be decided.\n\n- `payload` is the thing being decided and may contain customer data.\n\n**From the schema:**\n\nThe approvals inbox: requests an AI employee's workflow raised for a manager to approve or reject before it continues. Newest first, scoped to your workspace, owner-only. With neither `status` nor `statuses` it lists only PENDING requests (the work waiting for you); pass a status such as APPROVED, REJECTED, EXPIRED or CANCELLED to read history, or `statuses` to read several states in one newest-first list (for example every decided request: APPROVED, REJECTED, EXPIRED, CANCELLED). `status` and `statuses` together, or an empty `statuses` list, is a BAD_REQUEST error. Optional `workflowId` / `agentId` narrow it. Relay forward pagination: `first` (default 20, capped at 100) and `after` (a previous page's pageInfo.endCursor, which walks to OLDER rows). The payload is the thing being decided and may contain customer data. A request past `expiresAt` that the background sweeper has not yet closed still reads PENDING but can no longer be decided.\n\n**Allowed values:**\n\n- `ApprovalStatus`: `APPROVED`, `CANCELLED`, `CHANGES_REQUESTED`, `EXPIRED`, `PENDING`, `REJECTED`",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "query Inbox($statuses: [ApprovalStatus!], $first: Int) {\n  approvals(statuses: $statuses, first: $first) {\n    edges {\n      node {\n        id\n        status\n        title\n        summary\n        payload\n        editableFields\n        expiresAt\n        workflowRunId\n      }\n    }\n    pageInfo {\n      hasNextPage\n      endCursor\n    }\n  }\n}\n"
                    ]
                  },
                  "operationName": { "type": "string", "examples": ["Inbox"] },
                  "variables": {
                    "type": "object",
                    "properties": {
                      "statuses": {
                        "type": ["array", "null"],
                        "items": {
                          "$ref": "#/components/schemas/ApprovalStatus"
                        }
                      },
                      "first": { "type": ["integer", "null"] }
                    }
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "List approval requests, newest first. Without a filter it lists only PENDING requests.",
                  "value": {
                    "query": "query Inbox($statuses: [ApprovalStatus!], $first: Int) {\n  approvals(statuses: $statuses, first: $first) {\n    edges {\n      node {\n        id\n        status\n        title\n        summary\n        payload\n        editableFields\n        expiresAt\n        workflowRunId\n      }\n    }\n    pageInfo {\n      hasNextPage\n      endCursor\n    }\n  }\n}\n",
                    "operationName": "Inbox",
                    "variables": { "statuses": ["PENDING"], "first": 20 }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "approvals": {
                          "type": "object",
                          "properties": {
                            "edges": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "node": {
                                    "type": "object",
                                    "properties": {
                                      "id": {
                                        "type": "integer",
                                        "description": "Server-generated integer id of the approval request."
                                      },
                                      "status": {
                                        "$ref": "#/components/schemas/ApprovalStatus"
                                      },
                                      "title": {
                                        "type": "string",
                                        "description": "Headline shown in the inbox, rendered from the node's titleTemplate (1 to 200 characters)."
                                      },
                                      "summary": {
                                        "type": "string",
                                        "description": "What the approver reads, rendered from the node's summaryTemplate (at most 4000 characters; empty string when the node has none)."
                                      },
                                      "payload": {
                                        "description": "The thing being decided: a JSON object taken from the workflow variable named by the node's payloadVar, with secrets redacted before storage (at most 64 KB; an empty object when the node names none). May contain customer data."
                                      },
                                      "editableFields": {
                                        "type": "array",
                                        "items": { "type": "string" },
                                        "description": "Keys of payload the approver may change through decideApproval editedPayload; an empty list means approve or reject only."
                                      },
                                      "expiresAt": {
                                        "type": "string",
                                        "format": "date-time",
                                        "description": "When the request expires if nobody decides (ISO 8601): the node's expiresAfterSeconds after it opened, 72 hours by default and at most 14 days."
                                      },
                                      "workflowRunId": {
                                        "type": "integer",
                                        "description": "Id of the paused workflow run, readable through the workflowRuns history."
                                      }
                                    },
                                    "required": [
                                      "id",
                                      "status",
                                      "title",
                                      "summary",
                                      "payload",
                                      "editableFields",
                                      "expiresAt",
                                      "workflowRunId"
                                    ],
                                    "description": "The approval request."
                                  }
                                },
                                "required": ["node"],
                                "description": "One approval request and its cursor."
                              },
                              "description": "This page, newest first."
                            },
                            "pageInfo": {
                              "type": "object",
                              "properties": {
                                "hasNextPage": {
                                  "type": "boolean",
                                  "description": "True when an older page exists."
                                },
                                "endCursor": {
                                  "type": ["string", "null"],
                                  "description": "Pass as `after` to read the next (older) page; null when the page is empty."
                                }
                              },
                              "required": ["hasNextPage", "endCursor"],
                              "description": "Forward paging info."
                            }
                          },
                          "required": ["edges", "pageInfo"],
                          "description": "A page of approval requests, newest first."
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": {
                      "data": {
                        "approvals": {
                          "edges": [
                            {
                              "node": {
                                "id": 118,
                                "status": "PENDING",
                                "title": "Emergency escalation for ticket T-1001",
                                "summary": "Severity high, many people affected. Page the on-call lead?",
                                "payload": {
                                  "ticketId": "T-1001",
                                  "severity": "high",
                                  "queue": "emergency"
                                },
                                "editableFields": ["queue"],
                                "expiresAt": "2026-10-12T05:15:00.000Z",
                                "workflowRunId": 5530
                              }
                            }
                          ],
                          "pageInfo": {
                            "hasNextPage": false,
                            "endCursor": "MTE4"
                          }
                        }
                      }
                    }
                  },
                  "200-2": {
                    "summary": "BAD_REQUEST: Both status and statuses passed",
                    "value": {
                      "errors": [
                        {
                          "message": "Pass either `status` or `statuses`, not both",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["approvals"],
                          "extensions": {
                            "code": "BAD_REQUEST",
                            "originalError": {
                              "message": "Pass either `status` or `statuses`, not both",
                              "error": "Bad Request",
                              "statusCode": 400
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  },
                  "200-3": {
                    "summary": "FORBIDDEN: Caller is a member, not the owner",
                    "value": {
                      "errors": [
                        {
                          "message": "Only the owner of this workspace can do this",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["approvals"],
                          "extensions": {
                            "code": "FORBIDDEN",
                            "originalError": {
                              "message": "Only the owner of this workspace can do this",
                              "error": "Forbidden",
                              "statusCode": 403
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request itself was refused before running: variables that do not match their types (`BAD_USER_INPUT`) or an invalid document (`GRAPHQL_VALIDATION_FAILED`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "400-1": {
                    "summary": "BAD_USER_INPUT: Unknown status value",
                    "value": {
                      "errors": [
                        {
                          "message": "Variable \"$statuses\" got invalid value \"OPEN\" at \"statuses[0]\"; Value \"OPEN\" does not exist in \"ApprovalStatus\" enum.",
                          "locations": [{ "line": 1, "column": 13 }],
                          "extensions": { "code": "BAD_USER_INPUT" }
                        }
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "query",
          "field": "approvals",
          "capability": "approvals"
        }
      }
    },
    "/graphql#approval": {
      "post": {
        "operationId": "approval",
        "tags": ["Approvals"],
        "summary": "Read one approval request by id.",
        "description": "Read one approval request by id.\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\n**Workspace owner only.** A member is refused with `FORBIDDEN`.\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n**From the schema:**\n\nOne approval request in your workspace: title, summary, the payload under decision, which of its keys you may edit (`editableFields`), the workflow run that paused (`workflowRunId`, readable through the run history) and its current status. Owner-only. NotFound for an id that does not exist in your workspace, including another workspace's.\n\n**Allowed values:**\n\n- `ApprovalStatus`: `APPROVED`, `CANCELLED`, `CHANGES_REQUESTED`, `EXPIRED`, `PENDING`, `REJECTED`",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "query OneApproval($id: ID!) {\n  approval(id: $id) {\n    id\n    status\n    title\n    payload\n    editableFields\n    decidedAt\n    decisionComment\n  }\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["OneApproval"]
                  },
                  "variables": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": ["string", "integer"],
                        "description": "GraphQL ID: send a string or an integer."
                      }
                    },
                    "required": ["id"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "Read one approval request by id.",
                  "value": {
                    "query": "query OneApproval($id: ID!) {\n  approval(id: $id) {\n    id\n    status\n    title\n    payload\n    editableFields\n    decidedAt\n    decisionComment\n  }\n}\n",
                    "operationName": "OneApproval",
                    "variables": { "id": "118" }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "approval": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "integer",
                              "description": "Server-generated integer id of the approval request."
                            },
                            "status": {
                              "$ref": "#/components/schemas/ApprovalStatus"
                            },
                            "title": {
                              "type": "string",
                              "description": "Headline shown in the inbox, rendered from the node's titleTemplate (1 to 200 characters)."
                            },
                            "payload": {
                              "description": "The thing being decided: a JSON object taken from the workflow variable named by the node's payloadVar, with secrets redacted before storage (at most 64 KB; an empty object when the node names none). May contain customer data."
                            },
                            "editableFields": {
                              "type": "array",
                              "items": { "type": "string" },
                              "description": "Keys of payload the approver may change through decideApproval editedPayload; an empty list means approve or reject only."
                            },
                            "decidedAt": {
                              "type": ["string", "null"],
                              "format": "date-time",
                              "description": "When it was decided, cancelled or expired (ISO 8601); null while PENDING."
                            },
                            "decisionComment": {
                              "type": ["string", "null"],
                              "description": "The comment given with decideApproval, or the reason given with cancelApproval (at most 1000 characters); null when none was given."
                            }
                          },
                          "required": [
                            "id",
                            "status",
                            "title",
                            "payload",
                            "editableFields",
                            "decidedAt",
                            "decisionComment"
                          ],
                          "description": "A request for a manager to approve or reject a step of an AI employee."
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": {
                      "data": {
                        "approval": {
                          "id": 118,
                          "status": "APPROVED",
                          "title": "Emergency escalation for ticket T-1001",
                          "payload": {
                            "ticketId": "T-1001",
                            "severity": "high",
                            "queue": "emergency"
                          },
                          "editableFields": ["queue"],
                          "decidedAt": "2026-10-12T03:21:44.000Z",
                          "decisionComment": "Confirmed with the on-call lead."
                        }
                      }
                    }
                  },
                  "200-2": {
                    "summary": "NOT_FOUND: Not in your workspace",
                    "value": {
                      "errors": [
                        {
                          "message": "Approval #118 not found",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["approval"],
                          "extensions": {
                            "code": "NOT_FOUND",
                            "status": 404,
                            "originalError": {
                              "message": "Approval #118 not found",
                              "error": "Not Found",
                              "statusCode": 404
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "query",
          "field": "approval",
          "capability": "approvals"
        }
      }
    },
    "/graphql#decideApproval": {
      "post": {
        "operationId": "decideApproval",
        "tags": ["Approvals"],
        "summary": "Approve or reject a pending request. The paused run resumes asynchronously.",
        "description": "Approve or reject a pending request. The paused run resumes asynchronously.\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\n**Workspace owner only.** A member is refused with `FORBIDDEN`.\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- `comment` is optional (at most 1,000 characters); the workflow reads it as `approval.comment`.\n\n- `editedPayload` is honoured on `APPROVE` only. Its keys must all be in the request's `editableFields`. Send it through variables, never as an inline literal.\n\n- Only one of several simultaneous decisions wins; the others receive `CONFLICT`. Limited to 30 decisions per minute per client IP by default.\n\n**From the schema:**\n\nApproves, rejects or sends back (REQUEST_CHANGES) a pending approval request. ASYNCHRONOUS: the response is the DECIDED REQUEST, not the finished run. The paused workflow run is resumed by a background worker on its next tick, down the `approved`, `rejected` or `changes_requested` edge, so its reply or side effects follow shortly after this returns. Owner-only. `comment` is optional (max 1000 characters) and is available to the workflow as approval.comment. `editedPayload` is a JSON object (send it through `variables`, not as an inline literal) honoured on APPROVE only; its keys must all be in the request's `editableFields`, and the edited values replace the originals for the rest of the run (approval.payload). Errors: NotFound for an id in no workspace you own; Conflict if it was already decided or cancelled (the message names who and when), or if it expired (`expiresAt` passed, even if the sweeper has not closed it yet); BadRequest for a comment over the limit, an edit to a key that is not editable, REQUEST_CHANGES without a comment, REQUEST_CHANGES on an approval step that has no changes_requested edge, or REQUEST_CHANGES after 3 revisions. Only one of several simultaneous decisions wins.\n\n**Allowed values:**\n\n- `ApprovalDecisionChoice`: `APPROVE`, `REJECT`, `REQUEST_CHANGES`\n- `ApprovalStatus`: `APPROVED`, `CANCELLED`, `CHANGES_REQUESTED`, `EXPIRED`, `PENDING`, `REJECTED`",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "mutation Decide($id: ID!, $decision: ApprovalDecisionChoice!, $comment: String, $editedPayload: JSON) {\n  decideApproval(id: $id, decision: $decision, comment: $comment, editedPayload: $editedPayload) {\n    id\n    status\n    decidedAt\n    editedPayload\n  }\n}\n"
                    ]
                  },
                  "operationName": { "type": "string", "examples": ["Decide"] },
                  "variables": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": ["string", "integer"],
                        "description": "GraphQL ID: send a string or an integer."
                      },
                      "decision": {
                        "$ref": "#/components/schemas/ApprovalDecisionChoice"
                      },
                      "comment": { "type": ["string", "null"] },
                      "editedPayload": {
                        "description": "Any JSON value. Send it through variables, never inline in the query."
                      }
                    },
                    "required": ["id", "decision"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "Approve or reject a pending request. The paused run resumes asynchronously.",
                  "value": {
                    "query": "mutation Decide($id: ID!, $decision: ApprovalDecisionChoice!, $comment: String, $editedPayload: JSON) {\n  decideApproval(id: $id, decision: $decision, comment: $comment, editedPayload: $editedPayload) {\n    id\n    status\n    decidedAt\n    editedPayload\n  }\n}\n",
                    "operationName": "Decide",
                    "variables": {
                      "id": "118",
                      "decision": "APPROVE",
                      "comment": "Confirmed with the on-call lead.",
                      "editedPayload": { "queue": "emergency-weekend" }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "decideApproval": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "integer",
                              "description": "Server-generated integer id of the approval request."
                            },
                            "status": {
                              "$ref": "#/components/schemas/ApprovalStatus"
                            },
                            "decidedAt": {
                              "type": ["string", "null"],
                              "format": "date-time",
                              "description": "When it was decided, cancelled or expired (ISO 8601); null while PENDING."
                            },
                            "editedPayload": {
                              "description": "The edits made on approval: a JSON object holding only keys from editableFields, whose values replace the originals for the rest of the run (payload keeps the originals). null when nothing was edited or the request was not approved."
                            }
                          },
                          "required": [
                            "id",
                            "status",
                            "decidedAt",
                            "editedPayload"
                          ],
                          "description": "A request for a manager to approve or reject a step of an AI employee."
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success (the run continues shortly after)",
                    "value": {
                      "data": {
                        "decideApproval": {
                          "id": 118,
                          "status": "APPROVED",
                          "decidedAt": "2026-10-12T03:21:44.000Z",
                          "editedPayload": { "queue": "emergency-weekend" }
                        }
                      }
                    }
                  },
                  "200-2": {
                    "summary": "CONFLICT: Already decided",
                    "value": {
                      "errors": [
                        {
                          "message": "Approval #118 was already approved by user #3 at 2026-10-12T03:21:44.000Z",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["decideApproval"],
                          "extensions": {
                            "code": "CONFLICT",
                            "status": 409,
                            "originalError": {
                              "message": "Approval #118 was already approved by user #3 at 2026-10-12T03:21:44.000Z",
                              "error": "Conflict",
                              "statusCode": 409
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  },
                  "200-3": {
                    "summary": "CONFLICT: Expired",
                    "value": {
                      "errors": [
                        {
                          "message": "Approval #118 expired at 2026-10-12T05:15:00.000Z and can no longer be decided",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["decideApproval"],
                          "extensions": {
                            "code": "CONFLICT",
                            "status": 409,
                            "originalError": {
                              "message": "Approval #118 expired at 2026-10-12T05:15:00.000Z and can no longer be decided",
                              "error": "Conflict",
                              "statusCode": 409
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  },
                  "200-4": {
                    "summary": "BAD_REQUEST: Edited a key that is not editable",
                    "value": {
                      "errors": [
                        {
                          "message": "Approval #118 does not allow editing: severity",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["decideApproval"],
                          "extensions": {
                            "code": "BAD_REQUEST",
                            "originalError": {
                              "message": "Approval #118 does not allow editing: severity",
                              "error": "Bad Request",
                              "statusCode": 400
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request itself was refused before running: variables that do not match their types (`BAD_USER_INPUT`) or an invalid document (`GRAPHQL_VALIDATION_FAILED`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "400-1": {
                    "summary": "BAD_USER_INPUT: Decision value is not APPROVE or REJECT",
                    "value": {
                      "errors": [
                        {
                          "message": "Variable \"$decision\" got invalid value \"APPROVED\"; Value \"APPROVED\" does not exist in \"ApprovalDecisionChoice\" enum. Did you mean the enum value \"APPROVE\"?",
                          "locations": [{ "line": 1, "column": 27 }],
                          "extensions": { "code": "BAD_USER_INPUT" }
                        }
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "mutation",
          "field": "decideApproval",
          "capability": "approvals"
        }
      }
    },
    "/graphql#cancelApproval": {
      "post": {
        "operationId": "cancelApproval",
        "tags": ["Approvals"],
        "summary": "Withdraw a pending request. The paused run does not resume.",
        "description": "Withdraw a pending request. The paused run does not resume.\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\n**Workspace owner only.** A member is refused with `FORBIDDEN`.\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- `reason` is optional (at most 1,000 characters).\n\n**From the schema:**\n\nWithdraws a PENDING approval request (status CANCELLED). The paused workflow run does NOT resume: it stays paused and its conversation, if any, is left as it was. Owner-only. `reason` is optional (max 1000 characters). NotFound for an id in no workspace you own; Conflict if the request is already decided, expired or cancelled.\n\n**Allowed values:**\n\n- `ApprovalStatus`: `APPROVED`, `CANCELLED`, `CHANGES_REQUESTED`, `EXPIRED`, `PENDING`, `REJECTED`",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "mutation Cancel($id: ID!, $reason: String) {\n  cancelApproval(id: $id, reason: $reason) {\n    id\n    status\n  }\n}\n"
                    ]
                  },
                  "operationName": { "type": "string", "examples": ["Cancel"] },
                  "variables": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": ["string", "integer"],
                        "description": "GraphQL ID: send a string or an integer."
                      },
                      "reason": { "type": ["string", "null"] }
                    },
                    "required": ["id"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "Withdraw a pending request. The paused run does not resume.",
                  "value": {
                    "query": "mutation Cancel($id: ID!, $reason: String) {\n  cancelApproval(id: $id, reason: $reason) {\n    id\n    status\n  }\n}\n",
                    "operationName": "Cancel",
                    "variables": {
                      "id": "118",
                      "reason": "Raised by mistake during testing."
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "cancelApproval": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "integer",
                              "description": "Server-generated integer id of the approval request."
                            },
                            "status": {
                              "$ref": "#/components/schemas/ApprovalStatus"
                            }
                          },
                          "required": ["id", "status"],
                          "description": "A request for a manager to approve or reject a step of an AI employee."
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": {
                      "data": {
                        "cancelApproval": { "id": 118, "status": "CANCELLED" }
                      }
                    }
                  },
                  "200-2": {
                    "summary": "CONFLICT: Already decided, expired or cancelled",
                    "value": {
                      "errors": [
                        {
                          "message": "Approval #118 is already approved and cannot be cancelled",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["cancelApproval"],
                          "extensions": {
                            "code": "CONFLICT",
                            "status": 409,
                            "originalError": {
                              "message": "Approval #118 is already approved and cannot be cancelled",
                              "error": "Conflict",
                              "statusCode": 409
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "mutation",
          "field": "cancelApproval",
          "capability": "approvals"
        }
      }
    },
    "/graphql#createNamedApiKey": {
      "post": {
        "operationId": "createNamedApiKey",
        "tags": ["API keys"],
        "summary": "Create a named API key. The raw key is returned once and cannot be read again.",
        "description": "Create a named API key. The raw key is returned once and cannot be read again.\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- Store `key` immediately in your server's secret store. Only a SHA-256 hash is kept on the platform side.\n\n- Keep `id`; it is what `revokeNamedApiKey` takes.\n\n- `scopes` is optional and adds rights beyond runtime access. The only accepted value today is `templates:hire`, and only a workspace owner may grant a scope. Omit it for a plain runtime key.\n\n**From the schema:**\n\nWhen the platform enforces email verification, an account whose email is not verified is refused with FORBIDDEN 'Verify your email address first' (code EMAIL_NOT_VERIFIED). While the workspace has member-built drafts on, only the workspace OWNER can create an API key (a member is refused with FORBIDDEN): a key carries no signed-in user, so release audiences do not narrow it. Creates a new NAMED API key for the current tenant, independent of the legacy single generateApiKey key. Use this for a specific consumer/integration (e.g. a demo app, an internal tool) so it can be revoked individually without affecting any other consumer sharing the tenant. The raw key is returned exactly once and cannot be retrieved again — store it immediately. The result also carries the new key's `id` and `name` (use the id with revokeNamedApiKey). Optional `scopes` grants the key extra rights beyond runtime access (allowed: \"templates:hire\", which lets a partner key hire role templates into this workspace, and \"groups:decide\", which lets a platform call the group-chat decision API: groupDecide and its siblings). Granting a scope is restricted to the workspace OWNER: a member who passes scopes is refused with Forbidden, while a member can still create a plain key. An unknown scope is a BadRequest and nothing is created. Omit `scopes` (or pass an empty list) for a plain runtime key.",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "mutation CreateKey($name: String!, $scopes: [String!]) {\n  createNamedApiKey(name: $name, scopes: $scopes) {\n    id\n    name\n    key\n    warning\n  }\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["CreateKey"]
                  },
                  "variables": {
                    "type": "object",
                    "properties": {
                      "name": { "type": "string" },
                      "scopes": {
                        "type": ["array", "null"],
                        "items": { "type": "string" }
                      }
                    },
                    "required": ["name"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "Create a named API key. The raw key is returned once and cannot be read again.",
                  "value": {
                    "query": "mutation CreateKey($name: String!, $scopes: [String!]) {\n  createNamedApiKey(name: $name, scopes: $scopes) {\n    id\n    name\n    key\n    warning\n  }\n}\n",
                    "operationName": "CreateKey",
                    "variables": { "name": "helpdesk-sandbox" }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "createNamedApiKey": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "integer",
                              "description": "Id of the new key. Pass it to revokeNamedApiKey, or match it in the namedApiKeys list."
                            },
                            "name": {
                              "type": "string",
                              "description": "The name the key was created with."
                            },
                            "key": {
                              "type": "string",
                              "description": "The raw API key. Shown exactly once; it cannot be retrieved again."
                            },
                            "warning": {
                              "type": "string",
                              "description": "Fixed reminder text for a person: store the key now, it will not be shown again."
                            }
                          },
                          "required": ["id", "name", "key", "warning"],
                          "description": "A newly created named API key, including the raw key (shown only in this response)."
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": {
                      "data": {
                        "createNamedApiKey": {
                          "id": 31,
                          "name": "helpdesk-sandbox",
                          "key": "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
                          "warning": "Save this key immediately. It will not be shown again. Revoking it does not affect any other named key or the legacy tenant API key."
                        }
                      }
                    }
                  },
                  "200-2": {
                    "summary": "BAD_REQUEST: Unknown scope (sent with scopes [\"admin\"])",
                    "value": {
                      "errors": [
                        {
                          "message": "Unknown API key scope(s): admin. Allowed: templates:hire.",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["createNamedApiKey"],
                          "extensions": {
                            "code": "BAD_REQUEST",
                            "originalError": {
                              "message": "Unknown API key scope(s): admin. Allowed: templates:hire.",
                              "error": "Bad Request",
                              "statusCode": 400
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  },
                  "200-3": {
                    "summary": "FORBIDDEN: A member (not the owner) asked for a scope",
                    "value": {
                      "errors": [
                        {
                          "message": "Only the owner of this workspace can do this",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["createNamedApiKey"],
                          "extensions": {
                            "code": "FORBIDDEN",
                            "originalError": {
                              "message": "Only the owner of this workspace can do this",
                              "error": "Forbidden",
                              "statusCode": 403
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  },
                  "200-4": {
                    "summary": "UNAUTHENTICATED: Called with an API key instead of a JWT",
                    "value": {
                      "errors": [
                        {
                          "message": "Unauthorized",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["createNamedApiKey"],
                          "extensions": {
                            "code": "UNAUTHENTICATED",
                            "originalError": {
                              "message": "Unauthorized",
                              "statusCode": 401
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "mutation",
          "field": "createNamedApiKey",
          "capability": "api-keys"
        }
      }
    },
    "/graphql#namedApiKeys": {
      "post": {
        "operationId": "namedApiKeys",
        "tags": ["API keys"],
        "summary": "List the workspace's named API keys (metadata only, never the key itself).",
        "description": "List the workspace's named API keys (metadata only, never the key itself).\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- Revoked and expired keys are included; filter on `revokedAt` and `expiresAt` for an \"active only\" view.\n\n**From the schema:**\n\nLists the caller's tenant's named API keys (metadata only — never the hash or raw key). Includes revoked and expired keys so the dashboard can show full history; filter client-side on revokedAt/expiresAt for \"active only\" views.",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "query ListKeys {\n  namedApiKeys {\n    id\n    name\n    scopes\n    createdAt\n    revokedAt\n    expiresAt\n  }\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["ListKeys"]
                  },
                  "variables": { "type": "object", "properties": {} }
                }
              },
              "examples": {
                "default": {
                  "summary": "List the workspace's named API keys (metadata only, never the key itself).",
                  "value": {
                    "query": "query ListKeys {\n  namedApiKeys {\n    id\n    name\n    scopes\n    createdAt\n    revokedAt\n    expiresAt\n  }\n}\n",
                    "operationName": "ListKeys",
                    "variables": {}
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "namedApiKeys": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "integer",
                                "description": "Server-generated integer id of the key; pass it to revokeNamedApiKey."
                              },
                              "name": {
                                "type": "string",
                                "description": "Label given at creation to identify the consumer holding the key; descriptive only."
                              },
                              "scopes": {
                                "type": ["array", "null"],
                                "items": { "type": "string" },
                                "description": "Extra rights granted to this key beyond the runtime access every key has (for example \"templates:hire\"). null = a plain runtime key. Scopes only add rights, they never narrow runtime access."
                              },
                              "createdAt": {
                                "type": "string",
                                "format": "date-time",
                                "description": "When the key was created (ISO 8601, server-set)."
                              },
                              "revokedAt": {
                                "type": ["string", "null"],
                                "format": "date-time",
                                "description": "When the key was revoked (ISO 8601); null while it is usable. A revoked key is refused immediately and cannot be restored."
                              },
                              "expiresAt": {
                                "type": ["string", "null"],
                                "format": "date-time",
                                "description": "When the key stops working (ISO 8601); null means it never expires. Keys created with createNamedApiKey have no expiry."
                              }
                            },
                            "required": [
                              "id",
                              "name",
                              "scopes",
                              "createdAt",
                              "revokedAt",
                              "expiresAt"
                            ],
                            "description": "A named API key of your workspace, metadata only: the key itself and its hash are never returned."
                          }
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": {
                      "data": {
                        "namedApiKeys": [
                          {
                            "id": 31,
                            "name": "helpdesk-sandbox",
                            "scopes": null,
                            "createdAt": "2026-10-12T02:40:11.000Z",
                            "revokedAt": null,
                            "expiresAt": null
                          },
                          {
                            "id": 24,
                            "name": "old-test-key",
                            "scopes": null,
                            "createdAt": "2026-09-02T08:12:45.000Z",
                            "revokedAt": "2026-09-30T10:01:02.000Z",
                            "expiresAt": null
                          }
                        ]
                      }
                    }
                  },
                  "200-2": {
                    "summary": "UNAUTHENTICATED: Missing credential",
                    "value": {
                      "errors": [
                        {
                          "message": "Unauthorized",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["namedApiKeys"],
                          "extensions": {
                            "code": "UNAUTHENTICATED",
                            "originalError": {
                              "message": "Unauthorized",
                              "statusCode": 401
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "query",
          "field": "namedApiKeys",
          "capability": "api-keys"
        }
      }
    },
    "/graphql#revokeNamedApiKey": {
      "post": {
        "operationId": "revokeNamedApiKey",
        "tags": ["API keys"],
        "summary": "Revoke one named API key. Immediate and permanent; other keys are not affected.",
        "description": "Revoke one named API key. Immediate and permanent; other keys are not affected.\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- There is no un-revoke. Create a new key instead.\n\n**From the schema:**\n\nRevokes a single named API key by id, scoped to the caller's own tenant. Does not affect any other named key or the legacy generateApiKey key. Revocation is immediate and permanent — create a new key instead of un-revoking one.",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "mutation RevokeKey($id: Int!) {\n  revokeNamedApiKey(id: $id)\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["RevokeKey"]
                  },
                  "variables": {
                    "type": "object",
                    "properties": { "id": { "type": "integer" } },
                    "required": ["id"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "Revoke one named API key. Immediate and permanent; other keys are not affected.",
                  "value": {
                    "query": "mutation RevokeKey($id: Int!) {\n  revokeNamedApiKey(id: $id)\n}\n",
                    "operationName": "RevokeKey",
                    "variables": { "id": 31 }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "revokeNamedApiKey": { "type": "boolean" }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": { "data": { "revokeNamedApiKey": true } }
                  },
                  "200-2": {
                    "summary": "NOT_FOUND: Not a key of your workspace",
                    "value": {
                      "errors": [
                        {
                          "message": "Named API key #31 not found",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["revokeNamedApiKey"],
                          "extensions": {
                            "code": "NOT_FOUND",
                            "status": 404,
                            "originalError": {
                              "message": "Named API key #31 not found",
                              "error": "Not Found",
                              "statusCode": 404
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request itself was refused before running: variables that do not match their types (`BAD_USER_INPUT`) or an invalid document (`GRAPHQL_VALIDATION_FAILED`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "400-1": {
                    "summary": "BAD_USER_INPUT: Id sent as a string",
                    "value": {
                      "errors": [
                        {
                          "message": "Variable \"$id\" got invalid value \"thirty-one\"; Int cannot represent non-integer value: \"thirty-one\"",
                          "locations": [{ "line": 1, "column": 20 }],
                          "extensions": { "code": "BAD_USER_INPUT" }
                        }
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "mutation",
          "field": "revokeNamedApiKey",
          "capability": "api-keys"
        }
      }
    },
    "/graphql#myTenant": {
      "post": {
        "operationId": "myTenant",
        "tags": ["Workspace and usage"],
        "summary": "Read the workspace the signed-in user belongs to.",
        "description": "Read the workspace the signed-in user belongs to.\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- Returns `null` for a user who is not assigned to a workspace yet.\n\n- `allowedLlmModels` is the workspace's model allowlist: `null` means every model in the catalog, an empty list means none.\n\n**From the schema:**\n\nReturns the tenant belonging to the authenticated caller. Returns null for users not yet assigned to a tenant (see the register mutation's tenantId: null gotcha in AuthResolver).\n\n**Allowed values:**\n\n- `TenantPlanEnum`: `DEV`, `ENTERPRISE`, `PRO`",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "query MyWorkspace {\n  myTenant {\n    id\n    name\n    plan\n    isSuspended\n    allowedLlmModels\n    createdAt\n  }\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["MyWorkspace"]
                  },
                  "variables": { "type": "object", "properties": {} }
                }
              },
              "examples": {
                "default": {
                  "summary": "Read the workspace the signed-in user belongs to.",
                  "value": {
                    "query": "query MyWorkspace {\n  myTenant {\n    id\n    name\n    plan\n    isSuspended\n    allowedLlmModels\n    createdAt\n  }\n}\n",
                    "operationName": "MyWorkspace",
                    "variables": {}
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "myTenant": {
                          "type": ["object", "null"],
                          "properties": {
                            "id": { "type": "integer" },
                            "name": { "type": "string" },
                            "plan": {
                              "$ref": "#/components/schemas/TenantPlanEnum"
                            },
                            "isSuspended": { "type": "boolean" },
                            "allowedLlmModels": {
                              "type": ["array", "null"],
                              "items": { "type": "string" },
                              "description": "Per-tenant LLM model allowlist: canonical model ids (as listed by llmModelCatalog; Opus effort variants are separate ids) and optional 'provider:<PROVIDER_TYPE>' wildcards. null = unrestricted (platform default). An empty list allows no model at all. Enforced on every LLM call path (sessions, workflow LLM nodes, testLlmProvider, generateText, workflow generation, evaluation); llmModelCatalog is filtered to it."
                            },
                            "createdAt": {
                              "type": "string",
                              "format": "date-time"
                            }
                          },
                          "required": [
                            "id",
                            "name",
                            "plan",
                            "isSuspended",
                            "allowedLlmModels",
                            "createdAt"
                          ]
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": {
                      "data": {
                        "myTenant": {
                          "id": 3,
                          "name": "Example Support Team",
                          "plan": "PRO",
                          "isSuspended": false,
                          "allowedLlmModels": null,
                          "createdAt": "2026-08-04T02:10:00.000Z"
                        }
                      }
                    }
                  },
                  "200-2": {
                    "summary": "UNAUTHENTICATED: Called with an API key instead of a JWT",
                    "value": {
                      "errors": [
                        {
                          "message": "Unauthorized",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["myTenant"],
                          "extensions": {
                            "code": "UNAUTHENTICATED",
                            "originalError": {
                              "message": "Unauthorized",
                              "statusCode": 401
                            }
                          }
                        }
                      ],
                      "data": { "myTenant": null }
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "query",
          "field": "myTenant",
          "capability": "usage"
        }
      }
    },
    "/graphql#apiUsageSummary": {
      "post": {
        "operationId": "apiUsageSummary",
        "tags": ["Workspace and usage"],
        "summary": "Model spend for one calendar month, the plan's monthly cap, and a breakdown by model and operation.",
        "description": "Model spend for one calendar month, the plan's monthly cap, and a breakdown by model and operation.\n\n**Credential:** Dashboard JWT. `Authorization: Bearer <jwt>`, from `login` (a person or a one-off script).\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- Covers all model usage of the workspace (sessions, workflow runs, evaluation and the LLM API). It cannot be split by API key; use `customerUsageSummary` for one end customer.\n\n- `yearMonth` (`YYYY-MM`, UTC) reads a past month; omit it for the current month. Every figure covers that month, whatever the field name says.\n\n- `usageByBucket` always has the four rows `AI_CREDITS`, `TTS`, `STT` and `AVATAR`, even when a bucket is zero.\n\n**From the schema:**\n\nLLM spend for the caller's own tenant for one calendar month (current month by default, or any past month via `yearMonth`), the tenant's monthly spend cap (by plan tier), per-bucket usage, and a call-count + cost breakdown by model and operation for that same month (the breakdown was all-time before 2026-09-30). Covers ALL of the tenant's LLM usage — agent sessions, workflow runs, evaluation, and the LLM API — not just one source. Cannot be broken down by individual API key; usage is tracked per-tenant only.\n\n**Allowed values:**\n\n- `UsageBucketEnum`: `AI_CREDITS`, `AVATAR`, `STT`, `TTS`",
        "security": [{ "dashboardJwt": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "query Usage($yearMonth: String) {\n  apiUsageSummary(yearMonth: $yearMonth) {\n    yearMonth\n    costThisMonthUsd\n    monthlySpendCapUsd\n    creditsUsedThisMonthApprox\n    usageByBucket {\n      bucket\n      callCount\n      costUsd\n      creditsUsed\n    }\n    breakdown {\n      model\n      operation\n      count\n      costUsd\n      creditsUsed\n    }\n  }\n}\n"
                    ]
                  },
                  "operationName": { "type": "string", "examples": ["Usage"] },
                  "variables": {
                    "type": "object",
                    "properties": {
                      "yearMonth": { "type": ["string", "null"] }
                    }
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "Model spend for one calendar month, the plan's monthly cap, and a breakdown by model and operation.",
                  "value": {
                    "query": "query Usage($yearMonth: String) {\n  apiUsageSummary(yearMonth: $yearMonth) {\n    yearMonth\n    costThisMonthUsd\n    monthlySpendCapUsd\n    creditsUsedThisMonthApprox\n    usageByBucket {\n      bucket\n      callCount\n      costUsd\n      creditsUsed\n    }\n    breakdown {\n      model\n      operation\n      count\n      costUsd\n      creditsUsed\n    }\n  }\n}\n",
                    "operationName": "Usage",
                    "variables": { "yearMonth": "2026-09" }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "apiUsageSummary": {
                          "type": "object",
                          "properties": {
                            "yearMonth": {
                              "type": "string",
                              "description": "The calendar month (UTC, 'YYYY-MM') every figure in this summary covers — the `yearMonth` argument, or the current month when omitted."
                            },
                            "costThisMonthUsd": {
                              "type": "number",
                              "description": "LLM spend for `yearMonth` (the current month unless a past month was requested — the field name predates the yearMonth argument)."
                            },
                            "monthlySpendCapUsd": { "type": "number" },
                            "creditsUsedThisMonthApprox": { "type": "integer" },
                            "usageByBucket": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "bucket": {
                                    "$ref": "#/components/schemas/UsageBucketEnum"
                                  },
                                  "callCount": { "type": "integer" },
                                  "costUsd": { "type": "number" },
                                  "creditsUsed": {
                                    "type": "integer",
                                    "description": "Real, non-approximated credits for TTS/STT/AVATAR and for the tool_call portion of AI_CREDITS (all computed from an exact, already-known formula/multiplier at read time from stored costUsd/inputTokens — see usage-buckets.ts in @stack/ai-providers for the exact per-operation formula). The generate/embed/evaluate portion of AI_CREDITS is still an approximation (same blended-multiplier approach as creditsUsedThisMonthApprox below, applied to this bucket's own cost only) — a94's per-model-tier credit ledger is what closes that gap, not yet built."
                                  }
                                },
                                "required": [
                                  "bucket",
                                  "callCount",
                                  "costUsd",
                                  "creditsUsed"
                                ]
                              },
                              "description": "This month's usage broken down into a101's four consumption buckets (AI_CREDITS, TTS, STT, AVATAR), always all four rows even when a bucket saw zero usage. Extends breakdown/costThisMonthUsd above with real per-bucket cost and credits figures — see UsageBucketDto.creditsUsed's own description for which buckets are exact vs. approximate, and UsageBucketDto.bucket's description for the AVATAR/HOSTED_API gap this does not yet close."
                            },
                            "breakdown": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "model": {
                                    "type": "string",
                                    "description": "The provider model string recorded for the call, or the tool/voice/avatar identifier for non-LLM operations — not always an LLM model, so clients should label this column 'Item'."
                                  },
                                  "operation": { "type": "string" },
                                  "count": { "type": "integer" },
                                  "costUsd": {
                                    "type": "number",
                                    "description": "Summed real cost in USD for this (model, operation) pair in the selected month."
                                  },
                                  "creditsUsed": {
                                    "type": "integer",
                                    "description": "Credits this (model, operation) row used in the selected month, computed by the same rule as the usage bucket it belongs to (see UsageBucketDto): EXACT real charges for tool_call (the credits charged per call, including any per-action creditsPerCall override) and avatar_render (flat fee per reply); TTS (synthesize) and STT (transcribe) are real, margin-applied charges; generate/embed/evaluate (LLM) credits are an APPROXIMATION (blended multiplier, same as the AI_CREDITS bucket). A row is always one operation, so it never mixes approximate and exact credits. For rounded operations the operation's month total is rounded once and shared across its rows by largest remainder, so the rows of each bucket sum EXACTLY to that bucket's creditsUsed; a single row can therefore differ from rounding its own cost alone by at most 1 credit (a tiny STT/TTS row may show 0). Operations not mapped to any bucket report 0. Not reconciled with creditsUsedThisMonthApprox, which applies one blended approximation to the whole month's cost."
                                  }
                                },
                                "required": [
                                  "model",
                                  "operation",
                                  "count",
                                  "costUsd",
                                  "creditsUsed"
                                ]
                              },
                              "description": "Per (model, operation) call count, cost and credits for `yearMonth` only, most-called first; the creditsUsed of the rows in a bucket sum exactly to that bucket's creditsUsed. Was all-time before 2026-09-30 despite this being documented as this month's usage."
                            }
                          },
                          "required": [
                            "yearMonth",
                            "costThisMonthUsd",
                            "monthlySpendCapUsd",
                            "creditsUsedThisMonthApprox",
                            "usageByBucket",
                            "breakdown"
                          ]
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": {
                      "data": {
                        "apiUsageSummary": {
                          "yearMonth": "2026-09",
                          "costThisMonthUsd": 3.4182,
                          "monthlySpendCapUsd": 200,
                          "creditsUsedThisMonthApprox": 97419,
                          "usageByBucket": [
                            {
                              "bucket": "AI_CREDITS",
                              "callCount": 1840,
                              "costUsd": 3.2011,
                              "creditsUsed": 91231
                            },
                            {
                              "bucket": "TTS",
                              "callCount": 52,
                              "costUsd": 0.2171,
                              "creditsUsed": 6188
                            },
                            {
                              "bucket": "STT",
                              "callCount": 0,
                              "costUsd": 0,
                              "creditsUsed": 0
                            },
                            {
                              "bucket": "AVATAR",
                              "callCount": 0,
                              "costUsd": 0,
                              "creditsUsed": 0
                            }
                          ],
                          "breakdown": [
                            {
                              "model": "gemini-2.5-flash",
                              "operation": "generate",
                              "count": 1712,
                              "costUsd": 3.1904,
                              "creditsUsed": 90926
                            },
                            {
                              "model": "gemini-embedding-001",
                              "operation": "embed",
                              "count": 128,
                              "costUsd": 0.0107,
                              "creditsUsed": 305
                            }
                          ]
                        }
                      }
                    }
                  },
                  "200-2": {
                    "summary": "BAD_REQUEST: Month in the future or badly formatted",
                    "value": {
                      "errors": [
                        {
                          "message": "yearMonth must be a past or current month formatted 'YYYY-MM'",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["apiUsageSummary"],
                          "extensions": {
                            "code": "BAD_REQUEST",
                            "originalError": {
                              "message": "yearMonth must be a past or current month formatted 'YYYY-MM'",
                              "error": "Bad Request",
                              "statusCode": 400
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  },
                  "200-3": {
                    "summary": "UNAUTHENTICATED: Called with an API key instead of a JWT",
                    "value": {
                      "errors": [
                        {
                          "message": "Unauthorized",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["apiUsageSummary"],
                          "extensions": {
                            "code": "UNAUTHENTICATED",
                            "originalError": {
                              "message": "Unauthorized",
                              "statusCode": 401
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "query",
          "field": "apiUsageSummary",
          "capability": "usage"
        }
      }
    },
    "/graphql#customerUsageSummary": {
      "post": {
        "operationId": "customerUsageSummary",
        "tags": ["Workspace and usage"],
        "summary": "Tokens, estimated cost and session count across every session of one end customer.",
        "description": "Tokens, estimated cost and session count across every session of one end customer.\n\n**Credential:** API key. `X-Api-Key: sk_...`, kept on your server only.\n\nEvery request also needs the `x-huat-platform: customer` header.\n\n- `externalCustomerId` is the `clientId` you passed to `sdkStart`. Give each ticket its own `clientId` to read usage per ticket.\n\n- `costUsd` is an estimate (about 4 characters per token), not a billing figure; the workspace's billed spend is in `apiUsageSummary`.\n\n- An identifier with no sessions returns zeros, not an error.\n\n**From the schema:**\n\nRolled-up token/cost usage (tokensIn, tokensOut, costUsd) across every Session belonging to `externalCustomerId`, scoped to the API key's tenant. `costUsd` is a character-based estimate, not billing-exact — see the field-level cost tracked per Message for the same caveat.",
        "security": [{ "apiKey": [] }],
        "parameters": [
          {
            "name": "x-huat-platform",
            "in": "header",
            "required": true,
            "description": "Required on every request, whatever the credential.",
            "schema": { "type": "string", "enum": ["customer"] }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GraphQL document. Select only the fields you need.",
                    "examples": [
                      "query CustomerUsage($externalCustomerId: String!) {\n  customerUsageSummary(externalCustomerId: $externalCustomerId) {\n    externalCustomerId\n    sessionCount\n    tokensIn\n    tokensOut\n    costUsd\n    creditsUsedApprox\n  }\n}\n"
                    ]
                  },
                  "operationName": {
                    "type": "string",
                    "examples": ["CustomerUsage"]
                  },
                  "variables": {
                    "type": "object",
                    "properties": {
                      "externalCustomerId": { "type": "string" }
                    },
                    "required": ["externalCustomerId"]
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "Tokens, estimated cost and session count across every session of one end customer.",
                  "value": {
                    "query": "query CustomerUsage($externalCustomerId: String!) {\n  customerUsageSummary(externalCustomerId: $externalCustomerId) {\n    externalCustomerId\n    sessionCount\n    tokensIn\n    tokensOut\n    costUsd\n    creditsUsedApprox\n  }\n}\n",
                    "operationName": "CustomerUsage",
                    "variables": { "externalCustomerId": "ticket-1001" }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL answers HTTP 200 for a result and for errors raised while running the operation. Check `errors[].extensions.code`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": ["object", "null"],
                      "properties": {
                        "customerUsageSummary": {
                          "type": "object",
                          "properties": {
                            "externalCustomerId": { "type": "string" },
                            "sessionCount": { "type": "integer" },
                            "tokensIn": { "type": "integer" },
                            "tokensOut": { "type": "integer" },
                            "costUsd": { "type": "number" },
                            "creditsUsedApprox": { "type": "integer" }
                          },
                          "required": [
                            "externalCustomerId",
                            "sessionCount",
                            "tokensIn",
                            "tokensOut",
                            "costUsd",
                            "creditsUsedApprox"
                          ]
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "200-1": {
                    "summary": "Success",
                    "value": {
                      "data": {
                        "customerUsageSummary": {
                          "externalCustomerId": "ticket-1001",
                          "sessionCount": 2,
                          "tokensIn": 1840,
                          "tokensOut": 612,
                          "costUsd": 0.0021,
                          "creditsUsedApprox": 60
                        }
                      }
                    }
                  },
                  "200-2": {
                    "summary": "UNAUTHENTICATED: Missing API key",
                    "value": {
                      "errors": [
                        {
                          "message": "Missing X-Api-Key header",
                          "locations": [{ "line": 2, "column": 3 }],
                          "path": ["customerUsageSummary"],
                          "extensions": {
                            "code": "UNAUTHENTICATED",
                            "originalError": {
                              "message": "Missing X-Api-Key header",
                              "error": "Unauthorized",
                              "statusCode": 401
                            }
                          }
                        }
                      ],
                      "data": null
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request itself was refused before running: variables that do not match their types (`BAD_USER_INPUT`) or an invalid document (`GRAPHQL_VALIDATION_FAILED`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "errors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/GraphQLError" }
                    }
                  }
                },
                "examples": {
                  "400-1": {
                    "summary": "BAD_USER_INPUT: Identifier sent as a number",
                    "value": {
                      "errors": [
                        {
                          "message": "Variable \"$externalCustomerId\" got invalid value 1001; String cannot represent a non string value: 1001",
                          "locations": [{ "line": 1, "column": 21 }],
                          "extensions": { "code": "BAD_USER_INPUT" }
                        }
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "x-graphql": {
          "kind": "query",
          "field": "customerUsageSummary",
          "capability": "usage"
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Api-Key",
        "description": "Long-lived server credential (`sk_...`). Never ship it to a browser or app."
      },
      "dashboardJwt": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "Dashboard credential from the `login` mutation. For people and one-off scripts."
      },
      "sessionToken": {
        "type": "http",
        "scheme": "bearer",
        "description": "The `avatarToken` returned by `sdkStart`. Valid for one session, about one hour."
      }
    },
    "schemas": {
      "AgentStatus": {
        "type": "string",
        "enum": ["ACTIVE", "INACTIVE"],
        "description": "Whether an agent is switched on."
      },
      "AgentUseCase": {
        "type": "string",
        "enum": [
          "CUSTOM",
          "EDUCATION",
          "INTERVIEW",
          "ONBOARDING",
          "SALES",
          "SUPPORT"
        ],
        "description": "What kind of conversation an agent has. Selects the built-in prompt that evaluates a finished session and therefore its score meaning and recommendation labels."
      },
      "ApprovalDecisionChoice": {
        "type": "string",
        "enum": ["APPROVE", "REJECT", "REQUEST_CHANGES"],
        "description": "What a manager decides on an approval request."
      },
      "ApprovalStatus": {
        "type": "string",
        "enum": [
          "APPROVED",
          "CANCELLED",
          "CHANGES_REQUESTED",
          "EXPIRED",
          "PENDING",
          "REJECTED"
        ],
        "description": "State of an approval request. PENDING is the only open state; the others are final records kept for audit."
      },
      "AttachmentKind": {
        "type": "string",
        "enum": ["AUDIO", "DOCUMENT", "IMAGE", "OTHER", "VIDEO"],
        "description": "Broad class of a message attachment. Inferred from the MIME type when you do not send it. Only IMAGE attachments are exposed to workflow nodes as image URLs today (ctx.attachmentImageUrls); every kind is listed in ctx.attachments."
      },
      "AvatarBackendType": {
        "type": "string",
        "enum": ["CLIENT_3D", "HOSTED_API", "SELF_HOSTED_VIDEO_STREAM"],
        "description": "How a client renders an agent avatar."
      },
      "AvatarGenderCode": {
        "type": "string",
        "enum": ["F", "M"],
        "description": "Gender of an agent avatar model."
      },
      "AwaitReplyMatchMode": {
        "type": "string",
        "enum": ["FREE_TEXT", "OPTIONS"]
      },
      "ConfirmKnowledgeDocumentUploadInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "fileName": {
            "type": "string",
            "description": "File name, as sent to requestKnowledgeDocumentUpload (at most 255 characters)."
          },
          "knowledgeBaseId": {
            "type": "integer",
            "description": "Id of the knowledge base, as sent to requestKnowledgeDocumentUpload."
          },
          "mimeType": {
            "type": "string",
            "description": "MIME type, as sent to requestKnowledgeDocumentUpload (same allowed values)."
          },
          "objectStorageKey": {
            "type": "string",
            "description": "The objectStorageKey returned by requestKnowledgeDocumentUpload; a key outside your workspace is refused."
          }
        },
        "required": [
          "fileName",
          "knowledgeBaseId",
          "mimeType",
          "objectStorageKey"
        ],
        "description": "Confirms an upload made with requestKnowledgeDocumentUpload."
      },
      "CreateAgentInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "avatarBackend": {
            "$ref": "#/components/schemas/AvatarBackendType",
            "description": "How clients render the avatar (default CLIENT_3D)."
          },
          "avatarBackendConfig": {
            "description": "Settings for a HOSTED_API avatar, a JSON object with the hosted avatar id and model (pass it through variables); no secrets. Not needed for CLIENT_3D."
          },
          "avatarGender": {
            "anyOf": [
              { "$ref": "#/components/schemas/AvatarGenderCode" },
              { "type": "null" }
            ],
            "description": "Gender of the avatar model (default F)."
          },
          "avatarGlb": {
            "type": ["string", "null"],
            "description": "File name of the 3D avatar model: lower-case letters, digits, \"-\" or \"_\", ending in .glb (at most 100 characters; default \"brunette-t.glb\"). agentConfigOptions lists the available models."
          },
          "evaluationPromptTemplate": {
            "type": ["string", "null"],
            "description": "Your own evaluation prompt (at most 10000 characters), required when useCase is CUSTOM and then it must contain {transcript}; it may also use {subject} and {recommendationLabels}."
          },
          "evaluationRecommendationLabels": {
            "type": ["array", "null"],
            "items": { "type": "string" },
            "description": "Recommendation labels a CUSTOM evaluation may return (at most 20, each at most 50 characters), required when useCase is CUSTOM."
          },
          "externalSyncOwnerId": {
            "type": ["integer", "null"],
            "description": "Owner id your endpoint expects when the mirrored conversation is created (a positive integer of your system)."
          },
          "externalSyncSchemaConfig": {
            "description": "Optional overrides for how mirrored turns are written to externalSyncUrl: a JSON object with any of conversationInputTypeName, messageInputTypeName, userRoleValue, assistantRoleValue."
          },
          "externalSyncSecretRef": {
            "type": ["string", "null"],
            "description": "Reference to a secret stored for your workspace whose value is sent as the bearer token to externalSyncUrl (at most 200 characters; never the secret itself). A reference belonging to another workspace is refused."
          },
          "externalSyncUrl": {
            "type": ["string", "null"],
            "description": "URL of your own GraphQL endpoint that receives a best-effort copy of each turn the workflow answers (at most 2000 characters). Private and internal addresses are refused."
          },
          "lipsyncLang": {
            "type": ["string", "null"],
            "description": "Short language code for 3D avatar lip-sync, such as \"en\" (at most 10 characters; default \"en\")."
          },
          "llmModelOverride": {
            "type": ["string", "null"],
            "description": "Specific model id from llmModelCatalog (at most 100 characters). Set alone, the provider that serves it is used; a model no enabled provider serves, a llmProviderOverride that does not serve it, or a model outside the workspace allowlist is refused. Omit for the provider default."
          },
          "llmProviderOverride": {
            "anyOf": [
              { "$ref": "#/components/schemas/LlmProviderType" },
              { "type": "null" }
            ],
            "description": "Model provider for this agent; omit to use the workspace setting or the platform default."
          },
          "name": {
            "type": "string",
            "description": "Display name (at most 100 characters)."
          },
          "openingGreeting": {
            "type": ["string", "null"],
            "description": "Fixed opening line, a template that may use {{clientExternalId}} (at most 2000 characters). Sent when a session starts on an agent without a workflow; without it the model writes a greeting."
          },
          "personaPrompt": {
            "type": "string",
            "description": "System prompt: identity, tone and boundaries of the agent (at most 10000 characters)."
          },
          "position": {
            "type": "string",
            "description": "Role title, for example \"Support Specialist\" (at most 200 characters); also the evaluation subject when subject is empty."
          },
          "speakingRate": {
            "type": ["number", "null"],
            "description": "Text-to-speech speaking rate, 0.25 to 4.0 where 1.0 is normal (default 1.0)."
          },
          "sttAlternativeLanguageCodes": {
            "type": ["array", "null"],
            "items": { "type": "string" },
            "description": "Speech-to-text fallback languages besides the primary (BCP-47). null/omitted = the platform default: English (\"en-US\") and Malay (\"ms-MY\"), minus the primary. [] = recognise in the primary only. A list = exactly those languages, at most 3: Google silently ignores the ENTIRE list past 3, so more is rejected rather than truncated. Mandarin and Cantonese are NOT in the default, so list \"cmn-Hans-CN\" / \"yue-Hant-HK\" explicitly when Chinese speech is expected. Duplicates, and an entry equal to the effective primary (sttLanguageCode, else ttsLang), are rejected."
          },
          "sttLanguageCode": {
            "type": ["string", "null"],
            "description": "Speech-to-text PRIMARY language (BCP-47, e.g. \"en-US\", \"ms-MY\", \"cmn-Hans-CN\"): the language most callers speak. null/omitted means \"use ttsLang\". Separate from ttsLang so an agent can reply in one language but mostly hear another. The primary biases recognition much more than the alternatives, so never set a secondary language here."
          },
          "subject": {
            "type": ["string", "null"],
            "description": "What sessions are about (the job, product or course), used by the evaluation prompt; position is used when empty (at most 200 characters)."
          },
          "timezone": {
            "type": ["string", "null"],
            "description": "IANA timezone of this agent (e.g. \"Asia/Kuala_Lumpur\", \"America/New_York\"). Used by the {{today}} and {{now}} workflow template helpers. Defaults to Asia/Kuala_Lumpur."
          },
          "ttsLang": {
            "type": ["string", "null"],
            "description": "Language the agent speaks, a BCP-47 code such as \"en-US\" (at most 20 characters; default \"en-US\")."
          },
          "ttsVoice": {
            "type": ["string", "null"],
            "description": "Text-to-speech voice name, for example \"en-US-Neural2-F\" (at most 100 characters; default \"en-US-Standard-C\"). See agentConfigOptions for recommended voices."
          },
          "useCase": {
            "$ref": "#/components/schemas/AgentUseCase",
            "description": "Kind of conversation; selects how finished sessions are evaluated (default INTERVIEW). CUSTOM requires evaluationPromptTemplate and evaluationRecommendationLabels."
          },
          "vadThreshold": {
            "type": ["number", "null"],
            "description": "Voice-activity detection threshold for the microphone, 0.1 to 1.0 (default 0.8)."
          },
          "voiceTier": {
            "$ref": "#/components/schemas/VoiceTier",
            "description": "Voice quality tier label (default STANDARD)."
          }
        },
        "required": ["name", "personaPrompt", "position"],
        "description": "Fields of a new agent. Only name, position and personaPrompt are required."
      },
      "CreateCustomActionInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "argsSchema": {
            "description": "Optional JSON Schema (an object, send it through variables) documenting the arguments; not enforced on calls."
          },
          "bearerToken": {
            "type": ["string", "null"],
            "description": "Optional static secret, sent as Authorization: Bearer (or under credentialSecretHeaderName). Stored as a secret and never returned. Omit for an endpoint that needs no credential."
          },
          "credentialSecretHeaderName": {
            "type": ["string", "null"],
            "description": "Optional header name to send the stored bearerToken under, for third-party APIs that authenticate with a static non-Bearer secret header (e.g. 'X-Client-Secret', 'X-Api-Key'). Omit for the default 'Authorization: Bearer <token>' behavior. When set, the raw secret value is sent under this header with no 'Bearer ' prefix, and no Authorization header is sent. Only accepted when bearerToken is supplied in the same call."
          },
          "credentialSource": {
            "type": ["string", "null"],
            "description": "Optional SESSION_ value that resolves the credential live, per call, from the end user signed in to the conversation through single sign-on, for the products that support it; an invalid value is refused with the list of allowed values. Leave unset for bearerToken or Lark app credentials."
          },
          "creditsPerCall": {
            "type": ["integer", "null"],
            "description": "The credits charged for one successful call of this action (an integer, 0 or more). Omit to use the platform default (2 credits)."
          },
          "description": {
            "type": ["string", "null"],
            "description": "Optional free-text description."
          },
          "extraHeaders": {
            "description": "Static, non-secret headers sent on every call: a JSON object of header name to string value (send it through variables). Never a credential; it is returned as is."
          },
          "larkAppId": {
            "type": ["string", "null"],
            "description": "App id of your own Lark (or Feishu) app; give it together with larkAppSecret instead of bearerToken. The action then authenticates with that app's tenant access token, fetched by the server."
          },
          "larkAppSecret": {
            "type": ["string", "null"],
            "description": "App secret of your own Lark (or Feishu) app, given with larkAppId. Stored as a secret and never returned."
          },
          "larkRegion": {
            "type": ["string", "null"],
            "description": "Which platform the Lark app lives on: FEISHU or LARK_SUITE (default LARK_SUITE)."
          },
          "method": {
            "type": ["string", "null"],
            "description": "GET, POST, PUT, PATCH or DELETE (default POST)."
          },
          "name": {
            "type": "string",
            "description": "Display name (1 to 100 characters)."
          },
          "url": {
            "type": "string",
            "description": "URL to call. Private and internal addresses are refused, and redirects are not followed."
          }
        },
        "required": ["name", "url"],
        "description": "A new custom action. Give at most one credential: bearerToken, larkAppId with larkAppSecret, or credentialSource."
      },
      "CreateKnowledgeBaseInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "description": {
            "type": ["string", "null"],
            "description": "Optional free-text description."
          },
          "name": {
            "type": "string",
            "description": "Display name (at most 120 characters)."
          }
        },
        "required": ["name"],
        "description": "A new, empty knowledge base."
      },
      "CreateMcpConnectorInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "apiKey": {
            "type": "string",
            "description": "Credential sent as Authorization: Bearer on every call to the server. Stored as a secret, never returned."
          },
          "creditsPerCall": {
            "type": ["integer", "null"],
            "description": "The credits charged for one successful call of ANY tool on this connector (an integer, 0 or more). Omit to use the platform default (2 credits)."
          },
          "name": {
            "type": "string",
            "description": "Display name (1 to 100 characters)."
          },
          "serverUrl": {
            "type": "string",
            "description": "URL of the MCP server (streamable HTTP transport). Private and internal addresses are refused."
          }
        },
        "required": ["apiKey", "name", "serverUrl"],
        "description": "A remote MCP server to register."
      },
      "CreateScheduledWorkflowInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "agentId": {
            "type": "integer",
            "description": "The agent whose published workflow runs on each fire. Must belong to the caller's tenant. The agent (not the schedule) decides which workflow runs."
          },
          "cronExpression": {
            "type": "string",
            "description": "Standard 5-field cron expression (minute hour day-of-month month day-of-week), e.g. \"0 9 * * 1-5\". Seconds-level (6-field) and @alias forms are rejected."
          },
          "externalConversationIdTemplate": {
            "type": ["string", "null"],
            "description": "Template for each run's session clientExternalId. Supported placeholders: {{scheduleId}}, {{date}}, {{firedAt}}. Default \"scheduled:{{scheduleId}}:{{firedAt}}\" gives every fire a fresh session; drop {{firedAt}} (e.g. \"scheduled:{{scheduleId}}\") to reuse one session across fires for cross-run memory."
          },
          "maxConsecutiveFailures": {
            "type": ["integer", "null"],
            "description": "Consecutive failed runs before the schedule auto-disables (1-20, default 3)."
          },
          "name": {
            "type": "string",
            "description": "Operator-facing label (max 100 chars)."
          },
          "timezone": {
            "type": ["string", "null"],
            "description": "IANA timezone the cron expression is evaluated in, e.g. \"Asia/Kuala_Lumpur\". Defaults to \"UTC\"."
          },
          "variables": {
            "description": "Flat map of scalar facts merged into every run's initial context — identical rules to runWorkflowTask's `variables` (reserved runner keys and nested values rejected). Pass via GraphQL variables, not an inline literal. For a CONDITION gate write `someId > 0`, never `someId != 0`."
          }
        },
        "required": ["agentId", "cronExpression", "name"]
      },
      "CreateWorkflowInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "description": {
            "type": ["string", "null"],
            "description": "Optional free-text description (at most 500 characters)."
          },
          "name": {
            "type": "string",
            "description": "Display name (at most 120 characters)."
          }
        },
        "required": ["name"],
        "description": "A new, empty workflow: name and description only. Add nodes and edges with updateWorkflow."
      },
      "CustomActionTestOutcome": {
        "type": "string",
        "enum": [
          "BLOCKED",
          "CREDENTIAL_REJECTED",
          "CREDENTIAL_UNAVAILABLE",
          "NOT_TESTABLE",
          "OK",
          "REQUEST_REJECTED",
          "SERVER_ERROR",
          "TIMEOUT",
          "UNKNOWN_ERROR",
          "UNREACHABLE",
          "WRITE_NOT_CONFIRMED"
        ],
        "description": "Classified result of testing a custom action. OK: the target answered 2xx. CREDENTIAL_REJECTED: the target answered 401/403, so the stored credential or its header is wrong (the usual cause of a workflow node failing with 401). CREDENTIAL_UNAVAILABLE: Wetel could not read the stored credential at all. REQUEST_REJECTED: another 4xx, so the call itself (URL, body, schema) is wrong. SERVER_ERROR: 5xx from the target. TIMEOUT / UNREACHABLE: no answer. BLOCKED: refused by the outbound-address safety check. NOT_TESTABLE: the action needs a signed-in end user and cannot be tested here. WRITE_NOT_CONFIRMED: the method is not GET and `confirmWrite` was not set."
      },
      "GraphQLError": {
        "type": "object",
        "required": ["message"],
        "properties": {
          "message": {
            "type": "string",
            "description": "For people. Do not branch on it; branch on extensions.code."
          },
          "locations": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "line": { "type": "integer" },
                "column": { "type": "integer" }
              }
            }
          },
          "path": {
            "type": "array",
            "items": { "type": ["string", "integer"] }
          },
          "extensions": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "description": "BAD_USER_INPUT, BAD_REQUEST, UNAUTHENTICATED, FORBIDDEN, NOT_FOUND, CONFLICT, BAD_GATEWAY, GRAPHQL_VALIDATION_FAILED, QUERY_TOO_COMPLEX or INTERNAL_SERVER_ERROR."
              },
              "status": {
                "type": "integer",
                "description": "HTTP-style status, present on NOT_FOUND, CONFLICT, BAD_GATEWAY and INTERNAL_SERVER_ERROR."
              },
              "originalError": {
                "type": "object",
                "description": "The underlying refusal: message, error and statusCode."
              }
            }
          }
        }
      },
      "IngestDocumentInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "fileName": {
            "type": "string",
            "description": "Label shown as the document name (at most 255 characters); it need not be a real file name."
          },
          "knowledgeBaseId": {
            "type": "integer",
            "description": "Id of a knowledge base of your workspace."
          },
          "text": {
            "type": "string",
            "description": "The text to store, chunk and embed (at most 500000 characters). Stored as text/plain."
          }
        },
        "required": ["fileName", "knowledgeBaseId", "text"],
        "description": "Pasted text to add to a knowledge base as one document."
      },
      "LlmProviderType": {
        "type": "string",
        "enum": ["BEDROCK_MANTLE", "GEMINI_DIRECT"],
        "description": "Which model provider answers an agent's turns when overridden. Unset means the workspace or platform default. Internal values are not listed."
      },
      "MessageAttachmentInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "filename": {
            "type": ["string", "null"],
            "description": "Original filename, at most 255 characters. Quotes, square brackets and control characters are removed."
          },
          "kind": {
            "anyOf": [
              { "$ref": "#/components/schemas/AttachmentKind" },
              { "type": "null" }
            ],
            "description": "Kind of file. When omitted it is inferred from the MIME type (image/*, audio/*, video/*, PDF and Office or text types are DOCUMENT, anything else OTHER)."
          },
          "mime": {
            "type": ["string", "null"],
            "description": "MIME type, at most 100 characters (for example image/png). When omitted it is inferred from the filename, then from the URL path extension."
          },
          "url": {
            "type": "string",
            "description": "Absolute http(s) URL of the file, at most 2048 characters, without user:password credentials. Treat a signed URL as a credential: it is redacted wherever messages are read back."
          }
        },
        "required": ["url"],
        "description": "A file that arrives with a message. Wetel never downloads it at send time; a workflow node fetches it later (subject to the tenant host allowlist, SSRF guard and size caps)."
      },
      "MessageRole": {
        "type": "string",
        "enum": ["MODEL", "USER"],
        "description": "Who wrote a message."
      },
      "NodeType": {
        "type": "string",
        "enum": [
          "ACTION",
          "APPROVAL",
          "AWAIT_REPLY",
          "CONDITION",
          "END_SESSION",
          "GENERATE_ARTIFACT",
          "KNOWLEDGE_SEARCH",
          "LLM",
          "PDF_EXPORT",
          "RESPONSE",
          "ROUTER",
          "SET_VARIABLE",
          "START",
          "SUB_AGENT",
          "SWITCH",
          "TOOL",
          "WEBHOOK"
        ],
        "description": "Kind of a workflow node. In the nodes JSON of a workflow the type is written in lower case (for example \"llm\", \"end_session\"); nodeConfigSchemas gives the config schema of each type."
      },
      "RefreshAvatarTokenInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "avatarToken": {
            "type": "string",
            "description": "The current avatarToken, still valid or expired for at most the grace window (10 minutes by default). At most 4096 characters."
          }
        },
        "required": ["avatarToken"],
        "description": "Input of refreshAvatarToken."
      },
      "RequestKnowledgeDocumentUploadInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "declaredSizeBytes": {
            "type": "integer",
            "description": "File size in bytes as you know it (a positive integer, at most 20971520, 20 MB). Used for an early quota check only; the real size is checked at confirm."
          },
          "fileName": {
            "type": "string",
            "description": "Original file name (at most 255 characters)."
          },
          "knowledgeBaseId": {
            "type": "integer",
            "description": "Id of a knowledge base of your workspace."
          },
          "mimeType": {
            "type": "string",
            "description": "MIME type of the file, one of: application/pdf, application/vnd.openxmlformats-officedocument.wordprocessingml.document (DOCX), text/markdown, text/csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet (XLSX). Send the same value as Content-Type on the upload."
          }
        },
        "required": [
          "declaredSizeBytes",
          "fileName",
          "knowledgeBaseId",
          "mimeType"
        ],
        "description": "The file you are about to upload."
      },
      "RunWorkflowTaskInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "agentId": {
            "type": "integer",
            "description": "Id of an ACTIVE agent of the API key workspace with a workflow attached; NotFound for an unknown agent, BadRequest when it is inactive or has no workflow."
          },
          "attachmentFilename": {
            "type": ["string", "null"],
            "description": "Filename for attachmentUrl (at most 255 characters)."
          },
          "attachmentMime": {
            "type": ["string", "null"],
            "description": "MIME type for attachmentUrl (at most 255 characters); when omitted it is taken from the filename extension, then from the URL path."
          },
          "attachmentUrl": {
            "type": ["string", "null"],
            "description": "Single-file shortcut: absolute http(s) URL of one file. Equivalent to `attachments: [{url, mime, filename}]` with the attachmentMime and attachmentFilename fields below. Send EITHER this OR `attachments`, never both (both is refused with a 400)."
          },
          "attachments": {
            "type": ["array", "null"],
            "items": { "$ref": "#/components/schemas/MessageAttachmentInput" },
            "description": "Optional files for this task (at most 10), the same structured shape and validation as `attachments` on sdkSendMessage: absolute http(s) URL, no embedded credentials, sanitised filename, MIME and kind inferred when omitted. Not fetched at accept time; a workflow node downloads them when it needs them. In workflow context they appear as ctx.attachments (list of {url, mime, filename, kind}), ctx.attachmentImageUrls (the IMAGE urls, usable as imageUrlsVar), and the first entry fills ctx.attachment, ctx.attachmentUrl, ctx.attachmentMime and ctx.attachmentFilename. Do not combine with the single `attachmentUrl` field (400)."
          },
          "clientTurnId": {
            "type": ["string", "null"],
            "description": "Optional opaque correlation token of your own (at most 255 characters), echoed on every AiResponseEvent this run publishes; not stored."
          },
          "externalConversationId": {
            "type": "string",
            "description": "Your own id for the conversation or job (1 to 255 characters). It becomes the session clientExternalId: calls with the same id reuse the open session for that agent, so they share history."
          },
          "variables": {
            "description": "Facts for the run as a flat JSON object (send it through variables): at most 32 keys matching [A-Za-z][A-Za-z0-9_]* (64 characters at most), values string (at most 4096 characters), number, boolean or null. Keys starting with \"_\" and runner names such as sessionId, userMessage or channel are refused. Merged into the run variables unchanged, never coerced."
          }
        },
        "required": ["agentId", "externalConversationId"],
        "description": "One headless task for an agent workflow: which agent, which conversation, and the facts and files it should work on."
      },
      "ScheduledWorkflowStatus": {
        "type": "string",
        "enum": ["ACTIVE", "AUTO_DISABLED", "PAUSED"],
        "description": "ACTIVE = registered and firing on its cron; PAUSED = stopped by an operator (resumeScheduledWorkflow re-registers it); AUTO_DISABLED = stopped automatically after maxConsecutiveFailures consecutive failed runs (resumeScheduledWorkflow re-registers it and resets the failure counter)."
      },
      "SdkEndSessionInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "sessionId": {
            "type": "integer",
            "description": "Id of the session to end (sdkStart.sessionId). Must be the session the avatarToken was issued for."
          }
        },
        "required": ["sessionId"],
        "description": "Input of sdkEndSession."
      },
      "SdkSendMessageInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "attachments": {
            "type": ["array", "null"],
            "items": { "$ref": "#/components/schemas/MessageAttachmentInput" },
            "description": "Optional files sent with this message (at most 10). Validated here (absolute http(s) URL, no embedded credentials, sanitised filename, MIME and kind inferred when omitted) but NOT fetched: a workflow node downloads them when it needs them. In workflow context they appear as ctx.attachments (list of {url, mime, filename, kind}), ctx.attachmentImageUrls (the IMAGE urls, usable as imageUrlsVar), and the first entry fills ctx.attachment, ctx.attachmentUrl, ctx.attachmentMime and ctx.attachmentFilename. When present they take precedence over any attachment marker written inside the text. `text` may be empty when at least one attachment is sent."
          },
          "clientTurnId": {
            "type": ["string", "null"],
            "description": "Optional opaque correlation token of your own (at most 255 characters). Not stored or interpreted; echoed back verbatim on every AiResponseEvent of this turn's reply so you can match a reply to the call that caused it."
          },
          "sessionId": {
            "type": "integer",
            "description": "Id of the session (sdkStart.sessionId). Must be the session the avatarToken was issued for, otherwise the call is refused."
          },
          "text": {
            "type": "string",
            "description": "The user message, up to 100,000 characters. May be an empty string only when `attachments` has at least one entry."
          },
          "turnContext": {
            "description": "Optional per-turn data as a flat JSON object (pass it through `variables`, not an inline literal). At most 32 keys matching [A-Za-z][A-Za-z0-9_]*, values string (up to 4096 characters), number, boolean or null, 8 KB in total; runner-reserved names such as session, channel or userMessage are refused. Never put secrets here. Appears in workflow context as ctx.turn.context (for example {{turn.context.orderId}}); a string `trigger` key also sets ctx.channel.trigger. Applies to this turn only."
          }
        },
        "required": ["sessionId", "text"],
        "description": "Input of sdkSendMessage: one user turn."
      },
      "SdkStartInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "agentId": {
            "type": ["integer", "null"],
            "description": "Id of the agent to talk to (an integer from the agents query); NotFound when it is not in the API key's workspace. Omit to use the workspace's oldest ACTIVE agent; when the workspace has no active agent at all, a basic agent is created from agentName, position, personaPrompt, voiceTier and useCase."
          },
          "agentName": {
            "type": ["string", "null"],
            "description": "Name for the agent created when the workspace has no active agent (at most 100 characters, default \"AI Assistant\"). Ignored whenever an existing agent is used."
          },
          "clientId": {
            "type": ["string", "null"],
            "description": "Your own identifier for the end user or conversation (at most 255 characters), stored on the session as clientExternalId so you can find it again with sessionsByExternalCustomerId. When omitted the server generates \"sdk-\" followed by a random UUID."
          },
          "context": {
            "description": "Optional per-session data as a flat JSON object (pass it through `variables`, not an inline literal), for example {accountId, contactId, channelName, company} (`channel` itself is a reserved name and is refused). Same rules as turnContext: at most 32 keys matching [A-Za-z][A-Za-z0-9_]*, scalar values (strings up to 4096 characters), 8 KB in total, reserved runner names refused, never secrets. Stored on the session (readable through the session metadata the dashboard and API-key session queries return) and exposed to every workflow turn as ctx.session.context (for example {{session.context.company}})."
          },
          "personaPrompt": {
            "type": ["string", "null"],
            "description": "System prompt for the agent created when the workspace has no active agent (at most 2000 characters; a generic friendly-assistant prompt when omitted). Ignored whenever an existing agent is used: it never overrides an agent's own persona."
          },
          "position": {
            "type": ["string", "null"],
            "description": "Role title for the agent created when the workspace has no active agent (at most 100 characters, default \"Software Engineer\"). Ignored whenever an existing agent is used."
          },
          "useCase": {
            "anyOf": [
              { "$ref": "#/components/schemas/AgentUseCase" },
              { "type": "null" }
            ],
            "description": "Use case for the agent created when the workspace has no active agent (default INTERVIEW). Ignored whenever an existing agent is used."
          },
          "voiceTier": {
            "anyOf": [
              { "$ref": "#/components/schemas/VoiceTier" },
              { "type": "null" }
            ],
            "description": "Voice tier for the agent created when the workspace has no active agent (default STANDARD). Ignored whenever an existing agent is used."
          }
        },
        "description": "Input of sdkStart. Every field is optional; send {} to start a session with the default agent."
      },
      "SdkStartVoiceCallInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "agentId": {
            "type": "integer",
            "description": "Id of the agent that answers the call (an integer from the agents query); NotFound when it is not in the API key's workspace. The agent's published workflow answers every turn, so it should be a voice-safe workflow: short spoken sentences, no lists or markdown."
          },
          "clientId": {
            "type": ["string", "null"],
            "description": "Your own identifier for the caller or the call, shown on the session (at most 255 characters). A fresh one is generated when omitted."
          },
          "context": {
            "description": "Optional per-call data as a flat JSON object (pass it through `variables`, not an inline literal), exposed to the workflow as ctx.session.context. Same rules as the context of sdkStart."
          },
          "engine": {
            "anyOf": [
              { "$ref": "#/components/schemas/VoiceEngine" },
              { "type": "null" }
            ],
            "description": "Who answers the call (default WORKFLOW: the agent's published workflow). LIVE uses a speech-to-speech model from the agent's instructions only (no workflow, no tools, transcript recorded after the call) and is refused unless the deployment has it enabled."
          }
        },
        "required": ["agentId"],
        "description": "Input of sdkStartVoiceCall: which agent answers the call, and optional per-call data."
      },
      "SessionStatus": {
        "type": "string",
        "enum": ["ACTIVE", "ENDED", "EXPIRED", "PENDING"],
        "description": "Lifecycle state of a session."
      },
      "TenantPlanEnum": {
        "type": "string",
        "enum": ["DEV", "ENTERPRISE", "PRO"]
      },
      "TestCustomActionInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "confirmWrite": {
            "type": ["boolean", "null"],
            "description": "Required to test any action whose method is not GET, because the call really happens and may change data on the target. Default false."
          },
          "id": {
            "type": "integer",
            "description": "The custom action to test."
          },
          "sampleArgs": {
            "description": "A flat JSON object sent as the request body (or the query string for GET/DELETE), exactly as a workflow node would send its rendered arguments. Values are sent verbatim: template syntax such as {{x}} is rejected. Omit to send no arguments."
          }
        },
        "required": ["id"],
        "description": "Which custom action to call once for real, and with what arguments."
      },
      "UpdateAgentInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "avatarBackend": {
            "anyOf": [
              { "$ref": "#/components/schemas/AvatarBackendType" },
              { "type": "null" }
            ],
            "description": "How clients render the avatar. Omit to leave unchanged; null is refused."
          },
          "avatarBackendConfig": {
            "description": "Settings for a HOSTED_API avatar, a JSON object with the hosted avatar id and model (pass it through variables); no secrets. Not needed for CLIENT_3D. Omit to leave unchanged; null is refused."
          },
          "avatarGender": {
            "anyOf": [
              { "$ref": "#/components/schemas/AvatarGenderCode" },
              { "type": "null" }
            ],
            "description": "Gender of the avatar model (default F). Omit to leave unchanged; null is refused."
          },
          "avatarGlb": {
            "type": ["string", "null"],
            "description": "File name of the 3D avatar model: lower-case letters, digits, \"-\" or \"_\", ending in .glb (at most 100 characters; default \"brunette-t.glb\"). agentConfigOptions lists the available models. Omit to leave unchanged; null is refused."
          },
          "evaluationPromptTemplate": {
            "type": ["string", "null"],
            "description": "Your own evaluation prompt (at most 10000 characters), required when useCase is CUSTOM and then it must contain {transcript}; it may also use {subject} and {recommendationLabels}. Omit to leave unchanged; null is refused."
          },
          "evaluationRecommendationLabels": {
            "type": ["array", "null"],
            "items": { "type": "string" },
            "description": "Recommendation labels a CUSTOM evaluation may return (at most 20, each at most 50 characters), required when useCase is CUSTOM. Omit to leave unchanged; null is refused."
          },
          "externalSyncOwnerId": {
            "type": ["integer", "null"],
            "description": "Owner id your endpoint expects when the mirrored conversation is created (a positive integer of your system). Omit to leave unchanged; null is refused."
          },
          "externalSyncSchemaConfig": {
            "description": "Optional overrides for how mirrored turns are written to externalSyncUrl: a JSON object with any of conversationInputTypeName, messageInputTypeName, userRoleValue, assistantRoleValue. Omit to leave unchanged; null is refused."
          },
          "externalSyncSecretRef": {
            "type": ["string", "null"],
            "description": "Reference to a secret stored for your workspace whose value is sent as the bearer token to externalSyncUrl (at most 200 characters; never the secret itself). A reference belonging to another workspace is refused. Omit to leave unchanged; null is refused."
          },
          "externalSyncUrl": {
            "type": ["string", "null"],
            "description": "URL of your own GraphQL endpoint that receives a best-effort copy of each turn the workflow answers (at most 2000 characters). Private and internal addresses are refused. Omit to leave unchanged; null is refused."
          },
          "lipsyncLang": {
            "type": ["string", "null"],
            "description": "Short language code for 3D avatar lip-sync, such as \"en\" (at most 10 characters; default \"en\"). Omit to leave unchanged; null is refused."
          },
          "llmModelOverride": {
            "type": ["string", "null"],
            "description": "Specific model id from llmModelCatalog (at most 100 characters); send null to clear it. Set alone, the provider that serves it is used; an unknown model, a provider mismatch or a model outside the workspace allowlist is refused."
          },
          "llmProviderOverride": {
            "anyOf": [
              { "$ref": "#/components/schemas/LlmProviderType" },
              { "type": "null" }
            ],
            "description": "Model provider for this agent; send null to clear the override (back to the workspace setting or platform default)."
          },
          "name": {
            "type": ["string", "null"],
            "description": "Display name (at most 100 characters). Omit to leave unchanged; null is refused."
          },
          "openingGreeting": {
            "type": ["string", "null"],
            "description": "Fixed opening line, a template that may use {{clientExternalId}} (at most 2000 characters). Sent when a session starts on an agent without a workflow; without it the model writes a greeting. Omit to leave unchanged; null is refused."
          },
          "personaPrompt": {
            "type": ["string", "null"],
            "description": "System prompt: identity, tone and boundaries of the agent (at most 10000 characters). Omit to leave unchanged; null is refused."
          },
          "position": {
            "type": ["string", "null"],
            "description": "Role title, for example \"Support Specialist\" (at most 200 characters); also the evaluation subject when subject is empty. Omit to leave unchanged; null is refused."
          },
          "speakingRate": {
            "type": ["number", "null"],
            "description": "Text-to-speech speaking rate, 0.25 to 4.0 where 1.0 is normal (default 1.0). Omit to leave unchanged; null is refused."
          },
          "status": {
            "anyOf": [
              { "$ref": "#/components/schemas/AgentStatus" },
              { "type": "null" }
            ],
            "description": "ACTIVE or INACTIVE. An INACTIVE agent is not picked as the default by sdkStart and is refused by runWorkflowTask. Omit to leave unchanged; null is refused."
          },
          "sttAlternativeLanguageCodes": {
            "type": ["array", "null"],
            "items": { "type": "string" },
            "description": "Speech-to-text fallback languages besides the primary (BCP-47). null/omitted = the platform default: English (\"en-US\") and Malay (\"ms-MY\"), minus the primary. [] = recognise in the primary only. A list = exactly those languages, at most 3: Google silently ignores the ENTIRE list past 3, so more is rejected rather than truncated. Mandarin and Cantonese are NOT in the default, so list \"cmn-Hans-CN\" / \"yue-Hant-HK\" explicitly when Chinese speech is expected. Duplicates, and an entry equal to the effective primary (sttLanguageCode, else ttsLang), are rejected."
          },
          "sttLanguageCode": {
            "type": ["string", "null"],
            "description": "Speech-to-text PRIMARY language (BCP-47, e.g. \"en-US\", \"ms-MY\", \"cmn-Hans-CN\"): the language most callers speak. null/omitted means \"use ttsLang\". Separate from ttsLang so an agent can reply in one language but mostly hear another. The primary biases recognition much more than the alternatives, so never set a secondary language here."
          },
          "subject": {
            "type": ["string", "null"],
            "description": "What sessions are about (the job, product or course), used by the evaluation prompt; position is used when empty (at most 200 characters). Omit to leave unchanged; null is refused."
          },
          "timezone": {
            "type": ["string", "null"],
            "description": "IANA timezone of this agent (e.g. \"Asia/Kuala_Lumpur\", \"America/New_York\"). Used by the {{today}} and {{now}} workflow template helpers. Defaults to Asia/Kuala_Lumpur."
          },
          "ttsLang": {
            "type": ["string", "null"],
            "description": "Language the agent speaks, a BCP-47 code such as \"en-US\" (at most 20 characters; default \"en-US\"). Omit to leave unchanged; null is refused."
          },
          "ttsVoice": {
            "type": ["string", "null"],
            "description": "Text-to-speech voice name, for example \"en-US-Neural2-F\" (at most 100 characters; default \"en-US-Standard-C\"). See agentConfigOptions for recommended voices. Omit to leave unchanged; null is refused."
          },
          "useCase": {
            "anyOf": [
              { "$ref": "#/components/schemas/AgentUseCase" },
              { "type": "null" }
            ],
            "description": "Kind of conversation. Switching to CUSTOM requires evaluationPromptTemplate and evaluationRecommendationLabels in the same call. Omit to leave unchanged; null is refused."
          },
          "vadThreshold": {
            "type": ["number", "null"],
            "description": "Voice-activity detection threshold for the microphone, 0.1 to 1.0 (default 0.8). Omit to leave unchanged; null is refused."
          },
          "voiceTier": {
            "anyOf": [
              { "$ref": "#/components/schemas/VoiceTier" },
              { "type": "null" }
            ],
            "description": "Voice quality tier label. Omit to leave unchanged; null is refused."
          },
          "workflowId": {
            "type": ["integer", "null"],
            "description": "Attach a workflow of your workspace (its published version drives every turn; publish it first). An unknown id is refused. It cannot be detached by sending null. Omit to leave unchanged."
          }
        },
        "description": "Changes to an agent. Every field is optional: send only what changes."
      },
      "UpdateWorkflowInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "description": {
            "type": ["string", "null"],
            "description": "New description (at most 500 characters)."
          },
          "edges": {
            "description": "The complete edge list of the draft, replacing the stored one: a JSON array (send it through variables) of {id, source, target, label?}, at most 400 edges. source and target are node ids; label selects the branch for routing nodes (for example \"true\" and \"false\" after a condition node)."
          },
          "id": {
            "type": "integer",
            "description": "Integer id of the workflow to change."
          },
          "name": {
            "type": ["string", "null"],
            "description": "New display name (at most 120 characters)."
          },
          "nodes": {
            "description": "The complete node list of the draft, replacing the stored one: a JSON array (send it through variables) of {id, type, label, config, position: {x, y}}, at most 200 nodes. type is a NodeType value in lower case (for example \"llm\"); config follows that type's schema from nodeConfigSchemas and is validated on save."
          },
          "settings": {
            "anyOf": [
              { "$ref": "#/components/schemas/WorkflowSettingsInput" },
              { "type": "null" }
            ],
            "description": "Workflow-level settings to change, merged into the stored ones (an omitted field inside keeps its value; omitting settings, or sending null, changes nothing). Effective on save, not on publish."
          }
        },
        "required": ["id"],
        "description": "Changes to a workflow DRAFT. Every field except id is optional: an omitted field keeps its stored value."
      },
      "UsageBucketEnum": {
        "type": "string",
        "enum": ["AI_CREDITS", "AVATAR", "STT", "TTS"]
      },
      "VoiceEngine": {
        "type": "string",
        "enum": ["LIVE", "WORKFLOW"],
        "description": "Who answers a voice call. WORKFLOW: the agent's published workflow answers (guardrails, tools, transcript). LIVE: a speech-to-speech model answers from instructions only (faster, no workflow or tools, transcript recorded after the call)."
      },
      "VoiceTier": {
        "type": "string",
        "enum": ["CHIRP3_HD", "NEURAL2", "STANDARD", "STUDIO", "WAVENET"],
        "description": "Text-to-speech quality tier of an agent voice. A label for choosing voices: the voice actually spoken is ttsVoice, and agentConfigOptions lists recommended voices per tier."
      },
      "WorkflowDiagnosticSeverity": {
        "type": "string",
        "enum": ["INFO", "WARNING"],
        "description": "WARNING: the graph will probably not do what the author meant at run time. INFO: a hint or a mirror of a publish rule. Neither blocks saving or publishing."
      },
      "WorkflowFailureNotify": {
        "type": "string",
        "enum": ["NONE", "OWNERS"],
        "description": "Who is told when a run of this workflow fails. In-app notifications only (there is no email yet)."
      },
      "WorkflowRunStatus": {
        "type": "string",
        "enum": [
          "AWAITING_INPUT",
          "COMPLETED",
          "FAILED",
          "INTERRUPTED",
          "PENDING",
          "RUNNING"
        ],
        "description": "State of one workflow run."
      },
      "WorkflowRunWaitStatus": {
        "type": "string",
        "enum": ["CANCELLED", "EXPIRED", "RESUMED", "WAITING"]
      },
      "WorkflowSettingsInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "failureResponse": {
            "type": ["string", "null"],
            "description": "Plain text (1 to 500 characters after trimming) sent instead of the standard error reply when a run fails. Refused (BAD_USER_INPUT) when it contains {{ }} templates, HTML tags, control characters or anything shaped like a credential or signed URL. null clears it (back to the standard reply); omit it to keep the stored value."
          },
          "notifyOnFailure": {
            "anyOf": [
              { "$ref": "#/components/schemas/WorkflowFailureNotify" },
              { "type": "null" }
            ],
            "description": "OWNERS turns on the in-app failure notice to the workspace owners; NONE (or null) turns it off. Omit it to keep the stored value. Effective on save, no publish needed."
          }
        },
        "description": "Changes to workflow-level settings. A partial merge: an omitted field keeps its stored value. Saved and effective immediately; no publish needed."
      },
      "WorkflowVersionSource": {
        "type": "string",
        "enum": ["BACKFILL", "HIRE", "PUBLISH", "ROLLBACK", "SEED"],
        "description": "How a workflow version was created."
      }
    }
  }
}
