Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.geekflare.com/llms.txt

Use this file to discover all available pages before exploring further.

Cloudflare Workers run on the V8 edge runtime — no Node.js, no npm packages needed. All three examples use the native fetch API and store the API key as a Wrangler secret so it never touches your codebase.

Prerequisites

Create a Worker

pnpm create cloudflare@latest geekflare-worker
cd geekflare-worker
Choose “Hello World” Worker when prompted. Select TypeScript.

Set your API key

Store the key as a secret — never hardcode it:
# Local development
echo "GEEKFLARE_API_KEY=your_api_key_here" >> .dev.vars

# Production
wrangler secret put GEEKFLARE_API_KEY
.dev.vars is the Workers equivalent of .env. It is gitignored by default. Do not commit it.
Declare the binding in wrangler.toml so TypeScript knows it exists:
wrangler.toml
name = "geekflare-worker"
main = "src/index.ts"
compatibility_date = "2024-01-01"

[vars]
# Non-secret vars go here. Secrets are set via `wrangler secret put`.
Add the type to your Worker’s Env interface:
src/index.ts
export interface Env {
  GEEKFLARE_API_KEY: string;
}

Shared helper

A small typed helper keeps each handler clean:
src/geekflare.ts
export const GEEKFLARE_BASE = "https://api.geekflare.com";

export async function geekflarePost<T>(
  env: { GEEKFLARE_API_KEY: string },
  path: string,
  body: Record<string, unknown>,
): Promise<T> {
  const res = await fetch(`${GEEKFLARE_BASE}${path}`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "x-api-key": env.GEEKFLARE_API_KEY,
    },
    body: JSON.stringify(body),
  });

  if (!res.ok) {
    const error = await res.json<{ apiCode: number; message: string }>();
    throw new Error(`[${error.apiCode}] ${error.message}`);
  }

  return res.json<T>();
}

Web Scraping

src/index.ts
import { geekflarePost } from "./geekflare";

export interface Env {
  GEEKFLARE_API_KEY: string;
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    if (request.method !== "POST") {
      return new Response("Method Not Allowed", { status: 405 });
    }

    const { url } = await request.json<{ url: string }>();

    if (!url) {
      return Response.json({ error: "url is required" }, { status: 400 });
    }

    try {
      const result = await geekflarePost(env, "/webscraping", {
        url,
        renderJS: true,
        blockAds: true,
        format: "html,markdown",
      });

      return Response.json(result);
    } catch (error: any) {
      return Response.json({ error: error.message }, { status: 500 });
    }
  },
};
Test locally:
wrangler dev
curl -X POST http://localhost:8787 \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://toscrape.com/" }'

Route requests to different Geekflare endpoints based on the URL path:
src/index.ts
import { geekflarePost } from "./geekflare";

export interface Env {
  GEEKFLARE_API_KEY: string;
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const { pathname } = new URL(request.url);

    if (request.method !== "POST") {
      return new Response("Method Not Allowed", { status: 405 });
    }

    if (pathname === "/search") {
      const {
        query,
        limit = 10,
        location = "us",
      } = await request.json<{
        query: string;
        limit?: number;
        location?: string;
      }>();

      if (!query) {
        return Response.json({ error: "query is required" }, { status: 400 });
      }

      try {
        const result = await geekflarePost(env, "/search", {
          query,
          limit,
          location,
          source: "web",
          format: "json",
        });

        return Response.json(result);
      } catch (error: any) {
        return Response.json({ error: error.message }, { status: 500 });
      }
    }

    return new Response("Not Found", { status: 404 });
  },
};
Test locally:
curl -X POST http://localhost:8787/search \
  -H "Content-Type: application/json" \
  -d '{ "query": "Cloudflare Workers best practices", "limit": 5 }'

Screenshot

src/index.ts
import { geekflarePost } from "./geekflare";

export interface Env {
  GEEKFLARE_API_KEY: string;
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const { pathname } = new URL(request.url);

    if (request.method !== "POST") {
      return new Response("Method Not Allowed", { status: 405 });
    }

    if (pathname === "/screenshot") {
      const { url } = await request.json<{ url: string }>();

      if (!url) {
        return Response.json({ error: "url is required" }, { status: 400 });
      }

      try {
        const result = await geekflarePost(env, "/screenshot", {
          url,
          type: "png",
          fullPage: true,
          device: "desktop",
          viewportWidth: 1280,
          viewportHeight: 800,
          blockAds: true,
          hideCookie: true,
          quality: 90,
        });

        return Response.json(result);
      } catch (error: any) {
        return Response.json({ error: error.message }, { status: 500 });
      }
    }

    return new Response("Not Found", { status: 404 });
  },
};

Complete multi-route Worker

A single Worker handling all three endpoints:
src/index.ts
import { geekflarePost } from "./geekflare";

export interface Env {
  GEEKFLARE_API_KEY: string;
}

type RouteHandler = (request: Request, env: Env) => Promise<Response>;

const routes: Record<string, RouteHandler> = {
  "/scrape": async (req, env) => {
    const {
      url,
      renderJS = true,
      blockAds = true,
    } = await req.json<{
      url: string;
      renderJS?: boolean;
      blockAds?: boolean;
    }>();
    if (!url)
      return Response.json({ error: "url is required" }, { status: 400 });
    const result = await geekflarePost(env, "/webscraping", {
      url,
      renderJS,
      blockAds,
      format: "html,markdown",
    });
    return Response.json(result);
  },

  "/search": async (req, env) => {
    const {
      query,
      limit = 10,
      location = "us",
    } = await req.json<{ query: string; limit?: number; location?: string }>();
    if (!query)
      return Response.json({ error: "query is required" }, { status: 400 });
    const result = await geekflarePost(env, "/search", {
      query,
      limit,
      location,
      source: "web",
      format: "json",
    });
    return Response.json(result);
  },

  "/screenshot": async (req, env) => {
    const { url } = await req.json<{ url: string }>();
    if (!url)
      return Response.json({ error: "url is required" }, { status: 400 });
    const result = await geekflarePost(env, "/screenshot", {
      url,
      type: "png",
      fullPage: true,
      device: "desktop",
      viewportWidth: 1280,
      viewportHeight: 800,
      blockAds: true,
      hideCookie: true,
      quality: 90,
    });
    return Response.json(result);
  },
};

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    if (request.method !== "POST") {
      return new Response("Method Not Allowed", { status: 405 });
    }

    const { pathname } = new URL(request.url);
    const handler = routes[pathname];

    if (!handler) {
      return new Response("Not Found", { status: 404 });
    }

    try {
      return await handler(request, env);
    } catch (error: any) {
      return Response.json({ error: error.message }, { status: 500 });
    }
  },
};

Deploy

wrangler deploy

Error handling

CodeMeaning
401Missing or invalid x-api-key
402API credits exhausted
403Endpoint not available on your plan
429Rate limit exceeded
The geekflarePost helper above throws on non-2xx responses. Wrap every handler in try/catch and map error codes to appropriate HTTP responses:
try {
  const result = await geekflarePost(env, "/webscraping", { url });
  return Response.json(result);
} catch (error: any) {
  const message: string = error.message ?? "Unknown error";

  if (message.startsWith("[429]")) {
    return Response.json({ error: "Rate limit exceeded" }, { status: 429 });
  }
  if (message.startsWith("[402]")) {
    return Response.json({ error: "Credits exhausted" }, { status: 402 });
  }

  return Response.json({ error: message }, { status: 500 });
}

Next steps