Hive RouterCustomizationsCoprocessors

Stages and Protocol

This page describes how Hive Router and a coprocessor service communicate.

Each configured stage is translates to a request-response exchange. Hive Router sends a JSON payload, your service returns a JSON decision, and the router applies that decision before continuing.

Understanding this contract is key to building reliable and predictable coprocessors.

If you need the YAML option reference, see coprocessor configuration reference.

Version

Every coprocessor request and response must include a version field. The version identifies the protocol contract used by Hive Router.

{
  "version": 1,
  "control": "continue"
  // ...
}

Including an explicit version allows the protocol to evolve over time without breaking existing integrations. Future versions may introduce new fields, stricter validation, or different mutation rules.

If the version is missing or unsupported, Hive Router treats the response as a coprocessor failure.

Hive Router always sends the current version in the request. Your coprocessor should echo the same version in its response.

Control flow

Each enabled stage sends a JSON payload to your coprocessor and waits for a JSON response.

The response must include a control field that determines the next step:

  • "continue" - proceed to the next step in the pipeline
  • { "break": <status> } - stop processing and return an immediate HTTP response

Continue

In case you do not want to modify anything and just let the router proceed to the next step, send back only version and control.

Simple continue decision
{
  "version": 1,
  "control": "continue"
}

Continue with mutations

However, if you want to modify the state to pass data to the next stages, adjust pipeline's behaviour or alter the request's body, you're able to send back more than just version and control.

Response for 'graphql.request' stage event
{
  "version": 1,
  "control": "continue",
  "headers": {
    "x-custom-header": "value"
  },
  "context": {
    "custom.key": "value"
  }
}

In the example above, the "custom.key": "value" will be inserted to the context and available for the next stages and plugin hooks. The headers will be dropped and replaced with only "x-custom-header": "value going forward. Every next step or a plugin hook, will receive new headers, that applies to header propagation feature as well.

Break (short-circuit)

At every stage you're able to short-circuit the pipeline and return an early response. Be aware that sending back no body will result in an empty response for the client's request.

Early response with 401 status code, body and headers
{
  "version": 1,
  "control": { "break": 401 },
  "headers": {
    "content-type": "application/json"
  },
  "body": {
    "errors": [{ "message": "Unauthorized" }]
  }
}

Protocol envelope

Stage payloads sent by Hive Router to Coprocessors always include these fields:

  • version (currently 1)
  • stage (current stage name)
  • control (always starts as "continue")
  • id (request identifier)

Depending on include settings, payloads may also contain fields like headers, body, context, method, path, status_code, and sdl.

Your coprocessor must return OK 200 with valid JSON. Invalid JSON, invalid control values, unsupported version, or forbidden stage mutations are treated as coprocessor failures and fail the entire client request.

Stages

The following diagram describes the execution order and available stages.

HTTP request

HTTP response

HTTP request

HTTP response

HTTP request

HTTP response

HTTP request

HTTP response

HTTP request

HTTP response

Client Request

Stage: router.request

Read GraphQL request

Parse and validate GraphQL

Stage: graphql.request

auth, JWT, policy, progressive labels

Stage: graphql.analysis

Plan GraphQL operation

Execute query plan

Stage: graphql.response

Build HTTP response

Stage: router.response

Client Response

Coprocessor Service

router.request

Runs when the inbound HTTP request enters Hive Router. Use this stage for early traffic checks such as auth header checks and fast request rejection.

Available properties in include:

PropertyMutableDescription
headersHeaders of the client request
methodHTTP method of the client request
pathHTTP request's path of the client request
bodyI think you know already, HTTP request's body
contextSerialized request context

graphql.request

Runs after the router recognizes GraphQL request data and extracts the GraphQL payload from either body or query params. Use this stage for request shaping, variable normalization, and request-level guardrails.

PropertyMutableDescription
headersHeaders of the client request
methodHTTP method of the client request
pathHTTP request's path of the client request
bodyGraphQL request payload (contains query, operationName, variables and extensions)
sdlText representation (SDL) of the public schema
contextSerialized request context

graphql.analysis

Runs after GraphQL parsing and validation, but right before planning and execution. This is the stage is used in the progressive override label-injection example above.

PropertyMutableDescription
headersHeaders of the client request
methodHTTP method of the client request
pathHTTP request's path of the client request
bodyGraphQL request payload (contains query, operationName, variables and extensions)
sdlText representation (SDL) of the public schema
contextSerialized request context

