# servermarket

> Live prices and stock of dedicated, auction, cloud, VPS and GPU servers from many hosting providers. Free, read-only, no account or API key.

## How to use (for AI agents)

Call the tools below over plain HTTP. GET with query parameters for simple arguments (lists comma-separated), or POST a JSON body for nested ones. Every response is JSON.

```bash
curl -s 'https://servermarket.dev/api/tools/search_servers?countries=de&minCores=8&minRamGb=64&sort=effectiveMonthly&limit=5'
curl -s 'https://servermarket.dev/api/tools/get_offer?provider=hetzner&offer=ax42-ax42'
curl -s -X POST https://servermarket.dev/api/tools/plan_deployment -H 'content-type: application/json' \
  -d '{"roles":[{"name":"app","count":3,"minCores":8,"minRamGb":32},{"name":"db","count":1,"minCores":16,"minRamGb":128,"storageType":"nvme"}],"countries":["de"]}'
```

- Tool list as JSON: https://servermarket.dev/api/tools
- MCP endpoint (same tools, for MCP clients): https://servermarket.dev/mcp
- Rate limit: 60 requests per minute per IP.
- When you show results to a user, include the price, the provider, the location code and the "orderUrl" link, and say how fresh the data is ("ageMinutes").

## Data conventions

Live prices and stock of dedicated and auction servers from 15+ hosting providers (Hetzner, OVHcloud/Kimsufi,
Leaseweb, Scaleway Dedibox, Contabo, OneProvider, …). Cloud and VPS coverage is still narrow: Hetzner Cloud and netcup
only (see list_providers → serverTypes); say so when answering a cloud/VPS question. Read-only; no account or API key.
Transport: Streamable HTTP, stateless — send each JSON-RPC message as an HTTP POST with headers
"Content-Type: application/json" and "Accept: application/json, text/event-stream"; responses are plain JSON. No session:
no initialize handshake state, no Mcp-Session-Id, GET/DELETE are not used.
Units: RAM and storage in GB (1 TB = 1000 GB), port speed in Mbit/s (1000 = 1 Gbit/s), traffic in TB/month.
"cores" = physical cores (dedicated/auction); cloud and vps report "vcpus" instead (often shared), and their perCore is per
vCPU, so do not compare it with dedicated perCore. Cloud/VPS prices exclude a paid IPv4 add-on unless get_offer shows spec.ipv4Included.
Prices: all excl. VAT, in the requested currency (EUR or USD, default EUR), per server.
"monthly" = recurring monthly price; "setup" = one-time setup fee (0/absent = none); "effectiveMonthly" = monthly + setup/12
(setup spread over 12 months) — the number every ranking and "per…" metric uses; "hourly" for hourly-billed cloud.
"price.native": true = the provider publishes this price in this currency; false = converted. A converted price carries
"price.fx" {rate, date, source:"ECB", from} when we converted it from the provider's currency "from" at the ECB reference
rate of "date" (rate = units of the shown currency per 1 unit of "from"); native:false without "fx" = the provider itself
shows a converted price.
Stock: "stock" = {status: in_stock|low_stock|out_of_stock|preorder|unknown, quantity (servers orderable; null =
the provider does not publish a quantity), quantityKnown (false = quantity not published, so "in_stock" does not tell how
many), scope (location = per datacenter, region = shared by a region, offer = one pool for all locations), orderableLocations
(datacenter codes where the provider lets you order it now), inStockLocations (the same, but only when stock is actually
published; empty when status is unknown), checkedAt}.
Freshness: every offer, location and provider carries checkedAt (ISO time of the last successful poll that
confirmed it), ageMinutes and stale (true when older than 60 minutes or that source's last poll
failed); every result has top-level dataAsOf (the oldest checkedAt among the returned items), ageMinutes and stale.
Sources are polled every 15 minutes; re-check stale stock on the provider before ordering.
Links: "url" = our page for the offer; "orderUrl" = the provider's page to order it, with "orderUrlKind":
exact = opens the order/configuration for this server and location (or the listing filtered to this one server),
product = the provider's page of this model (pick options/location there), generic = a range/listing page.
Typical flow: search_servers → get_offer / price_history / availability_history → plan_deployment. Identify offers by
{provider, offer} exactly as returned in "id".

## Sort metrics

- `effectiveMonthly`: effective monthly price (monthly + setup/12)
- `perCore`: effectiveMonthly / physical CPU cores
- `perThread`: effectiveMonthly / CPU threads
- `perGbRam`: effectiveMonthly / GB RAM
- `perTbStorage`: effectiveMonthly / TB of all drives (1 TB = 1000 GB, raw, before RAID)
- `perTbNvme`: effectiveMonthly / TB NVMe
- `perTbHdd`: effectiveMonthly / TB HDD
- `perGbps`: effectiveMonthly / Gbit/s of port (guaranteed bandwidth when published, else port speed × ports)
- `perTbTraffic`: effectiveMonthly / TB included traffic (only for volume-billed traffic)
- `perBenchmarkPoint`: effectiveMonthly / CPU benchmark point (compare only within one benchmark source)
- `perGpu`: effectiveMonthly / GPU
- `perGbVram`: effectiveMonthly / GB GPU memory

