WisyLink Blog

APICLIautomationdevelopersAI website builder

AI Website Builder API: Generate Sites From Code

Use the WisyLink AI website builder API to create, update and list hosted sites from code: upload files, send a brief, poll the status and get a live URL.

12 min read
AI Website Builder API: Generate Sites From Code

WisyLink's API lets you create and update hosted websites from code. Upload reference files, send a plain-language message to POST /chat, and the agent builds and hosts a site at its own wisylink.com address. Send another message with the same link id to change it. API usage shares your plan's 5-hour and weekly allowance.

  • A handful of REST endpoints cover the whole lifecycle: upload files, create or continue a site with POST /chat, poll its status, list and delete sites.
  • POST /chat talks to Wisy, the same assistant as in the app, so a reply can be a question instead of a build. Check status before you start polling.
  • API keys are created on the web, and each one carries its own spend cap of up to $10 per day, week or month, on top of your plan's windows.
  • The @wisylink/cli npm package wraps the same endpoints in a command-line tool and a Node client.
  • Custom domains and service keys (Stripe, email) are set in the web or mobile app; the public API doesn't cover them.

What you can automate

The API is the chat, minus the chat window. Every POST /chat is a message to Wisy; when the request is clear, an AI build agent writes a server-rendered web app and deploys it to https://<id>.wisylink.com, with saved data, accounts or file uploads when the brief calls for them. There are no template, theme or page-type parameters. Everything about the site, from sections and copy to features and style, goes into the message, the same way a person would describe it. (For the concepts behind this, see what an AI app builder is.)

The base URL is https://api.wisylink.com. Authenticate with an x-api-key: <key> header or Authorization: Bearer <key>. JSON endpoints take Content-Type: application/json, and timestamps are epoch milliseconds.

WisyLink API endpoints
EndpointMethodPurposeKey fields
/filesPOSTStart a file uploadname in, id out
/files/chunksPOSTUpload the file's bytes, at most 4 MB per requestid and last=true on the final chunk; raw binary body
/files/:idGETRead file metadatacontent_type, name, size
/files/:idDELETEDelete a fileAlso removed from any link's file_ids
/chatPOSTCreate a site (no id) or continue one (with id)message, up to 10 file_ids; returns id, url, status, answer
/linksGETList your sites, newest firstpage, limit (1 to 100); items, total_pages, has_next
/links/:idGETRead one sitestatus, url, file_ids
/links/:idDELETEDelete a site and its reference filesReturns {"ok": true}

What the public API does not do: set the keys a site needs for payments or email (the owner pastes those under Integrations), attach a custom domain, read or write a site's saved data, or export source code.

Get an API key and set a per-key spend limit

  1. Sign in at wisylink.com. Keys are created on the web only, not in the mobile apps.
  2. Open API Keys in your account settings and choose Generate key.
  3. Name it after where it will live, such as "acme-landing-pages-ci", so you know what breaks when you revoke it.
  4. Set the window and the cap. Pick daily, weekly or monthly and a spend limit in USD, up to $10 per window. Windows follow a UTC day, a week starting Monday, or a month.
  5. Copy the key immediately. It is shown once. Store it in your secret manager or CI secrets, for example as WISYLINK_API_KEY.

The cap is a safety throttle, not your plan. Every request also draws on your account's rolling 5-hour and weekly windows, the same ones the web and mobile chat use, metered by the AI work the request triggers, including reading your reference files. The cap is checked before each request and spend is recorded after it, so the last request in a window can land slightly over. Revoking a key deletes it at once, and anything still using it gets 401.

Can you use the API on the free plan? Yes. On Community, the same rules apply as in the app: one build per 5-hour window, and sites stay live for 30 days plus a week marked for deletion. That is enough to prototype an integration; for client work or batches, a paid plan gives you more allowance, builds in parallel and links that stay live while you're subscribed.

Upload reference files (chunked uploads)

Reference files are anything the agent should read or place on the site: a brand guide, a logo, product photos, a price list. Supported formats are JPEG, PNG, WebP, GIF, HEIC, SVG, MP3, MP4, PDF, XLSX, DOCX, CSV, TXT, JSON and HTML, up to 25 MB each. The real type is detected from the bytes, and empty files are rejected.

Uploading takes two calls. First create the file and keep the returned id:

curl -s -X POST https://api.wisylink.com/files -H "x-api-key: $WISYLINK_API_KEY" -H "Content-Type: application/json" -d '{"name":"brand-guide.pdf"}'

Then send the bytes. A file of 4 MB or less goes in one request with last=true:

curl -s -X POST "https://api.wisylink.com/files/chunks?id=$FILE_ID&last=true" -H "x-api-key: $WISYLINK_API_KEY" -H "Content-Type: application/octet-stream" --data-binary @brand-guide.pdf

For larger files, split them (split -b 4m brand-guide.pdf part_), send the parts in order to the same id, and add last=true only to the final part; that call finalizes and processes the file. An upload must finish within 15 minutes or the session expires. In Node, the client described below does the chunking for you.

