Nuvq AI API

Free model access, one link at a time.

Text, images, and vision — one link at a time, no API key anywhere. Call them from any app — Flutter, a website, a shortcut, curl. Everything runs through this server, so there's no CORS to fight and nothing to configure on your end.

Setup

None. Every endpoint below works anonymously out of the box — that was the whole point of this pass. Optional: a free personal key at aihorde.net/register (no payment) set as AI_HORDE_API_KEY in Netlify jumps you ahead of the anonymous queue for both images and chat. Not required.

API endpoints

GET/api/image

Runs on AI Horde — free, crowdsourced Stable Diffusion, no signup, no watermark. Because it's community-donated GPUs rather than dedicated servers, speed varies: usually returns the image directly, but if the queue is busy it returns 202 with a checkUrl to poll instead of making you wait indefinitely.

ParamRequiredDefault
promptyes—
modelnobest available of a curated set (SDXL/Deliberate/DreamShaper/SD)
width / heightno512 / 512
seednorandom
safenotrue
enhancenofalse — set true for face-fix + 4x upscale (slower)
sourceImageno— a URL to transform (img2img) instead of generating from scratch
denoiseno0.6 — only with sourceImage; higher = more different from the source
GET /api/image?prompt=a+fox+asleep+in+a+teacup
GET /api/image?prompt=make+it+winter&sourceImage=https://example.com/photo.jpg&denoise=0.5
→ image bytes, OR if still queued:
{ "pending": true, "id": "...", "checkUrl": "/api/image/result?id=..." }
Calls this live server — anonymous queue can take anywhere from a few seconds to a minute or more.

GET/api/image/result

Poll this with the id from a pending /api/image response until it returns the image.

GET /api/image/result?id=abc123
→ image bytes once ready, or { "pending": true, "waitTime": 12, "queuePosition": 2 }

GET/api/image/ensemble

Runs the same prompt through several image models at once, using one shared seed so the results are a fair side-by-side. Returns links, not bytes — render whichever ones you get back.

ParamRequiredDefault
promptyes—
modelsnostable_diffusion, AlbedoBase XL (SDXL), Deliberate 3.0
width / height / seed / safenosame as /api/image
GET /api/image/ensemble?prompt=a+fox+asleep+in+a+teacup
→ { "prompt": "...", "seed": 4821093,
    "images": [
      { "model": "stable_diffusion", "seed": 4821093, "url": ".../api/image?...&model=stable_diffusion&seed=4821093" },
      { "model": "AlbedoBase XL (SDXL)", "seed": 4821093, "url": ".../api/image?...&model=AlbedoBase+XL...&seed=4821093" },
      { "model": "Deliberate 3.0", "seed": 4821093, "url": ".../api/image?...&model=Deliberate+3.0&seed=4821093" }
    ]}

There's no way to merge independent model outputs into one "better" pixel-level image — each is its own attempt. This gives you all of them at once so you (or your app) can pick the best, instead of gambling on a single model. Each link can independently return a 202 pending too.

GET/api/chat

Quick reply as plain text — good for a simple link/shortcut. Runs on AI Horde's KoboldAI text generation — free, no signup. Simple things (greetings, thanks, "who are you", arithmetic, unit/temperature conversion) answer instantly without touching AI Horde at all — no point making "hi" or "10km to miles" wait on a volunteer GPU. Weather questions ("what's the weather in Tokyo") get real current data injected before the model answers, same idea as the world headlines already there — it can't otherwise know either one. Honest tradeoff on the model-backed replies: quality and speed depend on whatever volunteer model happens to pick up the job, so it's more variable than a dedicated frontier-model API.

GET /api/chat?prompt=explain+recursion+simply
→ plain text reply, or { "pending": true, "id": "..." } if the queue is slow

POST/api/chat

Full conversations. Send either prompt for a single message, or a full messages array to keep context. Two optional fields make this pluggable into other apps/bots:

