# Hetzner Server Auction Monitor (`rl1987/hetzner-server-auction-monitor`) Actor

Polls the Hetzner Server Auction (Serverbörse), filters listings by your criteria, and fires a webhook for each new match to trigger downstream Actors.

- **URL**: https://apify.com/rl1987/hetzner-server-auction-monitor.md
- **Developed by:** [R.L.](https://apify.com/rl1987) (community)
- **Categories:** Developer tools, E-commerce, Automation
- **Stats:** 3 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 successful api checks

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Hetzner Server Auction Monitor — Instant Alerts for Dedicated Server Deals

**Never miss a cheap dedicated server again.** This Actor watches the [Hetzner Server Auction](https://www.hetzner.com/sb) (the *Serverbörse*) around the clock, filters every listing against **your** specs — price, RAM, ECC, disk, datacenter, CPU — and fires an instant **webhook** the moment a matching bargain appears. Plug it into Slack, Discord, Make, Zapier, n8n, your own API, or a downstream Apify Actor and turn Hetzner's fast-moving auction into a hands-off deal pipeline.

Hetzner's auction inventory changes by the minute and the best-value servers get snapped up in seconds. Refreshing the page yourself doesn't scale. This monitor does the watching for you and pings you only when something you actually want shows up.

### Why use this Actor

- ⚡ **Real-time bargain alerts** — get notified the second a server matching your criteria hits the auction, not hours later.
- 🎯 **Precise filtering** — match on max monthly price, minimum RAM, ECC memory, disk type (NVMe / SATA / HDD) and capacity, datacenter location, and CPU model.
- 🔔 **De-duplicated by design** — you're alerted **once** per server. No repeated pings for listings you've already seen.
- 🔗 **Wire it into anything** — generic JSON webhook to any HTTP endpoint, or native Apify dataset integrations for no-code automation chains.
- 🤖 **Self-scheduling** — optionally have the Actor maintain its own recurring poll, so you set it up once and forget it.
- 🛡️ **Robust** — feed shape is validated every run; flaky webhooks are retried with backoff and never crash the job.

### Who it's for

- **Self-hosters & homelabbers** hunting a cheap, high-RAM box for Proxmox, a NAS, game servers, or media.
- **Startups & agencies** sourcing low-cost dedicated compute without committing to retail pricing.
- **Developers & DevOps teams** automating infrastructure procurement.
- **Resellers & deal-hunters** tracking when specific configs drop below a target price.

### What it does

On each run the Actor:

1. Reads your input criteria.
2. (Optional) Ensures its own recurring **Schedule** exists, so it keeps polling automatically.
3. Fetches the live auction feed and validates its shape.
4. Filters listings (max price, min RAM, ECC, disk type/size, datacenter, CPU).
5. Diffs against the IDs it has already seen (stored in a named key-value store).
6. On the **first run**, silently records existing matches (seeding) so you aren't flooded.
7. For each fresh match: pushes it to the dataset **and** POSTs a webhook (if a URL is configured).

### Input

| Field | Default | Notes |
|---|---|---|
| `currency` | `EUR` | `EUR` or `USD`. Price criteria are interpreted in this currency. |
| `maxPrice` | `50` | Max monthly price. |
| `minRamGB` | `128` | Minimum RAM in GB. |
| `requireEcc` | `false` | Only ECC memory. |
| `diskType` | `any` | `any` / `nvme` / `sata` / `hdd`. |
| `minDiskGB` | – | Minimum total disk capacity (GB). |
| `datacenters` | `[]` | Location prefixes, e.g. `["FSN","NBG","HEL"]`. Empty = any. |
| `cpuIncludes` | – | Case-insensitive substring of the CPU model. |
| `genericWebhookUrl` | – | **Secret.** POST target for each fresh match. |
| `notifyOnFirstRun` | `false` | Fire webhooks on the first run too (skips seeding). |
| `selfSchedule` | `false` | Create/maintain a recurring Schedule. **Needs Full permissions.** |
| `cron` | `*/10 * * * *` | Schedule cron (only when `selfSchedule` is on). |
| `scheduleName` | – | Schedule name / idempotency key. |

The defaults (≤ €50/mo, ≥ 128 GB RAM) target large-RAM bargains; loosen them to see more matches.

### Output

Every fresh match is pushed to the Actor's **dataset** (one row per server) and is available as JSON, CSV, Excel, or via the API. Fields include the listing `id`, `cpu`, `ramGB`, `ecc`, `disk`, `price`, `currency`, `datacenter`, `specials`, and a direct `url` to the auction listing.

### Webhook payload

```json
{
  "event": "new_hetzner_match",
  "server": { "...full listing from the feed..." },
  "currency": "EUR",
  "summary": "AMD Ryzen 5 3600 · 64 GB RAM · 2x NVMe (1024 GB total) · 38 EUR/mo · FSN1-DC15",
  "url": "https://www.hetzner.com/sb?search=<id>"
}
```

Delivery is per-item and isolated: timeouts / 5xx are retried with backoff, and a permanently
failing webhook is logged and skipped — it never crashes the run.

### Wiring to a downstream Actor

Two supported options:

1. **Direct webhook** — set `genericWebhookUrl` to a downstream Actor's run endpoint
   (`https://api.apify.com/v2/acts/<actor>/runs?token=…`). Each match starts that Actor with the
   listing as input.
2. **Native Apify integration (recommended for publishing)** — leave `genericWebhookUrl` empty.
   Because only *fresh* matches are pushed to this Actor's dataset, you can chain a downstream
   Actor via Apify's built-in integrations on new dataset items / run success.

### Self-scheduling & Full permissions

`selfSchedule` makes the Actor create and maintain its own recurring Schedule (looked up by name,
so it never duplicates — running it manually or from the schedule both converge on one schedule).
Scheduled runs carry `selfSchedule: false` as a recursion guard.

Schedules are an **account-level** resource, so the run needs **Full permissions**:

- Set the permission level to **"Full permissions"** in the Actor's **Console → Settings**
  (this is *not* configured in `actor.json`).
- For your **own** Actor, no extra approval is needed.
- If you **don't own** the Actor, you must approve the Full-permission request once in the Console;
  otherwise API-triggered runs fail with `full-permission-actor-not-approved`.

If you'd rather not grant Full permissions, keep `selfSchedule: false` and create a normal Apify
Schedule for the Actor by hand — the polling logic is identical.

To stop self-scheduling, delete the schedule in the Console (or disable it there).

### Pricing

This Actor uses Apify's **pay-per-event** model: **$1 per 1,000 successful API checks** ($0.001 per
check). One "check" = one successful poll of the Hetzner auction feed; failed fetches are never
charged. A poll every 10 minutes is ~4,320 checks/month (~**$4.32/mo**); every 30 minutes is
\~1,440 checks/month (~**$1.44/mo**). You set your own max-charge cap on every run, so spend is
always under your control. Notifications and dataset pushes are included — you only pay per check.

### Local development

```bash
python -m venv .venv && source .venv/bin/activate
pip install -r requirements-dev.txt
pytest                 # unit tests (filter + state) against a saved sample feed
apify run              # run locally (needs the Apify CLI)
```

Self-scheduling and webhooks to Apify endpoints require a real run on the platform
(`apify push`, then run from the Console).

### Data source

Public internal endpoint:
`https://www.hetzner.com/_resources/app/data/app/live_data_sb_<EUR|USD>.json`.
It is undocumented and has changed shape before, so `src/feed.py` validates the structure on every
run and fails loudly if it drifts.

### FAQ

**What is the Hetzner Server Auction (Serverbörse)?**
It's Hetzner's marketplace of pre-owned dedicated servers offered at reduced monthly prices with no
setup fee. Inventory and prices fluctuate constantly as servers are claimed and released.

**How fast will I be notified about a new deal?**
As fast as your poll interval. The default schedule checks every 10 minutes; lower the `cron`
interval for tighter monitoring (at a slightly higher per-check cost).

**Will I get duplicate alerts for the same server?**
No. The Actor remembers every listing it has already reported and only notifies you about servers
it hasn't seen before.

**Can I send alerts to Slack, Discord, or email?**
Yes — point `genericWebhookUrl` at any HTTP endpoint, including an automation platform (Make,
Zapier, n8n) or a small relay that formats the JSON payload for Slack/Discord/email.

**Do I need to keep the Actor running myself?**
No. Enable `selfSchedule` (with Full permissions) and it maintains its own recurring schedule, or
create a standard Apify Schedule by hand.

**Does it work with both EUR and USD pricing?**
Yes. Set `currency` to `EUR` or `USD`; all price filters are interpreted in that currency.

# Actor input Schema

## `currency` (type: `string`):

Currency of the auction feed. All price criteria are interpreted in this currency.

## `minPrice` (type: `integer`):

Only match servers whose monthly price is at or above this value (in the selected currency). Leave empty for no lower bound.

## `maxPrice` (type: `integer`):

Only match servers whose monthly price is at or below this value (in the selected currency).

## `minRamGB` (type: `integer`):

Only match servers with at least this much RAM.

## `requireEcc` (type: `boolean`):

If enabled, only match servers with ECC memory.

## `diskType` (type: `string`):

Require at least one disk of this type. 'any' matches all servers.

## `minDiskGB` (type: `integer`):

Only match servers whose total disk capacity is at least this value. Leave empty for no disk-size filter.

## `datacenters` (type: `array`):

Restrict to these datacenter location prefixes (e.g. FSN, NBG, HEL). Empty matches any location.

## `cpuIncludes` (type: `string`):

Case-insensitive substring that must appear in the CPU model (e.g. 'Ryzen', 'Xeon'). Leave empty to match any CPU.

## `genericWebhookUrl` (type: `string`):

URL that receives a POST with the listing payload for each new match. Point it at the next Actor's run endpoint, a request bin, or any HTTP endpoint. Leave empty to rely on the dataset/native Apify integration instead.

## `notifyOnFirstRun` (type: `boolean`):

By default the first run silently records existing matches (seeding) to avoid an alert flood. Enable to fire webhooks for everything on the first run too.

## `selfSchedule` (type: `boolean`):

If enabled, the Actor creates and maintains its own recurring Schedule so it keeps polling automatically. Requires the Actor to run with Full permissions (see README). Keep disabled if you schedule the Actor manually or trigger it via an integration.

## `cron` (type: `string`):

Cron expression for the self-managed Schedule (only used when self-schedule is enabled).

## `scheduleName` (type: `string`):

Name for the self-managed Schedule (used as the idempotency key). Defaults to 'hetzner-monitor-<actorId>'.

## Actor input object example

```json
{
  "currency": "EUR",
  "maxPrice": 50,
  "minRamGB": 128,
  "requireEcc": false,
  "diskType": "any",
  "datacenters": [],
  "notifyOnFirstRun": false,
  "selfSchedule": false,
  "cron": "*/10 * * * *"
}
```

# Actor output Schema

## `matches` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "currency": "EUR",
    "maxPrice": 50,
    "minRamGB": 128,
    "diskType": "any",
    "datacenters": [],
    "cron": "*/10 * * * *"
};

// Run the Actor and wait for it to finish
const run = await client.actor("rl1987/hetzner-server-auction-monitor").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "currency": "EUR",
    "maxPrice": 50,
    "minRamGB": 128,
    "diskType": "any",
    "datacenters": [],
    "cron": "*/10 * * * *",
}

# Run the Actor and wait for it to finish
run = client.actor("rl1987/hetzner-server-auction-monitor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "currency": "EUR",
  "maxPrice": 50,
  "minRamGB": 128,
  "diskType": "any",
  "datacenters": [],
  "cron": "*/10 * * * *"
}' |
apify call rl1987/hetzner-server-auction-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=rl1987/hetzner-server-auction-monitor",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/aqzoDUgKD70wWH153/builds/HO3Q1wJhRgXAzgOYf/openapi.json