## Tools

### search_servers

`GET|POST https://servermarket.dev/api/tools/search_servers`

Find server offers matching hardware, location, stock and price filters, sorted ascending by one price metric.
Each item is one offer at its best matching location (orderable first, then cheapest effectiveMonthly). Item fields (flat;
absent = unknown): id {provider, offer}, name, provider, type, channel, cpu, cores, vcpus, threads, cpuScore {value, source}, ramGb,
ecc, storage (summary text), nvmeGb, ssdGb, hddGb, gpu, portMbps, bandwidth, datacenter, country, city, availableIn,
price {currency, monthly, setup, effectiveMonthly, hourly, bestTermMonthly, minContractMonths, native, fx}, perUnit {metric:
value}, promo, url, orderUrl, orderUrlKind, stock {status, quantity, quantityKnown, scope, orderableLocations, inStockLocations, checkedAt},
checkedAt, ageMinutes, stale. get_offer returns the same fields plus spec (drives, clocks, extras) and locations. Prices excl. VAT; effectiveMonthly =
monthly + setup/12. Sort metrics (exact names): effectiveMonthly = effective monthly price (monthly + setup/12); perCore = effectiveMonthly / physical CPU cores; perThread = effectiveMonthly / CPU threads; perGbRam = effectiveMonthly / GB RAM; perTbStorage = effectiveMonthly / TB of all drives (1 TB = 1000 GB, raw, before RAID); perTbNvme = effectiveMonthly / TB NVMe; perTbHdd = effectiveMonthly / TB HDD; perGbps = effectiveMonthly / Gbit/s of port (guaranteed bandwidth when published, else port speed × ports); perTbTraffic = effectiveMonthly / TB included traffic (only for volume-billed traffic); perBenchmarkPoint = effectiveMonthly / CPU benchmark point (compare only within one benchmark source); perGpu = effectiveMonthly / GPU; perGbVram = effectiveMonthly / GB GPU memory. Offers lacking the sort metric come last.
Top level: total, returned, offset, dataAsOf, ageMinutes, stale.

Arguments:

- `currency` (enum; default "EUR"; one of EUR, USD): Currency of all prices (excl. VAT). Prices the provider does not publish in it are converted at the ECB rate and marked native:false with fx.
- `text` (string): Free text; every word must occur in the offer name, provider, CPU, GPU or storage summary, e.g. "ax102" or "epyc 9454p".
- `serverTypes` (enum[]; one of dedicated, auction, vps, cloud): dedicated (new hardware), auction (used, Hetzner), cloud (VMs, hourly), vps.
- `providers` (string[]): Provider keys, e.g. ["hetzner","ovh"]. See list_providers.
- `countries` (string[]): ISO 3166-1 alpha-2 codes, lower case, e.g. ["de","fi"].
- `continents` (enum[]; one of eu, na, sa, as, oc, af)
- `minCores` (integer): Minimum physical CPU cores.
- `minThreads` (integer)
- `minRamGb` (number): Minimum RAM in GB.
- `minStorageGb` (number): Minimum total raw storage in GB over all drives (1 TB = 1000 GB).
- `minNvmeGb` (number): Minimum NVMe storage in GB.
- `minHddGb` (number): Minimum HDD storage in GB.
- `minGpus` (integer)
- `gpuModel` (string): Substring of the GPU model, e.g. "H100", "RTX 4000".
- `cpuVendor` (enum; one of AMD, Intel, Ampere)
- `cpuFamily` (string): Substring of the CPU model slug, e.g. "epyc", "xeon", "ryzen", "epyc-9454p".
- `cpuModel` (string): Case-insensitive substring of the CPU model, e.g. "EPYC", "Xeon Gold", "i9-13900".
- `arch` (enum; one of x86_64, arm64)
- `ecc` (boolean): true = only ECC RAM.
- `minPortMbps` (number): Minimum uplink port speed in Mbit/s (1000 = 1G, 10000 = 10G).
- `unmetered` (boolean): true = only unmetered traffic; false = only metered.
- `maxMonthly` (number): Maximum effectiveMonthly (monthly + setup/12, excl. VAT) in the chosen currency.
- `availableOnly` (boolean; default true): Only locations orderable right now (default true).
- `stock` (enum[]; one of in_stock, low_stock, out_of_stock, preorder, unknown)
- `channels` (enum[]; one of standard, outlet, auction, limited, instant, custom)
- `sort` (enum; default "effectiveMonthly"; one of effectiveMonthly, perCore, perThread, perGbRam, perTbStorage, perTbNvme, perTbHdd, perGbps, perTbTraffic, perBenchmarkPoint, perGpu, perGbVram): Ascending. effectiveMonthly = effective monthly price (monthly + setup/12); perCore = effectiveMonthly / physical CPU cores; perThread = effectiveMonthly / CPU threads; perGbRam = effectiveMonthly / GB RAM; perTbStorage = effectiveMonthly / TB of all drives (1 TB = 1000 GB, raw, before RAID); perTbNvme = effectiveMonthly / TB NVMe; perTbHdd = effectiveMonthly / TB HDD; perGbps = effectiveMonthly / Gbit/s of port (guaranteed bandwidth when published, else port speed × ports); perTbTraffic = effectiveMonthly / TB included traffic (only for volume-billed traffic); perBenchmarkPoint = effectiveMonthly / CPU benchmark point (compare only within one benchmark source); perGpu = effectiveMonthly / GPU; perGbVram = effectiveMonthly / GB GPU memory.
- `limit` (integer; default 10)
- `offset` (integer; default 0)

