DEV Community

vectronodeAPI
vectronodeAPI

Posted on

The Agent Never Reached the Model: A Network Preflight for OpenAI-Compatible APIs

When an AI Agent fails, developers often start by changing the prompt or model. That is the wrong layer if Python receives 401 Unauthorized, Node.js throws ECONNRESET, and Windows PowerShell reports a certificate validation error for the same endpoint.

This article addresses one concrete, verifiable problem: prove the DNS, TCP, TLS, proxy, and HTTP path before sending the Agent's first model request. The preflight deliberately accepts any HTTP response as evidence that an HTTP-speaking hop responded. Only the second request evaluates the API key, model, and Agent response.

Disclosure: VectorNode is the gateway used in the examples. The network-preflight method is application-side and applies to other OpenAI-compatible endpoints.

1. The developer problem

Imagine a team moving the same Agent between a laptop, a CI runner, and a Windows workstation. The application code is unchanged, but the failures look unrelated:

Python       -> 401 Unauthorized
JavaScript   ->  TypeError: fetch failed / ECONNRESET
PowerShell   ->  The remote certificate is invalid
Enter fullscreen mode Exit fullscreen mode

These results are actually useful evidence. A 401 means the request reached an HTTP-speaking server or proxy. A socket reset suggests a network device, proxy, or TLS boundary closed the connection before an HTTP response existed. A certificate exception means the client rejected the peer before an HTTP status existed.

Without a preflight, the team may rotate a valid API key to fix a problem that never reached authentication, or disable certificate verification to make a local test appear green. Both actions hide the real boundary.

2. Why the same endpoint behaves differently

The official OpenAI Chat Completions reference describes a request body containing model and messages, and notes that parameter support can vary by model. For the gateway's current path and parameter details, check https://o1kqetbjmk.apifox.cn.

Those API fields are only the final layer of a longer path:

  1. DNS resolves the hostname to an address.
  2. TCP establishes a connection to port 443.
  3. TLS validates the certificate and negotiates encryption.
  4. Proxy policy may require authentication or a custom root certificate.
  5. HTTP carries the request and returns a status.
  6. API handling checks the key, model, parameters, and prompt.

Python requests, Node's native fetch, and PowerShell do not automatically inherit identical proxy and certificate behavior. Python may use REQUESTS_CA_BUNDLE; Node may need NODE_EXTRA_CA_CERTS or an approved Undici proxy dispatcher; PowerShell normally uses the Windows certificate store. A successful test in one runtime does not configure the other two.

3. Configure the endpoint without weakening trust

Use the same environment contract for the real API call:

API_KEY=replace-with-a-real-key-locally
BASE_URL=https://www.vectronode.com/v1
MODEL=replace-with-a-model-id-visible-in-your-account
Enter fullscreen mode Exit fullscreen mode

The preflight sends a GET to BASE_URL without a request body. The endpoint may answer 401, 404, or another status because the path is not the Chat Completions operation. That is acceptable: any HTTP response proves that DNS, TCP, TLS, and HTTP completed far enough to receive a response.

For a private corporate CA, install the approved root certificate through the operating system or runtime trust mechanism. Do not paste a private key into a script. Do not use these shortcuts:

requests.get(..., verify=False)
NODE_TLS_REJECT_UNAUTHORIZED=0
ServerCertificateCustomValidationCallback = { $true }
Enter fullscreen mode Exit fullscreen mode

Those settings remove the security property being tested. If a proxy is required, configure it explicitly through your organization's approved egress path. Node's native fetch does not automatically provide the same proxy behavior as Python or Windows PowerShell.

Keep the API key in .env for local Python and JavaScript development, add .env to .gitignore, and use a process-scoped or managed secret for PowerShell and CI.

Install the small client dependencies:

py -m venv .venv
.\.venv\Scripts\python.exe -m pip install requests python-dotenv
npm install dotenv
Enter fullscreen mode Exit fullscreen mode

4. Minimal runnable preflight and Agent calls

Each example uses two clearly separated requests:

  1. A no-body network preflight that records an HTTP status or a transport exception.
  2. A Chat Completions request that sends the first Agent probe only after preflight succeeds.

The probe asks for exactly AGENT_READY:v1. This is an application check, not a claim that every model will obey every instruction. The result records the selected and reported model so a model switch can be compared without changing the network diagnosis.

Python

Save as network_probe.py:

import os

import requests
from dotenv import load_dotenv

load_dotenv()

required = ("API_KEY", "BASE_URL", "MODEL")
missing = [name for name in required if not os.getenv(name)]
if missing:
    raise RuntimeError(f"Missing environment variables: {', '.join(missing)}")

base_url = os.environ["BASE_URL"].rstrip("/")

try:
    preflight = requests.get(
        base_url,
        allow_redirects=False,
        timeout=10,
    )
    print(f"preflight_http={preflight.status_code}")
except requests.exceptions.SSLError as exc:
    raise SystemExit(f"preflight_tls_error={exc.__class__.__name__}")
