Malformed JSON Moderation API Responses in Node.js: Schema Gates Over Adapters A developer describes building a Node.js schema gate in front of provider adapters for an e-commerce support triage service, arguing that malformed moderation API responses should be treated as untrusted input rather than parse errors. The approach validates raw responses, catches parsing failures, logs redacted evidence, and routes tickets to a conservative fallback or human review instead of silently classifying them as safe. The developer notes that provider-native structured outputs from OpenAI, Anthropic, and Gemini reduce formatting mistakes but do not eliminate schema drift, truncation, or content-type errors, so the local gate remains the authority. A malformed moderation response is an untrusted input, not a slightly inconvenient parse error. For an e-commerce support triage service, my default choice is a Node.js schema gate in front of a provider adapter: validate the raw response, catch parsing failures, record the evidence, and route the ticket to a conservative fallback. The adapter still matters, but it should not own the safety decision. This fits when the same flow may move between OpenAI, Anthropic, Gemini, or a REST gateway. A provider-native structured-output feature is reasonable when one vendor is fixed and its guarantees match your contract. The boundary is practical: provider features improve generation; your application decides what happens when the bytes are wrong. Three checks matter: syntax, schema, and policy. The parser is the bouncer. Separate transport success from semantic success. A 200 response can contain truncated JSON, a fenced object, an extra sentence, or a valid object with the wrong fields. JSON.parse checks only one of those cases. It does not prove that the result is safe to use for ticket routing. For a ticket such as “The blender arrived with a cracked jar,” the application needs a small contract: a decision, a reason, and perhaps a confidence value. If the model returns malformed output, do not silently classify the message as safe. Log a redacted record, retry only when the operation is safe to repeat, and send the item to an alternate moderation path or human review. That is the evaluation constraint: recovery must preserve the triage contract when the provider response does not. I initially thought a provider’s JSON mode would make the application parser nearly incidental. It does not. JSON mode can reduce formatting mistakes, but the application still owns schema drift, truncation, content-type mistakes, and the policy for an unusable result. Do not rescue it with regex. There are three credible designs. OpenAI Structured Outputs can constrain a response with JSON Schema. Anthropic tool use gives an application a typed-looking tool input. Gemini response schemas constrain generated JSON on Google’s API. These are meaningful differences, not interchangeable labels. The trade-off is portability. Each provider has its own request fields, refusal behavior, limits, and error vocabulary. An adapter can normalize those calls, but it cannot make their guarantees identical. A schema gate gives the business workflow one stable boundary after provider-specific code has finished. For this queue, use provider-native structured output as an upstream quality improvement and keep the local gate as the authority. Check content type before parsing, parse inside try/catch , validate the expected shape, and reject unknown or missing decisions. A correction pass can ask for the object again, but it must be bounded. Repeatedly asking a broken provider for “valid JSON” is a retry loop with no policy. A useful fallback is deliberately boring: deterministic checks, human review, or an unclassified queue. It should not invent a verdict from a half-read string. That small rule has a large consequence. A malformed object is not a moderation result, so downstream code must never receive it as one. Treating the parser as a gate also makes provider changes cheaper: the adapter can change its request format while the queue, audit record, and review state keep the same contract. The work is less glamorous than tuning a prompt, but it is the part that prevents a formatting regression from becoming a routing decision. The failure taxonomy is small enough to keep beside the code. Syntax failure means the bytes cannot be parsed. Schema failure means the object parses but lacks the required fields. Policy failure means the object is technically valid but asks the queue to make a decision your rules do not permit. Those are different operational events, so collapsing them into one catch-all “AI error” makes debugging slower and hides which fallback is doing the work. Measure it. Account controls and inference calls are easier to operate when they share one credential and one base URL. Before sending a batch of tickets, read the account budget, use that result to decide whether the batch is allowed, and pass the approved batch into the provider adapter. The output from the first capability becomes an input to the second-stage call. The account check below uses a listed account-platform route. The moderation call is injected because there is no dedicated moderation route in the verified surface; text moderation needs a chat model with a JSON Schema fallback. That keeps the example honest while showing the control boundary a worker should implement. type BudgetSnapshot = { allowed: boolean; remaining?: number; key: string : unknown }; type ModerationResult = { decision: "allow" | "review" | "block"; reason: string }; const baseUrl = process.env.INFRAI BASE URL ?? "https://api.example.invalid/v1"; const apiKey = process.env.INFRAI API KEY; if apiKey throw new Error "INFRAI API KEY is required" ; async function readBudget : Promise