- OpenAIUpdated
- OpenAI: AdvancedNew
- AnthropicUpdated
- Anthropic: AdvancedNew
- Google GeminiUpdated
- Google Gemini: AdvancedNew
- Google Vertex AIUpdated
- OllamaUpdated
- Grok (xAI)Updated
- GroqUpdated
- MistralUpdated
- CohereUpdated
- TypeSafeUpdated
- OllayaUpdated
- ElevenLabsUpdated
- fal.aiUpdated
- ReactorUpdated
- World LabsUpdated
- OpenRouter AdapterUpdated
- OpenRouter: AdvancedNew
- Perplexity SearchUpdated
- Vercel AI GatewayUpdated
- Lovable AI GatewayUpdated
- OpenAI-CompatibleUpdated
- OpenAI-Compatible: AdvancedNew
- LLM GatewayUpdated
- CloudflareUpdated
- Claude CodeUpdated
- CodexUpdated
- OpenCodeUpdated
- Grok BuildUpdated
- ACP-CompatibleUpdated
- Amazon BedrockUpdated
- BytePlusUpdated
- BytePlus: AdvancedNew
Lovable AI Gateway
The Lovable adapter calls Google and OpenAI models with one project key. It covers chat, images, video, embeddings, speech, and transcription.
1. Install#
npm i @tanstack/ai-lovablepnpm add @tanstack/ai-lovableyarn add @tanstack/ai-lovablebun add @tanstack/ai-lovable2. Set the API key#
Lovable creates a LOVABLE_API_KEY for each Cloud project. Put it in the environment of your server:
LOVABLE_API_KEY=your-lovable-keylovableText and the other short factories read LOVABLE_API_KEY. To pass the key in code, use the create* factory of the same adapter:
import { createLovableText } from "@tanstack/ai-lovable";
const adapter = createLovableText("google/gemini-3.7-flash", process.env.LOVABLE_API_KEY!);3. Stream a chat from the server#
Model ids use the google/ or openai/ form:
import {
chat,
chatParamsFromRequest,
toServerSentEventsResponse,
} from "@tanstack/ai";
import { lovableText } from "@tanstack/ai-lovable";
export async function POST(request: Request) {
const { messages, threadId, runId } = await chatParamsFromRequest(request);
const stream = chat({
adapter: lovableText("google/gemini-3.7-flash"),
messages,
threadId,
runId,
});
return toServerSentEventsResponse(stream);
}4. Render the chat#
The client is the same for every provider. Point useChat at your route:
import { useState } from "react";
import { useChat, fetchServerSentEvents } from "@tanstack/ai-react";
export function Chat() {
const [input, setInput] = useState("");
const { messages, sendMessage } = useChat({
connection: fetchServerSentEvents("/api/chat"),
threadId: "support-chat",
});
return (
<form
onSubmit={(event) => {
event.preventDefault();
sendMessage(input);
setInput("");
}}
>
{messages.map((message) => (
<p key={message.id}>
{message.parts
.map((part) => (part.type === "text" ? part.content : ""))
.join("")}
</p>
))}
<input value={input} onChange={(event) => setInput(event.target.value)} />
</form>
);
}For Vue, Solid, Svelte, Angular, and the other frameworks, see Quick Start.
You now have a streaming chat on Lovable. To use an OpenAI model, change the id, for example to openai/gpt-5.6-sol.
Images#
Make a new image from a prompt:
import { generateImage } from "@tanstack/ai";
import { lovableImage } from "@tanstack/ai-lovable";
const result = await generateImage({
adapter: lovableImage("openai/gpt-image-2"),
prompt: "a red guitar on a wooden bench",
threadId: "thread-1",
});To edit an image, pass image parts. OpenAI image models also take a mask part (metadata.role === "mask"). See Image Generation.
Video#
Note
Video generation is an experimental feature.
Video runs as a job. generateVideo() starts the job and returns a jobId. Poll the job until it has an MP4 URL:
import { generateVideo, getVideoJobStatus } from "@tanstack/ai";
import { lovableVideo } from "@tanstack/ai-lovable";
const adapter = lovableVideo("google/veo-3.1-lite");
const { jobId } = await generateVideo({
adapter,
prompt: "a red guitar on a wooden bench, slow camera push-in",
threadId: "thread-1",
duration: "4s",
size: "1280x720",
});
let status = await getVideoJobStatus({ adapter, jobId });
while (status.status === "pending" || status.status === "processing") {
await new Promise((resolve) => setTimeout(resolve, 5000));
status = await getVideoJobStatus({ adapter, jobId });
}
console.log(status.status === "completed" ? status.url : status.error);- A clip is 4, 6, or 8 seconds. You can write 4, "4", or "4s". The same forms work for 6 and 8.
- 1080p and 4K clips are always 8 seconds.
- 4K works only on google/veo-3.1-fast and google/veo-3.1.
- To animate a still frame, pass one image part in prompt.
Embeddings#
import { embed } from "@tanstack/ai";
import { lovableEmbedding } from "@tanstack/ai-lovable";
const result = await embed({
adapter: lovableEmbedding("google/gemini-embedding-2"),
input: "a red guitar",
});
console.log(result.embeddings[0]?.vector);Speech#
import { generateSpeech } from "@tanstack/ai";
import { lovableSpeech } from "@tanstack/ai-lovable";
const result = await generateSpeech({
adapter: lovableSpeech("openai/gpt-4o-mini-tts"),
text: "Welcome to the guitar shop.",
threadId: "thread-1",
voice: "nova",
});See Text-to-Speech.
Transcription#
audio takes a File, a Blob, an ArrayBuffer, a base64 string, or a data URL:
import { generateTranscription } from "@tanstack/ai";
import { lovableTranscription } from "@tanstack/ai-lovable";
const audio = await fetch("https://example.com/voice-note.mp3").then((response) =>
response.blob(),
);
const result = await generateTranscription({
adapter: lovableTranscription("openai/gpt-4o-mini-transcribe"),
audio,
language: "en",
threadId: "thread-1",
});
console.log(result.text);See Transcription.
Summarization#
import { summarize } from "@tanstack/ai";
import { lovableSummarize } from "@tanstack/ai-lovable";
const result = await summarize({
adapter: lovableSummarize("google/gemini-3.7-flash"),
text: "The Fender Stratocaster is a versatile electric guitar.",
threadId: "thread-1",
});Models and options#
Reasoning#
Set reasoning on chat(). The types take the levels of each model:
import { chat } from "@tanstack/ai";
import { lovableText } from "@tanstack/ai-lovable";
const stream = chat({
adapter: lovableText("openai/gpt-5.6-sol"),
messages: [{ role: "user", content: "Plan a database migration." }],
threadId: "thread-1",
reasoning: "xhigh",
});- Responses (the default) sends reasoning.effort. Chat Completions sends reasoning_effort.
- Neither takes budgetTokens.
- The OpenAI and Gemini models take the same levels as on OpenAI and Gemini.
Models#
Pass a model id from the curated lists. TypeScript rejects an id that is not on a list.
| Activity | Default model | List |
|---|---|---|
| Chat | google/gemini-3.7-flash | LOVABLE_CHAT_MODELS |
| Images | openai/gpt-image-2 | LOVABLE_IMAGE_MODELS |
| Video | google/veo-3.1-lite | LOVABLE_VIDEO_MODELS |
| Embeddings | google/gemini-embedding-2 | LOVABLE_EMBEDDING_MODELS |
| Speech | openai/gpt-4o-mini-tts | LOVABLE_TTS_MODELS |
| Transcription | openai/gpt-4o-mini-transcribe | LOVABLE_TRANSCRIPTION_MODELS |
For chat, openai/gpt-5.5 is a fast OpenAI choice.
Advanced#
Chat Completions#
The default text adapter uses the OpenAI Responses API. If a model must use Chat Completions, pass { api: "chat" }:
import { chat } from "@tanstack/ai";
import { lovableText } from "@tanstack/ai-lovable";
const stream = chat({
adapter: lovableText("openai/gpt-5.5", { api: "chat" }),
messages: [{ role: "user", content: "Hello" }],
threadId: "thread-1",
});- api: "responses" is the same as the default.
- api: "chat-completions" is the same as api: "chat".
Need a header on each request, or a log of each model call? The chat adapters on this page support wrapFetch.
Bring Your Own Key#
Users can paste a Lovable project key in the browser. Import lovableByok from @tanstack/ai-lovable/byok. The package main entry does not export it.
import {
chat,
chatParamsFromRequest,
toServerSentEventsResponse,
} from "@tanstack/ai";
import { byokMissing, getByokKey } from "@tanstack/ai/byok/server";
import { createLovableText } from "@tanstack/ai-lovable";
import { lovableByok } from "@tanstack/ai-lovable/byok";
export async function POST(request: Request) {
const apiKey = getByokKey(request, lovableByok);
if (!apiKey) return byokMissing(lovableByok);
const { messages, threadId, runId } = await chatParamsFromRequest(request);
const stream = chat({
adapter: createLovableText("google/gemini-3.7-flash", apiKey),
messages,
threadId,
runId,
});
return toServerSentEventsResponse(stream);
}See Bring Your Own Key for the client store and a save UI.
Requests#
- Calls go to https://ai.gateway.lovable.dev/v1.
- The adapter sends Authorization: Bearer, Lovable-API-Key, and X-Lovable-AIG-SDK: tanstack-ai.
Errors#
- 429 Too Many Requests: the workspace hit its limit of requests per minute.
- 402 Payment Required: the workspace has no credits left.
See Lovable AI features for models, credits, and rate limits.