{
  "openapi": "3.1.0",
  "info": {
    "title": "LocalSchema API",
    "version": "1.0.0",
    "summary": "Generate, validate and audit LocalBusiness JSON-LD structured data.",
    "description": "For AI website builders, coding agents and developers. Free, keyless endpoints generate schema.org LocalBusiness JSON-LD from structured fields, validate existing markup against Google's rich result requirements, audit a live page and return install snippets for LocalSchema hosted embeds (rate limits per client IP). Account endpoints (clients, locations, publishing, health checks) need a LocalSchema Pro API key sent as `Authorization: Bearer ls_…` or `X-API-Key: ls_…`; create keys under Settings → API keys. The same tools are available over MCP at https://local-schema-beacon.lovable.app/mcp (server card: https://local-schema-beacon.lovable.app/mcp/server-card).",
    "contact": {
      "name": "LocalSchema",
      "url": "https://local-schema-beacon.lovable.app/developers"
    }
  },
  "externalDocs": {
    "description": "Developer docs",
    "url": "https://local-schema-beacon.lovable.app/developers"
  },
  "servers": [
    {
      "url": "https://local-schema-beacon.lovable.app/api/public/v1"
    }
  ],
  "tags": [
    {
      "name": "Schema"
    },
    {
      "name": "Audit"
    },
    {
      "name": "Hosted embeds"
    },
    {
      "name": "Account",
      "description": "Pro API key required"
    }
  ],
  "paths": {
    "/business-types": {
      "get": {
        "operationId": "listBusinessTypes",
        "tags": [
          "Schema"
        ],
        "summary": "List supported business types",
        "description": "The 50 schema.org LocalBusiness subtypes LocalSchema supports, with labels and keywords for matching.",
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix time the window resets"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "count": {
                      "type": "integer"
                    },
                    "business_types": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/BusinessType"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/generate": {
      "post": {
        "operationId": "generateSchema",
        "tags": [
          "Schema"
        ],
        "summary": "Generate LocalBusiness JSON-LD",
        "description": "Builds schema.org JSON-LD from structured business fields and validates it. Returns the object, a ready-to-paste <script> tag and validation results. 60 requests/minute.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BusinessInput"
              },
              "example": {
                "businessType": "Dentist",
                "name": "Bright Smile Dental",
                "url": "https://brightsmile.example",
                "telephone": "+31 50 123 4567",
                "address": {
                  "streetAddress": "Oude Ebbingestraat 12",
                  "addressLocality": "Groningen",
                  "postalCode": "9712 HA",
                  "addressCountry": "NL"
                },
                "openingHours": [
                  {
                    "days": "Mo-Fr",
                    "opens": "08:30",
                    "closes": "17:30"
                  }
                ],
                "services": [
                  {
                    "name": "Check-up",
                    "price": 45
                  }
                ],
                "sameAs": [
                  "https://www.facebook.com/brightsmile"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix time the window resets"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "business_type": {
                      "type": "string"
                    },
                    "jsonld": {
                      "type": "object"
                    },
                    "script_tag": {
                      "type": "string",
                      "description": "<script type=\"application/ld+json\">…</script>"
                    },
                    "html_snippet": {
                      "type": "string",
                      "description": "script_tag preceded by an attribution comment — paste into <head>"
                    },
                    "powered_by_html": {
                      "type": "string",
                      "description": "Optional \"Powered by LocalSchema\" link for the page footer (never inside <head>)"
                    },
                    "validation": {
                      "$ref": "#/components/schemas/Validation"
                    },
                    "notes": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "testing": {
                      "type": "object",
                      "properties": {
                        "rich_results_test": {
                          "type": "string"
                        },
                        "schema_validator": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid fields",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/validate": {
      "post": {
        "operationId": "validateSchema",
        "tags": [
          "Schema"
        ],
        "summary": "Validate existing JSON-LD",
        "description": "Finds the LocalBusiness entity in JSON-LD (object or string), a <script> tag or a full HTML page and checks Google's required and recommended properties. 60 requests/minute.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "object",
                    "required": [
                      "jsonld"
                    ],
                    "properties": {
                      "jsonld": {
                        "oneOf": [
                          {
                            "type": "object"
                          },
                          {
                            "type": "string"
                          }
                        ]
                      }
                    }
                  },
                  {
                    "type": "object",
                    "required": [
                      "html"
                    ],
                    "properties": {
                      "html": {
                        "type": "string"
                      }
                    }
                  }
                ]
              },
              "example": {
                "jsonld": {
                  "@context": "https://schema.org",
                  "@type": "Plumber",
                  "name": "Lone Star Plumbing",
                  "telephone": "+1 512-555-0100"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix time the window resets"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidateResult"
                }
              }
            }
          },
          "422": {
            "description": "Nothing to validate",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/check-url": {
      "post": {
        "operationId": "checkUrl",
        "tags": [
          "Audit"
        ],
        "summary": "Health-check a live page",
        "description": "Fetches a public page, lists its JSON-LD, validates the LocalBusiness markup and detects LocalSchema hosted embeds. 10 requests/minute and 200/day.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri"
                  }
                }
              },
              "example": {
                "url": "https://example.com/contact"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix time the window resets"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PageCheck"
                }
              }
            }
          },
          "422": {
            "description": "Invalid or private address (bare IP addresses are refused)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: 10 per minute and 200 per day per client, and 60 anonymous checks per website per hour (redirects count against the site they lead to). Retry-After says when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Page could not be loaded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "checkUrlGet",
        "tags": [
          "Audit"
        ],
        "summary": "Health-check a live page (GET)",
        "parameters": [
          {
            "name": "url",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uri"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix time the window resets"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PageCheck"
                }
              }
            }
          },
          "422": {
            "description": "Invalid or private address (bare IP addresses are refused)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited: 10 per minute and 200 per day per client, and 60 anonymous checks per website per hour (redirects count against the site they lead to). Retry-After says when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Page could not be loaded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/embed-snippet": {
      "get": {
        "operationId": "getEmbedSnippet",
        "tags": [
          "Hosted embeds"
        ],
        "summary": "Install snippet for a hosted embed",
        "description": "Returns the <script> tag for a LocalSchema hosted embed key (loc_… for one location, org_… for a client with several) and whether it is serving. 120 requests/minute.",
        "parameters": [
          {
            "name": "key",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^(loc|org)_[a-z0-9]{6,32}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix time the window resets"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "found": {
                      "type": "boolean"
                    },
                    "key": {
                      "type": "string"
                    },
                    "kind": {
                      "type": "string",
                      "enum": [
                        "location",
                        "organization"
                      ]
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "ok",
                        "inactive",
                        "unpublished"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "script_url": {
                      "type": "string"
                    },
                    "json_url": {
                      "type": "string"
                    },
                    "script_tag": {
                      "type": "string"
                    },
                    "install": {
                      "type": "string"
                    },
                    "published_at": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "date-time"
                    },
                    "locations": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unknown key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Malformed key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/clients": {
      "get": {
        "operationId": "listClients",
        "tags": [
          "Account"
        ],
        "summary": "List clients",
        "description": "The agency's clients with location counts, the client-wide embed and the white-label report link. 240 requests/minute per key.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "account": {
                      "$ref": "#/components/schemas/Account"
                    },
                    "clients": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Client"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "No active Pro plan on the key's account, or location limit reached",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited for this key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "saveClient",
        "tags": [
          "Account"
        ],
        "summary": "Create or update a client",
        "description": "Creates a client, or updates one when client_id is set; optionally switches its white-label report link. 60 requests/minute per key.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Create a client (name required) or update one by client_id. Updates are partial: omitted fields keep their value, an empty string clears website or contact fields.",
                "additionalProperties": false,
                "properties": {
                  "client_id": {
                    "type": "string",
                    "description": "Update this client; omit to create one",
                    "format": "uuid"
                  },
                  "name": {
                    "type": "string",
                    "description": "Client (brand) name; required when creating",
                    "maxLength": 160
                  },
                  "website": {
                    "type": "string",
                    "description": "Brand website (https://…); links every branch to one Organization in the client embed",
                    "maxLength": 500
                  },
                  "contact_name": {
                    "type": "string",
                    "maxLength": 160
                  },
                  "contact_email": {
                    "type": "string",
                    "maxLength": 255
                  },
                  "report": {
                    "type": "boolean",
                    "description": "Turn the white-label report link on (true) or off (false); omit to leave it"
                  }
                }
              },
              "example": {
                "name": "Bright Smile Dental Group",
                "website": "https://brightsmile.example",
                "report": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "created": {
                      "type": "boolean"
                    },
                    "client": {
                      "$ref": "#/components/schemas/Client"
                    }
                  }
                }
              }
            }
          },
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "created": {
                      "type": "boolean"
                    },
                    "client": {
                      "$ref": "#/components/schemas/Client"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "No active Pro plan on the key's account, or location limit reached",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown client_id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Invalid fields",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited for this key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/locations": {
      "get": {
        "operationId": "listLocations",
        "tags": [
          "Account"
        ],
        "summary": "List saved locations",
        "description": "Saved locations with publish state, hosted embed and latest health check. 240 requests/minute per key.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "client_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "account": {
                      "$ref": "#/components/schemas/Account"
                    },
                    "locations": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Location"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "No active Pro plan on the key's account, or location limit reached",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited for this key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "saveLocation",
        "tags": [
          "Account"
        ],
        "summary": "Create or update a location",
        "description": "Saves a location (new ones count toward the plan's 25-location limit). With publish: true the hosted embed serves the new markup right away — client sites update without code changes. Pro output includes holiday hours, ratings and reviews. 60 requests/minute per key.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "business"
                ],
                "additionalProperties": false,
                "properties": {
                  "location_id": {
                    "type": "string",
                    "description": "Update this location; omit to create one",
                    "format": "uuid"
                  },
                  "client_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid",
                    "description": "Assign to a client; null removes it; omit to keep"
                  },
                  "website_url": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Page where the embed is installed (health checks scan it). Defaults to business.url."
                  },
                  "publish": {
                    "type": "boolean",
                    "default": false,
                    "description": "Publish to the hosted embed now"
                  },
                  "business": {
                    "$ref": "#/components/schemas/BusinessInput",
                    "description": "On update, only the fields you send change (address merges per field; lists replace)."
                  }
                }
              },
              "example": {
                "publish": true,
                "business": {
                  "businessType": "Dentist",
                  "name": "Bright Smile Dental — Centrum",
                  "url": "https://brightsmile.example/centrum",
                  "telephone": "+31 50 123 4567",
                  "address": {
                    "streetAddress": "Oude Ebbingestraat 12",
                    "addressLocality": "Groningen",
                    "postalCode": "9712 HA",
                    "addressCountry": "NL"
                  },
                  "openingHours": [
                    {
                      "days": "Mo-Fr",
                      "opens": "08:30",
                      "closes": "17:30"
                    }
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SaveLocationResult"
                }
              }
            }
          },
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SaveLocationResult"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "No active Pro plan on the key's account, or location limit reached",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown location_id or client_id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Invalid fields",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited for this key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/locations/{id}": {
      "get": {
        "operationId": "getLocation",
        "tags": [
          "Account"
        ],
        "summary": "Get a saved location",
        "description": "Business fields (same shape as /generate and POST /locations), current and published JSON-LD, validation and embed details.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "location": {
                      "$ref": "#/components/schemas/Location"
                    },
                    "business": {
                      "$ref": "#/components/schemas/BusinessInput"
                    },
                    "jsonld": {
                      "type": "object"
                    },
                    "published_jsonld": {
                      "type": [
                        "object",
                        "null"
                      ]
                    },
                    "validation": {
                      "$ref": "#/components/schemas/Validation"
                    },
                    "testing": {
                      "type": "object",
                      "properties": {
                        "rich_results_test": {
                          "type": "string"
                        },
                        "schema_validator": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "No active Pro plan on the key's account, or location limit reached",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown location",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited for this key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/locations/{id}/health-check": {
      "post": {
        "operationId": "runHealthCheck",
        "tags": [
          "Account"
        ],
        "summary": "Run a health check",
        "description": "Validates the location's markup, scans its website for the embed and conflicting hard-coded schema, flags unpublished changes and records the result (it appears in the dashboard and client reports). 60 checks/hour per key.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Recorded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthCheck"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "No active Pro plan on the key's account, or location limit reached",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown location",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited for this key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "LocalSchema Pro API key (ls_…). Create it under Settings → API keys."
      },
      "apiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "Alternative to the Authorization header."
      }
    },
    "schemas": {
      "BusinessInput": {
        "type": "object",
        "required": [
          "name"
        ],
        "additionalProperties": false,
        "properties": {
          "businessType": {
            "type": "string",
            "description": "Most specific schema.org type. See GET /business-types.",
            "default": "LocalBusiness",
            "examples": [
              "Dentist",
              "Plumber",
              "Restaurant"
            ]
          },
          "name": {
            "type": "string",
            "description": "Public business name",
            "maxLength": 200
          },
          "alternateName": {
            "type": "string"
          },
          "legalName": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "maxLength": 2000
          },
          "slogan": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "description": "Website URL",
            "format": "uri"
          },
          "logo": {
            "type": "string",
            "description": "Logo image URL",
            "format": "uri"
          },
          "images": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Photo URLs"
          },
          "foundingDate": {
            "type": "string",
            "description": "YYYY or YYYY-MM-DD"
          },
          "telephone": {
            "type": "string",
            "description": "International format",
            "examples": [
              "+31 50 123 4567"
            ]
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "priceRange": {
            "type": "string",
            "examples": [
              "€€",
              "€20-€80"
            ]
          },
          "currenciesAccepted": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "ISO 4217 codes, e.g. EUR"
          },
          "paymentAccepted": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "e.g. Cash, Credit card, iDEAL"
          },
          "knowsLanguage": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "e.g. nl, en"
          },
          "address": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "streetAddress": {
                "type": "string"
              },
              "addressLocality": {
                "type": "string",
                "description": "City"
              },
              "addressRegion": {
                "type": "string",
                "description": "State / province"
              },
              "postalCode": {
                "type": "string"
              },
              "addressCountry": {
                "type": "string",
                "description": "ISO 3166-1 alpha-2",
                "examples": [
                  "NL",
                  "US"
                ]
              }
            }
          },
          "serviceAreaOnly": {
            "type": "boolean",
            "description": "Service-area business without a storefront; the street address is hidden."
          },
          "geo": {
            "type": "object",
            "required": [
              "latitude",
              "longitude"
            ],
            "properties": {
              "latitude": {
                "type": "number",
                "minimum": -90,
                "maximum": 90
              },
              "longitude": {
                "type": "number",
                "minimum": -180,
                "maximum": 180
              }
            }
          },
          "hasMap": {
            "type": "string",
            "description": "Google Maps URL",
            "format": "uri"
          },
          "areaServed": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Cities, regions or postcodes served"
          },
          "openingHours": {
            "type": "array",
            "description": "Regular hours. Days not listed are closed; add two entries for split hours.",
            "items": {
              "type": "object",
              "required": [
                "days"
              ],
              "properties": {
                "days": {
                  "oneOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  ],
                  "examples": [
                    "Mo-Fr",
                    [
                      "Saturday"
                    ]
                  ]
                },
                "opens": {
                  "type": "string",
                  "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$",
                  "examples": [
                    "09:00"
                  ]
                },
                "closes": {
                  "type": "string",
                  "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$",
                  "examples": [
                    "09:00"
                  ]
                },
                "allDay": {
                  "type": "boolean",
                  "description": "Open 24 hours"
                }
              }
            }
          },
          "specialHours": {
            "type": "array",
            "description": "Holiday exceptions — LocalSchema Pro; omitted from free output.",
            "items": {
              "type": "object",
              "required": [
                "validFrom"
              ],
              "properties": {
                "label": {
                  "type": "string"
                },
                "validFrom": {
                  "type": "string",
                  "format": "date"
                },
                "validThrough": {
                  "type": "string",
                  "format": "date"
                },
                "closed": {
                  "type": "boolean"
                },
                "opens": {
                  "type": "string",
                  "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$",
                  "examples": [
                    "09:00"
                  ]
                },
                "closes": {
                  "type": "string",
                  "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$",
                  "examples": [
                    "09:00"
                  ]
                }
              }
            }
          },
          "services": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "name"
              ],
              "properties": {
                "name": {
                  "type": "string"
                },
                "description": {
                  "type": "string"
                },
                "price": {
                  "type": "number",
                  "minimum": 0
                },
                "priceMax": {
                  "type": "number",
                  "minimum": 0
                },
                "priceType": {
                  "type": "string",
                  "enum": [
                    "fixed",
                    "from",
                    "range"
                  ]
                },
                "unitText": {
                  "type": "string",
                  "examples": [
                    "per hour"
                  ]
                },
                "url": {
                  "type": "string",
                  "format": "uri"
                }
              }
            }
          },
          "rating": {
            "type": "object",
            "description": "Aggregate rating — LocalSchema Pro; omitted from free output.",
            "required": [
              "ratingValue",
              "reviewCount"
            ],
            "properties": {
              "ratingValue": {
                "type": "number"
              },
              "reviewCount": {
                "type": "integer",
                "minimum": 0
              },
              "bestRating": {
                "type": "number"
              }
            }
          },
          "reviews": {
            "type": "array",
            "description": "LocalSchema Pro; omitted from free output.",
            "items": {
              "type": "object",
              "required": [
                "author",
                "rating"
              ],
              "properties": {
                "author": {
                  "type": "string"
                },
                "rating": {
                  "type": "number"
                },
                "body": {
                  "type": "string"
                },
                "datePublished": {
                  "type": "string",
                  "format": "date"
                }
              }
            }
          },
          "sameAs": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Google Business Profile, Yelp, Facebook, LinkedIn… URLs"
          },
          "servesCuisine": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Restaurants"
          },
          "menu": {
            "type": "string",
            "description": "Restaurants: menu URL",
            "format": "uri"
          },
          "acceptsReservations": {
            "type": "boolean"
          },
          "medicalSpecialty": {
            "type": "string",
            "description": "Medical businesses, e.g. Dentistry"
          },
          "isAcceptingNewPatients": {
            "type": "boolean"
          }
        }
      },
      "BusinessType": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "category": {
            "type": "string"
          },
          "parent": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "keywords": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "Issue": {
        "type": "object",
        "properties": {
          "severity": {
            "type": "string",
            "enum": [
              "error",
              "warning",
              "info"
            ]
          },
          "property": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "message": {
            "type": "string"
          }
        }
      },
      "Validation": {
        "type": "object",
        "properties": {
          "valid": {
            "type": "boolean",
            "description": "No errors"
          },
          "rich_result_status": {
            "type": "string",
            "enum": [
              "eligible",
              "eligible_with_warnings",
              "not_eligible"
            ]
          },
          "score": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "errors": {
            "type": "integer"
          },
          "warnings": {
            "type": "integer"
          },
          "issues": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Issue"
            }
          }
        }
      },
      "ValidateResult": {
        "type": "object",
        "properties": {
          "found": {
            "type": "boolean",
            "description": "A LocalBusiness (or subtype) entity was found"
          },
          "source_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "business_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "candidates": {
            "type": "integer"
          },
          "validation": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Validation"
              },
              {
                "type": "null"
              }
            ]
          },
          "notes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "error": {
            "type": "string"
          }
        }
      },
      "PageCheck": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "url": {
            "type": "string"
          },
          "final_url": {
            "type": "string"
          },
          "http_status": {
            "type": [
              "integer",
              "null"
            ]
          },
          "checked_at": {
            "type": "string",
            "format": "date-time"
          },
          "jsonld_blocks": {
            "type": "integer"
          },
          "invalid_blocks": {
            "type": "integer"
          },
          "types_found": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "localschema_embed": {
            "type": "object",
            "properties": {
              "found": {
                "type": "boolean"
              },
              "keys": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "business": {
            "$ref": "#/components/schemas/ValidateResult"
          },
          "recommendations": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "testing": {
            "type": "object",
            "properties": {
              "rich_results_test": {
                "type": "string"
              },
              "schema_validator": {
                "type": "string"
              }
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "bad_request",
                  "invalid_input",
                  "not_found",
                  "payload_too_large",
                  "rate_limited",
                  "unavailable",
                  "missing_api_key",
                  "invalid_api_key",
                  "pro_required",
                  "location_limit"
                ]
              },
              "message": {
                "type": "string"
              },
              "details": {}
            }
          }
        }
      },
      "Account": {
        "type": "object",
        "properties": {
          "agency_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "plan": {
            "type": "string",
            "enum": [
              "pro",
              "free"
            ]
          },
          "locations": {
            "type": "integer"
          },
          "location_limit": {
            "type": "integer"
          },
          "clients": {
            "type": "integer"
          }
        }
      },
      "Client": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "website": {
            "type": [
              "string",
              "null"
            ]
          },
          "contact_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "contact_email": {
            "type": [
              "string",
              "null"
            ]
          },
          "locations": {
            "type": "integer"
          },
          "embed": {
            "type": "object",
            "properties": {
              "key": {
                "type": "string",
                "description": "org_…"
              },
              "script_tag": {
                "type": "string"
              },
              "note": {
                "type": "string"
              }
            }
          },
          "report_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "White-label health report link, when switched on"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Location": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "business_type": {
            "type": "string"
          },
          "client_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "client_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "website_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "city": {
            "type": [
              "string",
              "null"
            ]
          },
          "published": {
            "type": "boolean"
          },
          "published_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "embed": {
            "type": "object",
            "properties": {
              "key": {
                "type": "string",
                "description": "loc_…"
              },
              "enabled": {
                "type": "boolean"
              },
              "script_tag": {
                "type": "string"
              },
              "requests": {
                "type": "integer"
              },
              "last_requested_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              }
            }
          },
          "latest_check": {
            "$ref": "#/components/schemas/LatestCheck"
          },
          "dashboard_url": {
            "type": "string"
          }
        }
      },
      "LatestCheck": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "checked_at": {
            "type": "string",
            "format": "date-time"
          },
          "score": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "status": {
            "type": "string",
            "enum": [
              "healthy",
              "warning",
              "critical"
            ]
          },
          "rich_result_status": {
            "type": "string",
            "enum": [
              "eligible",
              "eligible_with_warnings",
              "not_eligible"
            ]
          },
          "embed_status": {
            "type": "string",
            "enum": [
              "live",
              "detected",
              "not_detected",
              "unreachable",
              "no_website",
              "unpublished"
            ]
          },
          "error_count": {
            "type": "integer"
          },
          "warning_count": {
            "type": "integer"
          },
          "drift_count": {
            "type": "integer",
            "description": "Changes saved but not published"
          }
        }
      },
      "SaveLocationResult": {
        "type": "object",
        "properties": {
          "created": {
            "type": "boolean"
          },
          "published": {
            "type": "boolean"
          },
          "location": {
            "$ref": "#/components/schemas/Location"
          },
          "validation": {
            "$ref": "#/components/schemas/Validation"
          },
          "notes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "HealthCheck": {
        "type": "object",
        "properties": {
          "check": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "location_id": {
                "type": "string",
                "format": "uuid"
              },
              "checked_at": {
                "type": "string",
                "format": "date-time"
              },
              "score": {
                "type": "integer"
              },
              "status": {
                "type": "string",
                "enum": [
                  "healthy",
                  "warning",
                  "critical"
                ]
              },
              "rich_result_status": {
                "type": "string"
              },
              "embed_status": {
                "type": "string"
              },
              "embed_meaning": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "error_count": {
                "type": "integer"
              },
              "warning_count": {
                "type": "integer"
              },
              "issues": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "severity": {
                      "type": "string"
                    },
                    "title": {
                      "type": "string"
                    },
                    "property": {
                      "type": "string"
                    }
                  }
                }
              },
              "unpublished_changes": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "path": {
                      "type": "string"
                    },
                    "kind": {
                      "type": "string"
                    }
                  }
                }
              },
              "site": {
                "type": [
                  "object",
                  "null"
                ]
              }
            }
          },
          "dashboard_url": {
            "type": "string"
          }
        }
      }
    }
  }
}