diff --git a/src/content/docs-es/examples.md b/src/content/docs-es/examples.md
index 96144d42..9c8ae050 100644
--- a/src/content/docs-es/examples.md
+++ b/src/content/docs-es/examples.md
@@ -78,7 +78,7 @@ Instalación: `npm install openai`
### salida estructurada en deepseek-v4-flash
-En `api.nan.builders`, `deepseek-v4-flash` rechaza `response_format` `json_schema` con un `400` (en `api.helmcode.com` se acepta), y `json_object` solo garantiza JSON válido, no su forma. Para obtener una salida que siga un esquema, define una única herramienta de tipo función cuyo `parameters` sea tu JSON Schema (raíz `"type": "object"`, `"strict": true`) y fuérzala con `tool_choice`. El modelo devuelve argumentos que siguen el esquema en `choices[0].message.tool_calls[0].function.arguments`, como una cadena JSON. `strict` pide que se ajusten exactamente; se aplica donde el modelo admite decodificación estricta, así que valida los argumentos si tu código depende de ellos.
+`deepseek-v4-flash` acepta `response_format` `json_schema` en peticiones a `/chat/completions` sin streaming y sin tools. En `api.nan.builders` lo rechaza con un `400` cuando la petición usa streaming, lleva `tools` o va a `/responses` (también con `n` mayor que 1, un esquema de más de 16 KB, un esquema no válido o un esquema que el gateway no valida (un `$ref` fuera del esquema, `$dynamicRef` o `$anchor`, un `$id` anidado o demasiado grande al resolver sus `$ref`); en `api.helmcode.com` se acepta en todos esos casos), y `json_object` solo garantiza JSON válido, no su forma. Para obtener una salida que siga un esquema en esas peticiones, define una única herramienta de tipo función cuyo `parameters` sea tu JSON Schema (raíz `"type": "object"`, `"strict": true`) y fuérzala con `tool_choice`. El modelo devuelve argumentos que siguen el esquema en `choices[0].message.tool_calls[0].function.arguments`, como una cadena JSON. `strict` pide que se ajusten exactamente; se aplica donde el modelo admite decodificación estricta, así que valida los argumentos si tu código depende de ellos.
```bash
curl https://api.nan.builders/v1/chat/completions \
diff --git a/src/content/docs-es/models.mdx b/src/content/docs-es/models.mdx
index 4d87024a..2290390e 100644
--- a/src/content/docs-es/models.mdx
+++ b/src/content/docs-es/models.mdx
@@ -50,7 +50,7 @@ OpenAI y la misma `base URL`.
tag="305B MoE"
leftLabel="generación de texto, chat y visión"
rightLabel="capacidades"
- description={'Modelo MoE de 305B parámetros, servido en su variante Vision-Exp: acepta imágenes de entrada. Contexto de 1M tokens. Tool calling y razonamiento. Cuota de 3B tokens al mes por miembro. La salida estructurada depende del host: en api.helmcode.com se aceptan response_format json_schema y json_object; en api.nan.builders, json_object es compatible (el prompt debe contener la palabra JSON; si no, se rechaza con un 400) y json_schema se rechaza con un 400. json_object solo garantiza JSON sintácticamente válido, no su forma: describe el esquema en el prompt. Para salida restringida a un esquema en api.nan.builders, usa qwen3.6 o gemma4, donde json_schema con strict restringe campos, tipos y claves obligatorias; o, en este modelo, fuerza una única herramienta de tipo función cuyo parameters sea tu esquema (ver Ejemplos). Con el razonamiento activado, un max_tokens por debajo de 16384 se sube a 16384 para que quepa el razonamiento, así que un prompt a menos de 16384 tokens de la ventana de 1.048.576 se rechaza con un 400 aunque pidas un max_tokens pequeño (normalmente Context length exceeded, a veces un Invalid request genérico). Con el razonamiento apagado (reasoning_effort "none" o cualquiera de sus alias) tu max_tokens se usa tal cual.'}
+ description={'Modelo MoE de 305B parámetros, servido en su variante Vision-Exp: acepta imágenes de entrada. Contexto de 1M tokens. Tool calling y razonamiento. Cuota de 3B tokens al mes por miembro. Salida estructurada: response_format json_object es compatible (en api.nan.builders el prompt debe contener la palabra JSON; si no, se rechaza con un 400). json_schema se acepta en peticiones a /chat/completions sin streaming y sin tools: la respuesta se comprueba contra tu esquema y, si no lo cumple, normalmente la vuelve a servir un proveedor que lo impone; una respuesta que no se pudo comprobar, o un fallo que no se pudo reemplazar a tiempo, se devuelve tal cual (valídala si tu código depende de ella). En api.nan.builders el resto de peticiones json_schema se rechazan con un 400: con streaming, con tools, a /responses, con n mayor que 1, con un esquema de más de 16 KB, que no sea un JSON Schema válido o que el gateway no valida (un $ref fuera del esquema, $dynamicRef o $anchor, un $id anidado o demasiado grande al resolver sus $ref); en api.helmcode.com se aceptan. json_object solo garantiza JSON sintácticamente válido, no su forma: describe el esquema en el prompt. Para salida restringida a un esquema en esas otras peticiones, usa qwen3.6 o gemma4, donde json_schema con strict restringe campos, tipos y claves obligatorias; o, en este modelo, fuerza una única herramienta de tipo función cuyo parameters sea tu esquema (ver Ejemplos). Con el razonamiento activado, un max_tokens por debajo de 16384 se sube a 16384 para que quepa el razonamiento, así que un prompt a menos de 16384 tokens de la ventana de 1.048.576 se rechaza con un 400 aunque pidas un max_tokens pequeño (normalmente Context length exceeded, a veces un Invalid request genérico). Con el razonamiento apagado (reasoning_effort "none" o cualquiera de sus alias) tu max_tokens se usa tal cual.'}
specs={[
{ label: 'Tipo', value: 'MoE (305B total)' },
{ label: 'Cuantización', value: 'FP8' },
@@ -63,7 +63,7 @@ OpenAI y la misma `base URL`.
'Tool calling',
'Control del razonamiento (reasoning_effort: none lo apaga · de minimal a max) · detalles',
'Visión (entrada de imagen)',
- 'Modo JSON (json_object; en api.nan.builders el prompt debe mencionar JSON) · json_schema: 400 en api.nan.builders, aceptado en api.helmcode.com',
+ 'Modo JSON (json_object; en api.nan.builders el prompt debe mencionar JSON) · json_schema en chat sin streaming y sin tools (en api.nan.builders el resto, p. ej. con streaming, con tools o en /responses: 400)',
'Contexto de 1M tokens',
'Generación en streaming (SSE)',
]}
diff --git a/src/content/docs/examples.md b/src/content/docs/examples.md
index dae81c4f..13795c47 100644
--- a/src/content/docs/examples.md
+++ b/src/content/docs/examples.md
@@ -78,7 +78,7 @@ Install: `npm install openai`
### structured output on deepseek-v4-flash
-On `api.nan.builders`, `deepseek-v4-flash` rejects `response_format` `json_schema` with a `400` (on `api.helmcode.com` it is accepted), and `json_object` only guarantees valid JSON, not its shape. To get output that follows a schema, define one function tool whose `parameters` is your JSON Schema (root `"type": "object"`, `"strict": true`) and force it with `tool_choice`. The model returns arguments that follow the schema in `choices[0].message.tool_calls[0].function.arguments`, as a JSON string. `strict` asks for an exact match; it is enforced where the model supports strict decoding, so validate the arguments if your code depends on them.
+`deepseek-v4-flash` accepts `response_format` `json_schema` on non-streamed `/chat/completions` requests without tools. On `api.nan.builders` it rejects it with a `400` when the request streams, carries `tools` or goes to `/responses` (also with `n` above 1, a schema over 16 KB, an invalid schema, or a schema the gateway will not validate (a `$ref` outside the schema, `$dynamicRef` or `$anchor`, a nested `$id`, or too large once its `$ref`s are inlined); on `api.helmcode.com` it is accepted in all of them), and `json_object` only guarantees valid JSON, not its shape. To get output that follows a schema in those requests, define one function tool whose `parameters` is your JSON Schema (root `"type": "object"`, `"strict": true`) and force it with `tool_choice`. The model returns arguments that follow the schema in `choices[0].message.tool_calls[0].function.arguments`, as a JSON string. `strict` asks for an exact match; it is enforced where the model supports strict decoding, so validate the arguments if your code depends on them.
```bash
curl https://api.nan.builders/v1/chat/completions \
diff --git a/src/content/docs/models.mdx b/src/content/docs/models.mdx
index 10603a60..d528c2df 100644
--- a/src/content/docs/models.mdx
+++ b/src/content/docs/models.mdx
@@ -50,7 +50,7 @@ with the same `base URL`.
tag="305B MoE"
leftLabel="text generation, chat & vision"
rightLabel="capabilities"
- description={'305B parameter MoE model, served as the Vision-Exp variant: it takes images as input. 1M token context. Tool calling and reasoning. 3B token monthly quota per member. Structured output depends on the host: on api.helmcode.com, response_format json_schema and json_object are accepted; on api.nan.builders, json_object is supported (the prompt must contain the word JSON, otherwise it is rejected with a 400) and json_schema is rejected with a 400. json_object guarantees syntactically valid JSON only, not its shape: describe the schema in the prompt. For schema-constrained output on api.nan.builders, use qwen3.6 or gemma4, where json_schema with strict constrains fields, types and required keys; or, on this model, force one function tool whose parameters is your schema (see Examples). While reasoning is on, a max_tokens below 16384 is raised to 16384 so the reasoning fits, so a prompt within 16384 tokens of the 1,048,576-token window is rejected with a 400 even with a small max_tokens (usually Context length exceeded, sometimes a generic Invalid request). With reasoning off (reasoning_effort "none" or any of its aliases) your max_tokens is used as sent.'}
+ description={'305B parameter MoE model, served as the Vision-Exp variant: it takes images as input. 1M token context. Tool calling and reasoning. 3B token monthly quota per member. Structured output: response_format json_object is supported (on api.nan.builders the prompt must contain the word JSON, otherwise it is rejected with a 400). json_schema is accepted on non-streamed /chat/completions requests without tools: the answer is checked against your schema and, when it does not match, usually re-served by a provider that enforces it; an answer that could not be checked, or a miss the re-serve could not replace in time, is returned as is (validate it if your code depends on it). On api.nan.builders other json_schema requests are rejected with a 400: streamed, with tools, on /responses, with n above 1, with a schema over 16 KB, not a valid JSON Schema, or one the gateway will not validate (a $ref outside the schema, $dynamicRef or $anchor, a nested $id, or too large once its $refs are inlined); on api.helmcode.com they are accepted. json_object guarantees syntactically valid JSON only, not its shape: describe the schema in the prompt. For schema-constrained output in those other requests, use qwen3.6 or gemma4, where json_schema with strict constrains fields, types and required keys; or, on this model, force one function tool whose parameters is your schema (see Examples). While reasoning is on, a max_tokens below 16384 is raised to 16384 so the reasoning fits, so a prompt within 16384 tokens of the 1,048,576-token window is rejected with a 400 even with a small max_tokens (usually Context length exceeded, sometimes a generic Invalid request). With reasoning off (reasoning_effort "none" or any of its aliases) your max_tokens is used as sent.'}
specs={[
{ label: 'Type', value: 'MoE (305B total)' },
{ label: 'Quantization', value: 'FP8' },
@@ -63,7 +63,7 @@ with the same `base URL`.
'Tool calling',
'Reasoning control (reasoning_effort: none turns it off · minimal to max) · details',
'Vision (image input)',
- 'JSON mode (json_object; on api.nan.builders the prompt must mention JSON) · json_schema: 400 on api.nan.builders, accepted on api.helmcode.com',
+ 'JSON mode (json_object; on api.nan.builders the prompt must mention JSON) · json_schema on non-streamed chat without tools (on api.nan.builders the other cases, e.g. streamed, with tools or on /responses: 400)',
'1M token context',
'Streaming generation (SSE)',
]}
diff --git a/src/data/openapi.json b/src/data/openapi.json
index 049f80a2..cd890eba 100644
--- a/src/data/openapi.json
+++ b/src/data/openapi.json
@@ -9385,7 +9385,7 @@
}
},
"ResponseFormat": {
- "description": "Force structured output. What each mode guarantees:\n\n- `json_object`: syntactically valid JSON only, NOT its shape. Describe the schema you want in the prompt. On `deepseek-v4-flash` on `api.nan.builders` the prompt must also contain the word JSON, otherwise `400`.\n- `json_schema` with `strict: true`: the output is constrained to the schema (fields, types and required keys).\n\n`json_schema` works on `qwen3.6` and `gemma4`. On `deepseek-v4-flash` it depends on the host: on the enterprise base URL (see the overview) it is accepted; on `api.nan.builders` it is rejected with a `400` before the request reaches the model, and `json_object` still works there. For output that follows a schema on `deepseek-v4-flash` there, define one function tool whose `parameters` is your JSON Schema (root `type: object`, `strict: true`), force it with `tool_choice: {\"type\": \"function\", \"function\": {\"name\": \"...\"}}` and read `choices[0].message.tool_calls[0].function.arguments`.",
+ "description": "Force structured output. What each mode guarantees:\n\n- `json_object`: syntactically valid JSON only, NOT its shape. Describe the schema you want in the prompt. On `deepseek-v4-flash` on `api.nan.builders` the prompt must also contain the word JSON, otherwise `400`.\n- `json_schema` with `strict: true`: the output is constrained to the schema (fields, types and required keys).\n\n`json_schema` works on `qwen3.6` and `gemma4`. On `deepseek-v4-flash` it is accepted on non-streamed `/chat/completions` requests without `tools`: the answer is checked against your schema and, when it does not match, usually re-served by a provider that enforces it; an answer that could not be checked, or a miss the re-serve could not replace in time, is returned as is (validate it if your code depends on it). Other `json_schema` requests on `deepseek-v4-flash` depend on the host: on the enterprise base URL (see the overview) they are accepted; on `api.nan.builders` a request that streams, carries `tools`, goes to `/responses`, sets `n` above 1, or whose schema is over 16 KB, invalid or one the gateway will not validate (a `$ref` outside the schema, `$dynamicRef` or `$anchor`, a nested `$id`, or too large once its `$ref`s are inlined) is rejected with a `400` before it reaches the model, and `json_object` still works there. For output that follows a schema on `deepseek-v4-flash` in those requests, define one function tool whose `parameters` is your JSON Schema (root `type: object`, `strict: true`), force it with `tool_choice: {\"type\": \"function\", \"function\": {\"name\": \"...\"}}` and read `choices[0].message.tool_calls[0].function.arguments`.",
"oneOf": [
{
"type": "object",
diff --git a/src/lib/openapiSpec.test.ts b/src/lib/openapiSpec.test.ts
index 660aa391..5841a19c 100644
--- a/src/lib/openapiSpec.test.ts
+++ b/src/lib/openapiSpec.test.ts
@@ -673,9 +673,14 @@ describe('getting-started guides: /usage parity', () => {
/**
* THE GATEWAY REJECTS TWO REQUEST SHAPES WITH A 400 BEFORE ROUTING: a tool
* name outside ^[a-zA-Z0-9_-]{1,64}$ (any model) and `response_format`
- * `json_schema` on `deepseek-v4-flash` on api.nan.builders (`json_object` still
- * works there). On api.helmcode.com the same model accepts `json_schema` (and
- * `json_object` without the word JSON), measured live 2026-10-15.
+ * `json_schema` on `deepseek-v4-flash` on api.nan.builders when the request
+ * streams, carries `tools` or goes to /responses (`json_object` still works
+ * there). Since 2026-10-16 a non-streamed /chat/completions json_schema request
+ * without tools IS served on api.nan.builders: the gateway checks the answer
+ * against the schema and re-serves a miss from a provider that enforces it
+ * (nan-devops, community json_schema wrapper). On api.helmcode.com the model
+ * accepts `json_schema` in every shape (and `json_object` without the word
+ * JSON), measured live 2026-10-15.
* A member who hits either should find it in the reference and on the model
* card, in both locales, not learn it from the error.
*/
@@ -701,11 +706,16 @@ describe('documented 400s: tool names and structured output', () => {
}
});
- it('says json_schema on deepseek-v4-flash depends on the host, and json_object works on both', () => {
+ it('says where json_schema on deepseek-v4-flash is served, where it is a 400, and that json_object works', () => {
const description: string = schemas.ResponseFormat.description;
expect(description).toContain(
- 'On `deepseek-v4-flash` it depends on the host: on the enterprise base URL (see the overview) it is accepted; on `api.nan.builders` it is rejected with a `400` before the request reaches the model, and `json_object` still works there.',
+ 'On `deepseek-v4-flash` it is accepted on non-streamed `/chat/completions` requests without `tools`: the answer is checked against your schema and, when it does not match, usually re-served by a provider that enforces it; an answer that could not be checked, or a miss the re-serve could not replace in time, is returned as is (validate it if your code depends on it).',
);
+ expect(description).toContain(
+ 'Other `json_schema` requests on `deepseek-v4-flash` depend on the host: on the enterprise base URL (see the overview) they are accepted; on `api.nan.builders` a request that streams, carries `tools`, goes to `/responses`, sets `n` above 1, or whose schema is over 16 KB, invalid or one the gateway will not validate (a `$ref` outside the schema, `$dynamicRef` or `$anchor`, a nested `$id`, or too large once its `$ref`s are inlined) is rejected with a `400` before it reaches the model, and `json_object` still works there.',
+ );
+ // The old blanket "rejected on api.nan.builders" must not come back.
+ expect(description).not.toContain('it depends on the host: on the enterprise base URL (see the overview) it is accepted');
expect(description).toContain('On `deepseek-v4-flash` on `api.nan.builders` the prompt must also contain the word JSON, otherwise `400`.');
// "accepted", never a strictness promise for that host.
expect(description).not.toMatch(/enterprise base URL[^.]*(guarantee|constrained)/);
@@ -724,14 +734,18 @@ describe('documented 400s: tool names and structured output', () => {
return page.slice(start, page.indexOf('/>', start));
};
const en = card('');
- expect(en).toContain('Structured output depends on the host: on api.helmcode.com, response_format json_schema and json_object are accepted; on api.nan.builders, json_object is supported (the prompt must contain the word JSON, otherwise it is rejected with a 400) and json_schema is rejected with a 400.');
+ expect(en).toContain('json_schema is accepted on non-streamed /chat/completions requests without tools: the answer is checked against your schema and, when it does not match, usually re-served by a provider that enforces it; an answer that could not be checked, or a miss the re-serve could not replace in time, is returned as is (validate it if your code depends on it). On api.nan.builders other json_schema requests are rejected with a 400: streamed, with tools, on /responses, with n above 1, with a schema over 16 KB, not a valid JSON Schema, or one the gateway will not validate (a $ref outside the schema, $dynamicRef or $anchor, a nested $id, or too large once its $refs are inlined); on api.helmcode.com they are accepted.');
+ expect(en).toContain('on api.nan.builders the prompt must contain the word JSON, otherwise it is rejected with a 400');
expect(en).toContain('use qwen3.6 or gemma4');
- expect(en).toContain('json_schema: 400 on api.nan.builders, accepted on api.helmcode.com');
+ expect(en).toContain('json_schema on non-streamed chat without tools (on api.nan.builders the other cases, e.g. streamed, with tools or on /responses: 400)');
+ expect(en).not.toContain('json_schema is rejected with a 400.');
expect(en).not.toMatch(/not supported \(400\)|json_schema is not and/);
const es = card('-es');
- expect(es).toContain('La salida estructurada depende del host: en api.helmcode.com se aceptan response_format json_schema y json_object; en api.nan.builders, json_object es compatible (el prompt debe contener la palabra JSON; si no, se rechaza con un 400) y json_schema se rechaza con un 400.');
+ expect(es).toContain('json_schema se acepta en peticiones a /chat/completions sin streaming y sin tools: la respuesta se comprueba contra tu esquema y, si no lo cumple, normalmente la vuelve a servir un proveedor que lo impone; una respuesta que no se pudo comprobar, o un fallo que no se pudo reemplazar a tiempo, se devuelve tal cual (valídala si tu código depende de ella). En api.nan.builders el resto de peticiones json_schema se rechazan con un 400: con streaming, con tools, a /responses, con n mayor que 1, con un esquema de más de 16 KB, que no sea un JSON Schema válido o que el gateway no valida (un $ref fuera del esquema, $dynamicRef o $anchor, un $id anidado o demasiado grande al resolver sus $ref); en api.helmcode.com se aceptan.');
+ expect(es).toContain('en api.nan.builders el prompt debe contener la palabra JSON; si no, se rechaza con un 400');
expect(es).toContain('usa qwen3.6 o gemma4');
- expect(es).toContain('json_schema: 400 en api.nan.builders, aceptado en api.helmcode.com');
+ expect(es).toContain('json_schema en chat sin streaming y sin tools (en api.nan.builders el resto, p. ej. con streaming, con tools o en /responses: 400)');
+ expect(es).not.toContain('json_schema se rechaza con un 400.');
expect(es).not.toMatch(/no soportado \(400\)|json_schema no y/);
});
});
@@ -1000,9 +1014,21 @@ describe('documented structured output: guarantees and the deepseek-v4-flash rec
expect(section).toMatch(locale ? /devuelve argumentos que siguen el esquema/ : /returns arguments that follow the schema/);
expect(section).toMatch(locale ? /donde el modelo admite decodificación estricta/ : /where the model supports strict decoding/);
expect(section).not.toMatch(/guaranteed|garantizad/);
- // The 400 is host-specific: api.helmcode.com accepts json_schema on this model.
- expect(section).toContain(locale ? 'En `api.nan.builders`, `deepseek-v4-flash` rechaza' : 'On `api.nan.builders`, `deepseek-v4-flash` rejects');
- expect(section).toContain(locale ? 'en `api.helmcode.com` se acepta' : 'on `api.helmcode.com` it is accepted');
+ // Since 2026-10-16 json_schema is served on non-streamed chat without
+ // tools; the 400 is for streamed / tools / /responses requests, and only
+ // on api.nan.builders (api.helmcode.com accepts those too).
+ expect(section).toContain(locale
+ ? '`deepseek-v4-flash` acepta `response_format` `json_schema` en peticiones a `/chat/completions` sin streaming y sin tools.'
+ : '`deepseek-v4-flash` accepts `response_format` `json_schema` on non-streamed `/chat/completions` requests without tools.');
+ expect(section).toContain(locale
+ ? 'En `api.nan.builders` lo rechaza con un `400` cuando la petición usa streaming, lleva `tools` o va a `/responses`'
+ : 'On `api.nan.builders` it rejects it with a `400` when the request streams, carries `tools` or goes to `/responses`');
+ expect(section).toContain(locale ? 'en `api.helmcode.com` se acepta en todos esos casos' : 'on `api.helmcode.com` it is accepted in all of them');
+ expect(section).toContain(locale ? 'un `$ref` fuera del esquema' : 'a `$ref` outside the schema');
+ // Schemas the gateway refuses to validate (remote refs, dynamic refs,
+ // nested $id, ref explosion) are still a 400 on api.nan.builders.
+ expect(section).toContain(locale ? 'un esquema que el gateway no valida' : 'a schema the gateway will not validate');
+ expect(section).toContain('`$dynamicRef`');
});
}
});