Skip to content
flypod
API & library

HTTP API

The flypod REST API at https://flypod.dev deploys sites from a zip, redeploys new versions, rolls back, and manages accounts with bearer-token auth.

Base URL: https://flypod.dev. Authenticated requests use Authorization: Bearer <token>, where the token is a per-site manage_token or a Better Auth account session token. See Tokens & ownership.

Endpoints

MethodPathAuthBody
GET/healthzNone(none)
POST/sitesOptional account sessionzip
POST/sites/:id/deploysRequiredzip
POST/sites/:id/rollbackRequiredJSON
GET/sites/:idRequired(none)
POST/sites/:id/claimIdentity + claim_tokenJSON
POST/account/bulk-attachIdentityJSON
GET/account/sitesIdentity(none)
PATCH/account/sites/:idAccount session (dashboard)JSON

GET /healthz

Liveness check.

curl https://flypod.dev/healthz
{ "ok": true }

POST /sites

Deploy a new site. Send a zip of the site as the raw request body with content-type: application/zip. Anonymous unless you send an account session.

curl -X POST https://flypod.dev/sites \
  -H "content-type: application/zip" \
  --data-binary @site.zip

Send Authorization: Bearer <session_token> to make the deploy owned and permanent.

Returns the deploy JSON: url, site_id, version_id, expires_at, manage_token, optional claim_token, optional owner_account_id, next_actions, healed, warnings, and render.

expires_at is epoch milliseconds on an anonymous deploy and null when the site is owned. The optional keys are omitted when they don't apply, not returned as null: claim_token appears only on anonymous deploys, owner_account_id only on owned ones.

healed lists what flypod fixed on your behalf (e.g. spa-fallback). warnings lists what it could not, each with a code and a fix_recipe naming the change to make — check warnings.length before treating the URL as good, because a deploy can return 200 and still serve a broken site.

render is a headless-browser receipt — screenshot, console output, failed requests, DOM summary. It requires a render engine the server operator opts into, and flypod.dev does not run one: on this host render.status is always "pending", the arrays are always empty, and no screenshot key is returned. Treat it as reserved. It populates only on a self-hosted deployment started with RENDER=playwright.

Save the manage_token from the response. Calling the API directly, you are the only thing holding it — the server keeps just a hash, and there's no registry doing it for you the way the CLI does. It's the bearer token for redeploys, rollback, and reads on this site. It does not authorize deletion; that's account-only.

POST /sites/:id/deploys

Redeploy. Uploads a new version that becomes live. Requires a bearer token (manage_token or account session). Body is a zip.

curl -X POST https://flypod.dev/sites/<id>/deploys \
  -H "authorization: Bearer <manage_token>" \
  -H "content-type: application/zip" \
  --data-binary @site.zip

POST /sites/:id/rollback

Repoint the live site to a prior version. Requires a bearer token. JSON body with the target version_id.

curl -X POST https://flypod.dev/sites/<id>/rollback \
  -H "authorization: Bearer <manage_token>" -H "content-type: application/json" \
  -d '{"version_id":"<version_id>"}'

An unknown version returns 404 Unknown version.

GET /sites/:id

Returns the site record and its version list. Requires a bearer token.

curl https://flypod.dev/sites/<id> -H "authorization: Bearer <manage_token>"

label is the display name set from the dashboard (null when unset — clients fall back to slug). Each version carries the title of that deploy's index.html, or null when it has none.

POST /sites/:id/claim

Attach a single anonymous deploy to an account using its claim_token. Requires an authenticated identity and a valid claim_token (from the original anonymous deploy response).

curl -X POST https://flypod.dev/sites/<id>/claim \
  -H "authorization: Bearer <session_token>" -H "content-type: application/json" \
  -d '{"claim_token":"<claim_token>"}'

Returns 409 if the site already belongs to a different account: a claim_token proves you deployed the site, not that you still hold it, so it cannot re-point a site away from its current owner. 401 Invalid claim covers an unknown site or a bad token. Rate-limited per IP.

Most clients use POST /account/bulk-attach instead — see below. claim_token is the older, weaker path and is not the recommended one.

POST /account/bulk-attach

Attach multiple anonymous sites to the caller's account in one request, using each site's manage_token as proof of ownership. This is the endpoint flypod login calls to auto-claim every anonymous site the local CLI remembers.

curl -X POST https://flypod.dev/account/bulk-attach \
  -H "authorization: Bearer <session_token>" -H "content-type: application/json" \
  -d '{"sites":[{"site_id":"abc","manage_token":"fk_..."},{"site_id":"def","manage_token":"fk_..."}]}'

Body: { sites: [{ site_id, manage_token }, …] }, 1 to 100 items. Returns:

{
  "attached": ["abc"],
  "skipped": [
    { "site_id": "def", "reason": "invalid_token" }
  ]
}

reason is one of not_found, already_owned, already_yours, invalid_token, or invalid_input. Rate-limited per IP.

Account

EndpointDescription
GET /account/sitesList your sites (site_id, slug, label, url). Identity-gated.
PATCH /account/sites/:idRename a site: {"label":"Client pitch"}. An empty label clears it. Owning account session only.
DELETE /account/sites/:idDelete one site you own (blobs + metadata + versions).
POST /account/sites/bulk-deleteDelete several sites you own. Ids you don't own are skipped.
DELETE /accountDelete the account and every site it owns.

All of these require an account session. Deletion is account-only — a manage_token does not authorize it, and there is no route that accepts one for delete.

Serving behavior

Served responses carry:

cache-control: public, max-age=60, must-revalidate

The ETag is "<live_version_id>:<path>".

StatusMeaning
404Unknown host.
410Site expired.
451Operator-disabled.

On this page