viby

Web API host

createVibyApi() turns a configured Viby client into one framework-neutral fetch(Request): Promise<Response> handler. It uses only Web Request, Response, Headers, ReadableStream, URL, and crypto-compatible SDK surfaces, so the same host can be mounted in Node, Bun, Workers, or a framework route.

const api = createVibyApi({
  viby,
  basePath: "/api/viby",
  authenticate: async (request) => {
    const session = await sessions.read(request);
    return session
      ? { tenantId: session.tenantId, userId: session.userId }
      : new Response("Unauthorized", { status: 401 });
  },
});

The host owns authentication. Returning null produces a JSON 401; returning a Response preserves a product-specific redirect or denial. Every resource route after authentication uses the returned tenant/user scope. Integration and tool-source callbacks are deliberately public because their hashed, single-use authorization state establishes the original scope.

An authenticator that creates or rotates a cookie can return the scope and response headers together. The headers are applied to both successful route responses and SDK error responses, and are never persisted or exposed to generation code:

authenticate: async (request) => {
  const session = await sessions.readOrCreate(request);
  return {
    scope: { tenantId: session.tenantId, userId: session.userId },
    headers: session.setCookie ? { "Set-Cookie": session.setCookie } : undefined,
  };
},

Typed Web client

createVibyWebClient() consumes this contract from browsers, Workers, Bun, Node, or another Web-compatible runtime. It owns route construction, attachment encoding, typed errors, binary download responses, and SSE reconnection from the last acknowledged cursor.

import { createVibyWebClient } from "@viby/sdk/core";

const viby = createVibyWebClient<"farmjs">({
  baseUrl: "/api/viby",
  fetch: authenticatedFetch,
});

const { chat, generation } = await viby.chats.create({
  title: "Analytics",
  prompt: "Build a polished analytics dashboard",
});

const imported = await viby.chats.import({
  title: "Existing app",
  source: { type: "zip", bytes: uploadedZip },
});
await viby.chats.versions.apply(imported.chat.id, imported.version.id, {
  changes: [{ type: "write", path: "src/theme.css", content: theme }],
});

for await (const event of viby.generations.stream(generation.id)) {
  saveCursor(event.cursor);
  render(event);
}

const state = await viby.generations.get(generation.id);
const source = await viby.chats.versions.download(chat.id, state.version!.id);

const restored = await viby.chats.versions.restore(chat.id, state.version!.id);
const forked = await viby.chats.versions.fork(chat.id, restored.version.id, {
  title: "Analytics experiment",
});

const previews = await viby.previews.list({ chatId: chat.id, status: "ready" });
if (previews.previews[0]) await viby.previews.stop(previews.previews[0].id);

const tools = await viby.toolSources.create({
  type: "mcp",
  name: "Company tools",
  configuration: { endpoint: "https://tools.example.com/mcp" },
});
await viby.chats.toolSources.set(chat.id, [tools.toolSource.id]);

Pass after to generations.stream() when restoring a cursor from application storage. The client sends it as Last-Event-ID, updates it after each event, and reconnects a prematurely closed retryable stream without replaying acknowledged events. File, ZIP, and attachment bytes remain Uint8Array values in application code; the client owns their base64 HTTP encoding. Authentication remains host-owned: use headers, a header factory, or a custom fetch implementation to attach the product session.

Routes

All paths are relative to basePath (default /api/viby).

