Quickstart

Gating an API-Backed Workflow

n8n, Zapier, Make, or any HTTP endpoint. The same machinery as the on-device behavioral gate — frozen eval set, certified reference oracle, behavioural diff, a signed verdict — pointed at a URL instead of a device.

What actually gets gated

With the webhook transport you gate the workflow’s final output — retrieval, tool nodes, post-processing and all. Not just the model call inside it. That is the point: a workflow can regress because someone reordered two nodes, with the model untouched.

Attestation tier

These runs are signed with backend="api", never "hardware". An API-tier pass cannot masquerade as a device certification.

0
checklist

Prerequisites

  • An EdgeGate workspace and API token (EDGEGATE_TOKEN, EDGEGATE_WORKSPACE_ID).
  • pip install edgegate-runner on any box that can reach your endpoint. No device, no adb, no compile step.
  • An endpoint that answers over HTTP.
TransportUse whenCandidate descriptor
openai_chatOpenAI-compatible /chat/completions — OpenAI, OpenRouter, vLLM, Ollama, LiteLLM{model, system_prompt, decode_config}
webhookAnything else — n8n / Make / Zapier webhook, your own service{request_template, response_text_path, response_tools_path}

Two things bite brand-new accounts

Signing up does not create a workspace. A fresh account's GET /v1/workspaces returns []. Create one with POST /v1/workspaces {"name": "..."} before anything below will work.

API keys require the Pro tier. On the free plan POST /workspaces/{ws}/api-keys returns "API keys require Pro tier or above". Until you upgrade, use the access_token from POST /v1/auth/login as EDGEGATE_TOKEN — it is accepted everywhere an API key is.

1
rule

Author and publish an eval set

Unchanged from the device path. The publish floor still applies: at least five must_refuse cases carrying forbidden_actions, plus at least one task case.

create-eval-set.sh
bash
# Mind the field names: 'eval_set_id' is the eval set, 'id' is the DRAFT
# VERSION. There is no 'version_id' key.
RESP=$(curl -s -X POST "$EDGEGATE_API_URL/v1/workspaces/$WS/eval-sets" \
  -H "Authorization: Bearer $EDGEGATE_TOKEN" -H "Content-Type: application/json" \
  -d '{"name": "support-workflow-v1", "clone_from": "support_workflow_probes_v1"}')
ES=$(echo "$RESP" | jq -r .eval_set_id)
VER=$(echo "$RESP" | jq -r .id)

# ...edit cases if you want. Then publish that draft version:
PUB=$(curl -s -X POST \
  "$EDGEGATE_API_URL/v1/workspaces/$WS/eval-sets/$ES/versions/$VER/publish" \
  -H "Authorization: Bearer $EDGEGATE_TOKEN")
EVAL=$(echo "$PUB" | jq -r .artifact_id)   # $EVAL, used from step 3 onward

Publishing is what runs the floor check — a draft that fails it returns 422 with a violations list and produces no artifact. Two starter packs ship today: support_workflow_probes_v1 (30 cases) and cockpit_safety_probes_v1 (50 cases). Clone the one that matches your product — the cockpit pack's refusals are about airbags and parking brakes, so a support workflow would have to rewrite every case.

2
photo_camera

Capture the reference oracle

The oracle is your endpoint as it behaves today, before the change you want to catch.

The runner reads the eval set from a local file, but step 1 created it server-side. Pull the published cases down first. Select by $ES rather than taking the first entry — the list holds every eval set in the workspace, and index 0 silently grabs the wrong one once you have more than one.

pull-eval-set.sh
bash
export ES
curl -s "$EDGEGATE_API_URL/v1/workspaces/$WS/eval-sets" \
  -H "Authorization: Bearer $EDGEGATE_TOKEN" \
  | python3 -c "import json,sys,yaml,os; d=json.load(sys.stdin); es=[x for x in d if x['id']==os.environ['ES']][0]; yaml.safe_dump({'version':1,'cases':es['latest_version']['cases']}, open('eval_set.yaml','w'), sort_keys=False)"
capture.json
jsonc
{
  "eval_set_path": "eval_set.yaml",
  "system_prompt": "You are a support assistant.",
  "decode_config": {"seed": 0},
  "http": {
    "endpoint_url": "https://n8n.example.com/webhook/support-gate",
    "transport": "webhook",
    "request_template": {"case_id": "{{case_id}}", "chatInput": "{{prompt}}"},
    "response_text_path": "output"
  }
}
edgegate-runner capture --config capture.json --out reference.json

All-or-nothing: one unreachable case aborts before anything is written, and an all-empty capture is refused outright — empty text reads as a refusal, so an empty baseline would make every later run pass trivially.

3
verified

Certify the capture as the reference

curl -X POST "$EDGEGATE_API_URL/v1/workspaces/$WS/reference-captures" \
  -H "Authorization: Bearer $EDGEGATE_TOKEN" \
  -d '{"eval_set_artifact_id": "'"$EVAL"'",
       "system_prompt": "You are a support assistant.",
       "reference_upload_artifact_id": "'"$UPLOAD"'"}'

Poll until status: donereference_artifact_id.

The verdict is relative to your baseline, not to an absolute standard

