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

# Jobs

> Estimate, create, list, inspect, cancel, and delete BrezelScraper jobs.

## Job statuses

| Status      | Meaning                                         |
| ----------- | ----------------------------------------------- |
| `pending`   | The job has been accepted and is waiting to run |
| `running`   | The scraper is actively collecting data         |
| `completed` | The job finished successfully                   |
| `failed`    | The job did not complete successfully           |
| `cancelled` | The job was cancelled                           |
| `aborting`  | Cancellation is in progress                     |

## `POST /api/v1/jobs`

Create a new scraping job.

### Request body

| Field            | Type      | Required | Default | Notes                                                                                                             |
| ---------------- | --------- | -------- | ------- | ----------------------------------------------------------------------------------------------------------------- |
| `name`           | string    | Yes      | none    | 1 to 200 characters                                                                                               |
| `keywords`       | string\[] | Yes      | none    | 1 to 5 keywords, each 1 to 200 characters                                                                         |
| `language`       | string    | Yes      | none    | 2-character language code                                                                                         |
| `depth`          | integer   | No       | `5`     | Min `1`, max `20`                                                                                                 |
| `include_emails` | boolean   | No       | `false` | Enables email scraping from business websites                                                                     |
| `max_images`     | integer   | No       | `0`     | Image cap **per place**, max `500`                                                                                |
| `max_reviews`    | integer   | No       | `0`     | Review cap **per place**, max `500`                                                                               |
| `max_results`    | integer   | No       | `50`    | Place cap for the whole job, max `500`. With a `website_filter`, counts only **matching** places                  |
| `website_filter` | string    | No       | `all`   | Scrape-time web-presence pre-filter: `all`, `no_website`, or `has_website`. See [Website filter](#website-filter) |
| `lat`            | string    | No       | empty   | Latitude for geo-targeted runs                                                                                    |
| `lon`            | string    | No       | empty   | Longitude for geo-targeted runs                                                                                   |
| `zoom`           | integer   | No       | `0`     | Map zoom, max `21`                                                                                                |
| `radius`         | integer   | No       | `0`     | Meters, max `50000`                                                                                               |
| `max_time`       | integer   | No       | `1800`  | Seconds, max `3600`                                                                                               |
| `fast_mode`      | boolean   | No       | `false` | Faster, lighter data collection                                                                                   |
| `proxies`        | string\[] | No       | empty   | Up to 100 custom proxy URLs                                                                                       |

There is no unlimited sentinel for cap fields. If you want the maximum allowed value, send the maximum explicitly.

### Website filter

`website_filter` restricts a scrape by each business's **web presence** — letting you target the "no real website" segment, the high-value lead list for web and marketing agencies.

| Value             | Keeps                                                                                                                                                                                            |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `all` *(default)* | Every business. No filtering, and no filter fee.                                                                                                                                                 |
| `no_website`      | Only businesses **without a real owned website**: no website at all, or only a social / link-in-bio page, a directory listing, a free website-builder page, or a booking/ordering-platform page. |
| `has_website`     | Only businesses **with a real owned website**.                                                                                                                                                   |

How it behaves:

* **Filtered at scrape time.** Non-matching businesses are dropped before they are stored, so they never appear in your results and you are never billed for them.
* **`max_results` counts only matches.** A job for 100 `no_website` leads keeps scraping until it finds 100 matches or the search area is exhausted — sparse areas may return fewer than the cap.
* **Pricing.** When `website_filter` is `no_website` or `has_website`, a `filters_applied` fee of `0.001` credits is charged per matching place, on top of the normal per-place cost. `all` is free. See [Credits and billing](/docs/api-reference/credits-and-billing).
* **Re-segment for free at read time.** The same vocabulary is available on the results endpoint via the [`web_presence`](/docs/api-reference/results-and-exports) query parameter, which additionally exposes six fine-grained tiers.

<Note>
  `website_filter` is a coarse, one-shot scrape-time choice. To slice already-scraped results by finer categories (social-only vs. directory-only, etc.), use the `web_presence` filter on the results endpoint — no re-scrape and no extra cost.
</Note>

### Supported `language` values

`ar`, `bg`, `cs`, `da`, `de`, `el`, `en`, `es`, `et`, `fi`, `fr`, `he`, `hr`, `hu`, `id`, `it`, `ja`, `ko`, `lt`, `lv`, `ms`, `nl`, `no`, `pl`, `pt`, `ro`, `ru`, `sk`, `sl`, `sv`, `th`, `tr`, `uk`, `vi`, `zh`

### Example request

```bash theme={null}
curl -X POST https://api.brezelscraper.com/api/v1/jobs \
  -H "Authorization: Bearer bscraper_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Berlin cafes without a website",
    "keywords": ["cafes in berlin"],
    "language": "en",
    "depth": 5,
    "max_results": 100,
    "max_reviews": 0,
    "max_images": 0,
    "max_time": 1800,
    "website_filter": "no_website"
  }'
```

### Success response

```json theme={null}
{
  "id": "d8d8a24e-cef2-4b02-8396-abc290b6f299"
}
```

### Common failure modes

* `400 Bad Request` for validation failures
* `402 Payment Required` if your credit balance is too low
* `409 Conflict` if you reuse an idempotency key incorrectly
* `422 Unprocessable Entity` for invalid JSON
* `429 Too Many Requests` if you hit the concurrent job limit, with `Retry-After: 60`

## `POST /api/v1/jobs/estimates`

Check how much a scraping job will cost before you create it.

Use this endpoint to show users a price preview in your UI, or to decide programmatically
whether to proceed with a job based on cost and available credits.

### Why use this endpoint

* You get a cost breakdown before spending any credits.
* The response includes your current credit balance so you know if you can afford the job.
* The estimate shows a range (min to max) because Google Maps results vary by location and search term.

### Request body

| Parameter        | Type      | Required | Default          | Description                                                                                                                                                                                                                                                               |
| ---------------- | --------- | -------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `keywords`       | string\[] | Yes      |                  | The search terms for your job. 1 to 5 keywords, each up to 200 characters. More keywords means more places found and higher cost.                                                                                                                                         |
| `depth`          | integer   | No       | `5`              | How far to scroll in Google Maps search results. Higher depth finds more places per keyword. Range: 1 to 20.                                                                                                                                                              |
| `include_emails` | boolean   | No       | `false`          | Whether to extract emails from business websites. Adds a per-place cost.                                                                                                                                                                                                  |
| `max_images`     | integer   | No       | `0`              | Number of images to collect **per place**. 0 means skip images. Range: 0 to 500.                                                                                                                                                                                          |
| `max_reviews`    | integer   | No       | *(uses average)* | Maximum reviews to collect per place. 0 means skip reviews. If omitted, the estimate uses a realistic average (\~50 per place). Range: 0 to 500.                                                                                                                          |
| `max_results`    | integer   | No       | *(uses depth)*   | Hard cap on total places returned. If omitted, the estimate is based on depth and keyword count. Range: 1 to 500.                                                                                                                                                         |
| `website_filter` | string    | No       | `all`            | Scrape-time web-presence pre-filter: `all`, `no_website`, or `has_website`. When set to `no_website` or `has_website`, the estimate adds a `filters_applied` line (`breakdown.filter_cost`) of `0.001` credits per matching place. See [Website filter](#website-filter). |

Language is not needed here because it does not affect cost.

### Example request

```json theme={null}
{
  "keywords": ["Cafe Mitte Berlin"],
  "depth": 5,
  "include_emails": false,
  "max_reviews": 0,
  "max_images": 0
}
```

### Example response

```json theme={null}
{
  "estimate": {
    "object": "job_estimate",
    "currency": "credits",
    "places": 40,
    "places_min": 20,
    "places_max": 48,
    "keyword_count": 1,
    "places_per_keyword": 40,
    "total": 0.127,
    "total_min": 0.067,
    "total_max": 0.151,
    "breakdown": {
      "job_start_cost": 0.007,
      "places_cost": 0.12,
      "contact_details_cost": 0,
      "reviews_cost": 0,
      "images_cost": 0,
      "filter_cost": 0
    },
    "reviews": 0,
    "images": 0,
    "includes_emails": false,
    "unit_prices": {
      "job_start": 0.007,
      "place_scraped": 0.003,
      "contact_details": 0.002,
      "review": 0.0005,
      "image": 0.0005,
      "filters_applied": 0.001
    },
    "max_results_provided": false,
    "description": "Estimated ~40 places for 1 keyword at depth 5 (range: 20 to 48).",
    "expires_at": 1713110460
  },
  "balance": {
    "current": 24.99,
    "sufficient": true
  }
}
```

### Response fields

**estimate** object containing the cost calculation:

| Field                  | Type    | Description                                                                                                                                                                                                                                              |
| ---------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `object`               | string  | Always `"job_estimate"`.                                                                                                                                                                                                                                 |
| `currency`             | string  | Always `"credits"`. One credit equals one US dollar.                                                                                                                                                                                                     |
| `places`               | integer | How many places the job will likely scrape. This is the primary estimate used for pricing.                                                                                                                                                               |
| `places_min`           | integer | Conservative lower bound on places. Sparse markets (few results for your search term) may produce this many.                                                                                                                                             |
| `places_max`           | integer | Upper bound on places. Dense markets (many results) may produce this many.                                                                                                                                                                               |
| `keyword_count`        | integer | Number of keywords in your request.                                                                                                                                                                                                                      |
| `places_per_keyword`   | integer | Estimated places per keyword based on the depth setting.                                                                                                                                                                                                 |
| `total`                | number  | Estimated total cost in credits for the primary estimate.                                                                                                                                                                                                |
| `total_min`            | number  | Lowest possible cost (based on places\_min).                                                                                                                                                                                                             |
| `total_max`            | number  | Highest possible cost (based on places\_max).                                                                                                                                                                                                            |
| `breakdown`            | object  | Cost broken down by component: `job_start_cost` (flat fee per job), `places_cost`, `contact_details_cost` (emails), `reviews_cost`, `images_cost`, and `filter_cost` (the `website_filter` fee — `0` unless a `no_website`/`has_website` filter is set). |
| `reviews`              | integer | Estimated total reviews to be collected.                                                                                                                                                                                                                 |
| `images`               | integer | Estimated total images to be collected.                                                                                                                                                                                                                  |
| `includes_emails`      | boolean | Whether email extraction was included in the estimate.                                                                                                                                                                                                   |
| `unit_prices`          | object  | The per-unit prices used for this estimate, including `filters_applied`. Lets you verify the math yourself.                                                                                                                                              |
| `max_results_provided` | boolean | Whether you set a `max_results` cap. If false, the estimate was based on depth.                                                                                                                                                                          |
| `description`          | string  | A human-readable summary of the estimate.                                                                                                                                                                                                                |
| `expires_at`           | integer | Unix timestamp. The estimate is accurate until this time. After that, pricing may have changed.                                                                                                                                                          |

**balance** object containing your account state:

| Field        | Type    | Description                                                                                                                          |
| ------------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `current`    | number  | Your current credit balance.                                                                                                         |
| `sufficient` | boolean | Whether you have enough credits to run this job. Uses the conservative lower bound so you are not blocked by an optimistic estimate. |

### Notes

* All costs are in credits. One credit equals one US dollar.
* The estimate uses your account's active pricing rules at request time.
* If you set `max_results`, it acts as a hard cap. The estimate will not exceed this number of places even if depth would produce more.
* A `no_website`/`has_website` `website_filter` adds `breakdown.filter_cost` = matching places × `0.001` credits. Because billing runs on the places that actually match, the filter both narrows your results and is priced per match.
* The `Cache-Control: no-store` header is set on responses because estimates are ephemeral and should not be cached.

## `GET /api/v1/jobs`

List your jobs with pagination and sorting.

### Query parameters

| Parameter | Type    | Default      | Notes                                                 |
| --------- | ------- | ------------ | ----------------------------------------------------- |
| `page`    | integer | `1`          | Must be positive                                      |
| `limit`   | integer | `10`         | Max `100`                                             |
| `sort`    | string  | `created_at` | Allowed: `created_at`, `name`, `status`, `updated_at` |
| `order`   | string  | `desc`       | `asc` or `desc`                                       |
| `search`  | string  | empty        | Max 200 bytes                                         |

### Example response

```json theme={null}
{
  "jobs": [
    {
      "id": "d8d8a24e-cef2-4b02-8396-abc290b6f299",
      "name": "Berlin cafes",
      "status": "completed",
      "data": {
        "keywords": ["cafes in berlin"],
        "language": "en",
        "depth": 5,
        "include_emails": false,
        "max_images": 0,
        "max_reviews": 0,
        "max_results": 100,
        "max_time": 1800,
        "fast_mode": false
      },
      "created_at": "2026-04-10T09:00:00Z",
      "updated_at": "2026-04-10T09:05:00Z",
      "source": "api",
      "result_count": 87,
      "total_cost": "12.50"
    }
  ],
  "total": 1,
  "page": 1,
  "limit": 10,
  "has_more": false
}
```

### Response fields

| Field          | Type    | Description                                                                                  |
| -------------- | ------- | -------------------------------------------------------------------------------------------- |
| `id`           | string  | Job UUID                                                                                     |
| `name`         | string  | The name you set when creating the job                                                       |
| `status`       | string  | Current status (see [Job statuses](#job-statuses))                                           |
| `data`         | object  | The configuration you submitted. `data.max_time` is in seconds, matching the request format. |
| `created_at`   | string  | When the job was created (ISO 8601)                                                          |
| `updated_at`   | string  | When the job status last changed (ISO 8601)                                                  |
| `source`       | string  | `"api"` if created via API key, `"web"` if created from the dashboard                        |
| `result_count` | integer | Number of places scraped so far                                                              |
| `total_cost`   | string  | Credits charged for this job (6-decimal string)                                              |

## `GET /api/v1/jobs/{id}`

Fetch one job by UUID.

* `200 OK` returns the same job object shape shown above
* `404 Not Found` if the job does not belong to you or does not exist
* `422 Unprocessable Entity` if `id` is not a valid UUID

## `POST /api/v1/jobs/{id}/cancel`

Cancel a pending or active job.

Returns `204 No Content` on success.

## `DELETE /api/v1/jobs/{id}`

Delete a job you own.

Returns `204 No Content` on success. Invalid UUID values return `422 Unprocessable Entity`.
