Skip to content
Adapters

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

shell
npm i @tanstack/ai-lovable

2. Set the API key

Lovable creates a LOVABLE_API_KEY for each Cloud project. Put it in the environment of your server:

shell
LOVABLE_API_KEY=your-lovable-key

lovableText and the other short factories read LOVABLE_API_KEY. To pass the key in code, use the create* factory of the same adapter:

ts
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:

ts
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:

tsx
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:

ts
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:

ts
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

ts
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

ts
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:

ts
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

ts
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:

ts
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.

ActivityDefault modelList
Chatgoogle/gemini-3.7-flashLOVABLE_CHAT_MODELS
Imagesopenai/gpt-image-2LOVABLE_IMAGE_MODELS
Videogoogle/veo-3.1-liteLOVABLE_VIDEO_MODELS
Embeddingsgoogle/gemini-embedding-2LOVABLE_EMBEDDING_MODELS
Speechopenai/gpt-4o-mini-ttsLOVABLE_TTS_MODELS
Transcriptionopenai/gpt-4o-mini-transcribeLOVABLE_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" }:

ts
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.

ts
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.