All tutorials

Structured JSON output that always parses

10 min

Make any model return JSON matching your schema, across every provider, with validation and automatic re-prompts so your parser never breaks in production.

Prerequisites
  • A Ringside API key exported as FC_API_KEY (see the 5-minute quickstart)

  • curl or any OpenAI SDK

01

The problem with "just ask for JSON"

If you put "reply in JSON" in a prompt, you get JSON most of the time. The rest of the time you get a code fence, an apology, or a field renamed, and your JSON.parse throws in production. Ringside fixes this with response_format, which makes the model return JSON that matches a schema you define, or fail loudly instead of silently malforming.

The useful part: it works the same way across every provider, even ones with no native JSON mode. For those, Ringside validates the output against your schema and re-prompts the model up to twice before giving up with a clear error.

02

Define the shape you want

Pass response_format with type: "json_schema" and a JSON Schema describing the object. Here we extract a support ticket into fields. Set strict: true so the model cannot add or drop keys.

curl https://api.fightclub.pro/v1/chat/completions \
  -H "Authorization: Bearer $FC_API_KEY" -H "Content-Type: application/json" \
  -d '{
    "model": "fc:openai/gpt-4o-mini",
    "messages": [
      {"role":"system","content":"Extract the ticket as JSON."},
      {"role":"user","content":"Hi, order #4471 never arrived and I am furious. Refund me."}
    ],
    "response_format": {
      "type": "json_schema",
      "json_schema": {
        "name": "ticket",
        "strict": true,
        "schema": {
          "type": "object",
          "additionalProperties": false,
          "required": ["intent","sentiment","order_id"],
          "properties": {
            "intent":    {"type":"string","enum":["refund","status","complaint","other"]},
            "sentiment": {"type":"string","enum":["positive","neutral","negative"]},
            "order_id":  {"type":"string"}
          }
        }
      }
    }
  }'
03

What you get back

The message content is a JSON string that matches the schema exactly, so JSON.parse (or json.loads) never surprises you. No code fence, no extra prose, only the object.

{ "intent": "refund", "sentiment": "negative", "order_id": "4471" }
04

When the model cannot comply

For providers without native JSON mode, Ringside runs the output through your schema and, if it does not validate, re-prompts the model with the validation error, up to two times. If it still fails, you get a 422 response_schema_validation_failed rather than a broken object. So your code only ever sees valid JSON or a clean error to handle.

Practically: wrap the call, parse on success, and on a 422 fall back to a simpler prompt or a default. You never have to defend against half-formed JSON downstream.

This works with the same match: and fc: model refs as any other chat call, and the same FC-Customer header for per-customer billing. Structured output is just a flag on the normal endpoint.

Where to next