> For the complete documentation index, see [llms.txt](https://docs.iproyal.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.iproyal.com/scraping-tools/web-scraper-api-beta/making-a-request.md).

# Making A Request

This guide shows how to send your first request, read the response, and handle errors.

### Before You Start

You need two things:

* Credits on your Web Scraper API balance
* Your API token, which you can find in the IPRoyal dashboard on the 'Web Scraper API' page

Store the token in an environment variable, such as `WEB_SCRAPER_API_TOKEN`. Never put it in browser code or a public repository. The examples below read it from that variable.

The Node.js examples need Node.js 18 or later and run as ES modules, so save them as `.mjs` files. The Java examples need Java 11 or later and use Jackson to read JSON. The C# examples are .NET 6 console apps.

### Step 1: Send a Request

Send a `POST` request to `https://scrape.iproyal.com/v1/scrape`. Put your token in the `Authorization` header and the target in a JSON body.

{% tabs %}
{% tab title="cURL" %}

```bash
curl -X POST https://scrape.iproyal.com/v1/scrape \
  -H "Authorization: Bearer $WEB_SCRAPER_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"source": "universal", "url": "https://example.com"}' \
  --max-time 90
```

{% endtab %}

{% tab title="PHP" %}

```php
<?php

$ch = curl_init('https://scrape.iproyal.com/v1/scrape');

curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 90,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('WEB_SCRAPER_API_TOKEN'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'source' => 'universal',
        'url' => 'https://example.com',
    ]),
]);

$body = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);

if ($status === 200) {
    $result = $body['results'][0];
    echo $result['status_code'] . ' ' . $result['job_id'] . PHP_EOL;
    $html = $result['content'];
} else {
    echo $status . ' ' . $body['error']['code'] . ': ' . $body['error']['message'] . PHP_EOL;
}
```

{% endtab %}

{% tab title="Python" %}

```python
import os

import requests

response = requests.post(
    "https://scrape.iproyal.com/v1/scrape",
    headers={"Authorization": f"Bearer {os.environ['WEB_SCRAPER_API_TOKEN']}"},
    json={"source": "universal", "url": "https://example.com"},
    timeout=90,
)

if response.ok:
    result = response.json()["results"][0]
    print(result["status_code"], result["job_id"])
    html = result["content"]
else:
    error = response.json()["error"]
    print(response.status_code, error["code"], error["message"])
```

{% endtab %}

{% tab title="Node.js" %}

```javascript
const response = await fetch('https://scrape.iproyal.com/v1/scrape', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.WEB_SCRAPER_API_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ source: 'universal', url: 'https://example.com' }),
  signal: AbortSignal.timeout(90_000),
});

const body = await response.json();

if (response.ok) {
  const [result] = body.results;
  console.log(result.status_code, result.job_id);
  const html = result.content;
} else {
  console.error(response.status, body.error.code, body.error.message);
}
```

{% endtab %}

{% tab title="Java" %}

```java
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.Map;

public class ScrapeExample {
    public static void main(String[] args) throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        String payload = mapper.writeValueAsString(Map.of(
            "source", "universal",
            "url", "https://example.com"
        ));

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://scrape.iproyal.com/v1/scrape"))
            .header("Authorization", "Bearer " + System.getenv("WEB_SCRAPER_API_TOKEN"))
            .header("Content-Type", "application/json")
            .timeout(Duration.ofSeconds(90))
            .POST(HttpRequest.BodyPublishers.ofString(payload))
            .build();

        HttpResponse<String> response = HttpClient.newHttpClient()
            .send(request, HttpResponse.BodyHandlers.ofString());
        JsonNode body = mapper.readTree(response.body());

        if (response.statusCode() == 200) {
            JsonNode result = body.get("results").get(0);
            System.out.println(result.get("status_code").asInt() + " " + result.get("job_id").asText());
            String html = result.get("content").asText();
        } else {
            JsonNode error = body.get("error");
            System.out.println(response.statusCode() + " " + error.get("code").asText() + ": " + error.get("message").asText());
        }
    }
}
```

{% endtab %}

{% tab title="Go" %}

```go
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"time"
)

type apiResponse struct {
	Results []struct {
		Content    json.RawMessage `json:"content"`
		JobID      string          `json:"job_id"`
		StatusCode int             `json:"status_code"`
	} `json:"results"`
	Error struct {
		Code    string `json:"code"`
		Message string `json:"message"`
	} `json:"error"`
}

func main() {
	payload, _ := json.Marshal(map[string]any{
		"source": "universal",
		"url":    "https://example.com",
	})

	req, _ := http.NewRequest(http.MethodPost, "https://scrape.iproyal.com/v1/scrape", bytes.NewReader(payload))
	req.Header.Set("Authorization", "Bearer "+os.Getenv("WEB_SCRAPER_API_TOKEN"))
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{Timeout: 90 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		fmt.Println("request failed:", err)
		return
	}
	defer resp.Body.Close()

	var body apiResponse
	if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
		fmt.Println("unexpected response:", err)
		return
	}

	if resp.StatusCode != http.StatusOK {
		fmt.Println(resp.StatusCode, body.Error.Code, body.Error.Message)
		return
	}

	result := body.Results[0]
	fmt.Println(result.StatusCode, result.JobID)

	var html string
	json.Unmarshal(result.Content, &html)
}
```

{% endtab %}

{% tab title="C#" %}

```csharp
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;

using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(90) };
client.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("WEB_SCRAPER_API_TOKEN"));

var response = await client.PostAsJsonAsync(
    "https://scrape.iproyal.com/v1/scrape",
    new { source = "universal", url = "https://example.com" });

using var body = JsonDocument.Parse(await response.Content.ReadAsStringAsync());

if (response.IsSuccessStatusCode)
{
    var result = body.RootElement.GetProperty("results")[0];
    Console.WriteLine($"{result.GetProperty("status_code").GetInt32()} {result.GetProperty("job_id").GetString()}");
    var html = result.GetProperty("content").GetString();
}
else
{
    var error = body.RootElement.GetProperty("error");
    Console.WriteLine($"{(int)response.StatusCode} {error.GetProperty("code").GetString()}: {error.GetProperty("message").GetString()}");
}
```

{% endtab %}
{% endtabs %}

### Step 2: Read the Response

A successful request returns HTTP `200`:

```json
{
  "results": [
    {
      "content": "<!doctype html><html lang=\"en\">...</html>",
      "created_at": "2026-10-06 09:15:02",
      "updated_at": "2026-10-06 09:15:07",
      "page": 1,
      "url": "https://example.com",
      "job_id": "dc86c742-9843-4593-a796-1d90db73f5e5",
      "status_code": 200
    }
  ]
}
```

The fields you use most:

* `content` holds the scraped content. It is HTML for a web page, JSON for Google results or parsed pages, and a Base64 string for an image.
* `status_code` is the HTTP status the target website returned. Check it before you use `content`. A `404` here means the target page does not exist.
* `job_id` identifies the request. Keep it in your logs. IPRoyal Support can trace any request by its `job_id`.

[Scraping](/scraping-tools/web-scraper-api-beta/api/scraping.md) describes every field.

### Step 3: Choose What to Scrape

Change the request body to scrape something else.

**A web page, seen from Germany**

```json
{ "source": "universal", "url": "https://example.com", "geo_location": "de" }
```

**Structured data from a web page**

```json
{ "source": "universal", "url": "https://example.com/products/42", "parse": true }
```

**Google search results**

```json
{ "source": "google", "query": "running shoes", "geo_location": "us" }
```

**An image**

```json
{ "source": "image", "url": "https://example.com/images/photo.jpg", "geo_location": "us" }
```

#### Country Targeting

`geo_location` takes a two-letter country code in the ISO 3166-1 alpha-2 format, for example `us`, `gb`, `de`, `fr`, or `jp`. The target website then sees a visitor from that country. Use `gb` for the United Kingdom, not `uk`. [Supported Countries](/scraping-tools/web-scraper-api-beta/supported-countries.md) lists every code you can use.

Without `geo_location`, IPRoyal selects the location automatically.

### Step 4: Check Your Balance

[User](/scraping-tools/web-scraper-api-beta/api/user.md) returns your credit balance:

```json
{
  "credit_amount": 48250,
  "status": "active",
  "concurrency_limit": 50
}
```

### Set a Timeout

Response time depends on the target website. Websites with strong anti-bot protection take longer. In rare cases, a request takes up to 75 seconds.

Set your HTTP client timeout to at least 90 seconds. If your client gives up earlier, the request can still finish on IPRoyal's side and use a credit.

### Limits

Your account has a concurrency limit. It sets how many scrape requests can run at the same time.

[User](/scraping-tools/web-scraper-api-beta/api/user.md) returns your limit in the `concurrency_limit` field.

When the limit is reached, a new scrape request returns `429 concurrency_limit_exceeded`. The request is not charged. Retry after one of your running requests finishes.

`503 capacity_exceeded` is different. It means the whole service is at full capacity, regardless of your own limit.

To raise your concurrency limit, contact IPRoyal Support.

### Handle Errors

Every error returns a JSON body with a stable `code`:

```json
{
  "error": {
    "code": "insufficient_credit",
    "message": "Not enough credit for this call."
  }
}
```

Follow these rules:

* Branch on `error.code`, not on `message`. Messages can change.
* Retry temporary errors with exponential backoff: `concurrency_limit_exceeded`, `internal_error`, `upstream_unavailable`, and `capacity_exceeded`.
* Wait 2, 4, then 8 seconds between attempts, plus a short random delay. Stop after a few attempts.
* Do not retry `invalid_request`, `payload_too_large`, `invalid_token`, `insufficient_credit`, `account_suspended`, or `account_disabled` without fixing the cause.
* For `blocked`, retry later or with another `geo_location`. An immediate retry rarely helps.

IPRoyal already retries some temporary failures once before it responds. Requests that end in an error are not charged.

[API](/scraping-tools/web-scraper-api-beta/api.md) lists every error code.

### Retry Temporary Errors

These examples retry temporary errors with exponential backoff.

{% tabs %}
{% tab title="cURL" %}

```bash
curl -X POST https://scrape.iproyal.com/v1/scrape \
  -H "Authorization: Bearer $WEB_SCRAPER_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"source": "universal", "url": "https://example.com"}' \
  --max-time 90 \
  --retry 3 \
  --retry-max-time 600
```

{% endtab %}

{% tab title="PHP" %}

```php
<?php

const API_URL = 'https://scrape.iproyal.com/v1/scrape';
const MAX_ATTEMPTS = 4;
const RETRYABLE = [
    'concurrency_limit_exceeded',
    'internal_error',
    'upstream_unavailable',
    'capacity_exceeded',
];

function scrape(array $payload): array
{
    for ($attempt = 1; $attempt <= MAX_ATTEMPTS; $attempt++) {
        $ch = curl_init(API_URL);

        curl_setopt_array($ch, [
            CURLOPT_POST => true,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => 90,
            CURLOPT_HTTPHEADER => [
                'Authorization: Bearer ' . getenv('WEB_SCRAPER_API_TOKEN'),
                'Content-Type: application/json',
            ],
            CURLOPT_POSTFIELDS => json_encode($payload),
        ]);

        $raw = curl_exec($ch);
        $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $body = is_string($raw) ? json_decode($raw, true) : null;

        if ($status === 200) {
            return $body['results'][0];
        }

        $code = $body['error']['code'] ?? 'internal_error';

        if (!in_array($code, RETRYABLE, true) || $attempt === MAX_ATTEMPTS) {
            throw new RuntimeException($status . ' ' . $code . ': ' . ($body['error']['message'] ?? ''));
        }

        usleep(2 ** $attempt * 1_000_000 + random_int(0, 999_999));
    }

    throw new LogicException('No attempts were made.');
}

foreach (['https://example.com/page/1', 'https://example.com/page/2'] as $url) {
    try {
        $result = scrape(['source' => 'universal', 'url' => $url]);
        echo $url . ' ' . $result['status_code'] . ' ' . $result['job_id'] . PHP_EOL;
    } catch (RuntimeException $error) {
        echo $url . ' failed: ' . $error->getMessage() . PHP_EOL;
    }
}
```

{% endtab %}

{% tab title="Python" %}

```python
import os
import random
import time

import requests

API_URL = "https://scrape.iproyal.com/v1/scrape"
MAX_ATTEMPTS = 4
RETRYABLE = {
    "concurrency_limit_exceeded",
    "internal_error",
    "upstream_unavailable",
    "capacity_exceeded",
}

def scrape(payload):
    for attempt in range(1, MAX_ATTEMPTS + 1):
        response = requests.post(
            API_URL,
            headers={"Authorization": f"Bearer {os.environ['WEB_SCRAPER_API_TOKEN']}"},
            json=payload,
            timeout=90,
        )

        if response.ok:
            return response.json()["results"][0]

        try:
            error = response.json()["error"]
        except (ValueError, KeyError):
            error = {"code": "internal_error", "message": response.text[:200]}

        if error["code"] not in RETRYABLE or attempt == MAX_ATTEMPTS:
            raise RuntimeError(f"{response.status_code} {error['code']}: {error['message']}")

        time.sleep(2 ** attempt + random.random())

for url in ["https://example.com/page/1", "https://example.com/page/2"]:
    try:
        result = scrape({"source": "universal", "url": url})
        print(url, result["status_code"], result["job_id"])
    except (RuntimeError, requests.RequestException) as error:
        print(url, "failed:", error)
```

{% endtab %}

{% tab title="Node.js" %}

```javascript
const API_URL = 'https://scrape.iproyal.com/v1/scrape';
const MAX_ATTEMPTS = 4;
const RETRYABLE = new Set([
  'concurrency_limit_exceeded',
  'internal_error',
  'upstream_unavailable',
  'capacity_exceeded',
]);

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function scrape(payload) {
  for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
    const response = await fetch(API_URL, {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.WEB_SCRAPER_API_TOKEN}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(payload),
      signal: AbortSignal.timeout(90_000),
    });

    const body = await response.json().catch(() => null);

    if (response.ok) {
      return body.results[0];
    }

    const code = body?.error?.code ?? 'internal_error';

    if (!RETRYABLE.has(code) || attempt === MAX_ATTEMPTS) {
      throw new Error(`${response.status} ${code}: ${body?.error?.message ?? ''}`);
    }

    await sleep(2 ** attempt * 1000 + Math.random() * 1000);
  }
}

for (const url of ['https://example.com/page/1', 'https://example.com/page/2']) {
  try {
    const result = await scrape({ source: 'universal', url });
    console.log(url, result.status_code, result.job_id);
  } catch (error) {
    console.error(url, 'failed:', error.message);
  }
}
```

{% endtab %}

{% tab title="Java" %}

```java
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;

public class RetryExample {
    private static final String API_URL = "https://scrape.iproyal.com/v1/scrape";
    private static final int MAX_ATTEMPTS = 4;
    private static final Set<String> RETRYABLE = Set.of(
        "concurrency_limit_exceeded",
        "internal_error",
        "upstream_unavailable",
        "capacity_exceeded"
    );
    private static final HttpClient CLIENT = HttpClient.newHttpClient();
    private static final ObjectMapper MAPPER = new ObjectMapper();

    static JsonNode scrape(Map<String, Object> payload) throws IOException, InterruptedException {
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(API_URL))
            .header("Authorization", "Bearer " + System.getenv("WEB_SCRAPER_API_TOKEN"))
            .header("Content-Type", "application/json")
            .timeout(Duration.ofSeconds(90))
            .POST(HttpRequest.BodyPublishers.ofString(MAPPER.writeValueAsString(payload)))
            .build();

        for (int attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
            HttpResponse<String> response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString());

            if (response.statusCode() == 200) {
                return MAPPER.readTree(response.body()).get("results").get(0);
            }

            JsonNode error = readError(response.body());
            String code = error.path("code").asText("internal_error");

            if (!RETRYABLE.contains(code) || attempt == MAX_ATTEMPTS) {
                throw new IOException(response.statusCode() + " " + code + ": " + error.path("message").asText());
            }

            Thread.sleep((1L << attempt) * 1000 + ThreadLocalRandom.current().nextLong(1000));
        }

        throw new IllegalStateException("No attempts were made.");
    }

    private static JsonNode readError(String body) {
        try {
            return MAPPER.readTree(body).path("error");
        } catch (IOException e) {
            return MAPPER.missingNode();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        for (String url : List.of("https://example.com/page/1", "https://example.com/page/2")) {
            try {
                JsonNode result = scrape(Map.of("source", "universal", "url", url));
                System.out.println(url + " " + result.get("status_code").asInt() + " " + result.get("job_id").asText());
            } catch (IOException e) {
                System.out.println(url + " failed: " + e.getMessage());
            }
        }
    }
}
```

{% endtab %}

{% tab title="Go" %}

```go
package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"math/rand"
	"net/http"
	"os"
	"time"
)

const (
	apiURL      = "https://scrape.iproyal.com/v1/scrape"
	maxAttempts = 4
)

var retryable = map[string]bool{
	"concurrency_limit_exceeded": true,
	"internal_error":             true,
	"upstream_unavailable":       true,
	"capacity_exceeded":          true,
}

var client = &http.Client{Timeout: 90 * time.Second}

type result struct {
	Content    json.RawMessage `json:"content"`
	JobID      string          `json:"job_id"`
	StatusCode int             `json:"status_code"`
}

type apiResponse struct {
	Results []result `json:"results"`
	Error   struct {
		Code    string `json:"code"`
		Message string `json:"message"`
	} `json:"error"`
}

func scrape(payload map[string]any) (*result, error) {
	body, err := json.Marshal(payload)
	if err != nil {
		return nil, err
	}

	for attempt := 1; attempt <= maxAttempts; attempt++ {
		req, err := http.NewRequest(http.MethodPost, apiURL, bytes.NewReader(body))
		if err != nil {
			return nil, err
		}
		req.Header.Set("Authorization", "Bearer "+os.Getenv("WEB_SCRAPER_API_TOKEN"))
		req.Header.Set("Content-Type", "application/json")

		resp, err := client.Do(req)
		if err != nil {
			return nil, err
		}

		var decoded apiResponse
		decodeErr := json.NewDecoder(resp.Body).Decode(&decoded)
		resp.Body.Close()

		if resp.StatusCode == http.StatusOK && decodeErr == nil && len(decoded.Results) > 0 {
			return &decoded.Results[0], nil
		}

		code := decoded.Error.Code
		if code == "" {
			code = "internal_error"
		}

		if !retryable[code] || attempt == maxAttempts {
			return nil, fmt.Errorf("%d %s: %s", resp.StatusCode, code, decoded.Error.Message)
		}

		time.Sleep(time.Duration(1<<attempt)*time.Second + time.Duration(rand.Intn(1000))*time.Millisecond)
	}

	return nil, errors.New("no attempts were made")
}

func main() {
	for _, url := range []string{"https://example.com/page/1", "https://example.com/page/2"} {
		result, err := scrape(map[string]any{"source": "universal", "url": url})
		if err != nil {
			fmt.Println(url, "failed:", err)
			continue
		}
		fmt.Println(url, result.StatusCode, result.JobID)
	}
}
```

{% endtab %}

{% tab title="C#" %}

```csharp
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;

const string ApiUrl = "https://scrape.iproyal.com/v1/scrape";
const int MaxAttempts = 4;
var retryable = new HashSet<string>
{
    "concurrency_limit_exceeded",
    "internal_error",
    "upstream_unavailable",
    "capacity_exceeded",
};

using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(90) };
client.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("WEB_SCRAPER_API_TOKEN"));

foreach (var url in new[] { "https://example.com/page/1", "https://example.com/page/2" })
{
    try
    {
        var result = await Scrape(new { source = "universal", url });
        Console.WriteLine($"{url} {result.GetProperty("status_code").GetInt32()} {result.GetProperty("job_id").GetString()}");
    }
    catch (Exception error) when (error is HttpRequestException or TaskCanceledException)
    {
        Console.WriteLine($"{url} failed: {error.Message}");
    }
}

async Task<JsonElement> Scrape(object payload)
{
    for (var attempt = 1; attempt <= MaxAttempts; attempt++)
    {
        using var response = await client.PostAsJsonAsync(ApiUrl, payload);
        var text = await response.Content.ReadAsStringAsync();

        if (response.IsSuccessStatusCode)
        {
            using var document = JsonDocument.Parse(text);
            return document.RootElement.GetProperty("results")[0].Clone();
        }

        var (code, message) = ReadError(text);

        if (!retryable.Contains(code) || attempt == MaxAttempts)
        {
            throw new HttpRequestException($"{(int)response.StatusCode} {code}: {message}");
        }

        await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)) + TimeSpan.FromMilliseconds(Random.Shared.Next(1000)));
    }

    throw new InvalidOperationException("No attempts were made.");
}

static (string Code, string Message) ReadError(string text)
{
    try
    {
        using var document = JsonDocument.Parse(text);
        var error = document.RootElement.GetProperty("error");
        return (error.GetProperty("code").GetString() ?? "internal_error", error.GetProperty("message").GetString() ?? "");
    }
    catch (Exception error) when (error is JsonException or KeyNotFoundException or InvalidOperationException)
    {
        return ("internal_error", "");
    }
}
```

{% endtab %}
{% endtabs %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.iproyal.com/scraping-tools/web-scraper-api-beta/making-a-request.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