Method and pathOperation
GET /chatslist chats with limit, after, and JSON-encoded metadata filters
POST /chatscreate a chat; include prompt to start its first generation
POST /chats/importsimport a project from text/binary files, a base64 ZIP, or a connected repository
GET/PATCH/DELETE /chats/:chatIdload, update, or retention-delete a chat
POST /chats/:chatId/restorerestore a retention-deleted chat before its purge deadline
GET/POST /chats/:chatId/messageslist messages or start a generation
GET /chats/:chatId/messages/:messageIdload one durable message
GET /chats/:chatId/attachments/:attachmentIdstream private attachment bytes with verified metadata headers
GET /chats/:chatId/environmentlist public and redacted environment-variable records
PUT/DELETE /chats/:chatId/environment/:environment/:nameset or delete a scoped variable or secret
GET/PUT /chats/:chatId/tool-sourceslist or replace the chat's explicit durable tool-source selection
GET /chats/:chatId/{repository-links,repository-pushes}reload durable repository connection and push history
GET /chats/:chatId/{deployment-projects,deployments}reload durable deployment project and deployment history
GET /chats/:chatId/versionslist immutable versions
GET /chats/:chatId/versions/:versionIdload version metadata and entries
GET/POST /chats/:chatId/versions/:versionId/changesinspect or apply immutable source changes
POST /chats/:chatId/versions/:versionId/restorecreate a new version from a previous immutable snapshot
POST /chats/:chatId/versions/:versionId/forkcreate a new chat whose first version references the source snapshot
POST /chats/:chatId/versions/:versionId/messagesiterate from an exact version
GET /chats/:chatId/versions/:versionId/downloaddownload raw framework source as ZIP
POST /chats/:chatId/versions/:versionId/previewopen a configured durable preview or invoke a host override
GET /chats/:chatId/versions/:versionId/artifacts/:artifactIdstream a binary project entry
GET /chats/:chatId/versions/:versionId/visual-artifacts[/:artifactId]list visual evidence or stream one screenshot
GET/POST /chats/:chatId/versions/:versionId/repository-pusheslist version push history or push/open a PR through an integration
GET/POST /chats/:chatId/versions/:versionId/deploymentslist deployment history or deploy the immutable version
GET /chats/:chatId/versions/:versionId/deployments/:deploymentId/artifactdownload immutable prebuilt deployment output
GET /generations/:generationIdload status, attempts, tasks, tools, artifacts, and result version
GET /generations/:generationId/eventsstream resumable SSE using Last-Event-ID
GET /generations/:generationId/events/pageread a JSON event page with after and limit
GET /generations/:generationId/artifacts/:artifactIdstream a generated image, audio, document, or binary output
POST /generations/:generationId/cancelcancel an active generation
POST /generations/:generationId/retryadd a retry attempt
POST /generations/:generationId/resumeresume an interrupted attempt
POST /generations/:generationId/tasks/:taskIdresolve a typed plan, question, or permission task
GET /previewslist durable previews by chat, version, or status
GET/DELETE /previews/:previewIdload or stop a durable preview
POST /previews/:previewId/reconnectreconnect the configured sandbox to a durable preview lease
POST /previews/cleanupstop and release expired previews with an optional bounded limit
GET/POST /integrations/callbackcomplete a provider authorization through durable state
GET/POST /tool-sources/callbackcomplete tool-source authorization through durable single-use state
GET/POST /tool-sourceslist/filter registrations or create a public credential-free registration
GET/PATCH/DELETE /tool-sources/:sourceIdload, update, disable, or archive a registration
GET /tool-sources/:sourceId/connectioninspect redacted connection metadata
POST /tool-sources/:sourceId/{connect,disconnect}authorize, reconnect, or revoke a source connection
GET /integrations[/:category]list configured repository/deployment adapters and connection state
GET/POST/DELETE /integrations/:category/:id/{connections,connect}inspect, authorize, or revoke user connections
GET/POST /integrations/repository/:id/{owners,repositories,branches,pull-requests}drive provider-neutral repository selection and PR workflows
GET/POST /integrations/deployment/:id/projectslist or create deployment projects
GET/DELETE /integrations/deployment/:id/deployments/:deploymentIdrefresh or cancel a provider deployment

Compact /versions/:versionId/{iterations,preview,download} aliases accept chatId in the JSON body or download query for small clients that already keep the chat identity in UI state.

Generation JSON accepts prompt, model, instructions, categorized skills, metadata, and attachments. HTTP attachments use { filename, mediaType, base64 }; the host converts them to immutable byte inputs before calling Viby. maxBodyBytes defaults to 10 MiB and is enforced even when Content-Length is absent.

Project imports use { source: { type: "files", files } }, { source: { type: "zip", base64 } }, or { source: { type: "repository", integrationId, connectionId?, repository, ref } }. Binary file entries carry type: "artifact" and base64; text entries carry content. Repository credentials remain in the selected integration adapter and never enter the request or response.

Provider workflow routes accept only provider-neutral fields at the stable boundary. Vendor-specific options, when needed, stay inside providerOptions. Push and deployment effects execute against one immutable version and use the SDK's durable idempotency and history records.

Tool-source routes accept only public registration configuration. connect returns either an existing healthy connection or a provider authorization URL; callback state restores the original tenant/user scope without a product session. Connection responses never contain credential bytes or secret-store references.

Preview boundary

The API does not invent a preview URL. When createViby() has a durable preview configuration, opt into the built-in lifecycle with preview: true. It reuses a ready preview for the immutable version and otherwise materializes the version, runs every configured prepare command, starts the server, waits for readiness, and persists the result:

const viby = createViby({
  framework: "farmjs",
  model,
  sandbox,
  preview: {
    files: [{ path: "preview.config.ts", content: "export const preview = true;\n" }],
    prepare: [{ command: "pnpm", args: ["install", "--frozen-lockfile"] }],
    start: { command: "pnpm", args: ["dev", "--host", "0.0.0.0"] },
    port: 3000,
  },
});

const api = createVibyApi({
  viby,
  authenticate,
  preview: true,
});

preview.files contains preview-only overrides written after the immutable version is materialized. It is useful for host allowlists and development-server settings that should never alter version history or raw downloads. Send Accept: text/event-stream to the preview route, or use web.chats.versions.previewStream(...), to receive workspace, command, stdout/stderr, readiness, and final result events while startup is running.

Pass a callback instead when the product needs a custom cache, deployment result, or complete Response. Without preview, preview routes return 501 preview_not_configured. CORS, CSRF policy, rate limits, and sessions remain application responsibilities; durable sandbox preview cleanup is available through user.previews.cleanupExpired().