## Guaranteed JSON response

The `response_format` parameter forces the model to return a machine-readable response instead of free text. There are two levels of strictness: `json_object` guarantees valid JSON of an arbitrary shape, while `json_schema` enforces conformance to a given schema.

## `json_object` mode

With `response_format: {"type": "json_object"}` the model returns valid JSON in the `content` field. Vibecode automatically adds an instruction to the system message to return only JSON. Describe the result structure explicitly in the system message:

```bash
curl -X POST https://vibecode.bitrix24.com/v1/chat/completions \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "bitrix/bitrixgpt-5.5",
    "messages": [
      {"role": "system", "content": "Classify the lead. Return JSON: {\"quality\": \"high|medium|low\", \"score\": 0-100}"},
      {"role": "user", "content": "Acme LLC, budget 2 million per month"}
    ],
    "response_format": {"type": "json_object"}
  }'
```

Response:

```json
{
  "id": "chatcmpl-acdb112cf9cdf9f9",
  "object": "chat.completion",
  "model": "bitrix/bitrixgpt-5.5",
  "choices": [
    {
      "index": 0,
      "finish_reason": "stop",
      "message": {
        "role": "assistant",
        "content": "{\"quality\":\"high\",\"score\":92}"
      }
    }
  ],
  "usage": {"prompt_tokens": 56, "completion_tokens": 14, "total_tokens": 70}
}
```

## `json_schema` mode

`response_format: {"type": "json_schema", "json_schema": {...}}` guarantees that the model response **strictly conforms** to the given JSON Schema — the model cannot return extra fields, omit required ones, or mix up types. This is stricter than `json_object`, which guarantees only valid JSON.

Not all models support this mode. For the current list, see the `data.ai.structuredOutputs.models` field in the [`GET /v1/me`](/docs/keys-auth/me) response.

## `json_schema` fields

| Field | Type | Required | Description |
|------|-----|:-----:|---------|
| `name` | string | yes | Schema name. Up to 64 characters, `a-zA-Z0-9_-` allowed |
| `schema` | object | yes | The JSON Schema itself |
| `strict` | boolean | no | `true` — strict conformance checking against the schema. Recommended value |
| `description` | string | no | Schema description for the model that helps it interpret the fields correctly |

## Example with a schema

```bash
curl -X POST https://vibecode.bitrix24.com/v1/chat/completions \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "bitrix/bitrixgpt-5.5",
    "messages": [
      {"role": "system", "content": "Return strict JSON per the schema."},
      {"role": "user", "content": "Hi"}
    ],
    "response_format": {
      "type": "json_schema",
      "json_schema": {
        "name": "hello_response",
        "strict": true,
        "schema": {
          "type": "object",
          "properties": {"message": {"type": "string"}},
          "required": ["message"],
          "additionalProperties": false
        }
      }
    }
  }'
```

The `choices[0].message.content` field contains a string that parses into an object matching the schema exactly — for example `{"message":"Hi"}`.

## Early refusal on repeated JSON

The optional `repetition_guard` accepts `"off"` or `"abort"`. Omitting it or sending `"off"` preserves existing behavior, including the terminal repetition check after `finish_reason: "length"`. Enable `"abort"` explicitly when you accept a heuristic refusal on a legitimate repetitive prefix, such as an array of identical objects.

The `"abort"` mode requires `response_format` of type `json_object` or `json_schema`, no declared tools (`tools` omitted or empty), and a selected model that supports streaming, even with `stream: false`. It is unavailable for Cowork control/completion requests and quota announcements. An incompatible request is refused before reservation or inference: `400 invalid_request`, `type: "invalid_request_error"`, `param: "repetition_guard"`. Values such as `null`, booleans and strings outside the enum also produce `400 invalid_request`.

The guard inspects normalized answer `content` separately from reasoning. A complete JSON document at inspection is excluded, and actual tool or function calls disable detection. The reasoning guard retains its existing behavior. This heuristic does not detect every loop or bound the wait: it needs enough text to decide. Early detection is disabled when the inspected text exceeds 1,048,576 characters.

With `stream: false`, the platform reads the model stream privately and the client receives ordinary completion JSON or `422`, without SSE headers or chunks. With `stream: true`, the HTTP status stays `200`, with one `error` event followed by one `data: [DONE]`. The client must discard the partial answer already received. Detection cancels the active model request without retry, answer recovery or fallback to another model. Transport cancellation does not guarantee immediate GPU release.

The early refusal has fixed `message` and `hint` values:

```json
{
  "error": {
    "message": "Repeated JSON content was detected and this request was stopped early.",
    "type": "invalid_request_error",
    "code": "reasoning_repetition",
    "hint": "Disable repetition_guard for expected repeated content; otherwise revise the request or use another model."
  }
}
```