except requests.exceptions.RequestException as exc:
    raise SystemExit(f"preflight_transport_error={exc.__class__.__name__}")

payload = {
    "model": os.environ["MODEL"],
    "messages": [
        {
            "role": "system",
            "content": "You are an Agent startup probe. Return exactly AGENT_READY:v1.",
        },
        {"role": "user", "content": "Run the first Agent probe."},
    ],
}

try:
    response = requests.post(
        f"{base_url}/chat/completions",
        headers={
            "Authorization": f"Bearer {os.environ['API_KEY']}",
            "Content-Type": "application/json",
        },
        json=payload,
        timeout=60,
    )
except requests.exceptions.RequestException as exc:
    raise SystemExit(f"api_transport_error={exc.__class__.__name__}")

request_id = response.headers.get("x-request-id", "unavailable")
if not response.ok:
    raise SystemExit(f"api_http={response.status_code}; request_id={request_id}")

data = response.json()
choice = data["choices"][0]
content = choice["message"].get("content")
text = content.strip() if isinstance(content, str) else ""
result = {
    "ok": text == "AGENT_READY:v1",
    "preflight_http": preflight.status_code,
    "requested_model": os.environ["MODEL"],
    "reported_model": data.get("model"),
    "finish_reason": choice.get("finish_reason"),
    "request_id": request_id,
    "text": text,
}
print(result)

if not result["ok"]:
    raise SystemExit("Agent startup contract failed.")
Enter fullscreen mode Exit fullscreen mode

Run it with the virtual-environment interpreter:

.\.venv\Scripts\python.exe .\network_probe.py
Enter fullscreen mode Exit fullscreen mode

JavaScript

Save as network_probe.mjs. This uses the native fetch in Node.js 18 or later.

import "dotenv/config";

const required = ["API_KEY", "BASE_URL", "MODEL"];
const missing = required.filter((name) => !process.env[name]);
if (missing.length) {
  throw new Error(`Missing environment variables: ${missing.join(", ")}`);
}

const baseUrl = process.env.BASE_URL.replace(/\/$/, "");
let preflight;

try {
  preflight = await fetch(baseUrl, {
    method: "GET",
    redirect: "manual",
  });
  console.log(`preflight_http=${preflight.status}`);
} catch (error) {
  throw new Error(`preflight_transport_error=${error.name}`);
}

const payload = {
  model: process.env.MODEL,
  messages: [
    {
      role: "system",
      content: "You are an Agent startup probe. Return exactly AGENT_READY:v1.",
    },
    { role: "user", content: "Run the first Agent probe." },
  ],
};

let response;
try {
  response = await fetch(`${baseUrl}/chat/completions`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(payload),
  });
} catch (error) {
  throw new Error(`api_transport_error=${error.name}`);
}

const requestId = response.headers.get("x-request-id") ?? "unavailable";
if (!response.ok) {
  throw new Error(`api_http=${response.status}; request_id=${requestId}`);
}

const data = await response.json();
const choice = data.choices?.[0];
const content = choice?.message?.content;
const text = typeof content === "string" ? content.trim() : "";
const result = {
  ok: text === "AGENT_READY:v1",
  preflight_http: preflight.status,
  requested_model: process.env.MODEL,
  reported_model: data.model ?? null,
  finish_reason: choice?.finish_reason ?? null,
  request_id: requestId,
  text,
};
console.log(JSON.stringify(result, null, 2));

if (!result.ok) process.exitCode = 1;
Enter fullscreen mode Exit fullscreen mode

Run it from the directory containing .env:

node .\network_probe.mjs
Enter fullscreen mode Exit fullscreen mode

PowerShell

This script treats an HTTP response from the preflight as network evidence, even when the response is an expected 401 or 404. It does not disable Windows certificate validation.

$required = "API_KEY", "BASE_URL", "MODEL"
$missing = @(
    $required | Where-Object {
        -not [Environment]::GetEnvironmentVariable($_, "Process")
    }
)
if ($missing.Count -gt 0) {
    throw "Missing environment variables: $($missing -join ', ')"
}

$baseUrl = $env:BASE_URL.TrimEnd('/')

try {
    try {
        $preflight = Invoke-WebRequest `
            -Uri $baseUrl `
            -Method Get `
            -MaximumRedirection 0 `
            -TimeoutSec 10
        $preflightStatus = [int]$preflight.StatusCode
    } catch {
        if ($_.Exception.Response) {
            $preflightStatus = [int]$_.Exception.Response.StatusCode
        } else {
            throw
        }
    }
    Write-Host "preflight_http=$preflightStatus"
} catch {
    throw "preflight_transport_or_tls_error=$($_.Exception.GetType().Name)"
}

