> ## 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

> Call the Geekflare API from a Cloudflare Worker using the edge-native fetch API and Wrangler secrets.

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

* [Wrangler CLI](https://developers.cloudflare.com/workers/wrangler/) installed (`pnpm add -g wrangler`)
* A Cloudflare account
* A Geekflare API key — [get one free](https://dash.geekflare.com)

## Create a Worker

```bash theme={null}
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:

```bash theme={null}
# Local development
echo "GEEKFLARE_API_KEY=your_api_key_here" >> .dev.vars

# Production
wrangler secret put GEEKFLARE_API_KEY
```

<Warning>
  `.dev.vars` is the Workers equivalent of `.env`. It is gitignored by default.
  Do **not** commit it.
</Warning>

Declare the binding in `wrangler.toml` so TypeScript knows it exists:

```toml wrangler.toml theme={null}
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:

```ts src/index.ts theme={null}
export interface Env {
  GEEKFLARE_API_KEY: string;
}
```

***

## Shared helper

A small typed helper keeps each handler clean:

```ts src/geekflare.ts theme={null}
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

```ts src/index.ts theme={null}
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:**

```bash theme={null}
wrangler dev
curl -X POST http://localhost:8787 \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://toscrape.com/" }'
```

***

## Search

Route requests to different Geekflare endpoints based on the URL path:

```ts src/index.ts theme={null}
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:**

```bash theme={null}
curl -X POST http://localhost:8787/search \
  -H "Content-Type: application/json" \
  -d '{ "query": "Cloudflare Workers best practices", "limit": 5 }'
```

***

## Screenshot

```ts src/index.ts theme={null}
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:

```ts src/index.ts theme={null}
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

```bash theme={null}
wrangler deploy
```

***

## Error handling

| Code  | Meaning                             |
| ----- | ----------------------------------- |
| `401` | Missing or invalid `x-api-key`      |
| `402` | API credits exhausted               |
| `403` | Endpoint not available on your plan |
| `429` | Rate 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:

```ts theme={null}
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

* [PHP quickstart](/api/quickstart/php) — cURL and Guzzle
* [API Reference](/api/endpoint/reference) — Full endpoint documentation
* [SDK on npm](https://www.npmjs.com/package/@geekflare/api-node)
