diff --git a/src/content/docs-es/choose-a-model.md b/src/content/docs-es/choose-a-model.md index 1cc24ce..7db9d77 100644 --- a/src/content/docs-es/choose-a-model.md +++ b/src/content/docs-es/choose-a-model.md @@ -35,6 +35,7 @@ Si no sabes cuál coger, busca en la primera columna lo que quieres hacer. | Convertir texto en audio | `kokoro` | 67 voces, dos de ellas en español | | Transcribir audio | `whisper` | Más de 99 idiomas, con detección automática | | Generar o editar una imagen | `flux-2-klein` | Texto a imagen e imagen a imagen | +| Generar una imagen con texto legible | `qwen-image-2.1` | Texto a imagen | ## Todos los modelos @@ -53,6 +54,7 @@ Si no sabes cuál coger, busca en la primera columna lo que quieres hacer. | `kokoro` | Texto a voz | - | texto | sin contador | | `whisper` | Voz a texto | - | audio | sin contador | | `flux-2-klein` | Generar y editar imágenes | - | texto · imagen | 100 peticiones/mes | +| `qwen-image-2.1` | Generar imágenes (texto→imagen) | - | texto | 100 peticiones/mes (pool compartido) | Las fichas completas, con parámetros, licencias y modos de razonamiento, están en [Modelos](/es/docs/models). diff --git a/src/content/docs-es/examples.md b/src/content/docs-es/examples.md index 41d7e2a..62612a4 100644 --- a/src/content/docs-es/examples.md +++ b/src/content/docs-es/examples.md @@ -472,6 +472,73 @@ console.log(image.data[0].url); `/images/edits` acepta hasta cuatro imágenes de referencia (PNG, JPEG o WebP, de menos de 25 MB cada una) y no admite `mask`: mandar una devuelve `400`. +Las imágenes necesitan membresía de inferencia, `403` si no la tienes, y van por su propio presupuesto: 20 peticiones por minuto y 100 al mes, que no toca tu cuota de tokens. +## model: qwen-image-2.1 + +generación de texto a imagen + +### curl + +```bash +curl https://api.nan.builders/v1/images/generations \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer sk-your-key-here" \ + -d '{ + "model": "qwen-image-2.1", + "prompt": "Una pizarra con la palabra faro escrita en ella, rotulación limpia", + "size": "1024x1024", + "n": 1 + }' +# → {"created":...,"data":[{"url":"https://..."}]} +``` + +Cada lado de `size` tiene que ser divisible entre 16 y estar entre 512 y 1280, con una relación de aspecto entre 1:3 y 3:1. `n` llega hasta 4. Sin imágenes de referencia: este modelo es solo texto a imagen. `seed` es un entero en 0..2147483647. + +### python + +```python +from openai import OpenAI + +client = OpenAI( + api_key="sk-your-key-here", + base_url="https://api.nan.builders/v1" +) + +image = client.images.generate( + model="flux-2-klein", + prompt="Una pizarra con la palabra faro escrita en ella, rotulación limpia", + size="1024x1024", + extra_body={"seed": 42} +) + +print(image.data[0].url) +``` + +El enlace es temporal, alrededor de 60 minutos. Pide `response_format="b64_json"` y los bytes llegan en `data[0].b64_json`, en base64, en lugar de detrás de un enlace. `seed` y `guidance` son extensiones de NaN, así que el SDK de OpenAI las manda por `extra_body`. + +### node.js (imagen a imagen) + +```javascript +import OpenAI from "openai"; +import fs from "fs"; + +const client = new OpenAI({ + apiKey: "sk-your-key-here", + baseURL: "https://api.nan.builders/v1", +}); + +const image = await client.images.edit({ + model: "flux-2-klein", + image: fs.createReadStream("referencia.png"), + prompt: "Convierte la escena en invierno, con nieve", + size: "1024x1024", +}); + +console.log(image.data[0].url); +``` + +`/images/edits` acepta hasta cuatro imágenes de referencia (PNG, JPEG o WebP, de menos de 25 MB cada una) y no admite `mask`: mandar una devuelve `400`. + Las imágenes necesitan membresía de inferencia, `403` si no la tienes, y van por su propio presupuesto: 20 peticiones por minuto y 100 al mes, que no toca tu cuota de tokens. ## Conectar tu editor o tu agente diff --git a/src/content/docs-es/models.mdx b/src/content/docs-es/models.mdx index 785710a..0b08ab8 100644 --- a/src/content/docs-es/models.mdx +++ b/src/content/docs-es/models.mdx @@ -332,6 +332,26 @@ OpenAI y la misma `base URL`. ]} /> +/v1/images/generations)', + 'Salida como URL temporal (R2, ~60 min) o base64', + 'Reproducibilidad vía seed (0–2147483647)', + ]} +/> + ## Controlar el razonamiento. Todos los modelos de chat de arriba razonan antes de responder, y el diff --git a/src/content/docs/choose-a-model.md b/src/content/docs/choose-a-model.md index 0b05aef..da1ba27 100644 --- a/src/content/docs/choose-a-model.md +++ b/src/content/docs/choose-a-model.md @@ -35,6 +35,7 @@ If you do not know which one to pick, look for what you want to do in the first | Turn text into audio | `kokoro` | 67 voices, two of them Spanish | | Transcribe audio | `whisper` | More than 99 languages, with automatic detection | | Generate or edit an image | `flux-2-klein` | Text to image and image to image | +| Generate an image with clean text rendering | `qwen-image-2.1` | Text to image | ## Every model @@ -53,6 +54,7 @@ If you do not know which one to pick, look for what you want to do in the first | `kokoro` | Text to speech | - | text | no counter | | `whisper` | Speech to text | - | audio | no counter | | `flux-2-klein` | Generate and edit images | - | text · image | 100 requests/month | +| `qwen-image-2.1` | Generate images (text→image) | - | text | 100 requests/month (shared pool) | The full spec sheets, with parameters, licenses and reasoning modes, are in [Models](/docs/models). diff --git a/src/content/docs/examples.md b/src/content/docs/examples.md index b7a9cc2..6be92f2 100644 --- a/src/content/docs/examples.md +++ b/src/content/docs/examples.md @@ -472,6 +472,73 @@ console.log(image.data[0].url); `/images/edits` takes up to four reference images (PNG, JPEG or WebP, under 25 MB each) and does not support `mask`: sending one returns `400`. +Images need inference membership, `403` otherwise, and they run on their own budget: 20 requests per minute and 100 per month, which does not touch your token quota. +## model: qwen-image-2.1 + +text-to-image generation + +### curl + +```bash +curl https://api.nan.builders/v1/images/generations \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer sk-your-key-here" \ + -d '{ + "model": "qwen-image-2.1", + "prompt": "A chalkboard with the word lighthouse written on it, clean lettering", + "size": "1024x1024", + "n": 1 + }' +# → {"created":...,"data":[{"url":"https://..."}]} +``` + +Each side of `size` has to be divisible by 16 and between 512 and 1280, with an aspect ratio between 1:3 and 3:1. `n` goes up to 4. No reference images: this model is text-to-image only. `seed` is an integer in 0..2147483647. + +### python + +```python +from openai import OpenAI + +client = OpenAI( + api_key="sk-your-key-here", + base_url="https://api.nan.builders/v1" +) + +image = client.images.generate( + model="flux-2-klein", + prompt="A chalkboard with the word lighthouse written on it, clean lettering", + size="1024x1024", + extra_body={"seed": 42} +) + +print(image.data[0].url) +``` + +The link is temporary, about 60 minutes. Ask for `response_format="b64_json"` and the bytes arrive inline in `data[0].b64_json` instead, base64-encoded. `seed` and `guidance` are NaN extensions, so the OpenAI SDK sends them through `extra_body`. + +### node.js (image to image) + +```javascript +import OpenAI from "openai"; +import fs from "fs"; + +const client = new OpenAI({ + apiKey: "sk-your-key-here", + baseURL: "https://api.nan.builders/v1", +}); + +const image = await client.images.edit({ + model: "flux-2-klein", + image: fs.createReadStream("reference.png"), + prompt: "Turn the scene into winter, with snow", + size: "1024x1024", +}); + +console.log(image.data[0].url); +``` + +`/images/edits` takes up to four reference images (PNG, JPEG or WebP, under 25 MB each) and does not support `mask`: sending one returns `400`. + Images need inference membership, `403` otherwise, and they run on their own budget: 20 requests per minute and 100 per month, which does not touch your token quota. ## Connect your editor or your agent diff --git a/src/content/docs/models.mdx b/src/content/docs/models.mdx index 20f0d87..b2b2160 100644 --- a/src/content/docs/models.mdx +++ b/src/content/docs/models.mdx @@ -331,6 +331,26 @@ with the same `base URL`. ]} /> +/v1/images/generations)', + 'Output as temporary URL (R2, ~60 min) or base64', + 'Reproducibility via seed (0–2147483647)', + ]} +/> + ## Controlling reasoning. Every chat model above thinks before it answers, and the reasoning trace diff --git a/src/data/modelos.json b/src/data/modelos.json index 2c6c056..41e02d4 100644 --- a/src/data/modelos.json +++ b/src/data/modelos.json @@ -129,8 +129,15 @@ "specs": "FLUX diffusion · text→image · image→image · 256-1536 px · 1-4 per request", "cuota": "100 req/mes", "frontier": false + }, + { + "id": "qwen-image-2.1", + "by": "Alibaba", + "specs": "Qwen diffusion · text→image · 512-1280 px · 1-4 per request", + "cuota": "100 req/mes", + "frontier": false } ] } ] -} +} \ No newline at end of file diff --git a/src/data/openapi.json b/src/data/openapi.json index a6a930d..0f15cb1 100644 --- a/src/data/openapi.json +++ b/src/data/openapi.json @@ -3,7 +3,7 @@ "info": { "title": "NaN API", "version": "1.0.0", - "description": "Open models on a shared EU inference cluster. Zero logs.\n\nThe NaN API is OpenAI-compatible: predictable, resource-oriented URLs, JSON request and response bodies, and standard HTTP verbs and status codes. Point any OpenAI SDK at our base URL and your existing code keeps working. Change the base URL and the API key, and that's it.\n\nOne schema across every model, so you only learn the API once. Change the `model` field to switch models; everything else stays the same.\n\n- Base URL: `https://api.nan.builders/v1`\n- OpenAPI spec: this document. Import it into Postman, Insomnia, or your own tooling.\n\nIf you use the [Helmcode](https://helmcode.com) enterprise service, the base URL is `https://api.helmcode.com/v1` instead. Every other endpoint is identical.\n\n## Authentication\n\nEvery request authenticates with an API key, sent as a Bearer token:\n\n```\nAuthorization: Bearer $NAN_API_KEY\n```\n\nYou must be a NaN community member. Generate your key from user settings, under \"API Keys\", on the [platform](https://cloud.nan.builders/). The key is personal and non-transferable. Keep it secret: never embed one in client-side code or commit it to source control. Requests must go over HTTPS; calls over plain HTTP fail.\n\n## Making requests\n\nThe API is OpenAI-compatible, so point an official OpenAI SDK at our base URL and change nothing else:\n\n```python\nfrom openai import OpenAI\n\nclient = OpenAI(\n api_key=\"$NAN_API_KEY\",\n base_url=\"https://api.nan.builders/v1\",\n)\n\nresp = client.chat.completions.create(\n model=\"deepseek-v4-flash\",\n messages=[{\"role\": \"user\", \"content\": \"Hello\"}],\n)\nprint(resp.choices[0].message.content)\n```\n\n## Streaming\n\nChat responses can stream token-by-token. Set `\"stream\": true` on `/chat/completions` and the response arrives as Server-Sent Events: each event is a `data:` line carrying a `chat.completion.chunk`, with the new text in `choices[0].delta.content`. A final `data: [DONE]` line ends the stream. Only `/chat/completions` streams incrementally; `/responses` currently emits a single terminal event.\n\n## Rate limits\n\n{{RATE_LIMITS}}\n\nImage endpoints run on their own budget, separate from the model endpoints: 20 requests per minute and 100 requests per month. The usage endpoint is metered separately too: 30 requests per minute per member. Exceed any limit and you get a `429`.\n\n## Errors\n\nNaN uses conventional HTTP status codes: `2xx` on success, `4xx` for a problem with the request (a missing parameter, an invalid key, an unavailable model) and `5xx` for a server-side error. Every error returns a JSON body in the OpenAI shape:\n\n```json\n{\n \"error\": {\n \"message\": \"The model 'foo' does not exist.\",\n \"type\": \"invalid_request_error\",\n \"param\": \"model\",\n \"code\": \"model_not_found\"\n }\n}\n```\n\n`message` is human-readable, `param` names the offending field when applicable, and `code` is a short machine-readable string you can branch on.\n\n| Status | Meaning | `code` |\n| --- | --- | --- |\n| `400` | Invalid or malformed parameter (`param` says which); or content blocked by the safety filter. | `invalid_request_error` · `content_policy_violation` |\n| `401` | Missing or invalid API key, or a key whose tier does not reach the requested model (`glm5.3`): \"This API key does not have access to the requested model\", `type: auth_error`. Measured 2026-09-12. | `invalid_api_key` |\n| `402` | The token allowance is spent on a model that carries one. Not retryable: the counter returns to zero when that model's quota period does, the calendar month for the models counted per month and your billing period for `glm5.3`. | `monthly_cap_reached` |\n| `403` | Your tier can't access this endpoint. Image generation requires inference membership. A model your tier cannot reach answers `401`, not this. | `tier_restricted` |\n| `404` | The requested model doesn't exist. | `model_not_found` |\n| `429` | Rate limit hit (`rpm_limit`, `max_parallel_requests`), the rolling 4h token budget of `glm5.3`, or a quota exhausted. | `rate_limit_exceeded` · `insufficient_quota` · `quota_exceeded` |\n| `500` | Something went wrong on our side (includes upstream model errors). | (none) |\n| `524` | Timeout, typical with large audio files on `/audio/transcriptions`. | (none) |\n\nRetry `429` and `5xx` responses with exponential backoff. Don't retry `400`, `401`, `403`, or `404` blindly: they'll fail the same way every time until you change the request. `402` cannot be fixed by repetition either: it clears when that model's quota period resets.\n\n## Model catalog\n\nEvery endpoint takes a `model` id. Capabilities vary by model:\n\n| Model | Use for | Capabilities |\n| --- | --- | --- |\n| `deepseek-v4-flash` | Chat, vision, reasoning | Streaming, tool calling, reasoning, image input, 1M-token context. 3B tokens/month per member |\n| `mimo-v2.5` | Chat, vision, audio | Streaming, tool calling, reasoning, image input, audio input, 1M-token context. 1.0B tokens/month per member |\n| `mimo-v2.6-flash` | Chat, vision, audio | Streaming, tool calling, reasoning, image input, audio input, 1M-token context. 1.0B tokens/month per member |\n| `qwen3.8-flash` | Chat, vision, agents | Streaming, tool calling, reasoning (on by default), vision, 262K-token context. 500M tokens/month per member |\n| `glm5.3-flash` | Chat, vision, agents | Streaming, tool calling, reasoning, vision, 1M-token context. 2B tokens/month per member |\n| `qwen3.6` | Chat, agents | Streaming, tool calling, vision, reasoning (opt-out, returns `reasoning_content`) |\n| `gemma4` | Chat, vision, agents | Streaming, tool calling, vision, reasoning (opt-in) |\n| `glm5.3` | Coding, long-horizon agents | Streaming, tool calling, reasoning trace, text-only input, 1M-token context. Premium tier only |\n| `qwen3-embedding` | Embeddings | 4096-dimension vectors |\n| `rerank` | RAG reranking | Qwen3-Reranker-8B, 100+ languages |\n| `kokoro` | Text-to-speech | Multiple voices and audio formats |\n| `whisper` | Speech-to-text | Transcription with word/segment timestamps |\n| `flux-2-klein` | Image generation | Text-to-image and image-to-image |\n\n`glm5.3` is served only to keys on the GLM 5.3 premium tier; every other model is available to any inference member. Call [List models](#tag/Models) for the exact set available to your key.\n\n## Versioning & compatibility\n\nThe API tracks the OpenAI API surface, so OpenAI SDKs and tools work against `https://api.nan.builders/v1` unchanged. This reference documents the stable public `/v1` endpoints, and we add capabilities without breaking existing fields.", + "description": "Open models on a shared EU inference cluster. Zero logs.\n\nThe NaN API is OpenAI-compatible: predictable, resource-oriented URLs, JSON request and response bodies, and standard HTTP verbs and status codes. Point any OpenAI SDK at our base URL and your existing code keeps working. Change the base URL and the API key, and that's it.\n\nOne schema across every model, so you only learn the API once. Change the `model` field to switch models; everything else stays the same.\n\n- Base URL: `https://api.nan.builders/v1`\n- OpenAPI spec: this document. Import it into Postman, Insomnia, or your own tooling.\n\nIf you use the [Helmcode](https://helmcode.com) enterprise service, the base URL is `https://api.helmcode.com/v1` instead. Every other endpoint is identical.\n\n## Authentication\n\nEvery request authenticates with an API key, sent as a Bearer token:\n\n```\nAuthorization: Bearer $NAN_API_KEY\n```\n\nYou must be a NaN community member. Generate your key from user settings, under \"API Keys\", on the [platform](https://cloud.nan.builders/). The key is personal and non-transferable. Keep it secret: never embed one in client-side code or commit it to source control. Requests must go over HTTPS; calls over plain HTTP fail.\n\n## Making requests\n\nThe API is OpenAI-compatible, so point an official OpenAI SDK at our base URL and change nothing else:\n\n```python\nfrom openai import OpenAI\n\nclient = OpenAI(\n api_key=\"$NAN_API_KEY\",\n base_url=\"https://api.nan.builders/v1\",\n)\n\nresp = client.chat.completions.create(\n model=\"deepseek-v4-flash\",\n messages=[{\"role\": \"user\", \"content\": \"Hello\"}],\n)\nprint(resp.choices[0].message.content)\n```\n\n## Streaming\n\nChat responses can stream token-by-token. Set `\"stream\": true` on `/chat/completions` and the response arrives as Server-Sent Events: each event is a `data:` line carrying a `chat.completion.chunk`, with the new text in `choices[0].delta.content`. A final `data: [DONE]` line ends the stream. Only `/chat/completions` streams incrementally; `/responses` currently emits a single terminal event.\n\n## Rate limits\n\n{{RATE_LIMITS}}\n\nImage endpoints run on their own budget, separate from the model endpoints: 20 requests per minute and 100 requests per month. The usage endpoint is metered separately too: 30 requests per minute per member. Exceed any limit and you get a `429`.\n\n## Errors\n\nNaN uses conventional HTTP status codes: `2xx` on success, `4xx` for a problem with the request (a missing parameter, an invalid key, an unavailable model) and `5xx` for a server-side error. Every error returns a JSON body in the OpenAI shape:\n\n```json\n{\n \"error\": {\n \"message\": \"The model 'foo' does not exist.\",\n \"type\": \"invalid_request_error\",\n \"param\": \"model\",\n \"code\": \"model_not_found\"\n }\n}\n```\n\n`message` is human-readable, `param` names the offending field when applicable, and `code` is a short machine-readable string you can branch on.\n\n| Status | Meaning | `code` |\n| --- | --- | --- |\n| `400` | Invalid or malformed parameter (`param` says which); or content blocked by the safety filter. | `invalid_request_error` · `content_policy_violation` |\n| `401` | Missing or invalid API key, or a key whose tier does not reach the requested model (`glm5.3`): \"This API key does not have access to the requested model\", `type: auth_error`. Measured 2026-09-12. | `invalid_api_key` |\n| `402` | The token allowance is spent on a model that carries one. Not retryable: the counter returns to zero when that model's quota period does, the calendar month for the models counted per month and your billing period for `glm5.3`. | `monthly_cap_reached` |\n| `403` | Your tier can't access this endpoint. Image generation requires inference membership. A model your tier cannot reach answers `401`, not this. | `tier_restricted` |\n| `404` | The requested model doesn't exist. | `model_not_found` |\n| `429` | Rate limit hit (`rpm_limit`, `max_parallel_requests`), the rolling 4h token budget of `glm5.3`, or a quota exhausted. | `rate_limit_exceeded` · `insufficient_quota` · `quota_exceeded` |\n| `500` | Something went wrong on our side (includes upstream model errors). | (none) |\n| `524` | Timeout, typical with large audio files on `/audio/transcriptions`. | (none) |\n\nRetry `429` and `5xx` responses with exponential backoff. Don't retry `400`, `401`, `403`, or `404` blindly: they'll fail the same way every time until you change the request. `402` cannot be fixed by repetition either: it clears when that model's quota period resets.\n\n## Model catalog\n\nEvery endpoint takes a `model` id. Capabilities vary by model:\n\n| Model | Use for | Capabilities |\n| --- | --- | --- |\n| `deepseek-v4-flash` | Chat, vision, reasoning | Streaming, tool calling, reasoning, image input, 1M-token context. 3B tokens/month per member |\n| `mimo-v2.5` | Chat, vision, audio | Streaming, tool calling, reasoning, image input, audio input, 1M-token context. 1.0B tokens/month per member |\n| `mimo-v2.6-flash` | Chat, vision, audio | Streaming, tool calling, reasoning, image input, audio input, 1M-token context. 1.0B tokens/month per member |\n| `qwen3.8-flash` | Chat, vision, agents | Streaming, tool calling, reasoning (on by default), vision, 262K-token context. 500M tokens/month per member |\n| `glm5.3-flash` | Chat, vision, agents | Streaming, tool calling, reasoning, vision, 1M-token context. 2B tokens/month per member |\n| `qwen3.6` | Chat, agents | Streaming, tool calling, vision, reasoning (opt-out, returns `reasoning_content`) |\n| `gemma4` | Chat, vision, agents | Streaming, tool calling, vision, reasoning (opt-in) |\n| `glm5.3` | Coding, long-horizon agents | Streaming, tool calling, reasoning trace, text-only input, 1M-token context. Premium tier only |\n| `qwen3-embedding` | Embeddings | 4096-dimension vectors |\n| `rerank` | RAG reranking | Qwen3-Reranker-8B, 100+ languages |\n| `kokoro` | Text-to-speech | Multiple voices and audio formats |\n| `whisper` | Speech-to-text | Transcription with word/segment timestamps |\n| `flux-2-klein` | Image generation | Text-to-image and image-to-image |\n| `qwen-image-2.1` | Image generation (text→image) | 512-1280 px, 1-4 per request, seed 0-2147483647. 100 images/month per member (shared pool with flux-2-klein) |\n\n`glm5.3` is served only to keys on the GLM 5.3 premium tier; every other model is available to any inference member. Call [List models](#tag/Models) for the exact set available to your key.\n\n## Versioning & compatibility\n\nThe API tracks the OpenAI API surface, so OpenAI SDKs and tools work against `https://api.nan.builders/v1` unchanged. This reference documents the stable public `/v1` endpoints, and we add capabilities without breaking existing fields.", "contact": { "name": "NaN", "url": "https://nan.builders" @@ -1178,7 +1178,7 @@ "model": { "type": "string", "default": "flux-2-klein", - "description": "The image model. An unknown model returns `404`.", + "description": "The image model: `flux-2-klein` (default) or `qwen-image-2.1`. An unknown model returns `404`.", "example": "flux-2-klein" }, "n": { @@ -2563,4 +2563,4 @@ "description": "NaN Docs", "url": "https://nan.builders/docs" } -} +} \ No newline at end of file diff --git a/src/lib/modelCatalog.ts b/src/lib/modelCatalog.ts index dc19449..f74ca9f 100644 --- a/src/lib/modelCatalog.ts +++ b/src/lib/modelCatalog.ts @@ -224,6 +224,18 @@ export const MODELS: ModelSpec[] = [ es: 'Generar y editar imágenes', }, }, + { + id: 'qwen-image-2.1', + by: 'Alibaba', + kind: 'image', + inputs: ['text'], + quota: { kind: 'monthly', label: { en: '100 requests / mo', es: '100 peticiones/mes' } }, + endpoint: '/images/generations', + bestFor: { + en: 'Text-to-image with clean text rendering', + es: 'Texto a imagen con texto legible en la imagen', + }, + }, ]; /** Every id the cluster answers to, for validating what the docs write. */ diff --git a/src/lib/openapiSpec.test.ts b/src/lib/openapiSpec.test.ts index 4cf07d6..0711033 100644 --- a/src/lib/openapiSpec.test.ts +++ b/src/lib/openapiSpec.test.ts @@ -53,6 +53,7 @@ const NAN_MODELS = [ 'kokoro', 'whisper', 'flux-2-klein', + 'qwen-image-2.1', ]; describe('openapi.json: structure', () => {