$headers = @{ Authorization = "Bearer $env:API_KEY" }
$payload = [ordered]@{
    model = $env:MODEL
    messages = @(
        [ordered]@{
            role = "system"
            content = "You are an Agent startup probe. Return exactly AGENT_READY:v1."
        }
        [ordered]@{
            role = "user"
            content = "Run the first Agent probe."
        }
    )
}
$json = $payload | ConvertTo-Json -Depth 6 -Compress

try {
    $response = Invoke-RestMethod `
        -Uri "$baseUrl/chat/completions" `
        -Method Post `
        -Headers $headers `
        -ContentType "application/json" `
        -Body $json `
        -TimeoutSec 60
} catch {
    if ($_.Exception.Response) {
        $status = [int]$_.Exception.Response.StatusCode
        throw "api_http=$status"
    }
    throw "api_transport_error=$($_.Exception.GetType().Name)"
}

$choice = $response.choices[0]
$text = ([string]$choice.message.content).Trim()
$result = [ordered]@{
    ok = ($text -eq "AGENT_READY:v1")
    preflight_http = $preflightStatus
    requested_model = $env:MODEL
    reported_model = $response.model
    finish_reason = $choice.finish_reason
    text = $text
}
$result | ConvertTo-Json

if (-not $result.ok) {
    exit 1
}
Enter fullscreen mode Exit fullscreen mode

Run it from the configured PowerShell process:

.\network_probe.ps1
Enter fullscreen mode Exit fullscreen mode

5. Verify the path before switching models

A healthy diagnostic output may look like this:

{
  "ok": true,
  "preflight_http": 404,
  "requested_model": "model-id-from-your-account",
  "reported_model": "model-id-from-your-account",
  "finish_reason": "stop",
  "text": "AGENT_READY:v1"
}
Enter fullscreen mode Exit fullscreen mode

The 404 in this example is not a failure of the preflight. It means a reachable HTTP layer answered the GET request at the configured base path; a proxy could have generated that response. The real API request is a separate POST /chat/completions call and must be judged by its own status and response shape.

Use the following decision order:

  1. No HTTP status: investigate DNS, TCP, TLS, proxy, or local firewall settings.
  2. An HTTP status but no API response: investigate authentication, path, method, or request parameters.
  3. HTTP success with ok: false: inspect the model response contract; do not call the network layer broken.
  4. Only after the first model passes should you change MODEL and repeat the same test.

For a model comparison, change one variable:

$env:MODEL = "first-current-model-id"
node .\network_probe.mjs

$env:MODEL = "second-current-model-id"
node .\network_probe.mjs
Enter fullscreen mode Exit fullscreen mode

Keep the endpoint, key, prompt, parameters, and runtime trust settings unchanged. A model switch should not also become a proxy or certificate experiment.

6. Common errors and the correct layer

Symptom Layer What to check
DNS name not resolved DNS Hostname spelling, resolver policy, and split-horizon DNS
Connection refused or reset TCP, firewall, or proxy Port 443 access, egress rules, proxy route, and local security software
Certificate verify failed TLS trust Approved CA installation, hostname match, system clock, and NODE_EXTRA_CA_CERTS or REQUESTS_CA_BUNDLE where appropriate
HTTP 407 Proxy authentication Proxy credentials and the approved proxy configuration
Preflight 401 or 404 Network path reached Continue to the authenticated Chat Completions request and diagnose it separately
API 401 or 403 Authentication or permission Key presence, account scope, and whether the key was revoked
API 404 URL or endpoint path One /v1 segment and the correct operation path
model_not_found Model selection Use a model ID currently visible to the account
API 429 Rate or quota Check limits and account status; do not add an unbounded retry loop
HTTP 200 but ok: false Response contract Inspect choices, message.content, and finish_reason without logging secrets

Do not rotate a key to fix a TLS error, and do not turn off certificate verification to fix a proxy policy. Those actions change security state without proving that the Agent can reach the intended API.

7. Security boundaries and when this pattern fits

Certificate validation is part of authentication. verify=False, NODE_TLS_REJECT_UNAUTHORIZED=0, and permissive PowerShell callbacks are not troubleshooting solutions; they allow an untrusted endpoint to impersonate the API.

Use organization-approved CA distribution and proxy configuration instead. Keep API keys in a local ignored .env, process-scoped variables, or a production secret manager. Never place a complete key in browser JavaScript, mobile code, source control, screenshots, logs, or public issues. If it is exposed, revoke and replace it.

The preflight is useful when the same Agent must run in different runtimes, networks, or CI environments. It is not a latency benchmark, an API availability guarantee, or a substitute for model-specific evaluation. It also does not prove that a proxy preserves streaming, tool calls, images, or large request bodies; test those capabilities separately.

The practical boundary is simple: first prove that the request can reach an HTTP server securely, then debug API semantics, and only then evaluate the model response.

Try the example

After creating an account and selecting a current model ID, use the tested Python, JavaScript, and PowerShell examples in the VectorNode API Quickstart:

https://github.com/Vector-Compute-Engine/api-quickstart

Never publish your complete API key in source code, screenshots, videos, or public issues.

Top comments (0)