### get_offer

`GET|POST https://servermarket.dev/api/tools/get_offer`

Full details of one offer: spec (drives, GPUs, network), and every location with its own stock
{status, quantity, quantityKnown, scope, signal (the provider's raw stock text), checkedAt}, prices in EUR and USD (each
marked native true/false, converted ones with fx {rate, date, source:"ECB", from}; incl. term discounts "termMonthly",
IPv4 and traffic overage costs), orderUrl + orderUrlKind, checkedAt, ageMinutes, stale. Also add-on options, the offer's
percentile vs. comparable offers per metric (0 = cheapest, 100 = most expensive) and similar offers. Prices excl. VAT;
effectiveMonthly = monthly + setup/12.

Arguments:

- `provider` (string; required): Provider key from a previous result's id.provider, e.g. "hetzner".
- `offer` (string; required): Offer slug from a previous result's id.offer, e.g. "ax42-ax42".
- `currency` (enum; default "EUR"; one of EUR, USD): Currency of all prices (excl. VAT). Prices the provider does not publish in it are converted at the ECB rate and marked native:false with fx.

### price_history

`GET|POST https://servermarket.dev/api/tools/price_history`

Price intervals of an offer per datacenter and currency (EUR/USD, as published by the provider — not
converted) over the window: each entry {from, to, datacenter, currency, monthly, setup} is valid from "from" until "to"
(null = still current). Prices excl. VAT. Use it to judge whether a price is good or recently changed. Header: id, name,
url, orderUrl, orderUrlKind, checkedAt, ageMinutes, stale, dataAsOf.

Arguments:

- `provider` (string; required): Provider key from a previous result's id.provider, e.g. "hetzner".
- `offer` (string; required): Offer slug from a previous result's id.offer, e.g. "ax42-ax42".
- `days` (integer; default 90): Look-back window in days.
- `datacenter` (string): Restrict to one datacenter code from get_offer.

### availability_history

`GET|POST https://servermarket.dev/api/tools/availability_history`

Stock status intervals of an offer per datacenter over the window ({from, to (null = current), datacenter,
stockStatus, stockQty (null = quantity not published)}), plus availabilityShare: the share of time (0..1) each datacenter was
orderable. Answers "is it really in stock, since when, how often is it sold out?". Header: id, name, url, orderUrl,
orderUrlKind, checkedAt, ageMinutes, stale, dataAsOf.

Arguments:

- `provider` (string; required): Provider key from a previous result's id.provider, e.g. "hetzner".
- `offer` (string; required): Offer slug from a previous result's id.offer, e.g. "ax42-ax42".
- `days` (integer; default 90): Look-back window in days.
- `datacenter` (string): Restrict to one datacenter code from get_offer.

### compare_offers

`GET|POST https://servermarket.dev/api/tools/compare_offers`

Side-by-side comparison of 2–6 offers: spec, best location, price (excl. VAT; effectiveMonthly = monthly +
setup/12; native/fx as in search_servers), per-unit metrics, stock, order link, freshness and market percentiles (0 =
cheapest, 100 = most expensive among comparable offers).

Arguments:

- `offers` (object[]; required)
- `currency` (enum; default "EUR"; one of EUR, USD): Currency of all prices (excl. VAT). Prices the provider does not publish in it are converted at the ECB rate and marked native:false with fx.

### list_providers

`GET|POST https://servermarket.dev/api/tools/list_providers`

All tracked providers with offer counts, datacenters, countries, server types, cheapest effectiveMonthly in
EUR (excl. VAT), data health, checkedAt, ageMinutes and stale (stale also when one of the provider's sources failed).

### get_provider

`GET|POST https://servermarket.dev/api/tools/get_provider`

One provider: summary, provider-wide facts (IPv6, DDoS, contract terms, payment, KYC …) — each with a verbatim
quote and source URL; facts without a source are never returned, so a missing fact means "not verified" — plus its
locations and cheapest offers (offer shape as in search_servers). Prices excl. VAT.

Arguments:

- `provider` (string; required): Provider key from a previous result's id.provider, e.g. "hetzner".
- `currency` (enum; default "EUR"; one of EUR, USD): Currency of all prices (excl. VAT). Prices the provider does not publish in it are converted at the ECB rate and marked native:false with fx.

### list_locations

`GET|POST https://servermarket.dev/api/tools/list_locations`

Countries and cities with datacenters, the providers there, offer counts and the cheapest effectiveMonthly
in EUR (excl. VAT). Filter by country (ISO alpha-2) or continent. Top level: dataAsOf (oldest last successful poll of all
sources), ageMinutes, stale.

Arguments:

- `country` (string): ISO 3166-1 alpha-2, e.g. "de".
- `continent` (enum; one of eu, na, sa, as, oc, af)
- `level` (enum; default "all"; one of country, city, all)

### plan_deployment

`GET|POST https://servermarket.dev/api/tools/plan_deployment`

Turns an architecture into a bill of materials, e.g. 3 app servers + 1 database server.
Each role is a group of identical servers with hard minimums (an offer below any minimum is never chosen):
minCores (physical cores), minRamGb, minStorageGb (GB, counted per storageType), storageType ("nvme" = only NVMe counts,
"ssd" = NVMe + SATA/SAS SSD, "any" = all drives incl. HDD; default any), minNvmeGb, minPortMbps (Mbit/s), ecc (true = ECC RAM
required), minGpus, gpuModel, serverTypes, maxMonthlyPerUnit (max effectiveMonthly per server).
Strategies: "mixed" = cheapest offer per role from any provider; "single" = one provider for everything; "sameDatacenter" =
all roles in one datacenter (low latency, private network). Ranking uses effectiveMonthly (monthly + setup/12). Prices
excl. VAT in the requested currency (converted ones marked native:false with fx). When a provider publishes a stock quantity
a role of N servers needs ≥N; otherwise the line gets a stockWarning.
Returns up to 3 options, each with: summary (one line), totalMonthly (recurring), totalOneTimeSetup, totalEffectiveMonthly
(= totalMonthly + totalOneTimeSetup/12), providers, datacenters, notes, stale, and lines: {role, count, why (why this offer
was chosen + spec overshoot), fit [{dimension, need, have, verdict: meets|exceeds}] (exceeds = ≥1.5× the need; undershoot
cannot happen), subtotalMonthly, subtotalSetup, subtotalEffectiveMonthly, stockWarning ("only N in stock, need M" or
"… quantity not published …"; null = enough confirmed stock), offer (as in search_servers, incl. stock, orderUrl,
orderUrlKind, checkedAt, ageMinutes, stale)}. Plus unmet roles with the reason, dataAsOf, ageMinutes, stale.
Example arguments: {"roles":[{"name":"app","count":3,"minCores":8,"minRamGb":32,"storageType":"nvme"},{"name":"db","count":1,"minCores":16,"minRamGb":128,"minStorageGb":2000,"storageType":"nvme","ecc":true,"minPortMbps":1000,"maxMonthlyPerUnit":250}],"countries":["de"],"availableOnly":true,"currency":"EUR","strategy":"mixed"}

Arguments:

- `roles` (object[]; required)
- `currency` (enum; default "EUR"; one of EUR, USD): Currency of all prices (excl. VAT). Prices the provider does not publish in it are converted at the ECB rate and marked native:false with fx.
- `strategy` (enum; default "mixed"; one of mixed, single, sameDatacenter)
- `countries` (string[]): ISO 3166-1 alpha-2 codes, e.g. ["de"].
- `continents` (enum[]; one of eu, na, sa, as, oc, af)
- `budgetMonthly` (number): Maximum totalMonthly of an option (recurring, excl. VAT, without setup fees).
- `availableOnly` (boolean; default true): Only offers orderable right now (default true).

### get_status

`GET|POST https://servermarket.dev/api/tools/get_status`

Market totals (offers, providers, datacenters, changes in 24 h) and per-source poll status: kind, provider,
status (ok|partial|failed|never), lastOkAt (last successful poll), lastRunAt, error, ageMinutes and stale (> 60
minutes since lastOkAt, or the last poll failed). Top level: dataAsOf (oldest lastOkAt), ageMinutes, stale.
