# Structured outputs
> JSON mode and JSON Schema responses with response_format.
Use `response_format` to get JSON back instead of free text. There are two modes:
| Mode | `response_format` | What you get |
| --- | --- | --- |
| JSON mode | `{"type": "json_object"}` | Valid JSON, in a shape you describe in the prompt. |
| Structured outputs | `{"type": "json_schema", "json_schema": {…}}` | JSON that follows your JSON Schema. |
Model pages show which modes each model supports. Filter the [catalogue](https://clonvo.com/models.md) by **Structured outputs** to find models with schema support.
## JSON Schema
```python
completion = client.chat.completions.create(
model="openai/gpt-6.1-sol",
messages=[
{"role": "system", "content": "Extract the event details."},
{"role": "user", "content": "Team offsite in Porto on 12 March, 18 people."},
],
response_format={
"type": "json_schema",
"json_schema": {
"name": "event",
"strict": True,
"schema": {
"type": "object",
"properties": {
"title": {"type": "string"},
"city": {"type": "string"},
"date": {"type": "string"},
"attendees": {"type": "integer"},
},
"required": ["title", "city", "date", "attendees"],
"additionalProperties": False,
},
},
},
)
import json
event = json.loads(completion.choices[0].message.content)
```
The JSON arrives as a string in `message.content`. Parse it yourself, or use the OpenAI SDK’s `parse` helpers, which work the same way through Clonvo:
```python
from pydantic import BaseModel
class Event(BaseModel):
title: str
city: str
date: str
attendees: int
completion = client.beta.chat.completions.parse(
model="openai/gpt-6.1-sol",
messages=[{"role": "user", "content": "Team offsite in Porto on 12 March, 18 people."}],
response_format=Event,
)
event = completion.choices[0].message.parsed
```
## JSON mode
```js
const completion = await client.chat.completions.create({
model: "mistralai/mistral-large-4",
messages: [
{ role: "system", content: "Reply with a JSON object with keys `summary` and `tags`." },
{ role: "user", content: "Notes from today’s standup…" },
],
response_format: { type: "json_object" },
});
const data = JSON.parse(completion.choices[0].message.content);
```
With JSON mode, describe the shape you want in the prompt; the model guarantees valid JSON, not a particular structure.
## How Clonvo handles providers
`response_format` never causes a `400` because of the model. When a model has native support, Clonvo uses it; otherwise it falls back to a best-effort approach, so check the model page if you need a guarantee.
| Provider | `json_schema` | `json_object` |
| --- | --- | --- |
| OpenAI-compatible (OpenAI, xAI, DeepSeek, Mistral, Moonshot, Meta, Alibaba) | Sent as is on models with structured outputs. Otherwise Clonvo asks for JSON mode (where the model has it) and adds your schema to the prompt as an instruction. | Sent as is on models with JSON mode. Otherwise Clonvo adds an instruction to reply with JSON. |
| Anthropic | Sent as Anthropic’s native structured output format. On models without it, Clonvo defines a single tool with your schema, makes the model call it, and returns the tool’s input as `message.content`. | An instruction to reply with JSON is added to the system prompt. |
| Gemini | JSON output, with your schema as Gemini’s response schema on models with structured outputs. | JSON output. |
| Free models | Sent to OpenRouter as is. | Sent to OpenRouter as is. |
Either way the JSON arrives as a string in `message.content`.
## Tips
- Keep `strict: true` schemas simple: mark every property as required and set `additionalProperties: false`. Providers differ in which JSON Schema keywords they support.
- Check `finish_reason`. If it’s `length`, the JSON was cut off; raise `max_completion_tokens`.
- Validate the parsed result before trusting it, especially on models without schema enforcement.