Calling a service with a key
AI (OpenAI, Mistral…), payments, sending emails, maps: the key stays on the server and never reaches the browser.
Some things must not happen in your visitors’ browsers: calling an API with a secret key, sending an email, receiving a webhook. For that, your app can have its own server, with routes under /api. It only exists if your app needs it, and Highwind takes care of all its configuration.
Calling a service with a key
AI (OpenAI, Mistral…), payments, sending emails, maps: the key stays on the server and never reaches the browser.
Sending messages
A contact form by email, a notification in Slack, a sign-up added to a Google Sheets spreadsheet.
Receiving webhooks
An external service (payments, forms, automation) tells your app that something happened.
Reaching an API closed to browsers
Some APIs refuse calls coming straight from a web page: the server calls them instead.
If your app only needs to keep data for the visitor, or a database with user accounts, a server isn’t necessarily needed: see Where data goes.
An app has no server at first. When a feature needs one, the agent adds it itself (the “Adds a server to the app” step in the thread), or by wiring in a connector that needs one.
Highwind creates the worker/index.ts file from its template and wires all the configuration. The agent never writes that configuration itself.
The agent adds the routes your app needs to worker/index.ts, for example POST /api/contact.
Your app’s front end calls these routes with relative addresses (fetch("/api/contact")), which work in the preview as well as once published.
If a route needs a key, the agent asks you for it with a card in the chat: see Secrets.
You can also ask for it directly:
Add a contact form that emails me each message at contact@myworkshop.com, withfield validation.Add a route that writes a product description with OpenAI from the name andfeatures the user enters./api/* request. Everything else (pages, images, scripts) is your app’s front end.fetch, Request, Response, crypto.subtle), but no file system, no long-running process and no Express.worker/index.ts file is deleted, the app becomes static again.worker/index.ts fileThe server declares its routes in a routes table (“METHOD /api/path”), reads its secrets from env and answers with the json() helper:
export interface Env { RESEND_API_KEY: string;}
const routes: Record<string, Handler> = { "GET /api/health": () => json({ ok: true }), "POST /api/contact": async (request, env) => { const { email, message } = await request.json(); if (!email || !message) return json({ error: "Missing required fields" }, 400); if (!env.RESEND_API_KEY) return json({ error: "Resend key not added yet" }, 503); // … send the email with env.RESEND_API_KEY return json({ ok: true }); },};When a route doesn’t do what you expect, the logs show what happened: the server’s console.log output and errors, with file and line.
In the preview bar, click the Server logs icon. The panel opens at the bottom of the preview.
Pick the tab:
console.log output and errors of the /api routes, and Vite’s messages), refreshed continuously. Error lines are shown in red./api call shows up with its status and duration, along with console.log output and errors. Neither headers nor visitors’ IP addresses are shown.If you don’t understand the error, ask the agent to fix it: it reads these logs itself (the read_logs tool), in the preview or live.
The contact form returns an error in the published app although it works in thepreview. Check the live logs and fix it.Anyone can call your app’s /api routes, not just its pages. So the agent applies these rules to every route:
src/).Highwind adds its own safeguards on published apps:
| Safeguard | Value |
|---|---|
/api requests per visitor and per app |
120 per minute, then a 429 error (“try again in a minute”) |
/api requests per app, all visitors combined |
3,000 per minute |
| Compute time per request | 1 second of CPU (waiting on external APIs doesn’t count) |
| Outgoing calls per request | 200 |
Your app’s pages and static files aren’t affected by these limits.