# Quickstart

> From zero to your first page, your agent connected over MCP, and your first batch, in about ten minutes. cURL, Python and Node.js.

**TL;DR**: four steps, about ten minutes. Make a key, fetch one page, connect
your AI agent over MCP, and run one batch. Every block below runs as pasted
once `MEG_API_KEY` is set. Each step works on its own, so stop where you have
what you need.

<!-- steps -->
1. **Get a key (2 minutes)**

   Open `https://app.megalania.io/login` and sign in with Google or GitHub.
   There is no password. In the beta, only invited emails can sign in. Your
   beta account starts with 76,000 credits for 30 days.

   Open **API keys** and press **Create key**. A scrape key starts with `meg_sk_`. **We show it once**,
   so copy it now. Then put it in your shell:

   ```sh
   export MEG_API_KEY='meg_sk_your_key_here'
   ```

   Send the key in the `Authorization` header only. A key in a URL is
   refused with `401 bad_token`. A new key works within a second, at most a
   minute. [Get a key](/start/get-a-key) has the details.

2. **Fetch one page (2 minutes)**

   This asks for this page, from our own docs, as Markdown. Pick your language:

   ```sh tab="cURL"
   : "${MEG_API_KEY:?Set MEG_API_KEY first}"
   # export MEG_API_KEY=YOUR_KEY

   curl -X POST https://get.megalania.io/v1 \
     -H "Authorization: Bearer $MEG_API_KEY" \
     -H "Content-Type: application/json" \
     -H "Idempotency-Key: $(uuidgen 2>/dev/null || cat /proc/sys/kernel/random/uuid)" \
     -d '{"url":"https://docs.megalania.io/start/quickstart","response_types":"markdown"}' \
     --fail-with-body -D - -o page.md
   ```
   ```python tab="Python"
   # export MEG_API_KEY=YOUR_KEY

   import os
   import uuid

   import requests

   resp = requests.post(
       "https://get.megalania.io/v1",
       headers={"Authorization": f"Bearer {os.environ['MEG_API_KEY']}", "Idempotency-Key": str(uuid.uuid4())},
       json={
           "url": "https://docs.megalania.io/start/quickstart",
           "response_types": "markdown",
       },
       timeout=90,
   )
   print(resp.status_code, resp.headers.get("X-Credits-Charged"), resp.headers.get("X-Route"))
   print(resp.text[:500])
   ```
   ```js tab="Node.js"
   // export MEG_API_KEY=YOUR_KEY

   const res = await fetch("https://get.megalania.io/v1", {
     method: "POST",
     headers: {
       Authorization: `Bearer ${process.env.MEG_API_KEY}`,
       "Content-Type": "application/json",
       "Idempotency-Key": crypto.randomUUID(),
     },
     body: JSON.stringify({
       "url": "https://docs.megalania.io/start/quickstart",
       "response_types": "markdown"
     }),
   });
   console.log(res.status, res.headers.get("x-credits-charged"), res.headers.get("x-route"));
   console.log((await res.text()).slice(0, 500));
   ```
   ```go tab="Go"
   // export MEG_API_KEY=YOUR_KEY

   package main

   import (
   	"crypto/rand"
   	"fmt"
   	"io"
   	"log"
   	"net/http"
   	"os"
   	"strings"
   )

   func main() {
   	body := strings.NewReader(`{"url":"https://docs.megalania.io/start/quickstart","response_types":"markdown"}`)
   	req, err := http.NewRequest("POST", "https://get.megalania.io/v1", body)
   	if err != nil {
   		log.Fatal(err)
   	}
   	req.Header.Set("Authorization", "Bearer "+os.Getenv("MEG_API_KEY"))
   	req.Header.Set("Content-Type", "application/json")
   	req.Header.Set("Idempotency-Key", rand.Text())

   	resp, err := http.DefaultClient.Do(req)
   	if err != nil {
   		log.Fatal(err)
   	}
   	defer resp.Body.Close()
   	page, _ := io.ReadAll(resp.Body)
   	fmt.Println(resp.StatusCode, resp.Header.Get("X-Credits-Charged"), len(page))
   }
   ```

   [Open in Playground](https://app.megalania.io/#url=https%3A%2F%2Fdocs.megalania.io%2Fstart%2Fquickstart&response_types=markdown)

   The cURL example prints our headers and saves the page in `page.md`:

   ```text label="Response headers"
   HTTP/2 200
   content-type: text/markdown; charset=utf-8
   x-credits-charged: 1
   x-route: http/datacenter
   x-hops: [{"rung":"h:dc","identity":"chrome","ms":412}]
   x-request-id: 01J8Z3K6W2M4Q9T7B5N1C0XHDE
   ```

   | Header | What it tells you |
   | -- | -- |
   | `X-Credits-Charged` | What this request cost. Here, 1 credit. |
   | `X-Route` | The engine and proxy tier that delivered the page. |
   | `X-Hops` | Each try, in order, as a JSON array: `[{"rung":"h:dc","identity":"chrome","ms":412,"outcome":"ok"}]` is one HTTP try on datacenter, as Chrome, 412 ms. Tries that ended in a block we detected cost 0. |
   | `X-Billing` | Only on a 422 `blocked`: `blocked`. You paid nothing. |
   | `X-Request-Id` | The id of this request. Quote it when you write to support. |

   A `200` is a page you paid for. When the site blocks every route we try,
   the answer is HTTP `422` with `error.code` `blocked` and
   `X-Billing: blocked`, and it costs nothing. Any other error is also JSON
   with `error.code` and `error.message`.
   [Troubleshooting](/start/troubleshooting) has the fix for each one.

3. **Connect your AI agent (3 minutes)**

   The MCP server is `https://mcp.megalania.io`. Pick your client:

   ```sh tab="Claude Code"
   claude mcp add --transport http megalania https://mcp.megalania.io
   ```

   ```text tab="Claude.ai"
   Settings > Connectors > Add custom connector
   Name: Megalania
   URL:  https://mcp.megalania.io
   Leave the client ID and client secret empty.
   ```

   ```text tab="ChatGPT"
   Add a custom connector (MCP server) in ChatGPT's settings.
   URL:  https://mcp.megalania.io
   Authentication: OAuth. Leave the client ID and client secret empty.
   ```

   ```json tab="Cursor"
   {
     "mcpServers": {
       "megalania": {
         "url": "https://mcp.megalania.io",
         "headers": { "Authorization": "Bearer meg_sk_your_key_here" }
       }
     }
   }
   ```

   Claude Code, Claude.ai and ChatGPT open a Megalania tab the first time a
   tool runs: sign in, check the account, and press **Approve**. You copy no
   key. In Claude Code, type `/mcp` and pick `megalania` to start that sign-in.
   Cursor reads a key from `~/.cursor/mcp.json`, as above.

   Then ask your agent:

   ```text label="Prompt"
   Use megalania to fetch https://lab.megalania.io/cards and list the mug names and prices.
   ```

   The agent calls `scrape` and answers from the page. A blocked page or an
   error costs nothing. [MCP for agents](/guides/mcp) lists every tool and the VS
   Code setup.

4. **Run one batch (3 minutes)**

   A batch takes up to 10,000 URLs in one call and runs them without a
   connection held open. It lives on the API host, `api.megalania.io`, not
   on `get.`. This one fetches three pages of our [Lab](https://lab.megalania.io/) as Markdown, waits for them,
   and saves each result:

   ```sh tab="cURL"
   API=https://api.megalania.io
   BATCH_ID=$(curl -sS -X POST "$API/v1/batches" \
     -H "Authorization: Bearer $MEG_API_KEY" \
     -H "Content-Type: application/json" \
     -H "Idempotency-Key: $(uuidgen 2>/dev/null || cat /proc/sys/kernel/random/uuid)" \
     -d '{"urls": ["https://lab.megalania.io/paged?page=1", "https://lab.megalania.io/paged?page=2", "https://lab.megalania.io/paged?page=3"], "response_types": "markdown"}' | jq -r .id)
   echo "batch $BATCH_ID"
   until curl -sS -H "Authorization: Bearer $MEG_API_KEY" "$API/v1/batches/$BATCH_ID" | jq -e '.status | IN("queued", "running") | not' >/dev/null; do sleep 3; done
   curl -sS -H "Authorization: Bearer $MEG_API_KEY" "$API/v1/batches/$BATCH_ID/items" |
     jq -r '.items[] | [.position, .status, .credits_charged, (.result_url // "")] | @tsv' |
     while IFS=$'\t' read -r pos status credits url; do
       echo "item $pos: $status, $credits credits"
       [ -n "$url" ] && curl -sS -o "item-$pos.md" "$url"
     done
   ```

   ```python tab="Python"
   # pip install requests
   import os
   import time
   import uuid

   import requests

   API = "https://api.megalania.io"
   auth = {"Authorization": f"Bearer {os.environ['MEG_API_KEY']}"}

   batch = requests.post(
       f"{API}/v1/batches",
       headers={**auth, "Idempotency-Key": str(uuid.uuid4())},
       json={
           "urls": [
               "https://lab.megalania.io/paged?page=1",
               "https://lab.megalania.io/paged?page=2",
               "https://lab.megalania.io/paged?page=3",
           ],
           "response_types": "markdown",
       },
       timeout=30,
   )
   batch.raise_for_status()
   batch_id = batch.json()["id"]
   print("batch", batch_id)

   while requests.get(f"{API}/v1/batches/{batch_id}", headers=auth, timeout=30).json()["status"] in ("queued", "running"):
       time.sleep(3)

   items = requests.get(f"{API}/v1/batches/{batch_id}/items", headers=auth, timeout=30).json()["items"]
   for item in items:
       print(f"item {item['position']}: {item['status']}, {item['credits_charged']} credits")
       if item["result_url"]:
           with open(f"item-{item['position']}.md", "w", encoding="utf-8") as f:
               f.write(requests.get(item["result_url"], timeout=30).text)
   ```

   ```js tab="Node.js"
   import { writeFile } from "node:fs/promises";

   const API = "https://api.megalania.io";
   const auth = { Authorization: `Bearer ${process.env.MEG_API_KEY}` };
   const get = async (path) => (await fetch(API + path, { headers: auth })).json();

   const res = await fetch(`${API}/v1/batches`, {
     method: "POST",
     headers: { ...auth, "Content-Type": "application/json", "Idempotency-Key": crypto.randomUUID() },
     body: JSON.stringify({
       urls: ["https://lab.megalania.io/paged?page=1", "https://lab.megalania.io/paged?page=2", "https://lab.megalania.io/paged?page=3"],
       response_types: "markdown",
     }),
   });
   if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
   const { id } = await res.json();
   console.log("batch", id);

   while (["queued", "running"].includes((await get(`/v1/batches/${id}`)).status)) {
     await new Promise((r) => setTimeout(r, 3000));
   }

   for (const item of (await get(`/v1/batches/${id}/items`)).items) {
     console.log(`item ${item.position}: ${item.status}, ${item.credits_charged} credits`);
     if (item.result_url) await writeFile(`item-${item.position}.md`, await (await fetch(item.result_url)).text());
   }
   ```

   Nothing is charged when you submit. Each URL is priced and charged when a
   worker reaches it. `result_url` is a signed link that works for 15
   minutes and takes no key. The cURL tab needs `jq`; save the Node.js tab as
   `batch.mjs` and run `node batch.mjs` (Node 20 or newer).
   [Batches](/guides/batches) covers crawls, webhooks and paging.

5. **What it cost**

   <!-- cost -->
   **4** **credits, if every page took the cheapest route.** One for step 2
   and one for each batch page. The MCP call in step 3 costs the same as a
   fetch. Blocks we detect and errors are free.

   Bigger pages, a residential exit or a real browser cost more.
   [Credits and pricing](/guides/credits) has the table.

## Next steps

- [Fetch, extract and lists](/guides/fetch): fields as JSON with CSS rules in `extract`.
- [Troubleshooting](/start/troubleshooting): a symptom, its cause and the fix.
- [Monitors](/guides/monitors): watch a page on a schedule.
- [Error codes](/reference/errors): every code, its price and its fix.

## When the beta credits run out

The beta is free. When your credits or the beta limit run out, the answer is
`402 insufficient_credits` or `429 period_cost_cap`. Write to
support@megalania.io from your account's email, and we add more by hand.

## Report a problem

Open `https://app.megalania.io/report`, or **Help**, then **Report a
problem** in the dashboard. Send the `X-Request-Id` of the request, and what
you expected and what you got. Do not send your key.
We never need it.