It carries no fabricated `finishReason: "length"`, no `param`, `suggestedMaxTokens`, `retryable`, `retryAfter`, or advice to disable reasoning. Raw answer text, the prompt, schema and private provider identifiers are excluded from the error. Measured usage is retained under existing failed-call rules. If the model reported no usage, it stays unknown and is not estimated from text length. Early refusal does not mean the model spent no tokens.

With the OpenAI Python SDK, pass the extension through `extra_body`:

```python
from openai import OpenAI

client = OpenAI(base_url="https://vibecode.bitrix24.com/v1", api_key="YOUR_API_KEY")
response = client.chat.completions.create(
    model="bitrix/bitrixgpt-5.5",
    messages=[{"role": "user", "content": "Return a JSON object with a greeting."}],
    response_format={"type": "json_object"},
    extra_body={"repetition_guard": "abort"},
)
```

Use `"off"` or omit the option for expected repetition. Do not enable `"abort"` by default for all JSON requests.

## Error response example

`400 model_does_not_support_structured_outputs` — the model does not support `json_schema`. The `structuredOutputsModels` field contains the list of suitable models:

```json
{
  "error": {
    "message": "Model 'openai/o3' does not support response_format=json_schema. Use one of: bitrix/bitrixgpt-5.5, bitrix/bitrixgpt-5.5-thinking, openai/gpt-4o, openai/gpt-4o-mini.",
    "type": "invalid_request_error",
    "code": "model_does_not_support_structured_outputs",
    "param": "response_format.type",
    "structuredOutputsModels": ["bitrix/bitrixgpt-5.5", "bitrix/bitrixgpt-5.5-thinking", "openai/gpt-4o", "openai/gpt-4o-mini", "openai/o3-mini"]
  }
}
```

`422 structured_output_truncated` — generation was cut off at the token limit without detected repetition:

```json
{
  "error": {
    "message": "The model's response was cut off (finish_reason=length) before a complete JSON document was produced, so it does not satisfy the requested response_format. Raise max_tokens, or reduce the schema/prompt size.",
    "type": "invalid_request_error",
    "code": "structured_output_truncated",
    "hint": "For reasoning models, max_tokens must cover the reasoning phase plus the JSON answer.",
    "param": "max_tokens",
    "finishReason": "length",
    "suggestedMaxTokens": 2048
  }
}
```

`422 structured_output_invalid_json` — the response is non-empty but does not parse as a JSON document:

```json
{
  "error": {
    "message": "The model returned a non-empty response (finish_reason=stop) that is not a valid JSON document, so it does not satisfy the requested response_format. The response body was discarded rather than handed over as JSON.",
    "type": "invalid_request_error",
    "code": "structured_output_invalid_json",
    "hint": "Retry the request, or use a model with reliable structured output; for a strict shape use response_format=json_schema. A larger token budget will not change the result.",
    "finishReason": "stop"
  }
}
```

## Errors

| HTTP | Code | Description |
|------|-----|---------|
| 400 | `invalid_request` | An incompatible `repetition_guard: "abort"` request or an invalid option value. See the restrictions above |
| 400 | `model_does_not_support_structured_outputs` | The model does not support `json_schema`. The list of suitable models is in the `structuredOutputsModels` field |
| 422 | `structured_output_truncated` | The model cut off generation before completing the JSON without detected repetition or returned an empty response |
| 422 | `structured_output_invalid_json` | The model returned a non-empty response that does not parse as a JSON object or array |
| 422 | `reasoning_repetition` | The model got stuck in its reasoning — it kept repeating the same sentences and never started the answer. Send the request again with `reasoning_effort: "none"` |
| 422 | `reasoning_repetition` | By default (`repetition_guard` omitted or `"off"`), repetition was detected in non-empty incomplete JSON with `json_object` or `json_schema` after `finish_reason: "length"`. The check is terminal; This is a heuristic diagnosis: it can also trigger on a legitimate repetitive array that exceeded the budget. Check whether repetition is expected; if so, check the token budget, otherwise revise the request or use another model. There is no advice to disable reasoning |
| 422 | `reasoning_repetition` | Explicit `repetition_guard: "abort"` detected repeated JSON before completion. Disable the option for expected repetition, otherwise revise the request or use another model |

The full list of common API errors — [Errors](/docs/errors).

## Known specifics

**A truncated response yields `422 structured_output_truncated`.** If the model cut off generation before completing the JSON (`finish_reason: "length"`), a request with `response_format` without detected repetition returns `422 structured_output_truncated` with the fields `finishReason` and `suggestedMaxTokens`. Reasoning models behave this way: the reasoning phase consumes the `max_tokens` budget before the model writes the JSON. Handle this error and retry the request with a larger `max_tokens` — you can take the value from `suggestedMaxTokens`. For strict JSON, set `max_tokens` with some headroom or use a model without reasoning.