FieldWhat it does
modelPass "nuvq-pro" for a slower, more careful mode: holds out longer for a more capable worker, allows much longer replies (won't truncate mid-function), lower temperature for precision, and an explicit instruction to write complete working code rather than snippets/placeholders. Leave unset for the normal fast mode.
systemPromptA custom persona/instructions for this integration — e.g. a different personality for a Telegram bot vs. your app.
userIdAny stable string you choose per end-user. When set and you send just the latest message (not a full messages array), the server remembers the actual back-and-forth automatically — no need to resend history yourself — plus long-term facts (see /api/memory) and "remember that..." detection. Sending your own full messages array still works exactly as before and skips the auto-history (you're already managing it).
POST /api/chat
Content-Type: application/json

{ "model": "nuvq-pro",
  "messages": [
    { "role": "user", "content": "Write a function that debounces a JS callback" }
]}
→ { "reply": "..." }

GET/api/memory

Real persistent memory (Netlify Blobs) — not in-request-only. Facts get added automatically when a chat message matches "remember that...", "note that...", or "my name is...", or you can add them directly.

GET /api/memory?userId=telegram-8213552
→ { "userId": "...", "facts": ["The user's name is Sam."] }

POST /api/memory   { "userId": "...", "fact": "Prefers short answers." }
DELETE /api/memory?userId=...   (clears everything stored for that id)

Connect it to Telegram

A real webhook, not just an API — Telegram calls this whenever someone messages your bot.

1.Message @BotFather on Telegram, create a bot, copy its token.
2.In Netlify: add env var TELEGRAM_BOT_TOKEN = that token. Optional: TELEGRAM_SYSTEM_PROMPT for its personality, TELEGRAM_WEBHOOK_SECRET for basic request verification.
3.Redeploy, then point Telegram at it — once, from any browser or curl:
https://api.telegram.org/bot<TOKEN>/setWebhook?url=https://YOUR-SITE.netlify.app/api/telegram

That's it — message your bot and it replies for real. Same free AI Horde backend underneath, so the same speed/quality tradeoffs apply. It remembers the last ~10 exchanges per chat automatically, so it stays coherent across a real back-and-forth instead of treating every message as a fresh conversation. Personality is warm and a little playful by default (override with TELEGRAM_SYSTEM_PROMPT), and it recognizes its owner's Telegram username (kaos_king) to be a bit warmer with them specifically — everyone else gets the same helpful default.

POST/api/chat/combined

Also accepts model: "nuvq-pro", systemPrompt, and userId, same as above.

Fires 3 independent completions in parallel (separate AI Horde jobs, not one job asking for 3 at once — that turned out not to be reliably supported and was actually why this endpoint used to hang), then has a follow-up call merge whichever came back into one answer. Slower than /api/chat, and quality depends on the horde the same way — this is the "combine several models" feature adapted to a fully keyless backend, not a guarantee of frontier-model quality.

POST /api/chat/combined
Content-Type: application/json

{ "prompt": "What's the fastest way to learn Spanish?" }
→ { "reply": "...combined answer...", "synthesized": true,
    "drafts": [
      { "label": "Model A", "content": "..." },
      { "label": "Model B", "content": "..." },
      { "label": "Model C", "content": "..." }
    ]}

GET/api/news

The headlines chat quietly uses for current-events awareness, exposed directly in case you want a standalone headlines list or widget. Google News RSS under the hood — free, public, no key.

ParamRequiredDefault
topicnotop stories (or WORLD, NATION, BUSINESS, TECHNOLOGY, SPORTS, SCIENCE, HEALTH, ENTERTAINMENT)
limitno8 (max 20)
GET /api/news?topic=WORLD&limit=5
→ { "topic": "WORLD", "headlines": [
    { "title": "...", "pubDate": "...", "source": "Reuters" }
  ], "fetchedAt": "..." }

GET/api/weather

Real current weather and a 3-day outlook — something no chat model can know on its own. Powered by Open-Meteo, a free, open-source weather API — no key, official project, not a workaround.

ParamRequiredDefault
locationyes (or lat/lon)—
lat / lonyes (or location)—
GET /api/weather?location=Lagos
→ { "location": "Lagos, Nigeria",
    "current": { "temperatureC": 29.4, "feelsLikeC": 33.1, "humidityPct": 78, "windKph": 11.2, "condition": "partly cloudy" },
    "next3Days": [ { "date": "2026-09-13", "highC": 31, "lowC": 24, "condition": "slight rain" } ] }

POST/api/vision

Runs on AI Horde's image interrogation, then a quick follow-up text pass turns the raw caption/tags into a natural couple of sentences of feedback. Honest limit: this is generated commentary on a caption, not the model actually reasoning over the image the way a vision-LLM would — so it can describe and react to what's there, but can't reliably answer an arbitrary custom question about specific details. There's no keyless open-ended visual Q&A service available, so this is the real tradeoff of staying fully key-free here.

POST /api/vision
Content-Type: application/json

{ "imageUrl": "https://example.com/photo.jpg" }
→ { "caption": "...", "tags": ["...", "..."], "feedback": "...", "note": "..." }

Speech-to-text?

Dropped /api/transcribe too — same reasoning as TTS. AI Horde doesn't do audio at all, and there's no other legitimate free, keyless, hosted STT worth building on. The genuinely better free answer: run Whisper client-side (via Transformers.js/WASM in a browser, or native bindings in a mobile app) — free, no key, no server round-trip, and the audio never leaves the device. Or use the platform's built-in recognizer (Android SpeechRecognizer, iOS SFSpeechRecognizer, the browser's SpeechRecognition API) for something even simpler. Either beats proxying audio through a server.

Text-to-speech?

Dropped the /api/speak endpoint — there's no legitimate free, keyless TTS API worth building on (the ones that exist without signup are unofficial/reverse-engineered and can break without notice). The better answer is free anyway: every phone and browser already has built-in text-to-speech (Android TextToSpeech, iOS AVSpeechSynthesizer, the browser's speechSynthesis API) — instant, offline, zero server calls. Use that in whatever app is calling this API instead of round-tripping through a server for it.