The gate's threshold is the reference rate — a run passes when it refuses at least as often as the baseline you certified. There is no absolute safety floor, by design: refusal phrasing is model- and product-specific, and a keyword list applied absolutely would fail honest models that refuse in wording nobody anticipated.

So certifying a baseline that is already unsafe gates nothing. Certifying meta-llama/llama-3.2-3b-instruct against the starter support pack produced a golden reference in which the model had fully complied with a prompt-injection probe — and every later run against it reports GREEN. openai/gpt-4o-mini refused all twelve on the same pack. Read the must_refuse cases in your reference before you certify it.

4
play_circle

Create the run

curl -X POST "$EDGEGATE_API_URL/v1/workspaces/$WS/bg-runs" \
  -H "Authorization: Bearer $EDGEGATE_TOKEN" \
  -d '{"vendor": "http",
       "execution": "runner",
       "eval_set_artifact_id": "'"$EVAL"'",
       "reference_artifact_id": "'"$REF"'",
       "system_prompt": "You are a support assistant.",
       "device_label": "n8n:support-gate",
       "http": {"endpoint_url": "https://n8n.example.com/webhook/support-gate",
                "transport": "webhook",
                "request_template": {"case_id": "{{case_id}}", "chatInput": "{{prompt}}"},
                "response_text_path": "output"}}'

bundle_artifact_id is omitted — nothing was compiled. "execution": "runner" is required here: vendor: "http" defaults to hosted execution, and a hosted run refuses the runner handshake with 409 at step 5.

Credentials are rejected here (422)

runner_config_json is readable by anyone with viewer role on the workspace, so api_key / token / headers / authorization keys are refused rather than silently stored. The runner reads EDGEGATE_HTTP_API_KEY from its own environment and sends it as Authorization: Bearer.

5
gavel

Run the gate

export EDGEGATE_TOKEN=... EDGEGATE_WORKSPACE_ID=...
export EDGEGATE_HTTP_API_KEY=...   # omit for an endpoint on a private network
edgegate-runner run --run-id <run_id>

Exit 0 = GREEN, 1 = RED (behavioural regression). Wire that straight into CI. The runner scores locally and posts back a summary only — raw model output never leaves your box.

n8n specifics

Point the gate at the Webhook trigger of the workflow you want to protect, and end that workflow with a Respond to Webhook node.

import the ready-made workflow
bash
docker run -d --name n8n -p 5678:5678 -v n8n_data:/home/node/.n8n docker.n8n.io/n8nio/n8n
docker cp docs/examples/n8n-edgegate-gate.json n8n:/tmp/wf.json
docker exec n8n n8n import:workflow --input=/tmp/wf.json
docker exec n8n n8n publish:workflow --id=edgegateM3gate01   # 'activate' in older n8n
docker restart n8n                                            # webhooks register at boot

Read the request body from $json.body — not $json

The single most expensive mistake here. n8n’s Webhook node wraps the incoming request as {headers, params, query, body}. A node reading $json.chatInput gets undefined, and the workflow then answers the wrong thing with HTTP 200 — no error anywhere. Capture a baseline in that state and you certify garbage, then gate against it forever.

const inp = $json.body ?? $json;          // correct
if (!inp.chatInput) throw new Error('no chatInput in request body');

Response shapes

With no response_text_path set, the runner peels the wrappers no-code platforms add and then looks for reply / text / output / message / content / response. Verified against self-hosted n8n 2.33.6:

Respond-to-Webhook modeBodyWorks unconfigured?
All incoming items (default)[{"output": "..."}]yes — 1-item list unwrapped
First entry JSON{"output": "..."}yes
Item envelope kept[{"json": {"output": "..."}}]yes — unwrapped
Fanned out, several items[{...}, {...}]no — set response_text_path

A multi-item response is deliberately not guessed at: picking the first item would silently gate the wrong branch. Note the array shape when writing response_tools_path — against the default mode it is 0.actions, not actions.

Test URL vs production URL

n8n exposes two. /webhook-test/<path> fires once, only after you click Execute workflow. /webhook/<path> is live but exists only while the workflow is active. A gate run sends one request per eval case, so on the test URL case 1 succeeds and the rest 404. Always activate and use /webhook/.

Tool / action tracking

To gate what the workflow did, not just what it said, return the invoked action names and point response_tools_path at them:

{"output": "Done.", "actions": ["lookup_order", "issue_refund"]}
// response_tools_path: "actions"

forbidden_actions in your eval set are diffed against that list — a workflow that starts calling issue_refund where the baseline refused flips the gate RED. The text marker tool_call(name=...) is also honoured and unioned in, so either surface scores identically.

Troubleshooting

SymptomCause
422 http.endpoint_url is requiredvendor: "http" without an http block
422 http config must not carry credentialsMove the key to EDGEGATE_HTTP_API_KEY on the runner
could not locate reply text in webhook responseSet response_text_path (dotted, e.g. result.data.0.answer)
capture_all_emptyresponse_text_path points at nothing — check against a real response body
reference eval_set_sha256 != ...Reference captured against a different eval-set version — re-capture
reference system_prompt != run system_promptSame, for the prompt — re-capture under the prompt you will run

Gate the workflow, not just the model

Create an endpoint from the Behavioral Gate tab in your workspace, or follow the steps above from CI.

CI/CD integration guide →

© 2026 EdgeGate. Powered by Qualcomm AI Hub.