The Testing Academy · Build Notes

API Contract Validator
in Langflow — implementation guide

The research and design decisions behind Agent 3: how to make an AI agent run a real auth-then-validate API chain in Langflow, validate responses against a JSON Schema, and never fake a result.

TL;DR

The short version

01

The architecture to build

The verified pattern from the first two agents (Input → Prompt → Groq → Structured Output → Output) is right for the LLM-facing parts. But a contract validator has a hard requirement: the HTTP calls and schema checks must be real. So the real work goes in code:

  1. Chat Input — user pastes the cURL commands.
  2. LLM parse stage (Prompt → Groq → Structured Output) — converts messy cURLs into a clean structured request plan with per-call contract expectations. A legitimate LLM job.
  3. Custom Python component — receives the plan, performs the real auth call, extracts the token, injects Cookie: token=<token> on the protected calls, fires every request with requests, validates each response with jsonschema, returns a structured verdict.
  4. Second LLM stage — turns the raw pass/fail data into a readable explanation of why each call failed.
  5. Chat Output — the final verdict report.
The invariant. Real calls + real validation in code; the LLM only parses input and writes language. A hallucinated pass would make a contract validator worse than useless.
02

Native API Request component

In Langflow 1.7.x the component class is APIRequestComponent (type id APIRequest), under lfx.components.data.api_request (the legacy langflow.components… path still resolves). Its template carries mode, urls, curl, method, query_params, body, headers, timeout, follow_redirects (now defaults to false for SSRF protection), and include_httpx_metadata.

Headers and body are nested-dict inputs that can accept an upstream Data object, but in practice only the URL is reliably dynamic — header/body injection from upstream has open bugs, and the component holds no state between calls. The method dropdown has historically omitted DELETE. For a token-reuse chain, prefer the custom component.

03

The custom component

There is no native component that carries an auth token across multiple API Request nodes. The clean approach is one custom Python component using requests (a core Langflow dependency — importable without being declared).

custom_component_shape.pypython
from lfx.custom.custom_component.component import Component
from lfx.io import DataInput, MultilineInput, MessageTextInput, SecretStrInput, IntInput, Output
from lfx.schema.data import Data
import requests, jsonschema

class APIContractValidator(Component):
    display_name = "API Contract Validator"
    description = "Runs a real auth-then-validate API chain and validates each response against a JSON Schema."
    icon = "ShieldCheck"
    name = "APIContractValidator"

    inputs = [
        DataInput(name="request_plan", display_name="Request Plan"),
        MultilineInput(name="base_url", display_name="Base URL",
                       value="https://restful-booker.herokuapp.com"),
        MessageTextInput(name="username", display_name="Username", value="admin"),
        SecretStrInput(name="password", display_name="Password", value="password123"),
        IntInput(name="timeout", display_name="Timeout", value=30),
    ]
    outputs = [Output(name="report", display_name="Verdict Report", method="run_validation")]

    def run_validation(self) -> Data:
        session = requests.Session()
        # 1) auth -> token ; 2) iterate plan, inject Cookie: token=<token> on writes
        # 3) fire real requests ; 4) jsonschema.validate each response
        # 5) return Data(data={"results": [...]})
        ...

Inputs come from lfx.io (DataInput, MessageTextInput, MultilineInput, SecretStrInput, IntInput, …). Outputs are a class-level list, each mapping to a method returning Data or Message. Use a requests.Session() so cookies thread through automatically.

04

JSON Schema validation inside Langflow

There is no native JSON-Schema validation component. Structured Output coerces an LLM response into a schema; it does not validate an external API response against a strict schema. Use the jsonschema library inside the custom component:

validate.pypython
v = jsonschema.Draft7Validator(schema)
errors = [f"{'/'.join(map(str, e.path))}: {e.message}"
          for e in v.iter_errors(response_json)]
schema_ok = len(errors) == 0

jsonschema is a transitive dependency in most Langflow installs and requests is core, so both import without extra setup in a standard install. On the standalone lfx package or Langflow Desktop, pin jsonschema in the instance requirements.txt to be safe. Note: format: "date" is only enforced with a format checker — otherwise add a regex pattern for YYYY-MM-DD.

05

The Restful Booker contract

Base URL https://restful-booker.herokuapp.com. The sandbox ships with 10 records and resets every 10 minutes.

EndpointAuthStatusResponse shape
POST /auth200{token} — but 200 even on bad creds, so check the body
GET /booking200array of {bookingid}
GET /booking/{id}200full booking object
POST /booking200wrapped: {bookingid, booking}
PUT /booking/{id}Cookie: token=200updated booking object
PATCH /booking/{id}Cookie: token=200full booking object
DELETE /booking/{id}Cookie: token=201status only

The booking object: firstname (string), lastname (string), totalprice (integer), depositpaid (boolean), bookingdates (object with checkin/checkout date strings), additionalneeds (string, optional). The create response wraps it in {bookingid, booking}; the auth response is {token}.

Two quirks to validate around. DELETE returns 201 Created, not 200/204. And the live instance only reliably accepts the Cookie token form — the Basic-auth alternative in the docs often returns 403. Validate against what the API does, not just what it documents.
06

Groq model + Structured Output wiring

The Groq component generates text via ChatGroq and offers a dual output: "Model Response" (Message) and "Language Model" (LanguageModel). Key fields: api_key, model_name (dropdown populated from Groq's /models), base_url (https://api.groq.com/openai/v1), temperature. It needs langchain-groq.

openai/gpt-oss-120b is a current Groq production model (MoE, 120B total / ~5.1B active, 128k context, Apache-2.0). To feed Structured Output, switch the Groq output to Language Model and connect that to Structured Output's Language Model input, alongside the Message you want to extract from.

07

Cookie-based auth

Canonical form: literal header Cookie: token=<token>, where the token is the value from POST /auth. A requests.Session() carries cookies for you. Authenticate once, attach the token to every mutating request, re-auth on 401/403.

Teaching analogy: the token is a hotel key card — collected once at check-in (auth), presented for every privileged action (PUT/PATCH/DELETE), and never assumed valid without a real round-trip.

08

Flow JSON serialization — avoiding 422

Top-level keys: data, description, endpoint_name, id, is_component, last_tested_version, name, tags. data holds nodes, edges, viewport. Nodes are genericNode with NodeName-UUID ids; node.data.type must match a registered component.

The critical part is edge encoding. Each edge's sourceHandle/targetHandle string is the decoded JSON re-serialized with every " replaced by œ (U+0153):

edge handle (verbatim form)
"sourceHandle": "{œdataTypeœ:œChatInputœ,œidœ:œChatInput-jFwUmœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}"

The edge source must equal the sourceHandle's id; target must equal the targetHandle's id; both must be real node ids. Common 422 causes: corrupted or transliterated œ characters, missing nodes/edges, sending data as a string, or a data.type that doesn't match a registered component. Always read the detail array in the 422 body — it pinpoints the first failing field.

Build it in stages

  1. Stage 1 — the custom component in isolation. Run it with a hardcoded plan against Restful Booker; confirm real status codes and real jsonschema errors. A deliberately wrong schema must produce a populated error.
  2. Stage 2 — the parse stage. Chat Input → Prompt ("convert these cURLs to a JSON array") → Groq (output = Language Model) → Structured Output → into the component's request_plan.
  3. Stage 3 — the explanation stage + output. Feed the verdict into a second Prompt + Groq + Structured Output, then Chat Output.
  4. Stage 4 — export and validate. Export from the UI first (guarantees valid encoding), test import before any API call.
!

Caveats