FlatpageDocs

JavaScript and TypeScript

Publish from Node.js, Deno, Bun or a serverless function with fetch.

No SDK needed: the API is plain fetch. This works in Node.js 18+, Deno, Bun and serverless runtimes. Keep the key on the server; never ship it to a browser.

flatpage.ts
const API = "https://flatpage.app/v1";
const headers = { Authorization: `Bearer ${process.env.FLATPAGE_API_KEY}` };

type Site = {
  id: string;
  slug: string;
  url: string;
  status: string;
  version: number | null;
};
type PublishResult = {
  uploadId: string;
  status: string;
  site?: Site;
  warnings?: string[];
};

async function call<T>(path: string, init: RequestInit = {}): Promise<T> {
  const res = await fetch(`${API}${path}`, {
    ...init,
    headers: { ...headers, ...init.headers },
  });
  if (!res.ok) {
    const { error } = await res.json().catch(() => ({ error: res.statusText }));
    throw new Error(`Flatpage ${res.status}: ${error}`);
  }
  return res.status === 204 ? (undefined as T) : res.json();
}

/** Waits for a publish that answered 202 (still processing). */
async function untilLive(result: PublishResult): Promise<Site> {
  for (let i = 0; !result.site && i < 30; i++) {
    await new Promise((r) => setTimeout(r, 2000));
    result = await call<PublishResult>(`/uploads/${result.uploadId}`);
  }
  if (!result.site) throw new Error("Still processing after a minute");
  return result.site;
}

/** Publishes HTML as a new site, or as a new version of `site` when given. */
export async function publishHtml(
  html: string,
  opts: { site?: string; linkName?: string } = {},
) {
  const result = await call<PublishResult>(
    opts.site ? `/sites/${opts.site}` : "/sites",
    {
      method: opts.site ? "PUT" : "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        html,
        linkName: opts.site ? undefined : opts.linkName,
      }),
    },
  );
  return untilLive(result);
}

/** Publishes a .zip or .pdf from disk (up to 25 MB). */
export async function publishFile(
  path: string,
  contentType: "application/zip" | "application/pdf",
  site?: string,
) {
  const { readFile } = await import("node:fs/promises");
  const result = await call<PublishResult>(site ? `/sites/${site}` : "/sites", {
    method: site ? "PUT" : "POST",
    headers: { "Content-Type": contentType },
    body: await readFile(path),
  });
  return untilLive(result);
}

Use it:

const site = await publishHtml(
  "<!doctype html><title>Report</title><h1>Q3 report</h1>",
  {
    linkName: "q3-report",
  },
);
console.log(site.url); // https://q3-report.flatpage.page

// Later: same link, new content
await publishHtml(nextVersionHtml, { site: "q3-report" });

Want types for every response? Generate them from the OpenAPI spec, for example with npx openapi-typescript https://flatpage.app/openapi.json -o flatpage.d.ts.