graphql.response

Runs after GraphQL execution produces a GraphQL response. Use this stage for response normalization, error shaping, and redaction before final HTTP response handling.

PropertyMutableDescription
headersHeaders of the client response
status_codeStatus code of the client response
bodyGraphQL response
sdlText representation (SDL) of the public schema
contextSerialized request context

router.response

Runs right before the final HTTP response is sent to the client.

PropertyMutableDescription
headersHeaders of the client response
status_codeStatus code of the client response
bodyBody of the response
sdlText representation (SDL) of the public schema
contextSerialized request context

Request context

Request context is key-value state attached to a single request lifecycle and shared with built-in features, custom plugins and coprocessors.

Use it to pass decisions between stages, for example:

  • extract tenant.id from headers in router.request
  • read tenant.id in graphql.analysis to select feature flags or routing behavior

Treat context keys as shared API contracts:

  • use clear namespacing (hive:: is reserved)
  • keep values small and serializable
Context updates are propagated to later stages.

The include.context property supports three forms:

ValueAction
falseInclude no context
trueInclude full context
[string]Include only selected keys

Operation

PropertyDescription
hive::operation::nameThe name of the GraphQL operation.
hive::operation::kindThe kind of the GraphQL operation ("query", "mutation", "subscription").

Authentication

PropertyDescription
hive::authentication::jwt_scopesScopes extracted from the current authenticated user's JWT.
hive::authentication::jwt_statusAuthentication status. If true, the request has been verified asauthenticated.

Progressive Override

PropertyDescription
hive::progressive_override::unresolved_labelsThe set of labels that require an external decision
hive::progressive_override::labels_to_overrideThe set of labels that should be overridden for this request

Telemetry

PropertyMutableDescription
hive::telemetry::client_nameThe name of the client application
hive::telemetry::client_versionThe version of the client application

Body

  • Some stages allow body mutation, but graphql.analysis does not.
  • Body patches must stay valid for the stage contract.
  • Invalid body patches are rejected.

For graphql.request and graphql.analysis, include.body supports selective field inclusion:

ValueAction
falseInclude no body
trueInclude full body
[string]Include only selected properties
Available properties: query, operationName, variables, extensions
Selective body include
stages:
  graphql:
    request:
      include:
        body: [query, variables]

You can also patch body fields selectively in your coprocessor response. For example, in graphql.request, this response updates only variables:

Updates variables only
{
  "version": 1,
  "control": "continue",
  "body": {
    "variables": {
      "first": 20
    }
  }
}
Overwrites 'extensions' that were never selected
{
  "version": 1,
  "control": "continue",
  "body": {
    "extensions": {
      "code": "value"
    }
  }
}

Headers

The headers property represents either request or response headers, depending on the stage. Headers can be returned and mutated even when not included in inbound stage payload.

Sets new request headers for in 'router.request' stage
{
  "version": 1,
  "control": "continue",
  "headers": {
    "content-type": "application/json",
    "x-something": "true"
  }
}

To mutate headers without overwriting them you need to set include.headers: true and return them back, modified:

Pseudo code for mutating headers
let headers = stage_payload.headers;
Response.json({
  version: 1,
  control: "continue",
  headers: {
    ...headers,
    "x-something": "true",
  },
});

Include vs mutation semantics

The include property controls what Hive Router sends to your coprocessor in the request payload. It does not strictly limit what fields your coprocessor can return for mutation.

Examples:

  • include.headers: false can still return headers in response and overwrite headers.
  • include.context may omit a key, but response can still patch that context key .
Example: only operation's name is sent to coprocessor
stages:
  graphql:
    request:
      include:
        context: ["hive::operation::name"]

Even with selective inclusion, your response may still patch additional non-included keys:

{
  "version": 1,
  "control": "continue",
  "context": {
    "custom::b": "patched-value"
  }
}

This is why include should be treated as payload minimization, not as mutation permission.

Failure behavior

When a request from Hive Router to Coprocessor fails for some reason, the request lifecycle is stopped, the current progress is dropped and an HTTP response is returned to the client.

Common coprocessor failure causes:

  • non-200 response from the coprocessor
  • malformed JSON
  • unsupported protocol version
  • invalid control value
  • forbidden stage mutation

How client response looks like:

  • request fails (often HTTP 500)
  • GraphQL error message is masked as Internal server error
  • codes remain in extensions.code