GET/api/models

Curated list of known-good image models. Text doesn't use named models by default on this backend.

→ { "image": ["stable_diffusion", "AlbedoBase XL (SDXL)", ...], "text": [], "textNote": "..." }

POST/api/agent

A controller endpoint — reads what you're asking for and actually does it instead of just talking about it: generates an image, checks real weather, saves a memory, or falls through to normal chat.

POST /api/agent
Content-Type: application/json

{ "prompt": "draw me a picture of a fox in a teacup", "userId": "optional" }
→ { "action": "image", "reply": "...", "imageUrl": "https://.../api/image?prompt=..." }

Other actions: "weather" (real current conditions), "memory" (needs userId), or "chat" as the default fallback. Intent detection is pattern-based, not the language model guessing — more predictable, though it means very unusual phrasing might fall through to plain chat instead of triggering the action.

POST/api/os

Built for wiring an AI assistant into your own operating system (or any app where you control execution). This endpoint only interprets a command into a structured action — it never runs anything. Your own app decides whether and how to act on the result, and should show a confirmation step for anything with requiresConfirmation: true. That split matters because this API has no login and no way to verify who's calling it — treat every response as a suggestion your app validates, never as an instruction to execute blindly.

POST /api/os
Content-Type: application/json

{ "command": "set brightness to 80" }
→ { "recognized": true, "action": "set_brightness", "params": { "level": "80" } }
{ "command": "shut down" }
→ { "recognized": true, "action": "system_shut_down", "params": {}, "requiresConfirmation": true }
{ "command": "what's the capital of France" }
→ { "recognized": false, "message": "..." }  ← treat as a normal chat message instead

Recognized actions out of the box: open_app, close_app, set_volume, set_mute, set_brightness, set_wifi, set_bluetooth, take_screenshot, lock_screen, search_files, and destructive ones (system_shut_down, system_restart, etc.) which always come back with requiresConfirmation: true. This is a starting vocabulary, not a fixed one — the patterns live in netlify/functions/os.mts if you want to add actions specific to what your OS actually supports.

From Flutter / Dart

final res = await http.get(Uri.parse(
  'https://YOUR-SITE.netlify.app/api/chat?prompt=hello'));
print(res.body);