{
  "openapi": "3.1.0",
  "info": {
    "title": "StayToolbox API",
    "version": "1.1",
    "description": "Read-only access to a directory of vacation rental software products and sourced guides with explicit review provenance. Unknown values are null and listed in unknown_fields. Third-party ratings are labelled as snapshots. has_pricing_page only means a pricing page exists. Commercial relationships (affiliate, sponsored) are flagged and never affect ranking. Please link to the url field when citing.",
    "termsOfService": "https://staytoolbox.com/terms#api",
    "contact": {
      "url": "https://staytoolbox.com/contact"
    }
  },
  "servers": [
    {
      "url": "https://staytoolbox.com"
    }
  ],
  "paths": {
    "/api/v1/search": {
      "get": {
        "operationId": "searchProducts",
        "summary": "Search and filter products",
        "description": "Full-text search over product names, descriptions, categories and editorial summaries. Without q, lists products in the chosen sort order. If no product matches every word, results match any word and query.relaxed_match is true.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 120
            },
            "description": "Free text, e.g. \"dynamic pricing for rentals\". Product names match first."
          },
          {
            "name": "category",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Main category or subcategory slug from /api/v1/categories."
          },
          {
            "name": "subcategory",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Subcategory slug, e.g. \"pms-software\"."
          },
          {
            "name": "country",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Headquarters country, exact English name, e.g. \"Germany\"."
          },
          {
            "name": "min_rating",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "3",
                "3.5",
                "4",
                "4.5",
                "rated"
              ]
            },
            "description": "Minimum G2 rating (third-party snapshot). \"rated\" = has any rating."
          },
          {
            "name": "size",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "small",
                "medium",
                "large",
                "enterprise"
              ]
            },
            "description": "small: 1–50 people; medium: 51–200 people; large: 201–1,000 people; enterprise: 1,001+ people"
          },
          {
            "name": "has_pricing_page",
            "in": "query",
            "schema": {
              "type": "boolean"
            },
            "description": "Only products with a collected pricing page."
          },
          {
            "name": "sort",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "relevance",
                "name"
              ]
            },
            "description": "Default: relevance with q, name without."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 20
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "next_cursor from the previous page."
          },
          {
            "name": "facets",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "subcategory"
              ]
            },
            "description": "Include counts of matching products per subcategory."
          }
        ],
        "responses": {
          "200": {
            "description": "Matching products",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "total",
                    "next_cursor",
                    "query",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ProductSummary"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Pass as cursor to get the next page. Null on the last page."
                    },
                    "query": {
                      "type": "object"
                    },
                    "suggested_categories": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "facets": {
                      "type": "object"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ErrorResponse"
          },
          "429": {
            "$ref": "#/components/responses/ErrorResponse"
          }
        }
      }
    },
    "/api/v1/products/{slug}": {
      "get": {
        "operationId": "getProduct",
        "summary": "Full product record with provenance",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "Product slug (e.g. \"lodgify\") or stable id (e.g. \"str_lodgify\"). Old slugs resolve to the current product.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Product",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Product"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/Meta"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/ErrorResponse"
          }
        }
      }
    },
    "/api/v1/compare": {
      "get": {
        "operationId": "compareProducts",
        "summary": "Compare 2–5 products field by field",
        "parameters": [
          {
            "name": "ids",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated slugs or ids."
          }
        ],
        "responses": {
          "200": {
            "description": "Comparison",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Product"
                      }
                    },
                    "comparison": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "missing": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ErrorResponse"
          }
        }
      }
    },
    "/api/v1/categories": {
      "get": {
        "operationId": "listCategories",
        "summary": "Category tree with product counts and valid country names",
        "responses": {
          "200": {
            "description": "Categories"
          }
        }
      }
    },
    "/api/v1/guides": {
      "get": {
        "operationId": "listGuides",
        "summary": "Published editorial guides",
        "responses": {
          "200": {
            "description": "Guides"
          }
        }
      }
    },
    "/api/v1/guides/{slug}": {
      "get": {
        "operationId": "getGuide",
        "summary": "A guide with complete content, products, evidence and sources",
        "description": "Includes intro, conclusion, FAQ, content_markdown and review provenance. ordering distinguishes ranked lists from editorial sequence; source research does not imply independent human review.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Guide"
          },
          "404": {
            "$ref": "#/components/responses/ErrorResponse"
          },
          "410": {
            "$ref": "#/components/responses/ErrorResponse"
          }
        }
      }
    }
  },
  "components": {
    "responses": {
      "ErrorResponse": {
        "description": "Error",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "Catalog": {
        "type": [
          "object",
          "null"
        ],
        "description": "Official-document research, not a live integration test. Production returns immutable published snapshots with review provenance. Local drafts are explicitly marked. Unknown prices are never zero.",
        "required": [
          "schema_version",
          "product_id",
          "research",
          "sources",
          "pricing_plans",
          "integrations",
          "open_questions",
          "visibility",
          "freshness"
        ],
        "properties": {
          "schema_version": {
            "const": 1
          },
          "product_id": {
            "type": "string"
          },
          "summary": {
            "type": "string"
          },
          "publication_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "visibility": {
            "enum": [
              "published",
              "local_draft_preview"
            ]
          },
          "review": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "method": {
                "type": "string"
              },
              "agent": {
                "type": "string"
              },
              "human_reviewed": {
                "type": "boolean"
              },
              "independent_review": {
                "type": "boolean"
              },
              "release_id": {
                "type": "string"
              },
              "catalog_sha256": {
                "type": "string"
              },
              "editorial_sha256": {
                "type": "string"
              }
            }
          },
          "freshness": {
            "enum": [
              "current",
              "review_due"
            ]
          },
          "evidence_note": {
            "type": "string"
          },
          "research": {
            "type": "object",
            "required": [
              "status",
              "method",
              "checked_at",
              "next_review_at"
            ],
            "properties": {
              "status": {
                "const": "source_checked"
              },
              "method": {
                "const": "official_document_review"
              },
              "checked_at": {
                "type": "string",
                "format": "date"
              },
              "next_review_at": {
                "type": "string",
                "format": "date"
              }
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CatalogEvidence"
            }
          },
          "pricing_plans": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PricingPlan"
            }
          },
          "integrations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Integration"
            }
          },
          "open_questions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "CatalogEvidence": {
        "type": "object",
        "required": [
          "id",
          "url",
          "title",
          "type",
          "finding",
          "checked_at",
          "next_review_at"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "title": {
            "type": "string"
          },
          "type": {
            "enum": [
              "vendor_pricing",
              "vendor_docs",
              "vendor_integration"
            ]
          },
          "finding": {
            "type": "string"
          },
          "checked_at": {
            "type": "string",
            "format": "date"
          },
          "next_review_at": {
            "type": "string",
            "format": "date"
          }
        }
      },
      "PricingPlan": {
        "type": "object",
        "required": [
          "id",
          "name",
          "price_status",
          "currency",
          "billing_period",
          "unit",
          "min_units",
          "max_units",
          "markets",
          "complete_cost",
          "conditions",
          "exclusions",
          "source_ids",
          "rules"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "price_status": {
            "enum": [
              "published",
              "quote_only",
              "dynamic",
              "conflicting"
            ],
            "description": "published means a vendor publishes fee components, not editorial approval and not a complete quote."
          },
          "currency": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^[A-Z]{3}$",
            "description": "Settlement currency. No FX conversion."
          },
          "billing_period": {
            "enum": [
              "month",
              "year",
              "unknown"
            ]
          },
          "unit": {
            "enum": [
              "account",
              "property",
              "listing",
              "guidebook",
              "door"
            ]
          },
          "min_units": {
            "type": "integer",
            "minimum": 1
          },
          "max_units": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1
          },
          "markets": {
            "type": "array",
            "items": {
              "type": "string",
              "pattern": "^([A-Z]{2}|UNSPECIFIED)$"
            },
            "description": "Documented research scope, not worldwide product availability."
          },
          "complete_cost": {
            "type": "boolean",
            "description": "False when missing fees or unresolved terms prevent a total quote."
          },
          "conditions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "exclusions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "source_ids": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "notes": {
            "type": "string"
          },
          "included_capabilities": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "rules": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PricingRule"
            }
          }
        }
      },
      "PricingRule": {
        "type": "object",
        "required": [
          "id",
          "kind",
          "timing",
          "requirement",
          "qualifier",
          "source_ids"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "kind": {
            "enum": [
              "flat",
              "per_unit",
              "tiered",
              "percentage",
              "unknown"
            ]
          },
          "timing": {
            "enum": [
              "recurring",
              "one_time",
              "usage"
            ]
          },
          "requirement": {
            "enum": [
              "required",
              "optional"
            ]
          },
          "qualifier": {
            "enum": [
              "exact",
              "from",
              "unknown"
            ]
          },
          "source_ids": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "amount_minor": {
            "type": "integer",
            "minimum": 0,
            "description": "Integer minor currency units, e.g. USD 19.99 is 1999."
          },
          "included_units": {
            "type": "integer",
            "minimum": 0
          },
          "rate_bps": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10000,
            "description": "100 basis points = 1%."
          },
          "basis": {
            "type": "string",
            "description": "Explicit revenue/transaction basis, required for percentage estimates."
          },
          "minimum_minor": {
            "type": "integer",
            "minimum": 0
          },
          "minimum_per_unit": {
            "type": "boolean"
          },
          "tier_mode": {
            "enum": [
              "graduated",
              "volume"
            ]
          },
          "tiers": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "from",
                "to",
                "amount_minor"
              ],
              "properties": {
                "from": {
                  "type": "integer",
                  "minimum": 1
                },
                "to": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "minimum": 1
                },
                "amount_minor": {
                  "type": "integer",
                  "minimum": 0
                }
              }
            }
          }
        },
        "allOf": [
          {
            "if": {
              "properties": {
                "kind": {
                  "enum": [
                    "flat",
                    "per_unit"
                  ]
                }
              }
            },
            "then": {
              "required": [
                "amount_minor"
              ]
            }
          },
          {
            "if": {
              "properties": {
                "kind": {
                  "const": "per_unit"
                }
              }
            },
            "then": {
              "required": [
                "included_units"
              ]
            }
          },
          {
            "if": {
              "properties": {
                "kind": {
                  "const": "tiered"
                }
              }
            },
            "then": {
              "required": [
                "tier_mode",
                "tiers"
              ]
            }
          },
          {
            "if": {
              "properties": {
                "kind": {
                  "const": "percentage"
                }
              }
            },
            "then": {
              "required": [
                "rate_bps",
                "basis"
              ]
            }
          }
        ]
      },
      "Integration": {
        "type": "object",
        "description": "Direction is relative to this product. A listed partner alone does not establish sync scope; iCal only establishes calendar actions.",
        "required": [
          "id",
          "target_id",
          "method",
          "direction",
          "support",
          "actions",
          "conditions",
          "limitations",
          "source_ids"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "target_id": {
            "type": "string",
            "description": "Stable directory_targets ID; products use str_ IDs, channels use airbnb/vrbo/booking-com."
          },
          "method": {
            "enum": [
              "api",
              "ical",
              "native",
              "listed_unknown"
            ]
          },
          "direction": {
            "enum": [
              "inbound",
              "outbound",
              "bidirectional",
              "unspecified"
            ]
          },
          "support": {
            "enum": [
              "documented",
              "listed"
            ]
          },
          "actions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "conditions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "limitations": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "source_ids": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "Meta": {
        "type": "object",
        "properties": {
          "api_version": {
            "type": "string"
          },
          "generated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "status": {
                "type": "integer"
              },
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      },
      "CategoryRef": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "slug": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "url": {
            "type": "string"
          }
        }
      },
      "ProductSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable identifier. Never changes."
          },
          "slug": {
            "type": "string",
            "description": "URL slug. May change; old slugs redirect."
          },
          "name": {
            "type": "string"
          },
          "verification_status": {
            "type": "string",
            "enum": [
              "pending",
              "reviewed",
              "not_reviewed",
              "source_checked",
              "source_checked_preview"
            ]
          },
          "url": {
            "type": "string",
            "description": "StayToolbox page. Link here when citing."
          },
          "website_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "Official vendor website."
          },
          "summary": {
            "type": [
              "string",
              "null"
            ],
            "description": "Research note or vendor description; check verification_status."
          },
          "category": {
            "$ref": "#/components/schemas/CategoryRef"
          },
          "subcategory": {
            "$ref": "#/components/schemas/CategoryRef"
          },
          "country": {
            "type": [
              "string",
              "null"
            ],
            "description": "Headquarters country."
          },
          "founded_year": {
            "type": [
              "integer",
              "null"
            ]
          },
          "employee_count": {
            "type": [
              "integer",
              "null"
            ]
          },
          "g2_rating": {
            "type": [
              "number",
              "null"
            ],
            "description": "G2 rating 0–5 from the dated source snapshot. Not a StayToolbox score."
          },
          "g2_review_count": {
            "type": [
              "integer",
              "null"
            ]
          },
          "has_pricing_page": {
            "type": "boolean",
            "description": "A pricing page was found. Says nothing about price."
          },
          "editorial_reviewed": {
            "type": "boolean",
            "description": "True if a StayToolbox editor has reviewed this product."
          },
          "match": {
            "type": "array",
            "description": "Why this result matched the query.",
            "items": {
              "type": "object",
              "properties": {
                "field": {
                  "type": "string"
                },
                "type": {
                  "type": "string",
                  "enum": [
                    "exact",
                    "prefix",
                    "term"
                  ]
                },
                "terms": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      },
      "Product": {
        "type": "object",
        "description": "Every fact group carries its source. Values we do not know are null and named in unknown_fields.",
        "properties": {
          "id": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "markdown_url": {
            "type": "string"
          },
          "description": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "text": {
                "type": "string"
              },
              "source": {
                "type": "string"
              },
              "evidence_level": {
                "type": "string",
                "enum": [
                  "unverified",
                  "vendor_claim"
                ]
              },
              "fetched_at": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "verification_status": {
            "type": "string",
            "enum": [
              "pending",
              "reviewed",
              "not_reviewed",
              "source_checked",
              "source_checked_preview"
            ]
          },
          "catalog": {
            "$ref": "#/components/schemas/Catalog"
          },
          "company": {
            "type": "object"
          },
          "third_party_signals": {
            "type": "object"
          },
          "pricing": {
            "type": "object"
          },
          "links": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "editorial": {
            "type": "object",
            "description": "status is \"source_checked\", \"reviewed\" or \"not_reviewed\". source_checked is AI-prepared official-source research with explicit review provenance; it does not imply human review. Capabilities carry evidence_level: vendor_claim, verified or tested."
          },
          "guides": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "alternatives": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "commercial": {
            "type": "object",
            "properties": {
              "affiliate": {
                "type": "boolean"
              },
              "sponsored": {
                "type": "boolean"
              }
            }
          },
          "quality": {
            "type": "object"
          },
          "provenance": {
            "type": "object",
            "description": "directory.source_url is the official product evidence. directory.discovery_url identifies the directory or partner page where the product was discovered; inclusion does not establish an integration. fetched_at is the website check date."
          },
          "unknown_fields": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "citation": {
            "type": "object"
          }
        }
      }
    }
  }
}