The `suggestedMaxTokens` and `param: "max_tokens"` fields are present **only** when `finish_reason: "length"` in a `structured_output_truncated` error. Detected repetition produces `reasoning_repetition` without these fields. If the finish reason is anything else and valid JSON is still missing, the response is also `422`, but without those two fields — raising `max_tokens` will not help; use a model without reasoning. The `hint` string and the `finishReason` field are present in both cases, and `finishReason` may be `null`.

If a reasoning model in `json_object` mode finished on its own and placed a complete valid JSON document into the internal `reasoning_content` channel, the platform recovers that JSON and returns `200` with it in `content` — no extra handling is required.

**An unparseable response yields `422 structured_output_invalid_json`.** The platform parses the answer before handing it over: if, with `response_format`, the model returned non-empty text that is not a JSON object or array (prose around the object, an answer wrapped in a markdown code fence, a bare value such as `42`), that answer is not served as a success. The refusal carries `finishReason` and `hint`, but no `param` and no `suggestedMaxTokens` — raising the token budget does not help here; retrying does, and `json_schema` with `strict: true` does when the shape matters. Nested JSON inside a string field is not a violation: only the outer parse is checked.

**Looping reasoning yields `422 reasoning_repetition`, not `structured_output_truncated`.** If a reasoning model never started the answer because it kept repeating the same sentences in `reasoning_content`, the refusal carries the code `reasoning_repetition`. It has `finishReason` and `hint` but no `param` and no `suggestedMaxTokens`: a larger token budget only makes the repetition longer. Send the request again with reasoning turned off — `reasoning_effort: "none"`. Cycling through different wordings does not count as repetition: such a response stays `structured_output_truncated`. If the `reasoning_content` channel starts as a JSON document (with `{` or `[`), in a stream the decision is deferred while recovery is still possible (if the document is too long to be recovered, the repetition cuts the stream off at once): a complete document is recovered as described above, even with identical objects, and if nothing can be recovered, `reasoning_repetition` arrives. Recovery from the reasoning is done only for `json_object`: without `response_format` an answer that arrives only in `reasoning_content` is cut off with a `reasoning_repetition` event when it loops.

**A terminal attempt that ends in `422` is charged.** The `422` response — whether for truncation, an unparseable answer or looping reasoning — arrives after the model has already run, so the attempt's tokens count toward the usage statistics in [`GET /v1/ai/usage`](/docs/ai/consumption/usage) and toward the Bitrix24 account's monthly AI quota. A retry with a raised `max_tokens` is another billable call.

**A low `max_tokens` on a free reasoning model is raised automatically.** If a free reasoning model is asked for a structured response and `max_tokens` is set explicitly below the lower bound, the platform raises it to that bound and reports it in the successful `200` response: the body carries a `warnings` array with an element `{"code": "MAX_TOKENS_RAISED", "message": "..."}`. This is not an error — read `warnings` as an optional field. If `max_tokens` is not set in the request, no adjustment is applied.

**In streaming mode the error (and the recovered answer) arrives inside the stream.** The service event `{"error":{"code":"structured_output_truncated"}}`, `{"error":{"code":"structured_output_invalid_json"}}` or `{"error":{"code":"reasoning_repetition"}}` comes before `data: [DONE]`. In the second case the answer chunks have already been delivered — discard them; the stream status stays `200` by protocol. JSON recovered from `reasoning_content` arrives as a `content` chunk before the terminal `finish_reason` chunk — read the stream to the end.

**`json_object` does not check the structure.** It guarantees only that the response parses as JSON — the set of fields and their types are left to the model. If the structure matters, use `json_schema` with `strict: true`.

**Repetition in truncated JSON also produces `reasoning_repetition`.** With `repetition_guard` omitted or `"off"`, for `json_object` or `json_schema`, when generation ends with `finish_reason: "length"`, non-empty incomplete JSON is checked for repetition. A detected repetition produces `reasoning_repetition` and a JSON-specific message, without advice to disable reasoning and without `param` or `suggestedMaxTokens`. This is a heuristic diagnosis: it can also trigger on a legitimate repetitive array that exceeded the budget. Check whether repetition is expected; if so, check the token budget, otherwise revise the request or use another model. The check runs after generation ends and does not shorten the wait. Short truncation without detected repetition and complete JSON at `length` retain `structured_output_truncated`; a legitimate repetitive array reaches the client in full on normal completion.

## See also

- [Create a chat completion](./completions.md)
- [Streaming](./streaming.md)
- [Function calling](./tools.md)
- [Models list](/docs/ai/models/list)
