> ## Documentation Index
> Fetch the complete documentation index at: https://docs.spidra.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart

> Make your first scrape in under 2 minutes.

## Step 1: Create your account and get an API key

[Sign up](https://app.spidra.io/login) with your email, Google, or GitHub account.

<img className="rounded-lg" src="https://mintcdn.com/spidra/tjYTncBEy2qwqvSx/images/Spidra-QuickStart-Login.png?fit=max&auto=format&n=tjYTncBEy2qwqvSx&q=85&s=428171dbbee9bec6c7d0a6066f7723d0" alt="Sign up or log in using Google or GitHub" width="2396" height="1456" data-path="images/Spidra-QuickStart-Login.png" />

Once you're in, go to **Settings → API Keys** and create your first key.

<Note>
  Store your key in an environment variable called `SPIDRA_API_KEY`. Never commit it to source control.
</Note>

You get 300 free credits when you sign up. No credit card needed.

Now pick how you want to work. Both paths get to the same place.

<Tabs>
  <Tab title="Playground (no-code)">
    ## Step 2: Open the Playground and add your URL

    Click **Playground** in the sidebar. Paste a URL into the Target URL field, then write what you want to extract in plain English.

    <img className="rounded-lg" src="https://mintcdn.com/spidra/Ra1Ckqq20JGGS54L/images/Spidra-QuickStart-Playground.png?fit=max&auto=format&n=Ra1Ckqq20JGGS54L&q=85&s=7e6c5f81245ab7d2b075eaee0f268530" alt="Navigating to the Playground" width="2922" height="1576" data-path="images/Spidra-QuickStart-Playground.png" />

    Try this to start:

    **URL:** `https://remoteok.com/remote-react-jobs`

    **Prompt:**

    ```
    Extract all job listings on this page. For each job return the title,
    company name, link to the post, and salary if shown. Return as JSON.
    ```

    <img className="rounded-lg" src="https://mintcdn.com/spidra/Ra1Ckqq20JGGS54L/images/Spidra-QuickStart-targerurl.png?fit=max&auto=format&n=Ra1Ckqq20JGGS54L&q=85&s=74006058a93c51792483f57102fe4135" alt="Entering a target URL and extraction prompt" width="2922" height="1576" data-path="images/Spidra-QuickStart-targerurl.png" />

    ## Step 3: Set your options and run

    Choose **JSON** as your output format. Then toggle any options you need:

    * **Extract only content** removes navbars, footers, and boilerplate so the AI focuses on what matters.
    * **Stealth mode** routes the request through a residential proxy, useful on sites that block scrapers.
    * **Screenshot** captures the page at scrape time, handy for debugging.

    <img className="rounded-lg" src="https://mintcdn.com/spidra/tjYTncBEy2qwqvSx/images/Spidra-QuickStart-advanced-options.png?fit=max&auto=format&n=tjYTncBEy2qwqvSx&q=85&s=5653fa6f273b6615d4a9aeb72040c7e5" alt="Output format and options" width="1150" height="178" data-path="images/Spidra-QuickStart-advanced-options.png" />

    Hit **Start Scraping**. Spidra opens the page in a real browser, runs your prompt through the AI, and returns the extracted results on the right side of the screen.

    <img className="rounded-lg" src="https://mintcdn.com/spidra/Ra1Ckqq20JGGS54L/images/Spidra-QuickStart-scraping-start.png?fit=max&auto=format&n=Ra1Ckqq20JGGS54L&q=85&s=7ba2b0c50bfef2a96c892824b27dace3" alt="Extracted results shown in the results panel" width="2922" height="1576" data-path="images/Spidra-QuickStart-scraping-start.png" />

    ## Step 4: Save it as a preset

    Click **Save as Preset** and give it a name. A preset stores your URL, prompt, and all settings so you can rerun it with one click, put it on a schedule, or connect it to Slack, Discord, or a webhook.

    This is how recurring workflows are built in Spidra: preset, then integration, then schedule. Set it up once and the data flows automatically.

    <CardGroup cols={2}>
      <Card title="Browser actions" icon="hand-pointer" href="/features/actions">
        Click, scroll, and type before extracting. Works on any dynamic page.
      </Card>

      <Card title="Structured output" icon="brackets-curly" href="/features/structured-output">
        Guarantee exact field names and types with a JSON schema.
      </Card>

      <Card title="Integrations" icon="share-nodes" href="/features/integrations">
        Deliver data to Slack, Airtable, or webhooks automatically.
      </Card>

      <Card title="Marketplace" icon="store" href="/features/marketplace">
        Find pre-built presets for common scraping jobs.
      </Card>
    </CardGroup>

    Prefer to watch? [Three-minute video walkthrough →](https://www.youtube.com/watch?v=mjewZ4qU9Mk)
  </Tab>

  <Tab title="Developer (API + SDKs)">
    ## Step 2: Install an SDK

    Pick the language your project uses. They all wrap the same API. If you'd rather go direct with curl or HTTP, skip this step — there's nothing to install.

    <CodeGroup>
      ```bash Node.js theme={null}
      npm install spidra-js
      ```

      ```bash Python theme={null}
      pip install spidra
      ```

      ```bash PHP theme={null}
      composer require spidra/spidra-php
      ```

      ```bash Go theme={null}
      go get github.com/spidra-io/spidra-go
      ```

      ```bash Ruby theme={null}
      gem install spidra
      ```

      ```bash Elixir theme={null}
      # Add to mix.exs deps, then:
      mix deps.get
      ```

      ```bash .NET theme={null}
      dotnet add package Spidra
      ```

      ```bash Swift theme={null}
      # In Package.swift:
      .package(url: "https://github.com/spidra-io/spidra-swift.git", from: "1.0.0")
      ```

      ```bash Java theme={null}
      # Gradle:
      implementation 'io.spidra:spidra-java-sdk:0.1.0'

      # Maven:
      # <dependency><groupId>io.spidra</groupId><artifactId>spidra-java-sdk</artifactId><version>0.1.0</version></dependency>
      ```

      ```bash Rust theme={null}
      cargo add spidra
      ```
    </CodeGroup>

    ## Step 3: Make your first scrape

    Spidra jobs are async. The `run()` method handles polling for you and returns when the job completes. If you need more control, use `submit()` to queue the job and `get()` to check status yourself.

    <CodeGroup>
      ```bash curl theme={null}
      # 1. Submit the job
      curl -X POST https://api.spidra.io/api/scrape \
        -H "x-api-key: $SPIDRA_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{
          "urls": [{"url": "https://remoteok.com/remote-react-jobs"}],
          "prompt": "Extract all job listings. For each one return the title, company, and salary if shown.",
          "output": "json"
        }'
      # Response: {"jobId": "abc123", "status": "queued"}

      # 2. Poll until completed
      curl https://api.spidra.io/api/scrape/abc123 \
        -H "x-api-key: $SPIDRA_API_KEY"
      ```

      ```typescript Node.js theme={null}
      import { SpidraClient } from 'spidra-js';

      const spidra = new SpidraClient({ apiKey: process.env.SPIDRA_API_KEY });

      const job = await spidra.scrape.run({
        urls: [{ url: 'https://remoteok.com/remote-react-jobs' }],
        prompt: 'Extract all job listings. For each one return the title, company, and salary if shown.',
        output: 'json',
      });

      console.log(job.result.content);
      ```

      ```python Python theme={null}
      import asyncio
      from spidra import SpidraClient, ScrapeParams, ScrapeUrl

      async def main():
          spidra = SpidraClient(api_key="spd_YOUR_API_KEY")

          job = await spidra.scrape.run(ScrapeParams(
              urls=[ScrapeUrl(url="https://remoteok.com/remote-react-jobs")],
              prompt="Extract all job listings. For each one return the title, company, and salary if shown.",
              output="json",
          ))

          print(job.result.content)

      asyncio.run(main())
      ```

      ```php PHP theme={null}
      use Spidra\SpidraClient;

      $spidra = new SpidraClient(getenv('SPIDRA_API_KEY'));

      $job = $spidra->scrape->run([
          'urls'   => [['url' => 'https://remoteok.com/remote-react-jobs']],
          'prompt' => 'Extract all job listings. For each one return the title, company, and salary if shown.',
          'output' => 'json',
      ]);

      print_r($job['content']);
      ```

      ```go Go theme={null}
      package main

      import (
          "context"
          "fmt"
          "os"

          spidra "github.com/spidra-io/spidra-go"
      )

      func main() {
          client := spidra.New(os.Getenv("SPIDRA_API_KEY"))

          job, err := client.Scrape.Run(context.Background(), spidra.ScrapeParams{
              URLs:   []spidra.ScrapeURL{{URL: "https://remoteok.com/remote-react-jobs"}},
              Prompt: "Extract all job listings. For each one return the title, company, and salary if shown.",
              Output: "json",
          })
          if err != nil {
              panic(err)
          }

          fmt.Println(job.Result.Content)
      }
      ```

      ```ruby Ruby theme={null}
      require "spidra"

      client = Spidra.new(ENV["SPIDRA_API_KEY"])

      job = client.scrape.run(
        urls:   [{ url: "https://remoteok.com/remote-react-jobs" }],
        prompt: "Extract all job listings. For each one return the title, company, and salary if shown.",
        output: "json"
      )

      puts job["result"]["content"]
      ```

      ```elixir Elixir theme={null}
      config = Spidra.Config.new(api_key: System.get_env("SPIDRA_API_KEY"))

      {:ok, job} = Spidra.Scrape.run(config, %{
        urls: [%{url: "https://remoteok.com/remote-react-jobs"}],
        prompt: "Extract all job listings. For each one return the title, company, and salary if shown.",
        output: "json"
      })

      IO.inspect(job["result"]["content"])
      ```

      ```csharp .NET theme={null}
      using Spidra;
      using Spidra.Types.Scrape;

      var client = new SpidraClient(Environment.GetEnvironmentVariable("SPIDRA_API_KEY")!);

      var job = await client.Scrape.RunAsync(new ScrapeParams
      {
          Urls   = [new ScrapeUrl("https://remoteok.com/remote-react-jobs")],
          Prompt = "Extract all job listings. For each one return the title, company, and salary if shown.",
          Output = OutputFormat.Json
      });

      Console.WriteLine(job.Result.Content);
      ```

      ```swift Swift theme={null}
      import Spidra

      let spidra = SpidraClient(apiKey: ProcessInfo.processInfo.environment["SPIDRA_API_KEY"]!)

      let params = ScrapeParams(
          urls: [ScrapeUrl(url: "https://remoteok.com/remote-react-jobs")],
          prompt: "Extract all job listings. For each one return the title, company, and salary if shown.",
          output: "json"
      )

      let job = try await spidra.scrape.run(params)
      print(job.result?.content?.value ?? "No data")
      ```

      ```java Java theme={null}
      import io.spidra.sdk.SpidraClient;
      import io.spidra.sdk.model.scrape.ScrapeParams;
      import io.spidra.sdk.model.scrape.ScrapeJob;

      SpidraClient client = new SpidraClient(System.getenv("SPIDRA_API_KEY"));

      ScrapeParams params = ScrapeParams.builder()
          .url("https://remoteok.com/remote-react-jobs")
          .prompt("Extract all job listings. For each one return the title, company, and salary if shown.")
          .outputFormat("json")
          .build();

      ScrapeJob job = client.scrape().run(params).join();
      System.out.println(job.getResult().getContent());
      ```

      ```rust Rust theme={null}
      use spidra::{SpidraClient, types::{ScrapeParams, OutputFormat}};

      #[tokio::main]
      async fn main() -> Result<(), spidra::SpidraError> {
          let client = SpidraClient::new(std::env::var("SPIDRA_API_KEY").unwrap());

          let mut params = ScrapeParams::new("https://remoteok.com/remote-react-jobs");
          params.output_format = Some(OutputFormat::Json);
          params.prompt = Some(
              "Extract all job listings. For each one return the title, company, and salary if shown."
                  .to_string(),
          );

          let result = client.scrape().run(&params).await?;
          println!("{}", result.content.unwrap_or_default());
          Ok(())
      }
      ```
    </CodeGroup>

    The result comes back structured, ready to use:

    ```json theme={null}
    {
      "jobs": [
        {
          "title": "Senior React Engineer",
          "company": "Acme Corp",
          "salary": "$140,000 – $180,000",
          "link": "https://remoteok.com/jobs/123456"
        },
        {
          "title": "React Frontend Developer",
          "company": "Startup Inc",
          "salary": null,
          "link": "https://remoteok.com/jobs/654321"
        }
      ]
    }
    ```

    ## Step 4: Follow links with forEach

    The basic scrape reads a single page. `forEach` is what you use when you need to click into each item and pull detail from the destination page. This is where most scrapers fall short — Spidra handles it natively.

    <CodeGroup>
      ```bash curl theme={null}
      curl -X POST https://api.spidra.io/api/scrape \
        -H "x-api-key: $SPIDRA_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{
          "urls": [
            {
              "url": "https://remoteok.com/remote-react-jobs",
              "actions": [
                {
                  "type": "forEach",
                  "observe": "Find all job listing rows on the page",
                  "mode": "navigate",
                  "maxItems": 10,
                  "waitAfterClick": 1000,
                  "itemPrompt": "Extract the full job description, required skills, and salary range. Return as JSON."
                }
              ]
            }
          ],
          "prompt": "Return a clean JSON array of all jobs with their full details.",
          "output": "json"
        }'
      ```

      ```typescript Node.js theme={null}
      const job = await spidra.scrape.run({
        urls: [
          {
            url: 'https://remoteok.com/remote-react-jobs',
            actions: [
              {
                type: 'forEach',
                observe: 'Find all job listing rows on the page',
                mode: 'navigate',
                maxItems: 10,
                waitAfterClick: 1000,
                itemPrompt: 'Extract the full job description, required skills, and salary range. Return as JSON.',
              },
            ],
          },
        ],
        prompt: 'Return a clean JSON array of all jobs with their full details.',
        output: 'json',
      });
      ```

      ```python Python theme={null}
      from spidra import BrowserAction

      job = await spidra.scrape.run(ScrapeParams(
          urls=[
              ScrapeUrl(
                  url="https://remoteok.com/remote-react-jobs",
                  actions=[
                      BrowserAction(
                          type="forEach",
                          observe="Find all job listing rows on the page",
                          mode="navigate",
                          max_items=10,
                          wait_after_click=1000,
                          item_prompt="Extract the full job description, required skills, and salary range. Return as JSON.",
                      ),
                  ],
              ),
          ],
          prompt="Return a clean JSON array of all jobs with their full details.",
          output="json",
      ))
      ```

      ```php PHP theme={null}
      $job = $spidra->scrape->run([
          'urls' => [
              [
                  'url'     => 'https://remoteok.com/remote-react-jobs',
                  'actions' => [
                      [
                          'type'           => 'forEach',
                          'observe'        => 'Find all job listing rows on the page',
                          'mode'           => 'navigate',
                          'maxItems'       => 10,
                          'waitAfterClick' => 1000,
                          'itemPrompt'     => 'Extract the full job description, required skills, and salary range. Return as JSON.',
                      ],
                  ],
              ],
          ],
          'prompt' => 'Return a clean JSON array of all jobs with their full details.',
          'output' => 'json',
      ]);
      ```

      ```go Go theme={null}
      job, err := client.Scrape.Run(context.Background(), spidra.ScrapeParams{
          URLs: []spidra.ScrapeURL{
              {
                  URL: "https://remoteok.com/remote-react-jobs",
                  Actions: []spidra.BrowserAction{
                      {
                          Type:           "forEach",
                          Observe:        "Find all job listing rows on the page",
                          Mode:           "navigate",
                          MaxItems:       10,
                          WaitAfterClick: 1000,
                          ItemPrompt:     "Extract the full job description, required skills, and salary range. Return as JSON.",
                      },
                  },
              },
          },
          Prompt: "Return a clean JSON array of all jobs with their full details.",
          Output: "json",
      })
      ```

      ```ruby Ruby theme={null}
      job = client.scrape.run(
        urls: [
          {
            url:     "https://remoteok.com/remote-react-jobs",
            actions: [
              {
                type:           "forEach",
                observe:        "Find all job listing rows on the page",
                mode:           "navigate",
                maxItems:       10,
                waitAfterClick: 1000,
                itemPrompt:     "Extract the full job description, required skills, and salary range. Return as JSON."
              }
            ]
          }
        ],
        prompt: "Return a clean JSON array of all jobs with their full details.",
        output: "json"
      )
      ```

      ```elixir Elixir theme={null}
      {:ok, job} = Spidra.Scrape.run(config, %{
        urls: [
          %{
            url: "https://remoteok.com/remote-react-jobs",
            actions: [
              %{
                type: "forEach",
                observe: "Find all job listing rows on the page",
                mode: "navigate",
                max_items: 10,
                wait_after_click: 1000,
                item_prompt: "Extract the full job description, required skills, and salary range. Return as JSON."
              }
            ]
          }
        ],
        prompt: "Return a clean JSON array of all jobs with their full details.",
        output: "json"
      })
      ```

      ```csharp .NET theme={null}
      using Spidra;
      using Spidra.Types.Scrape;
      using System.Text.Json;

      // Browser actions are sent as raw JSON alongside the URL.
      // Build the request body as an anonymous object and post via RunAsync.
      var job = await client.Scrape.RunAsync(new ScrapeParams
      {
          Urls   = [new ScrapeUrl("https://remoteok.com/remote-react-jobs")],
          Prompt = "Return a clean JSON array of all jobs with their full details.",
          Output = OutputFormat.Json,
          // forEach actions are included via the raw JSON body through
          // the API — see the curl tab for the exact payload shape.
      });

      Console.WriteLine(job.Result.Content);
      ```

      ```swift Swift theme={null}
      import Spidra

      let forEachAction = BrowserAction.forEach(
          observe: "Find all job listing rows on the page",
          mode: "navigate",
          captureSelector: nil,
          maxItems: 10,
          itemPrompt: "Extract the full job description, required skills, and salary range. Return as JSON.",
          waitAfterClick: 1000,
          actions: nil,
          pagination: nil
      )

      let url = ScrapeUrl(
          url: "https://remoteok.com/remote-react-jobs",
          actions: [forEachAction]
      )

      let params = ScrapeParams(
          urls: [url],
          prompt: "Return a clean JSON array of all jobs with their full details.",
          output: "json"
      )

      let job = try await spidra.scrape.run(params)
      print(job.result?.content?.value ?? "No data")
      ```

      ```java Java theme={null}
      import io.spidra.sdk.model.scrape.BrowserAction;
      import java.util.List;

      // The Java SDK BrowserAction builder supports type, selector, value,
      // waitMs, and url. For forEach, pass the action type and supply
      // the observe/mode/itemPrompt fields via the REST API (curl tab)
      // or use a custom ObjectNode to extend the payload.
      ScrapeParams params = ScrapeParams.builder()
          .url("https://remoteok.com/remote-react-jobs")
          .browserActions(List.of(
              BrowserAction.builder()
                  .type("forEach")
                  .value("Find all job listing rows on the page")
                  .build()
          ))
          .prompt("Return a clean JSON array of all jobs with their full details.")
          .outputFormat("json")
          .build();

      ScrapeJob job = client.scrape().run(params).join();
      System.out.println(job.getResult().getContent());
      ```

      ```rust Rust theme={null}
      // The Rust SDK's typed BrowserAction enum does not yet include a
      // forEach variant. Use the curl example above to send forEach
      // actions directly via the REST API.
      use spidra::{SpidraClient, types::{ScrapeParams, OutputFormat}};

      #[tokio::main]
      async fn main() -> Result<(), spidra::SpidraError> {
          let client = SpidraClient::new(std::env::var("SPIDRA_API_KEY").unwrap());

          let mut params = ScrapeParams::new("https://remoteok.com/remote-react-jobs");
          params.output_format = Some(OutputFormat::Json);
          params.prompt = Some("Return a clean JSON array of all jobs with their full details.".to_string());

          let result = client.scrape().run(&params).await?;
          println!("{}", result.content.unwrap_or_default());
          Ok(())
      }
      ```
    </CodeGroup>

    Each matched element gets its own AI extraction pass. The results come back as a structured array. Read the full [browser actions guide](/features/actions) to see what else is possible.

    <CardGroup cols={2}>
      <Card title="Browser actions" icon="hand-pointer" href="/features/actions">
        The full forEach reference, plus click, scroll, type, and pagination.
      </Card>

      <Card title="Structured output" icon="brackets-curly" href="/features/structured-output">
        Use a JSON schema to enforce exact field names and types on every response.
      </Card>

      <Card title="Batch scraping" icon="layer-group" href="/features/batch-scraping">
        Submit up to 50 URLs in one request and process them all in parallel.
      </Card>

      <Card title="API reference" icon="code" href="/api-reference/introduction">
        Every endpoint with request and response examples if you're going direct.
      </Card>
    </CardGroup>
  </Tab>
</Tabs>