Create a site with POST /chat and wait for the live URL

Send the brief

Omit id to start a new site. The message can be up to 5,000 characters, which is room for a proper brief: purpose, audience, sections, real copy, features, style and language. End it with "choose sensible defaults for anything I haven't specified" to make a build on the first call more likely. Replace FILE_ID with the id from your upload.

curl -s -X POST https://api.wisylink.com/chat -H "x-api-key: $WISYLINK_API_KEY" -H "Content-Type: application/json" -d '{"message":"Build a one-page landing site for Ember, a specialty coffee roaster in Portland. Sections: hero, three single-origin coffees with prices, wholesale inquiry form saved to an admin area, opening hours, map link. Use the attached brand guide for colors and fonts. Choose sensible defaults for anything I have not specified.","file_ids":["FILE_ID"]}'

The same call from Node with fetch:

const res = await fetch("https://api.wisylink.com/chat", { method: "POST", headers: { "x-api-key": process.env.WISYLINK_API_KEY, "Content-Type": "application/json" }, body: JSON.stringify({ message: brief, file_ids: [fileId] }) }); let link = await res.json();

A new site returns 201 with id, url, status, answer, created_at and updated_at. Store the id: it is how you change, read or delete the site later. Treat it as an opaque string rather than validating its format.

Read the status before you poll

The status in the POST /chat response tells you whether a build actually started:

building or queued
A build is running or waiting for a free slot (queued isn't in the reference's list yet, but you can see it). Start polling. On a new site, the url serves a "building" page until the first build finishes.
idle on a new site
No build started. Wisy answered instead, and answer usually holds one short question, such as which payment provider to use. Reply with another POST /chat carrying the same id.
completed or aborted right after a follow-up
On an existing site, the status shows the latest build's outcome. If it didn't change to building, your message got a reply, not a build; read answer.

Poll until the build settles

There are no webhooks, so poll GET /links/:id. Builds run for minutes, not seconds, so every 15 to 30 seconds is plenty. Treat any status other than completed, aborted or idle as still running, which keeps your loop safe if a new in-progress status appears:

const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); while (!["completed", "aborted", "idle"].includes(link.status)) { await sleep(20000); link = await (await fetch(`https://api.wisylink.com/links/${link.id}`, { headers: { "x-api-key": process.env.WISYLINK_API_KEY } })).json(); }

When status is completed, url is your live site. aborted means the build stopped before finishing; a site that was already live keeps its previous version, and a new message tries again. If the site later gets a custom domain, url returns that domain instead. The build's full report, including any note that the site waits for Stripe or email keys, is in that link's chat in the web app.

To change a site, send only the new instruction with its id in place of LINK_ID; the server keeps the conversation history, and a continued site answers 200:

curl -s -X POST https://api.wisylink.com/chat -H "x-api-key: $WISYLINK_API_KEY" -H "Content-Type: application/json" -d '{"id":"LINK_ID","message":"Add a second location: 412 Alder St, open daily 7 am to 3 pm."}'

Poll again the same way. Each change is a new build; a failed one leaves the last good version live, while a successful one goes live as soon as it finishes, so run your checks after every change. Wait for one build to settle before sending the next change, unless the new message corrects the build that is running.

To list sites, page through GET /links?page=1&limit=100 until has_next is false. To remove one, DELETE /links/:id deletes the site and the reference files attached to it. A custom domain it used stays on your account, ready for another site.

The CLI and the Node client

The @wisylink/cli package (MIT licensed, Node 18.17 or later) maps commands one-to-one onto the endpoints above, which makes it the quickest way to script WisyLink from a shell or a CI job:

  • npm i -g @wisylink/cli, then export WISYLINK_API_KEY=... or pass --api-key per command.
  • wisylink files upload ./brand-guide.pdf uploads in 4 MB chunks automatically.
  • wisylink chat --message "Build a landing page for Ember Coffee." --file-id FILE_ID creates a site.
  • wisylink links list --page 1 --limit 50 lists your sites.

Commands print JSON, so jq can pick out the id or url. --timeout takes 1,000 to 120,000 milliseconds; raise it for chat, which waits for Wisy's reply.

The same package exports a Node client: import { CreateWisyLinkClient } from "@wisylink/cli"; const client = CreateWisyLinkClient({ apiKey: process.env.WISYLINK_API_KEY }); const file = await client.uploadFile("./brand-guide.pdf"); const link = await client.chat({ message: brief, fileIds: [file.id] });

Two version notes, as of the npm release 0.2.1. First, commands and client methods that take a link id (chat --link-id, links get, links delete, and linkId, getLink, deleteLink in Node) validate link ids against an older id format and reject current ones, so continue, read and delete sites over HTTP as shown above. Second, the CLI docs already describe a newer release with a browser login (wisylink login), an interactive chat with live build progress and a --json flag; run wisylink --version to see which one you have.

