flypod
API & library

Library

The flypod npm package deploys a folder of static files to a live URL from Node or Bun. Reference for deploySite, collectDist, resolveDeployDir, and the DeployOptions / DeployResult types.

flypod is a tiny, dependency-light npm package that turns a folder of static files into a live URL from JS/TS. It's the same deploy primitive the CLI wraps, exposed as functions you can call directly.

npm install flypod   # or: bun add flypod

Everything ships from one entry point. It's ESM-only and runs on Node 18+ and Bun:

import { deploySite, collectDist, resolveDeployDir, BUILD_DIR_CANDIDATES } from "flypod";
import type { DeployResult, DeployOptions } from "flypod";

The mental model

A deploy is two steps: read files into a map, then ship the map.

const files = await collectDist("./dist"); // { "index.html": Uint8Array, ... }
const { url, manage_token } = await deploySite({ files });

deploySite creates a new site every time it's called. Save the manage_token it returns — that token is what lets you update or roll back the site later. Two things worth knowing up front:

  • Deploys are anonymous by default (public, expires in ~14 days). Pass an account token to make a site owned and permanent — see Owned deploys.
  • The library only deploys. There's no update() or rollback() function. To ship a new version to an existing site or roll one back, use the HTTP API (POST /sites/:id/deploys) or the CLI with the manage_token.

Want to run something now? The TypeScript demo is a downloadable, working example you can deploy in one command.

deploySite

deploySite(opts: DeployOptions): Promise<DeployResult>

Zips a files map, uploads it as a new site, and returns the live URL plus the tokens you need to manage it. Throws Error("deploy failed: <status>") if the server responds with a non-2xx status, so wrap it in try/catch if you want to handle failures.

DeployOptions

FieldTypeRequiredDescription
filesRecord<string, Uint8Array>YesRelative path → file bytes. Build it with collectDist, or construct it by hand.
apiKeystringNoAn account token. Pass it to make the deploy owned and permanent. See Owned deploys.
baseUrlstringNoAPI host to deploy against. Defaults to https://flypod.dev; override it to target a self-hosted server.
handle(req: Request) => Promise<Response>NoAdvanced: a custom transport used in place of global fetch (for tests, in-process calls, or a proxying fetch). Defaults to fetch. Most callers never set this.

apiKey is a bit of a misnomer — it's sent verbatim as Authorization: Bearer <value>, so the value is an account session token (from flypod login or FLYPOD_TOKEN), not a separate API key. The field name is kept for backward compatibility. See Tokens & ownership.

DeployResult

FieldTypeDescription
urlstringThe live URL, e.g. https://ab12cd34.flypod.dev. Serves immediately.
manage_tokenstringSave this. Bearer token that authorizes updates, rollback, and reads for this one site.
site_idstringStable site identifier. Used in HTTP API paths (/sites/:id/...).
slugstringThe site's URL slug (the subdomain in url).
version_idstringIdentifier of the version this deploy created — pass it to rollback later.
expires_atnumber | nullEpoch-ms expiry for anonymous deploys; null when the site is owned (never expires).
owner_account_idstring?The owning account's ID. Present only on owned (authenticated) deploys.
claim_tokenstring?Anonymous deploys only. A fallback for claiming this site from a different machine via flypod claim. The usual login flow attaches sites with the manage_token and never needs this.
next_actionsunknown[]Suggested follow-up actions returned by the server (informational).

Example

import { deploySite, collectDist } from "flypod";

const files = await collectDist("./dist");
const result = await deploySite({ files });

console.log(result.url);          // https://ab12cd34.flypod.dev — live now
console.log(result.manage_token); // save this to update / roll back later

collectDist

collectDist(path: string): Promise<Record<string, Uint8Array>>

Reads files off disk into the { path → bytes } map that deploySite expects. Pass a directory and it's walked recursively (paths are relative to the directory, with / separators); pass a single file and you get a one-entry map.

const site = await collectDist("./dist");      // whole folder
const one  = await collectDist("./index.html"); // { "index.html": Uint8Array }

You don't have to use collectDist — any Record<string, Uint8Array> works, so you can generate files in memory (see the inline demo).

resolveDeployDir

resolveDeployDir(opts?: { cwd?: string; explicit?: string }): Promise<string>

Figures out which folder to deploy so callers don't have to hardcode dist. It's async. Behavior:

  • With explicit set, that path is returned as-is.
  • Otherwise it scans BUILD_DIR_CANDIDATES in order (relative to cwd, default process.cwd()) and returns the first folder that exists and contains an index.html.
  • If nothing matches, it throws — pass the folder explicitly in that case.
import { resolveDeployDir, collectDist, deploySite } from "flypod";

const dir = await resolveDeployDir();   // auto-detect, e.g. "/proj/dist"
const files = await collectDist(dir);
await deploySite({ files });

BUILD_DIR_CANDIDATES

The ordered list resolveDeployDir searches. public ranks low because some tools use it for source assets — requiring an index.html disambiguates a built site from source.

["dist", "build", "out", ".output/public", ".vercel/output/static", "public", "_site"]

Owned deploys

By default a deploy is anonymous: public, noindex, and it expires in ~14 days. To make a site owned and permanent, pass an account session token as apiKey. The standard source is the FLYPOD_TOKEN environment variable, which flypod login and CI both use:

const result = await deploySite({
  files,
  apiKey: process.env.FLYPOD_TOKEN,
});
// result.expires_at === null, result.owner_account_id is set

See Tokens & ownership for how tokens and ownership fit together, and Accounts & claiming to attach sites you already deployed anonymously.

On this page