> ## Documentation Index
> Fetch the complete documentation index at: https://developers.meshapi.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Structured Output

> Constrain chat completion output to valid JSON or a strict JSON schema with response_format.

The `response_format` field on `POST /v1/chat/completions` controls the shape of the model's output. Use it to get back machine-readable JSON instead of free-form prose — ideal for data extraction, classification, and tool-style workflows.

```http theme={null}
POST https://api.meshapi.ai/v1/chat/completions
Authorization: Bearer rsk_<your_key>
```

<Note>
  **Not every model enforces `response_format`.** It only takes effect on models that support structured output. If a model doesn't, the request still **succeeds and returns ordinary text** — it does not error. So if you get prose back instead of the JSON you asked for, the model most likely doesn't support structured output: check its support on the [Models page](https://app.meshapi.ai) in your dashboard (**app.meshapi.ai → Models**, i.e. `https://app.meshapi.ai/org/<your-org-id>/models`) — or the `supports_structured_output` flag returned by `GET /v1/models` — and switch to one with first-class support such as OpenAI models or Google Gemini (e.g. `google/gemini-2.5-flash`). See [Not getting JSON back?](#not-getting-json-back) and [How it works across providers](#how-it-works-across-providers).
</Note>

## Output modes

`response_format` is an object whose `type` selects the mode:

| `type`          | Behavior                                                                                                                                                   |
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `"text"`        | Default. Free-form text — identical to omitting `response_format`.                                                                                         |
| `"json_object"` | The model returns syntactically valid JSON. You describe the desired keys in your prompt; the structure is not otherwise enforced.                         |
| `"json_schema"` | The model returns JSON that conforms to the JSON Schema you supply. The strongest option — field names and types are constrained regardless of the prompt. |

In every mode the JSON is returned as a **string** in `choices[0].message.content`, which you parse client-side.

## Schema-enforced output (`json_schema`)

Supply a `json_schema` object containing a `name` (a label) and a `schema` (the JSON Schema to enforce):

<Tabs>
  <Tab title="curl">
    ```bash theme={null}
    curl https://api.meshapi.ai/v1/chat/completions \
      -H "Authorization: Bearer rsk_<your_key>" \
      -H "Content-Type: application/json" \
      -d '{
        "model": "google/gemini-2.5-flash",
        "messages": [
          {"role": "user", "content": "What is the capital of France? Use the provided schema."}
        ],
        "response_format": {
          "type": "json_schema",
          "json_schema": {
            "name": "country_info",
            "schema": {
              "type": "object",
              "properties": {
                "country": {"type": "string"},
                "capital": {"type": "string"}
              },
              "required": ["country", "capital"],
              "additionalProperties": false
            }
          }
        }
      }'
    ```
  </Tab>

  <Tab title="Python SDK">
    The Python SDK's `chat.completions.parse()` takes a Pydantic model, a
    `TypedDict`/dataclass, or a raw schema `dict`, and returns a parsed, typed
    result — no manual schema dict or `json.loads()` needed.

    ```python theme={null}
    from pydantic import BaseModel
    from meshapi import ChatCompletionParams, ChatMessage, MeshAPI

    client = MeshAPI(base_url="https://api.meshapi.ai", token="rsk_...")

    class CountryInfo(BaseModel):
        country: str
        capital: str

    country = client.chat.completions.parse(
        ChatCompletionParams(
            model="google/gemini-2.5-flash",
            messages=[
                ChatMessage(role="user", content="What is the capital of France?")
            ],
            temperature=0,
        ),
        response_format=CountryInfo,
    )

    print(country.capital)  # "Paris" — a typed CountryInfo instance
    ```

    See [Structured outputs](/sdk/chat#structured-outputs) in the Python
    SDK reference for the `TypedDict`/raw-dict paths and the opt-in retry loop.
  </Tab>

  <Tab title="Node.js SDK">
    ```ts theme={null}
    const schema = {
      type: "json_schema",
      json_schema: {
        name: "country_info",
        schema: {
          type: "object",
          properties: {
            country: { type: "string" },
            capital: { type: "string" },
          },
          required: ["country", "capital"],
          additionalProperties: false,
        },
      },
    };

    const resp = await client.chat.completions.create({
      model: "google/gemini-2.5-flash",
      messages: [
        { role: "user", content: "What is the capital of France? Use the provided schema." },
      ],
      response_format: schema,
      temperature: 0,
    });

    const data = JSON.parse(resp.choices[0].message?.content ?? "{}");
    console.log(data.capital); // "Paris"
    ```
  </Tab>
</Tabs>

## Valid JSON without a schema (`json_object`)

When you only need parseable JSON and don't want to define a schema, use `json_object` and describe the keys you expect in the prompt:

```bash theme={null}
curl https://api.meshapi.ai/v1/chat/completions \
  -H "Authorization: Bearer rsk_<your_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "google/gemini-2.5-flash",
    "messages": [
      {"role": "system", "content": "Respond only with JSON."},
      {"role": "user", "content": "List three fruits and their colors with keys name and color."}
    ],
    "response_format": {"type": "json_object"}
  }'
```

The output is guaranteed to be valid JSON, but the exact keys depend on the model following your prompt. Use `json_schema` when you need that guarantee.

## How it works across providers

`response_format` follows the OpenAI convention and is forwarded to the upstream provider. For providers with a different native contract, MeshAPI translates it automatically — for example, Google Gemini models on Vertex AI are converted to Gemini's native structured-output config (`responseMimeType` for `json_object`, plus `responseSchema` for `json_schema`), so schema enforcement runs on the provider side.

Structured output works with any model that supports `response_format`. If a specific model doesn't, it simply returns ordinary text.

## Tips

* Set `additionalProperties: false` in your schema to forbid extra fields.
* Use `temperature: 0` for the most deterministic, schema-faithful output.
* The content is a JSON string — parse it with `json.loads()` / `JSON.parse()`.
* On success `finish_reason` is `"stop"`.

## Not getting JSON back?

If the response comes back as ordinary prose instead of the JSON you asked for, the model you chose doesn't support structured output. `response_format` is forwarded to the provider, but a provider that doesn't support it just returns plain text — the request still succeeds with `finish_reason: "stop"`, so this fails **silently** rather than erroring.

<Warning>
  **Check that your model supports structured output.** Open the **Models** page in your dashboard — [app.meshapi.ai](https://app.meshapi.ai) → **Models** (`https://app.meshapi.ai/org/<your-org-id>/models`) — and confirm the model lists **Structured output** support, or read the `supports_structured_output` flag from `GET /v1/models`. Models with first-class support include OpenAI models and Google Gemini (e.g. `google/gemini-2.5-flash`).
</Warning>

## Common errors

| HTTP  | Meaning                                                 |
| ----- | ------------------------------------------------------- |
| `401` | Missing or invalid API key                              |
| `402` | Balance or spend limit issue                            |
| `422` | Invalid request body (e.g. malformed `response_format`) |
| `429` | Rate limit exceeded                                     |