Limits, errors and good practices

Errors come back as JSON with an error code. The ones worth handling explicitly:

429 limit_reached
Your plan's 5-hour or weekly window is used up (reason is 5h_limit or weekly_limit). Schedule the retry for reset_at, in epoch milliseconds, instead of retrying in a loop.
429 credit_limit_exceeded
This key reached its own spend cap. It can happen on any endpoint; wait for resets_at or raise the cap (up to $10) on the web.
429 build_concurrency_limit
Too many builds are running for your plan. Wait for one to settle, then send the next.
429 rate_limited
Too many requests in a short time. Back off and retry.
400 bad_request
Invalid JSON, an empty message or one over 5,000 characters, or file ids that don't exist (listed in missing_file_ids). Note that only the first 10 file_ids are used; extra ones are dropped without an error.
401, 404, 500 chat_unavailable
A missing or revoked key; a link or file that isn't yours; Wisy briefly unavailable, worth one retry after a pause.

Habits that save money and confusion:

  • Keep keys on the server. Never ship a key in browser or app code. Use one key per client or pipeline so each has its own cap and can be revoked alone.
  • Don't blindly retry POST /chat. It isn't idempotent: a request that timed out on your side may still have created a site or started a build. Check GET /links (newest first) before resending, and give the call a client timeout of a minute or more.
  • Queue batch jobs. Run as many builds at once as your plan allows and treat build_concurrency_limit as a signal to wait.
  • Review before you ship. AI output can contain mistakes. Open each site, test its forms and read its copy before a client or the public sees it; WisyLink's terms make reviewing and testing your responsibility.

Use cases: agency pages, event microsites, internal tools

Agency landing pages. Keep a brief template with slots for the client's name, offer, sections and tone, fill it from your CRM, and attach the client's logo and brand guide as files. Each campaign becomes its own site, client feedback becomes follow-up messages, and a key per client keeps spend separate. Connect the client's domain in Domains once the page is approved. A worked example of the page itself is in building a waitlist landing page with AI.

Per-event microsites. One row per event in a spreadsheet (name, date, venue, capacity) becomes one RSVP site per event, created by a script that stores each returned id next to its row. The guide to creating an event RSVP page with AI shows what to put in each brief. After the event, delete the site or keep it as an archive on a paid plan.

Internal automations. A script can create the first version of an internal tool, such as an inventory tracker or an approvals board. Don't rebuild to update data, though: ask Wisy to add an endpoint your systems can post records to, protected by a secret you set under Integrations. Records written that way reach visitors within about a minute without spending a build. More on this pattern in building internal tools with AI.

Get your key and build your first site from code

Create a free account, generate an API key with a small cap, and send your first brief with the curl call above. Keep the API reference open for field-level details, and when the integration graduates from prototype to production, pick a plan with the build allowance and parallel builds your volume needs on the pricing section.

F.A.Q.

Frequently asked questions

Can I use the API on the free plan?
Yes. Any account can create API keys, and calls draw on the same Community allowance as the chat: one build per 5-hour window, with free sites live for 30 days plus a marked week. That is enough to build and test an integration end to end. For production volumes, a paid plan raises the 5-hour and weekly windows, allows several builds at once and keeps sites live while subscribed.
How is API usage billed?
There is no separate API bill. Requests count against your plan's rolling 5-hour and weekly windows, metered by the AI work each one triggers, such as the agent's reasoning and reading your reference files. Uploading a file has no fee of its own. On top of that, each key can have its own USD limit, at most $10 per daily, weekly or monthly window, which stops that key without touching your other keys.
How do I know when a build has finished?
Poll GET /links/:id every 15 to 30 seconds until status is completed, then use the url it returns. If status turns aborted, the build stopped; an existing site keeps its last good version. If the create call returned idle, no build is running at all: read the answer field, which usually asks a question, and reply with another POST /chat for the same id.
Can I attach a custom domain through the API?
Not through the public API. Connect and verify the domain in Domains in the web app or the iOS and Android apps (if you don't have a domain yet, register one with any domain registrar first), then assign it to the site; this works on every plan, the free one included. After that, GET /links/:id returns the custom domain in its url field, so scripts that read the url keep working.
Is there an SDK?
The @wisylink/cli package includes both a command-line tool and a Node client created with CreateWisyLinkClient. It covers uploads, creating sites and listing them. In npm release 0.2.1, the methods that take a link id reject current ids, so use plain HTTP calls for continuing, reading and deleting sites until a newer release fixes that. Other languages can call the REST endpoints directly.
What errors should I handle?
At minimum: 429 limit_reached, which means waiting until reset_at; 429 credit_limit_exceeded, which means the key's own cap is spent until resets_at; and 429 build_concurrency_limit, which means queueing until a running build settles. Also back off on 429 rate_limited, retry 500 chat_unavailable once after a pause, and log 400 responses, since they usually point to an over-long message or a bad file id.
Share