GraphQL ExecModule
Overview
GraphQLModule executes real GraphQL-over-HTTP requests from ValkyrAI workflows. Version 2.0 replaces fabricated query, batch, and schema responses with bounded HTTPS transport, typed variables, optional named operations, validated provider responses, and secret-safe failures. It is the only discoverable generic GraphQL module; the duplicate GraphQLGenericModule is now a hidden compatibility shell.
The module supports three operation types: query, mutation, and introspection. Queries and introspection may retry a small set of transient transport failures. Mutations always run once because a timeout or provider error can occur after the remote system commits a side effect.
Authentication is resolved only from a bound IntegrationAccount. Workflow inputs cannot set Authorization, cookies, proxy credentials, or other transport-controlled headers.
Usage
- Identify the provider's GraphQL HTTPS endpoint and the exact operation document.
- For a protected endpoint, create an IntegrationAccount and choose
bearer,api_key, orbasicauthentication. Public endpoints can usenone. - Bind variables as a JSON object; do not interpolate credentials or user input into the document.
- Set
operation_namewhen the document contains multiple operations. - Treat mutation retries as an explicit workflow decision after reconciling provider state.
Migration from GraphQLGenericModule
Saved workflows may still reference GraphQLGenericModule. That class and its Spring bean remain loadable so persisted workflows can be inspected, but execution always returns MODULE_RETIRED without network access, credential resolution, or provider output. Replace the task with GraphQLModule before rerunning it.
| Legacy field | GraphQLModule field | Migration rule |
|---|---|---|
endpoint | endpoint | Use the provider's canonical absolute HTTPS endpoint. HTTP, userinfo, and fragments are rejected. |
query | document | Classify the document explicitly with operation_type. |
variables | variables | Supply a JSON object rather than an encoded string. |
operationName | operation_name | Rename to the canonical snake-case input. |
legacy IntegrationAccount.apiKey | graphqlAccount plus auth_scheme | Bind a READY account; never copy the credential into headers or workflow inputs. |
The retired output is always {"status":"error","error":{"code":"MODULE_RETIRED","retryable":false,"migrationTargets":["GraphQLModule"]}}. It never reuses the old api.gql.data or api.gql.errors workflow-state keys. Map the canonical structured outputs below instead.
Inputs
| Name | Type | Required | Default | Description and constraints |
|---|---|---|---|---|
endpoint | string | Yes | None | Absolute HTTPS URL with a host. Userinfo and fragments are rejected. |
operation_type | string | Yes | query | query, mutation, or introspection. It must match the document. |
document | string | Query or mutation | None | GraphQL document, at most 256,000 Unicode code points. Ignored for introspection. |
operation_name | string | No | None | GraphQL operation name, using GraphQL identifier syntax. |
variables | object | No | {} | Typed JSON variables. The complete request body is capped at 1 MiB. |
headers | object | No | {} | At most 20 non-secret string headers. Header values are capped at 4,096 code points and cannot contain line breaks. |
auth_scheme | string | Yes | bearer | none, bearer, api_key, or basic. |
api_key_header | string | API-key auth | X-API-Key | Valid non-reserved HTTP header name. |
timeout_ms | integer | No | 30000 | Total call timeout from 1,000 through 60,000 milliseconds. |
safe_read_retries | integer | No | 1 | Additional transient attempts from 0 through 2 for queries and introspection only. Mutations force this to zero. |
Map-I/O workflow input can override the corresponding non-secret payload values. IntegrationAccount credentials are never accepted from input or custom headers.
Outputs
| Name | Type | When present | Description |
|---|---|---|---|
status | string | Always | success, partial, or error. |
operationType | string | Parsed operation | Normalized safety class. |
httpStatus | integer | Provider response | HTTP response status. |
data | JSON value | GraphQL returned data | Provider data, including explicit JSON null values inside the data tree. |
errors | array | GraphQL returned errors | Provider GraphQL error array. |
extensions | object | GraphQL returned extensions | Optional response extensions. |
attempts | integer | Always | HTTP attempts consumed. |
error | object | Transport or validation failure | Safe code, message, and retryable fields without provider bodies or credentials. |
An HTTP 200 response with both data and a non-empty errors array is partial. Errors without data are error. A successful HTTP response containing neither data nor errors is rejected as malformed.
IntegrationAccount Requirements
| Authentication | Account fields | Requirement |
|---|---|---|
none | None | No IntegrationAccount is required. |
bearer | apiKey | READY account; the value is sent as Authorization: Bearer .... |
api_key | apiKey | READY account; the value is sent in api_key_header. |
basic | username, password | READY account; the values are encoded into the Basic authorization header. |
Use provider tokens with the least GraphQL scopes or object permissions needed by the document. Store them in IntegrationAccount SecureFields. Never put tokens in endpoint, document, variables, headers, examples, logs, or error-handling branches.
Configuration
The IntegrationAccount value below is a symbolic secure relationship, not plaintext configuration:
{
"version": "2.0.0",
"authConfig": {
"authStrategy": 1,
"integrationAccount": "integration-account:project-api"
},
"payloadConfig": {
"parameters": "{\"endpoint\":\"https://api.example.com/graphql\",\"auth_scheme\":\"bearer\",\"operation_type\":\"query\",\"operation_name\":\"GetProject\",\"document\":\"query GetProject($id: ID!) { project(id: $id) { id name } }\",\"variables\":{\"id\":\"project-42\"},\"safe_read_retries\":1}"
}
}
Operations
| Operation | Request | Side effects and retry policy |
|---|---|---|
query | JSON POST containing query, optional operationName, and variables | Intended for reads. Retries only HTTP 408, 429, 500, 502, 503, and 504 or network failure, up to safe_read_retries. |
mutation | JSON POST containing a document whose first operation is mutation | May write external state. One attempt only; redirects and automatic retry are disabled. |
introspection | JSON POST with ValkyrAI's fixed bounded __schema query | Read-only. Caller-supplied documents are ignored; transient retries follow query policy. |
The module does not implement subscriptions, WebSockets, multipart upload, persisted-query negotiation, automatic pagination, batching, federation composition, client-side schema validation, or redirect following.
Errors and Failure Modes
| Failure | Behavior | Recovery |
|---|---|---|
| Invalid endpoint, document, operation type, variables, headers, or limits | Fails before transport with VALIDATION_ERROR. | Correct the named input. |
| Missing or non-READY authenticated account | Fails before transport. | Bind or reauthorize the appropriate IntegrationAccount. |
| HTTP 401 or 403 | Returns GRAPHQL_HTTP_401 or GRAPHQL_HTTP_403; provider body is discarded. | Verify token validity, scope, object access, and auth scheme. |
| HTTP 408, 429, or transient 5xx on a query | Retries only within the configured bound, then returns a retryable safe error. | Schedule a later read and respect provider rate limits. |
| HTTP or network failure on a mutation | Returns a non-retryable or ambiguous-delivery error after one attempt. | Inspect the provider before a deliberate new mutation. |
| Redirect | Fails closed; the Location target is not followed. | Configure the canonical HTTPS endpoint explicitly. |
| Empty, oversized, non-object, or invalid JSON response | Fails without copying provider body content into the error. | Verify endpoint compatibility; response cap is 5 MiB. |
GraphQL errors | Preserves the bounded GraphQL error array and maps status to partial or error. | Branch on application-level errors; do not assume HTTP 200 means success. |
Example
Execute a named read with typed variables:
{
"endpoint": "https://api.example.com/graphql",
"operation_type": "query",
"operation_name": "GetProject",
"document": "query GetProject($id: ID!) { project(id: $id) { id name status } }",
"variables": {
"id": "project-42"
},
"auth_scheme": "bearer",
"safe_read_retries": 1
}
Expected normalized result after a valid provider response:
{
"status": "success",
"operationType": "query",
"httpStatus": 200,
"data": {
"project": {
"id": "project-42",
"name": "Launch",
"status": "READY"
}
},
"attempts": 1
}
Notes
- Pagination: GraphQL pagination is schema-specific. Use returned cursors in a later bounded workflow step; the generic module never assumes
pageInfosemantics or automatically follows cursors. - Limits: documents are capped at 256,000 code points, complete requests at 1 MiB, responses at 5 MiB, headers at 20 entries, and call timeout at 60 seconds.
- Rate limits: providers may return HTTP 429 or GraphQL errors inside HTTP 200. Only transient HTTP/network failures on reads are retried automatically.
- Idempotency: the module is classified mixed and not globally idempotent. GraphQL has no universal idempotency standard. Supply a provider-supported idempotency key as a non-secret custom header only when the provider documents it, and still reconcile ambiguous mutations.
- API constraints: execution uses JSON POST over GraphQL-over-HTTP. Servers requiring GET-only queries, multipart uploads, subscriptions, signed request bodies, or persisted-query handshakes need a provider-specific module.
- Destructive behavior: mutations can create, update, publish, or delete remote objects depending on the document and account permissions. Place destructive documents behind the workflow's approval policy.
- Privacy: GraphQL responses may contain confidential fields. Downstream mappings, EventLogs, exports, and ContentData must preserve the provider and ValkyrAI authorization boundaries.
- Unverified boundary: deterministic tests cover request construction, bearer authentication, typed variables, fixed introspection, transient read retries, single-attempt mutations, partial GraphQL responses, validation, metadata discovery, and secret redaction. Live provider execution requires a separately authorized IntegrationAccount and is not exercised by repository tests.
- Compatibility boundary:
GraphQLGenericModuleperforms no provider behavior and is retained only so saved workflows can be migrated safely. A normal ValkyrAI runtime deployment is required before its live catalog entry becomes hidden.