{
  "openapi": "3.1.0",
  "info": {
    "title": "StrikeHoney website API",
    "version": "0.15.8",
    "summary": "The machine-readable surface of the StrikeHoney marketing site.",
    "description": "Every page on https://www.strikehoney.com serves two representations of itself: the HTML a browser renders, and clean markdown for an agent. They live at the same URL and the `Accept` header chooses between them, per acceptmarkdown.com and RFC 9110 §12.5.1. Appending `.md` to any path returns the same markdown with no header at all.\n\n**There is no product API.** Nothing described here reads or writes a contractor's leads, jobs, quotes or invoices; StrikeHoney has no such endpoint, public or private. What this document describes is the website itself: its markdown representations, its machine-readable files and its liveness check.\n\nNo authentication, no API keys, no rate limit published. Every operation is a plain GET, and this document is the complete list. A path that is not in it is not one an outside caller can use.",
    "contact": {
      "name": "StrikeHoney team",
      "email": "hello@strikehoney.com",
      "url": "https://www.strikehoney.com/contact"
    },
    "termsOfService": "https://www.strikehoney.com/terms"
  },
  "servers": [
    {
      "url": "https://www.strikehoney.com",
      "description": "Production"
    }
  ],
  "externalDocs": {
    "description": "How to read this site as an agent, in prose",
    "url": "https://www.strikehoney.com/developers"
  },
  "tags": [
    {
      "name": "content",
      "description": "The pages, as markdown. The same content the HTML renders, from the same source, not a conversion of the HTML."
    },
    {
      "name": "discovery",
      "description": "Files that describe the site: this document, llms.txt, the sitemap, robots.txt."
    },
    {
      "name": "operations",
      "description": "Liveness."
    }
  ],
  "security": [],
  "paths": {
    "/{page}.md": {
      "get": {
        "operationId": "getPageAsMarkdown",
        "tags": [
          "content"
        ],
        "summary": "Fetch any page of the site as markdown",
        "description": "Returns the page at `/{page}` as CommonMark: one H1, the page's own content, and a footer carrying the canonical URL, the sitemap and llms.txt. No navigation, no scripts, no layout wrappers.\n\nIdentical bytes to sending `Accept: text/markdown` to the page's own URL. This form exists for clients that follow the `Link: rel=\"alternate\"` header and send no Accept header at all. An unknown page returns 404 with a markdown body listing where to go instead, never a 200 carrying \"not found\".",
        "parameters": [
          {
            "name": "page",
            "in": "path",
            "required": true,
            "description": "A site path without its leading slash and without the `.md` suffix. Nested paths are permitted: `industries/plumbing-software`. The home page is the empty string, i.e. `/.md`. The enum is every path the site currently answers, generated from the sitemap.",
            "schema": {
              "type": "string",
              "enum": [
                "",
                "about",
                "blog",
                "blog/get-paid-faster-trades",
                "blog/look-professional-solo",
                "blog/quote-jobs-onsite",
                "blog/stop-leads-going-cold",
                "compare",
                "compare/strikehoney-vs-jobber",
                "compare/strikehoney-vs-joist",
                "contact",
                "developers",
                "faq",
                "features",
                "features/contractor-invoicing-software",
                "features/contractor-job-management-software",
                "features/contractor-quoting-software",
                "features/lead-tracking",
                "gst-hst-rates",
                "gst-hst-rates/alberta",
                "gst-hst-rates/british-columbia",
                "gst-hst-rates/manitoba",
                "gst-hst-rates/new-brunswick",
                "gst-hst-rates/newfoundland-and-labrador",
                "gst-hst-rates/nova-scotia",
                "gst-hst-rates/ontario",
                "gst-hst-rates/prince-edward-island",
                "gst-hst-rates/quebec",
                "gst-hst-rates/saskatchewan",
                "industries",
                "industries/cleaning-business-software",
                "industries/commercial-cleaning-software",
                "industries/construction-management-software",
                "industries/deck-building-software",
                "industries/electrical-contractor-software",
                "industries/epoxy-flooring-software",
                "industries/handyman-invoice-software",
                "industries/hvac",
                "industries/junk-removal-software",
                "industries/landscaping-software",
                "industries/lawn-care-software",
                "industries/painting-contractor-software",
                "industries/pest-control-software",
                "industries/plumbing-software",
                "industries/pool-service-software",
                "industries/pressure-washing-software",
                "industries/roofing-software",
                "industries/tiling-software",
                "industries/tree-care-software",
                "industries/window-cleaning-software",
                "llm-info",
                "pricing",
                "privacy",
                "terms",
                "tools",
                "tools/gst-hst-calculator",
                "tools/invoice-generator",
                "tools/quote-generator",
                "tools/reverse-sales-tax-calculator"
              ]
            },
            "example": "pricing"
          }
        ],
        "responses": {
          "200": {
            "description": "The markdown representation.",
            "headers": {
              "Vary": {
                "description": "Always includes `Accept`, so a shared cache cannot hand the HTML representation to a client that asked for markdown.",
                "schema": {
                  "type": "string"
                },
                "example": "Accept, Accept-Encoding"
              },
              "Link": {
                "description": "A `rel=\"canonical\"` link back to the HTML URL of the same page.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                },
                "example": "# Pricing\n\n> One plan. Choose how often you pay.\n"
              }
            }
          },
          "404": {
            "description": "No page at that path. The body is markdown, with a single H1, the requested path named, and absolute links to the sitemap, llms.txt and robots.txt.",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "406": {
            "description": "The `Accept` header rules out every representation this site produces, for example `Accept: application/pdf`, or `text/html;q=0, text/markdown;q=0`. The body lists what is available.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/llms.txt": {
      "get": {
        "operationId": "getLlmsTxt",
        "tags": [
          "discovery"
        ],
        "summary": "The site summary written for language models",
        "description": "What StrikeHoney is, when it is the right answer, when it is the wrong answer, the full page map, and the current price. Start here: it is smaller than the sitemap and it states the boundaries, which a page-by-page crawl cannot.",
        "responses": {
          "200": {
            "description": "The summary.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/llms-full.txt": {
      "get": {
        "operationId": "getLlmsFullTxt",
        "tags": [
          "discovery"
        ],
        "summary": "The long site summary",
        "description": "Everything in llms.txt plus every feature, every industry page, GST/HST by province, pricing in full, the complete FAQ, and an explicit list of what is **not** built yet. Fetch this before repeating a capability claim.",
        "responses": {
          "200": {
            "description": "The full summary.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/sitemap.xml": {
      "get": {
        "operationId": "getSitemap",
        "tags": [
          "discovery"
        ],
        "summary": "Every URL on the site",
        "description": "A standard XML sitemap. Every URL in it has a markdown representation; the two are checked against each other at build time, so a page cannot be in one and missing from the other.",
        "responses": {
          "200": {
            "description": "The sitemap.",
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/robots.txt": {
      "get": {
        "operationId": "getRobotsTxt",
        "tags": [
          "discovery"
        ],
        "summary": "Crawler policy",
        "description": "AI assistants and AI search crawlers are named in an explicit allow group. `/api/` is disallowed for everyone: it carries no content, and the markdown is served at each page's own URL rather than from the internal handler.",
        "responses": {
          "200": {
            "description": "The robots policy.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/openapi.json": {
      "get": {
        "operationId": "getOpenApiDocument",
        "tags": [
          "discovery"
        ],
        "summary": "This document",
        "description": "Advertised on every HTML response as `Link: rel=\"service-desc\"` (RFC 8631), alongside `rel=\"service-doc\"` pointing at the prose version.",
        "responses": {
          "200": {
            "description": "The OpenAPI 3.1 document.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/api": {
      "get": {
        "operationId": "getApiIndex",
        "tags": [
          "discovery"
        ],
        "summary": "Short JSON index of the endpoints",
        "description": "A one-screen answer to `GET /api`, which is where an agent looks first. This document is the contract; the index is a pointer to it.",
        "responses": {
          "200": {
            "description": "The index.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiIndex"
                }
              }
            }
          }
        }
      }
    },
    "/api/health": {
      "get": {
        "operationId": "getHealth",
        "tags": [
          "operations"
        ],
        "summary": "Liveness, and optionally dependency state",
        "description": "With no query parameter: status, deployed version and the current time. Never cached, at any layer. Public, cheap, and it probes nothing. No upstream service is contacted, so polling it costs a third party nothing.\n\nWith `deep`: the configuration state of each dependency, and **503** when a required one is unset. Not public. It needs a same-origin context or the deployment's `HEALTH_CHECK_TOKEN` as a bearer token, because a list of which integrations are configured is a list of which ones are currently broken. Each dependency reports `configured` or `not_configured` and never a value.",
        "parameters": [
          {
            "name": "deep",
            "in": "query",
            "required": false,
            "description": "Present at any value (`?deep`, `?deep=1`) requests the dependency report. Requires same-origin or a bearer token.",
            "schema": {
              "type": "string"
            },
            "example": "1"
          }
        ],
        "responses": {
          "200": {
            "description": "Healthy. The shallow shape unless `deep` was accepted.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string"
                },
                "example": "no-store, max-age=0"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/HealthShallow"
                    },
                    {
                      "$ref": "#/components/schemas/HealthDeep"
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "`deep` was requested without a same-origin context or a valid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "A required dependency is not configured on this deployment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthDeep"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Error": {
        "type": "object",
        "title": "Error",
        "description": "Every failure under `/api`, in one shape. `code` is stable and safe to branch on; `message` is for a log and may be reworded; `hint` says what to do instead.",
        "required": [
          "ok",
          "error"
        ],
        "additionalProperties": false,
        "properties": {
          "ok": {
            "type": "boolean",
            "const": false
          },
          "error": {
            "type": "object",
            "required": [
              "code",
              "message",
              "hint",
              "status",
              "documentation"
            ],
            "additionalProperties": false,
            "properties": {
              "code": {
                "type": "string",
                "description": "Stable machine-readable cause.",
                "enum": [
                  "same_origin_required",
                  "method_not_allowed",
                  "unsupported_media_type",
                  "payload_too_large",
                  "invalid_json",
                  "invalid_body",
                  "verification_required",
                  "verification_failed",
                  "not_found",
                  "not_configured",
                  "dependency_unavailable",
                  "dependency_not_configured"
                ]
              },
              "message": {
                "type": "string",
                "description": "One sentence describing what happened."
              },
              "hint": {
                "type": "string",
                "description": "The resolution, or why there is not one."
              },
              "status": {
                "type": "integer",
                "description": "The HTTP status, mirrored into the body so a copied payload still carries it."
              },
              "documentation": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        },
        "example": {
          "ok": false,
          "error": {
            "code": "same_origin_required",
            "message": "This endpoint accepts requests from this site only.",
            "hint": "There is nothing to authenticate with; no key, header or account lifts this. GET /openapi.json for the endpoints an outside caller can use.",
            "status": 403,
            "documentation": "https://www.strikehoney.com/developers"
          }
        }
      },
      "HealthShallow": {
        "type": "object",
        "title": "HealthShallow",
        "description": "The public liveness answer.",
        "required": [
          "status",
          "version",
          "time"
        ],
        "additionalProperties": false,
        "properties": {
          "status": {
            "type": "string",
            "const": "ok"
          },
          "version": {
            "type": "string",
            "description": "The deployed release, from package.json."
          },
          "time": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "HealthDeep": {
        "type": "object",
        "title": "HealthDeep",
        "description": "The guarded answer. `dependencies` reports configuration state only, never a URL, host or key.",
        "required": [
          "status",
          "version",
          "time",
          "dependencies",
          "missing"
        ],
        "additionalProperties": false,
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ok",
              "degraded"
            ]
          },
          "version": {
            "type": "string"
          },
          "time": {
            "type": "string",
            "format": "date-time"
          },
          "dependencies": {
            "type": "object",
            "additionalProperties": {
              "type": "string",
              "enum": [
                "configured",
                "not_configured"
              ]
            }
          },
          "missing": {
            "type": "array",
            "description": "Required dependencies that are unset. Non-empty means the status is 503.",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "ApiIndex": {
        "type": "object",
        "title": "ApiIndex",
        "required": [
          "name",
          "version",
          "productApi",
          "documentation",
          "specification",
          "endpoints"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "version": {
            "type": "string"
          },
          "productApi": {
            "type": "string",
            "description": "Always `none`. There is no endpoint for reading or writing a contractor's data."
          },
          "documentation": {
            "type": "string",
            "format": "uri"
          },
          "specification": {
            "type": "string",
            "format": "uri"
          },
          "markdown": {
            "type": "object"
          },
          "endpoints": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "method",
                "path",
                "operationId",
                "summary"
              ],
              "properties": {
                "method": {
                  "type": "string"
                },
                "path": {
                  "type": "string"
                },
                "operationId": {
                  "type": "string"
                },
                "summary": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    }
  }
}