Publish a static site with one request
The Fylo.Host API turns an HTML document into a live website at your-name.fylo.dev, served from Cloudflare’s edge in 330+ cities. JSON in, JSON out, a Bearer key in the header. Built for scripts, CI and AI agents.
Quickstart
Create a key in the dashboard (API tab), then:
# publish
curl -s https://fylo.host/api/v1/sites \
-H "Authorization: Bearer fylo_live_..." \
-H "Content-Type: application/json" \
-d '{"html":"<!doctype html><h1>Hello</h1>","subdomain":"hello-world"}'
# → 201
{ "id": "NEtS3W8axVbi2aig72Jbt", "subdomain": "hello-world",
"url": "https://hello-world.fylo.dev", "status": "active", ... }
The page is live at that URL before the response arrives. Anyone can open it; no account, no app.
Authentication
Every request needs an API key in the Authorization header:
Authorization: Bearer fylo_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Keys are tied to your account and act with its plan and project limits. They are shown once when created and stored hashed; revoking one takes effect immediately. Keep keys in environment variables or a secrets manager, never in a file you publish.
For AI agents
If you are using Claude Code, Codex, Cursor or any agent that reads skills, install the Fylo skill once and then just say “publish this to Fylo”:
npx skills add raviblogger/fylo-host-skill --skill fylo -g
export FYLO_API_KEY=fylo_live_...
The skill knows how to inline assets, pick a subdomain, remember the site id in .fylo.json and update the same URL on the next publish. Source: github.com/raviblogger/fylo-host-skill. Machine-readable summary of this page: /llms.txt.
Skip the copy-paste. Tell your agent to publish it.
For coding agents on your machine (Claude Code, Codex CLI, Cursor, Cline): paste this once and it installs the Fylo skill, asks you for a key and puts the page on a live URL. Chat windows (claude.ai, ChatGPT) can’t reach fylo.host from their sandboxes — connect the MCP server instead, below.
If the Fylo skill is not installed, install it first: npx skills add raviblogger/fylo-host-skill --skill fylo -g
Use my FYLO_API_KEY environment variable; if it is not set, ask me for a key from https://fylo.host/dashboard/settings/api
Publish index.html as one self-contained HTML file (inline CSS and JS) to POST https://fylo.host/api/v1/sites and save the returned id in .fylo.json so future publishes update the same site.
Use from Claude, ChatGPT and other chat apps (MCP)
Chat apps can’t run scripts against fylo.host, but they can call an MCP server. Fylo.Host is one: connect it once and say “publish this to Fylo” in the chat where you built the page. Sign-in is OAuth — no key to paste.
https://fylo.host/mcp
| Client | How to connect |
|---|---|
| Claude (claude.ai, Desktop) | Settings → Connectors → Add custom connector → name Fylo.Host, URL above → Connect, then Allow on the Fylo page. Pro, Max, Team and Enterprise plans. |
| ChatGPT | Settings → Connectors → Create (developer mode) → MCP server URL above, authentication OAuth. |
| Claude Code | claude mcp add --transport http fylo https://fylo.host/mcp then /mcp to sign in. |
| Cursor, Windsurf, others | Add an MCP server of type streamable HTTP with the URL above; they open the same sign-in page. |
Tools exposed: publish_site (html or files, optional subdomain), update_site (site id + html or files), list_sites, list_versions / restore_site, and the blog set — list_posts, enable_blog, get_post, create_post (HTML body, draft or published), update_post, unpublish_post, delete_post — so “write a post about X on my site and publish it” lands at /blog/<slug>/ with the same plan limits as the editor — and the forms set: create_form, form_status, list_leads, disable_form (Pro and Business); analytics: get_analytics; speed settings: get_performance, set_performance; custom domains: list_domains, add_domain, check_domain, remove_domain (paid plans). They call the same API as above, so plan limits and errors are identical. Connected apps are listed under Settings → API in your dashboard, where Disconnect cuts an app off immediately; tokens also expire after an hour and refresh automatically for 30 days while connected.
Publish a site from chat
Build the page in the chat as usual, then ask for it to go live. Claude sends the code straight to Fylo — nothing to copy.
| You say | What happens |
|---|---|
| “Publish this to Fylo” | publish_site with the page’s HTML → live at a generated *.fylo.dev URL in a few seconds. Claude keeps the site id for later changes. |
| “Publish it as my-portfolio” | publish_site with subdomain: my-portfolio → https://my-portfolio.fylo.dev. If the name is taken you’re asked for another. |
| “Publish the whole project” (several pages, CSS, images) | publish_site with files → a multi-file site; relative links work as they do locally. |
| “Make the heading blue and update the site” | update_site → same URL, edge cache rebuilt, previous version saved. |
| “List my sites” / “which sites do I have?” | list_sites → ids and URLs, so you can refer to a site by name in later messages. |
| “Undo that” / “put it back to yesterday’s version” | list_versions + restore_site → the chosen snapshot goes live; the current one is saved first. |
Every site published this way appears in your dashboard like any other and follows your plan’s site and storage limits.
Add a contact form from chat
Fylo Forms give a static site a working contact form — submissions are spam-checked, emailed to you and listed in the dashboard. Pro and Business plans.
| You say | What happens |
|---|---|
| “Add a contact form to my portfolio site” | create_form → Claude gets the form snippet, places it in the page (styled to match) and calls update_site. The form works immediately. |
| “Build a landing page with a signup form and publish it” | publish_site for the page, then create_form and update_site with the snippet inside — or Claude asks for the site first, since a form belongs to a site. |
| “Any new leads this week?” | list_leads → the recent submissions with name, email, message, country and time. |
| “Is the form still on?” / “pause the form” | form_status (usage vs. the monthly cap) / disable_form. |
The snippet is a plain HTML <form> posting to https://fylo.host/api/forms/<key> with a honeypot and a timestamp; any input names are captured, so Claude can add fields (phone, company, budget…) as long as the hidden fylo_* inputs stay.
Publish a blog post from chat
Once Fylo.Host is connected, a blog post is one message. Claude picks the tools; you never name them.
| You say | What happens |
|---|---|
| “Enable the blog on my portfolio site” | enable_blog → the blog exists at /blog/ with the default theme. Once per site. |
| “Write a 400-word post titled Why static sites are fast and publish it” | create_post with status: published → live at https://portfolio.fylo.dev/blog/why-static-sites-are-fast/, listed on the blog index and in RSS. |
| “Draft a post about …, I’ll review it first” | create_post as a draft → you get a private preview link; say “publish it” later. |
| “Fix the typo in the second paragraph” / “change the title to …” | get_post + update_post → same URL, blog rebuilt. |
| “List my posts” / “take the launch post offline” | list_posts / unpublish_post. |
Posts are written as HTML by the model and sanitized on the server; the free plan allows 10 published posts, paid plans more. Everything an app publishes also appears in the dashboard’s Blog tab, where you can edit it by hand.
Check analytics from chat
Refer to a site by its id, its fylo subdomain or its custom domain.
| You say | What happens |
|---|---|
| “How are my sites doing this week?” | get_analytics without a site → views per site for the account, plus visitors in the last 5 minutes. |
| “Show analytics for example.com for the last 30 days” | get_analytics with the site and range: 30d → views by day, unique visitors, bounce rate, average time on page, top pages, referrers, countries and devices. |
Ranges: today, 7d, 30d, 90d; the free plan covers today and 7d. Views are sampled estimates, counted in IST days.
Tune speed settings from chat
The same switches as the dashboard’s Performance tab. Originals are never modified; every setting is reversible.
| You say | What happens |
|---|---|
| “What speed settings are on for my-portfolio?” | get_performance → every setting and its value. |
| “Turn on WebP images and lazy-load iframes” | set_performance with images_webp and lazy_iframes → saved, the site is rebuilt, and the reply reports what is actually stored. |
| “JS delay broke my menu, turn it off” | set_performance with delay_js: false. |
Settings: fonts, font_mode, preload_fonts, minify, inline_css, delay_js, js_mode, js_exclude, selfhost_external, selfhost_exclude, images_webp, images_exclude, image_quality, responsive_images, responsive_exclude, size_images, lazy_images, lazy_images_exclude, lazy_iframes, youtube_previews, preload_lcp, preload_lcp_image. On sites with many pages, change one or two per message — each change rebuilds the site.
Connect a custom domain from chat
Starter, Pro and Business plans. The app registers the domain; the DNS records are yours to create at your DNS provider.
| You say | What happens |
|---|---|
| “Which domains do I have?” | list_domains → each domain, the site it points to, verified or not. |
| “Connect example.com to my portfolio site” | add_domain → the domain and its www form are registered, and you get two CNAME records (@ and www → fylo.host) to add. |
| “I’ve added the records, check it” | check_domain → DNS and SSL status; the domain is marked verified once active. |
| “Remove example.com” | remove_domain → Claude asks you to confirm first. The domain and its www form are detached; the site stays live on its fylo subdomain. |
A connected app can never delete a site, change billing or manage API keys — those need your dashboard login.
https://fylo.host/mcp?v=2 (same server, fresh list), then start a new chat.Endpoints
Base URL https://fylo.host/api/v1. All bodies are application/json.
/meReturns the account behind the key. Handy to verify a key.
{ "id": "dkeV...", "email": "you@example.com", "name": "You", "plan": "pro" }
/sitesLists up to 100 of the account’s sites, newest first.
{ "sites": [ { "id", "subdomain", "url", "status", "created_at" }, ... ] }
/sitesCreates a site from one HTML document.
| Field | Type | Notes |
|---|---|---|
html | string | Required. A complete HTML document, up to 10 MB. CSS and JS inline; images as https:// URLs or data URIs. |
subdomain | string | Optional. 1–40 lowercase letters, digits, hyphens. Omit to get one generated. |
files | array | Instead of html: a list of { path, content, encoding? } objects.[ { "path": "index.html", "content": "<!doctype html>..." },
{ "path": "css/app.css", "content": "..." },
{ "path": "img/logo.png", "content": "iVBOR...", "encoding": "base64" } ]Text files as strings, binaries as base64. Must include at least one .html; index.html becomes the entry page. Up to 2,000 files and 20 MB per request. |
Returns 201 with the site object plus expires_at (always null for API publishes — they belong to your account and stay up).
files instead of html. Relative links between your files work as they would on any host. For sites over 20 MB, upload a ZIP from the homepage or dashboard./sites/:idReturns one site.
/sites/:idReplaces the site’s content. Same body as create: html for a single page (all plans), or files to replace the whole site (paid plans; the free plan gets 402 plan_limit and can use html). The URL stays the same and the edge cache is rebuilt for you. Prefer this over creating a new site for every change — each new site uses a project slot on your plan.
curl -s -X PUT https://fylo.host/api/v1/sites/NEtS3W8axVbi2aig72Jbt \
-H "Authorization: Bearer fylo_live_..." \
-H "Content-Type: application/json" \
-d '{"html":"<!doctype html><h1>Hello, v2</h1>"}'
Version history
Every time a site is replaced — from the dashboard, the API or an MCP client — the previous content is saved as a whole-site version first. Free keeps 3, Starter 5, Pro 20, Business 24; older ones are pruned automatically. Versions count against your plan’s storage: when a new snapshot wouldn’t fit, the oldest versions are removed first, and a publish is never blocked by its own backup. The dashboard’s Storage figure includes them.
GET /api/v1/sites/:id/versions
{ "site_id": "…", "limit": 20, "versions": [ { "id": "v_…", "created_at": 1758000000, "source": "mcp:Claude", "files": 12, "bytes": 348112, "pages": 3 } ] }
POST /api/v1/sites/:id/restore
{ "version_id": "v_…" }
Puts that version live at the same URL and rebuilds the cache. The current content is saved as a new version first (source: "pre-restore"), so a restore is always reversible. MCP clients get the same as list_versions and restore_site — “put my site back to yesterday’s version” works in chat.
Errors
Every error is { "error": { "code", "message" } } with a matching HTTP status.
| Status | code | Meaning |
|---|---|---|
| 401 | unauthenticated | Key missing, malformed or revoked. |
| 400 | missing_html / invalid_subdomain | Body is missing html, or the slug breaks the rule. |
| 409 | subdomain_taken | Pick another slug or omit it. |
| 402 | plan_limit | Project limit for the account’s plan reached. Update an existing site, delete one, or upgrade. |
| 413 | too_large | html over 10 MB. |
| 429 | rate_limited | Over 30 requests per minute on this key. Retry after 60 s. |
| 400 | invalid_path / duplicate_path / invalid_content | A files entry has a bad path (no .., no leading slash), repeats a path, or has non-string / bad base64 content. |
| 422 | rejected | A file was blocked by content moderation (executables, pirated-media names). |
| 502 | partial_publish | The site was created with its entry page but attaching the other files failed; the response includes site. Retry with PUT /sites/:id. |
| 404 | not_found | Unknown endpoint or a site that isn’t yours. |
Limits
- API: 30 requests per minute per key.
- AI apps (MCP): 30 tool calls per minute per account, and 60 per hour for the tools that rebuild a site —
publish_site,update_site,restore_site,set_performance. - New sites: 20 per hour on paid plans, 5 on the free plan, per account — from the API and from AI apps alike. Updating an existing site does not count.
- Over a limit the API answers
429 rate_limited; an MCP tool returns an error the app can read and retry later. Limits are per account, never per IP, so another customer using the same AI app cannot use up yours. - Up to 5 active keys per account.
- Site count and storage follow your plan — see pricing. Free accounts get 1 site and 10 MB.
- The API is served only on
fylo.host; it is never exposed on hosted sites’ domains.
Managing keys
Create, list and revoke keys under Settings → API in your dashboard. The same actions are available to a logged-in browser session at GET/POST /api/keys and DELETE /api/keys/:id; these endpoints use your session cookie, not a Bearer key.
Ready to script it?
Create a key, run the quickstart, and your first page is live in under a minute.
Get an API key