{
  "openapi": "3.1.0",
  "info": {
    "title": "VPSLedger static data API",
    "version": "1.0.0",
    "summary": "Read-only VPS plan and provider data, generated at build time",
    "description": "Three read-only GET resources backed by static JSON files regenerated on every site build. There is no server-side query execution, no pagination and no authentication: download a file and filter it client-side. Every plan record carries `source_url` (the official provider page the row was verified against) and `verified_at` (date of that check). `price_usd_monthly` normalizes currency and billing period to a USD monthly equivalent using a fixed FX snapshot (EUR 1.08, GBP 1.22, CAD 0.73, AUD 0.66, SGD 0.76 on 2026-09-10) — treat it as an approximation for comparison, not a quoted price.",
    "termsOfService": "https://vpsledger.com/methodology/"
  },
  "servers": [
    { "url": "https://vpsledger.com", "description": "Canonical production origin" }
  ],
  "paths": {
    "/api/v1/providers.json": {
      "get": {
        "operationId": "listProviders",
        "summary": "Provider directory with verified plan counts and cheapest verified plan",
        "tags": ["providers"],
        "parameters": [
          {
            "name": "If-None-Match",
            "in": "header",
            "required": false,
            "schema": { "type": "string" },
            "description": "Cloudflare serves a strong ETag per deploy; conditional GET works as usual."
          }
        ],
        "responses": {
          "200": {
            "description": "Provider records. `plan_count` counts rows in /api/v1/plans.json; `cheapest` is null when a provider has no verified plans yet.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ProviderCollection" }
              }
            }
          }
        }
      }
    },
    "/api/v1/plans.json": {
      "get": {
        "operationId": "listPlans",
        "summary": "All verified plans, sorted by price_usd_monthly ascending",
        "tags": ["plans"],
        "parameters": [
          { "$ref": "#/components/parameters/ClientSideFiltering" }
        ],
        "responses": {
          "200": {
            "description": "Full plan list. Missing values are null — never guessed; render them as 'Unknown'.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/PlanCollection" }
              }
            }
          }
        }
      }
    },
    "/api/v1/search.json": {
      "get": {
        "operationId": "getSearchIndex",
        "summary": "Search index with the filter manifest and value facets",
        "description": "Identical plan array to /api/v1/plans.json plus a `filters` manifest documenting each supported parameter's exact comparison semantics and a `facets` object with value counts (country, city, virtualization, disk_type, billing_period, provider) so an agent can decide a query before filtering.",
        "tags": ["search"],
        "parameters": [
          { "$ref": "#/components/parameters/ClientSideFiltering" },
          {
            "name": "max_price",
            "in": "query",
            "required": false,
            "schema": { "type": "number", "format": "float" },
            "description": "NOT executed server-side. Client-side semantics: keep plans where price_usd_monthly <= max_price (USD/month). The /vps-under-N/ pages use a strict < N, so a $5.00/mo plan is not 'under $5'."
          },
          {
            "name": "min_ram",
            "in": "query",
            "required": false,
            "schema": { "type": "integer" },
            "description": "NOT executed server-side. Client-side: ram_mb >= min_ram (MB)."
          },
          {
            "name": "min_vcpu",
            "in": "query",
            "required": false,
            "schema": { "type": "integer" },
            "description": "NOT executed server-side. Client-side: vcpu >= min_vcpu; null vcpu fails the filter."
          },
          {
            "name": "country",
            "in": "query",
            "required": false,
            "schema": { "type": "string", "minLength": 2, "maxLength": 2 },
            "description": "NOT executed server-side. Client-side: country equals this ISO 3166-1 alpha-2 code; null country never matches."
          },
          {
            "name": "virtualization",
            "in": "query",
            "required": false,
            "schema": { "type": "string", "enum": ["kvm", "openvz", "lxc", "xen", "vmware", "hyperv", "dedicated"] },
            "description": "NOT executed server-side. Client-side: virtualization equals this value; 'unknown' rows fail any virtualization filter."
          },
          {
            "name": "disk_type",
            "in": "query",
            "required": false,
            "schema": { "type": "string", "enum": ["nvme", "ssd", "hdd", "sas"] },
            "description": "NOT executed server-side. Client-side: disk_type equals this value."
          },
          {
            "name": "billing_period",
            "in": "query",
            "required": false,
            "schema": { "type": "string", "enum": ["hourly", "weekly", "monthly", "quarterly", "semiannually", "yearly", "biennially", "triennially"] },
            "description": "NOT executed server-side. Client-side: billing_period equals this value."
          }
        ],
        "responses": {
          "200": {
            "description": "Search index: plan array + filters manifest + facets.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SearchIndex" }
              }
            }
          }
        }
      }
    },
    "/.well-known/api-catalog": {
      "get": {
        "operationId": "getApiCatalog",
        "summary": "RFC 9727 linkset describing this API",
        "tags": ["discovery"],
        "responses": {
          "200": {
            "description": "application/linkset+json document.",
            "content": {
              "application/linkset+json": {
                "schema": {
                  "type": "object",
                  "required": ["linkset"],
                  "properties": {
                    "anchor": { "type": "string", "format": "uri" },
                    "linkset": { "type": "array", "items": { "type": "object" } }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "parameters": {
      "ClientSideFiltering": {
        "name": "filter-parameters",
        "in": "query",
        "required": false,
        "description": "This endpoint ignores all query parameters (static file). Filter the returned array client-side; /api/v1/search.json documents the exact semantics of each parameter.",
        "schema": { "type": "object", "additionalProperties": true },
        "style": "form",
        "explode": true
      }
    },
    "schemas": {
      "Money": {
        "type": "object",
        "required": ["price", "currency", "billing_period", "price_usd_monthly"],
        "properties": {
          "price": { "type": "number", "exclusiveMinimum": 0, "description": "Native published price." },
          "currency": { "type": "string", "minLength": 3, "maxLength": 3, "description": "ISO 4217 code of the published price." },
          "billing_period": { "$ref": "#/components/schemas/BillingPeriod" },
          "price_usd_monthly": { "type": "number", "description": "USD monthly-equivalent via the fixed FX snapshot. Approximate." },
          "renewal_price": { "type": ["number", "null"], "description": "Native renewal price when the provider publishes one, else null (render as 'Unknown')." }
        }
      },
      "BillingPeriod": {
        "type": "string",
        "enum": ["hourly", "weekly", "monthly", "quarterly", "semiannually", "yearly", "biennially", "triennially"]
      },
      "Plan": {
        "type": "object",
        "required": ["provider_id", "name", "slug", "price", "currency", "billing_period", "price_usd_monthly", "in_stock", "source_url", "verified_at"],
        "properties": {
          "provider_id": { "type": "string", "description": "Matches providers.json `id`." },
          "provider_name": { "type": "string" },
          "name": { "type": "string", "description": "Provider-published plan name." },
          "slug": { "type": "string" },
          "url": { "type": "string", "format": "uri", "description": "Human-readable detail page for this plan." },
          "vcpu": { "type": ["integer", "null"] },
          "ram_mb": { "type": ["integer", "null"] },
          "disk_gb": { "type": ["integer", "null"] },
          "disk_type": { "type": "string", "enum": ["nvme", "ssd", "hdd", "sas", "unknown"] },
          "os_type": { "type": ["array", "null"], "items": { "type": "string", "enum": ["linux", "windows", "bsd", "other"] }, "description": "Not yet verified for any plan — always null today. Never infer an OS from it." },
          "bandwidth_gb": { "type": ["number", "null"] },
          "port_mbps": { "type": ["number", "null"] },
          "virtualization": { "type": "string", "enum": ["kvm", "openvz", "lxc", "xen", "vmware", "hyperv", "dedicated", "unknown"] },
          "ipv4": { "type": ["boolean", "null"] },
          "ipv6": { "type": ["boolean", "null"] },
          "country": { "type": ["string", "null"] },
          "region": { "type": ["string", "null"] },
          "city": { "type": ["string", "null"] },
          "in_stock": { "type": "boolean" },
          "source_url": { "type": "string", "format": "uri", "description": "Official page this row was verified against. Cite it." },
          "verified_at": { "type": "string", "format": "date", "description": "Date of the last manual check. Cite it." }
        },
        "allOf": [{ "$ref": "#/components/schemas/Money" }]
      },
      "PlanCollection": {
        "type": "object",
        "required": ["updated", "count", "plans"],
        "properties": {
          "generated_at_build": { "type": "boolean", "const": true },
          "updated": { "type": "string", "format": "date", "description": "Latest verified_at across plans." },
          "origin": { "type": "string", "format": "uri" },
          "count": { "type": "integer" },
          "sort": { "type": "string" },
          "fx_snapshot_note": { "type": "string" },
          "plans": { "type": "array", "items": { "$ref": "#/components/schemas/Plan" } }
        }
      },
      "Provider": {
        "type": "object",
        "required": ["id", "name", "home_url", "plan_count"],
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" },
          "hq_country": { "type": ["string", "null"] },
          "regions": { "type": "array", "items": { "type": "string" } },
          "api_available": { "type": ["boolean", "null"] },
          "home_url": { "type": "string", "format": "uri" },
          "pricing_url": { "type": ["string", "null"], "format": "uri" },
          "plan_count": { "type": "integer", "description": "Verified plans in plans.json; 0 means no verification yet." },
          "cheapest": { "type": ["object", "null"], "description": "Cheapest verified plan by price_usd_monthly, or null." }
        }
      },
      "ProviderCollection": {
        "type": "object",
        "required": ["updated", "count", "providers"],
        "properties": {
          "generated_at_build": { "type": "boolean", "const": true },
          "updated": { "type": "string", "format": "date" },
          "origin": { "type": "string", "format": "uri" },
          "count": { "type": "integer" },
          "fx_snapshot_note": { "type": "string" },
          "providers": { "type": "array", "items": { "$ref": "#/components/schemas/Provider" } }
        }
      },
      "SearchIndex": {
        "type": "object",
        "required": ["updated", "filters", "facets", "plans"],
        "properties": {
          "generated_at_build": { "type": "boolean", "const": true },
          "updated": { "type": "string", "format": "date" },
          "origin": { "type": "string", "format": "uri" },
          "description": { "type": "string" },
          "filters": { "type": "object", "description": "Parameter name → {type, field, semantics}. Query parameters are not executed server-side; these describe client-side filtering." },
          "facets": { "type": "object", "description": "Field → {value: count} for country, city, virtualization, disk_type, billing_period, provider." },
          "plans": { "type": "array", "items": { "$ref": "#/components/schemas/Plan" } }
        }
      }
    }
  }
}
