Server

createLatchApp and createLatchHandler from @intentface/latch-server, every option, and the routes they serve.

@intentface/latch-server serves the runtime over HTTP. What it's for: Server. Mounting it in your framework: Mount in your framework.

createLatchApp

Builds the runtime with sensible defaults (crash recovery, schedules, the model catalog, built-in tools) and the handler over it. Returns { runtime, handler, models, stop }.

ts
const { runtime, handler } = await createLatchApp<Principal>({ storage, agents, resolveContext });
OptionTypeDefaultDetails
storageStorageAdapter<P>—
agentsunion—
resolveContext(request: Request) => P | Promise<P | null> | null—
context?ContextInput<P, RC>`{}`
models?ModelCatalog`loadModelCatalog()`
principalKey?((principal: P) => string)—
runtime?Partial<Omit<RuntimeConfig<P, RC>, "agents" | "context" | "storage">>—
handler?Partial<Omit<LatchHandlerConfig<P>, "resolveContext" | "runtime">>—
cronIntervalMs?number | false30000 on a long-running server

createLatchHandler

The handler on its own, over a runtime you built. Returns (request) => Promise<Response | undefined>: undefined means the path isn't one of its routes.

ts
const handler = createLatchHandler<Principal>({ runtime, resolveContext, basePath: "/api/latch" });
OptionTypeDefaultDetails
runtimeunion—
resolveContext(request: Request) => P | null | Promise<P | null>—
reconstructPrincipal?function—
oauthFlowMaxAgeMs?number30 minutes
connections?union—
onConnected?((principal: P, connectionName: string) => void | Promise<void>)—
onError?function—
connectedParam?string"connected"
basePath?string"/"
cronSecret?string—
cronRun?(() => Promise<unknown>)`runtime.cron()`

Routes

Paths are relative to basePath. Every route except the cron route and the connection sign-in callback calls resolveContext first. The callback restores the principal that started the sign-in instead (see reconstructPrincipal above).

RouteWhat it does
GET agentsThe agents the caller can use, for a picker.
POST agents/:agent/:chatIdSend a message; streams the answer. Body: { message } or the AI SDK's { messages }.
POST agents/:agent/:chatId/approvalsApprove or deny paused tools; streams the rest of the answer.
POST agents/:agent/:chatId/tool-resultAnswer a tool the browser handles, such as a question for the user.
GET chatsThe caller's chats.
GET chats/:chatIdOne chat's messages.
DELETE chats/:chatIdDelete the chat with its messages, runs and usage, and the subagent threads it spawned. 404 if it isn't the caller's, 409 while an answer runs, 501 if storage can't delete.
GET runsThe caller's runs, with usage and cost.
GET schedules, POST schedulesList or create the caller's schedules.
GET schedules/:id, DELETE schedules/:idRead or delete one.
POST schedules/:id/run-nowFire it on the next cron tick.
GET connectionsThe services the caller can connect, and which are connected.
GET connections/:name/authorizeStart signing in; redirects to the provider.
GET connections/:name/callbackWhere the provider sends the user back.
POST connections/:name/disconnectRemove the caller's credentials.
POST connections/:name/testCheck the connection works; returns its tool names.
GET / POST cron/runYour cronRun, or by default runtime.cron() for everyone (with a runtime per customer and no cronRun, it answers 400). Needs cronSecret, as a bearer token or x-latch-cron-secret, so Vercel Cron can call it.

Refusals come back as JSON with a code: see Errors.