When publishing Next.js apps via the v0 Platform API using chats.init({ type: "files" }), the preview (demo-*.vusercontent.net) does not execute API routes, Server Actions, or any server-side code. Every request — including POST /api/my-route — returns the page HTML instead.
This makes it impossible to build interactive demos that use workflow, database connections, or any backend logic when publishing via the Platform API.
The same app connected via chats.init({ type: "repo" }) gets a real Vercel Sandbox where API routes work, workflows execute, and server-side code runs as expected.
| Approach | API Routes Work? | Preview URL Pattern |
|---|---|---|
type: "files" (inline content) |
No | demo-*.vusercontent.net |
type: "zip" (data URI) |
No | demo-*.vusercontent.net |
type: "files" + sendMessage() |
No | demo-*.vusercontent.net |
type: "files" + deployments.create() |
Deployment works (separate URL), but preview unchanged | demo-*.vusercontent.net |
Extra fields (sandbox: true, fullStack: true, deploy: true) |
Silently ignored (additionalProperties: false) |
demo-*.vusercontent.net |
type: "repo" (GitHub URL) |
Yes | Real Vercel deployment URL |
curl -s -D - "https://demo-XXXX.vusercontent.net/api/fan-out" \
-X POST -H "Content-Type: application/json" \
-d '{"incidentId":"T","message":"t","failChannels":[]}'
# Response:
# HTTP/2 200
# content-type: text/html; charset=utf-8
# x-matched-path: /render/next <-- all routes hit the page rendererThe same app connected via type: "repo" pointing to vercel-labs/workflow-fan-out returns proper JSON from /api/fan-out and the Workflow DevKit runtime executes.
curl -H "Authorization: Bearer $V0_API_KEY" https://api.v0.dev/v1/openapi.jsonThe /chats/init schema uses additionalProperties: false on all type variants — no hidden fields can be passed. No sandbox/preview/fullStack parameters exist anywhere in the spec.
// After chats.init with type: "files":
const deployment = await client.deployments.create({
projectId: project.id,
chatId: chat.id,
versionId: chat.latestVersion.id,
});
// deployment.webUrl = "https://xxx.labs.vercel.dev" (real Vercel, behind SSO)
// But the v0 chat preview still shows demo-*.vusercontent.net
// Re-fetching the chat shows demoUrl is UNCHANGEDThe v0 docs state:
"Previews now run your full application exactly as it would in production. This means server-side code, API routes, database connections, and environment variables all work in the preview."
This is true for the interactive v0 UI (when you chat with v0 and it builds/modifies your app) and for repo-connected projects. But it is not true for Platform API file uploads.
The demo-*.vusercontent.net preview appears to be a lightweight static renderer that:
- Renders React server components (page.tsx)
- Serves static assets
- Does NOT execute API Route Handlers
- Does NOT run serverless functions
- Does NOT have access to the Vercel Workflow runtime
This blocks the use case of programmatically publishing interactive full-stack demos. We're building 56 standalone Workflow DevKit examples for a campaign, each needs:
- Working API routes (to call
start(),getRun(),getReadable()fromworkflow/api) - Real workflow execution (fan-out, retry, saga patterns)
- SSE streaming from
getWritable()in workflow steps
Currently the only path that works requires creating a GitHub repo per example and using type: "repo", which doesn't scale.
Allow type: "files" (and type: "zip") uploads to run in the full Vercel Sandbox with API route support — the same way type: "repo" does.
Alternatively, expose a parameter like sandbox: "full" or buildMode: "deploy" on chats.init to opt into the real sandbox for file uploads.
import { createClient } from "v0-sdk";
const client = createClient({ apiKey: process.env.V0_API_KEY });
const project = await client.projects.create({ name: "Test" });
// This gets a preview WITHOUT API route support:
const chat = await client.chats.init({
type: "files",
projectId: project.id,
files: [
{ name: "app/page.tsx", content: "export default function Home() { return <div>Hello</div> }" },
{ name: "app/api/test/route.ts", content: "export async function GET() { return Response.json({ ok: true }) }" },
{ name: "package.json", content: '{"dependencies":{"next":"16.0.10","react":"19.2.3","react-dom":"19.2.3"}}' },
{ name: "next.config.ts", content: "export default {}" },
{ name: "tsconfig.json", content: '{"compilerOptions":{"jsx":"react-jsx"}}' },
],
});
// This URL does NOT serve /api/test — returns page HTML instead:
console.log(chat.latestVersion?.demoUrl);
// Compare with type: "repo" which DOES serve /api/test:
const repoChat = await client.chats.init({
type: "repo",
projectId: project.id,
repo: { url: "https://github.com/your-org/your-repo" },
});- Community thread: https://community.vercel.com/t/platform-api-demourl-from-latestversion-returns-broken-sandbox-preview-generating-is-not-defined/34314
- The
demoUrlwas reportedly working better before the Feb 2026 sandbox migration - Try creating a chat via the v0 web UI with the same files and check if THAT preview runs API routes (it might — the web UI may use a different pipeline)
demo-*.vusercontent.net— Platform API file uploads (no API routes)preview-*.vusercontent.net— Older pattern, also no API routes- Git-connected projects get a real
.vercel.appor.labs.vercel.devURL
- https://github.com/vercel/v0-sdk
- Watch for new
typeoptions or sandbox parameters in the OpenAPI spec - The spec is at
https://api.v0.dev/v1/openapi.json— diff it periodically
- Manually create a v0 project via the web UI (not the API)
- Upload files via the chat interface ("Start from these files")
- Check if that preview has working API routes
- If yes, the web UI uses a different build pipeline than the Platform API
- https://vercel.com/docs/sandbox (if it exists)
- The sandbox VM that powers v0 previews may have its own API or configuration
// This links a v0 project to an existing Vercel project
await client.integrations.vercel.projects.create({
projectId: v0ProjectId,
name: "my-project",
});- If the v0 project is linked to a Vercel project that has a deployment, the preview might switch to using that deployment
Save and diff the spec over time:
curl -s -H "Authorization: Bearer $V0_API_KEY" \
https://api.v0.dev/v1/openapi.json | python3 -m json.tool > v0-openapi-$(date +%Y%m%d).json