{
  "openapi": "3.1.0",
  "info": {
    "title": "intelliThings website API",
    "version": "1.0.0",
    "summary": "Starter Pack catalog, website use-case check and sales enquiries for intelliThings.",
    "description": "Public API of intellithings.io, the website of the intelliThings IoT platform. Agents can look up the four Starter Pack pilot scopes, run the AI use-case check for a company website and send a sales enquiry on behalf of a user who asked to be contacted.\n\nNo authentication is needed. Requests are rate limited per IP address in a sliding 10-minute window: 6 use-case checks (analyze and refine together) and 5 sales enquiries. Exceeding a limit returns 429. Browser requests from other websites are rejected; server-side clients send no Origin header.\n\nThe intelliThings CORE platform (devices, MQTT, dashboards, workflows) is documented separately at https://docs.intellithings.io/docs.",
    "contact": {
      "name": "intelliThings sales",
      "email": "contact@mycistern.com",
      "url": "https://intellithings.io/contact"
    },
    "termsOfService": "https://intellithings.io/legal"
  },
  "externalDocs": {
    "description": "Guidance for AI agents (llms.txt)",
    "url": "https://intellithings.io/llms.txt"
  },
  "security": [],
  "servers": [
    {
      "url": "https://intellithings.io",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "Catalog",
      "description": "Starter Pack pilot scopes."
    },
    {
      "name": "Use-case check",
      "description": "AI review of a public company website that suggests IoT and AI opportunities."
    },
    {
      "name": "Sales",
      "description": "Enquiries to the intelliThings sales team."
    },
    {
      "name": "Service",
      "description": "Operational status."
    }
  ],
  "paths": {
    "/api/health": {
      "get": {
        "operationId": "getServiceHealth",
        "summary": "Check which website services are available",
        "description": "Returns whether the AI-backed services (chat, use-case check) and sales enquiries are currently configured. Call it before the other operations if a previous call returned 503.",
        "tags": [
          "Service"
        ],
        "responses": {
          "200": {
            "description": "Service status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Health"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/data/starter-catalog.json": {
      "get": {
        "operationId": "getStarterPackCatalog",
        "summary": "List the Starter Pack pilot scopes",
        "description": "Returns the published Starter Pack catalog: four proposed pilot scopes (asset tracking, level monitoring, PLC data, machine vision) with hardware, requirements and exclusions. Scopes are proposals for discussion with sales; prices are not published (null means unknown, never free) and nothing can be bought online.",
        "tags": [
          "Catalog"
        ],
        "responses": {
          "200": {
            "description": "The Starter Pack catalog.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StarterCatalog"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/api/readiness/analyze": {
      "post": {
        "operationId": "analyzeWebsiteUseCases",
        "summary": "Suggest IoT and AI use cases from a company website",
        "description": "Fetches up to a few public pages of the given website, then returns an indicative assessment in five areas, three to five follow-up questions and up to three opportunities. Every evidence quote is an exact excerpt of a fetched page. Scores stay null until the visitor provides information (context or answers), so a website-only check has no overall score. Usually takes one to two minutes; very slow websites can take up to about four, so allow a client timeout of at least 300 seconds. Pages rendered by JavaScript are read from their page data and script bundles. The returned analysis_id stays valid for 30 minutes for refineWebsiteUseCases.",
        "tags": [
          "Use-case check"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AnalyzeRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Initial assessment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Assessment"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/WebsiteNotAnalysable"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "502": {
            "$ref": "#/components/responses/AIFailed"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          },
          "504": {
            "$ref": "#/components/responses/Timeout"
          }
        }
      }
    },
    "/api/readiness/refine": {
      "post": {
        "operationId": "refineWebsiteUseCases",
        "summary": "Refine a use-case check with answers to its questions",
        "description": "Sends answers to the current follow-up questions of an earlier analyzeWebsiteUseCases result. Returns an updated assessment with scores for the areas the answers cover and zero to five further questions. Answer only questions from the most recent result; question IDs continue across rounds.",
        "tags": [
          "Use-case check"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RefineRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Refined assessment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Assessment"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "410": {
            "description": "The analysis expired (after 30 minutes) or is unknown. Run analyzeWebsiteUseCases again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "502": {
            "$ref": "#/components/responses/AIFailed"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          },
          "504": {
            "$ref": "#/components/responses/Timeout"
          }
        }
      }
    },
    "/api/sales": {
      "post": {
        "operationId": "createSalesEnquiry",
        "summary": "Send an enquiry to the intelliThings sales team",
        "description": "Delivers an enquiry to the sales team, who call the person back. Use it only when the user explicitly asks to be contacted, and only with their real details. It does not place an order or start a pilot. Retrying with the same request_id and identical fields is safe and does not create a second enquiry; reusing a request_id with changed fields returns 409.",
        "tags": [
          "Sales"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SalesEnquiry"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The enquiry was delivered to the sales team.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SalesConfirmation"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "The request_id was already used for an enquiry with different fields. Generate a new request_id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Error": {
        "type": "object",
        "description": "Error envelope used by every operation.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "description": "Stable machine-readable error code, for example invalid_url, too_many_requests or analysis_expired.",
                "examples": [
                  "invalid_url"
                ]
              },
              "message": {
                "type": "string",
                "description": "Human-readable explanation that can be shown to the user."
              }
            }
          }
        }
      },
      "Health": {
        "type": "object",
        "required": [
          "ok",
          "configured",
          "services"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "description": "Always true when the server answers."
          },
          "configured": {
            "type": "boolean",
            "description": "Whether the AI backend is configured."
          },
          "services": {
            "type": "object",
            "required": [
              "chat",
              "readiness",
              "sales"
            ],
            "properties": {
              "chat": {
                "type": "boolean",
                "description": "Vidya chat on the website is available."
              },
              "readiness": {
                "type": "boolean",
                "description": "analyzeWebsiteUseCases and refineWebsiteUseCases are available."
              },
              "sales": {
                "type": "boolean",
                "description": "createSalesEnquiry can deliver enquiries."
              }
            }
          }
        }
      },
      "StarterCatalog": {
        "type": "object",
        "required": [
          "catalog_version",
          "notice",
          "products"
        ],
        "properties": {
          "catalog_version": {
            "type": "string",
            "description": "Version of the catalog content.",
            "examples": [
              "0.2.0"
            ]
          },
          "language": {
            "type": "string",
            "description": "Language of the catalog texts.",
            "examples": [
              "en"
            ]
          },
          "mode": {
            "type": "string",
            "description": "Publication mode. public_consultation means scopes are discussed with sales, not sold online."
          },
          "status": {
            "type": "string",
            "description": "Approval status of the scopes.",
            "examples": [
              "proposed"
            ]
          },
          "notice": {
            "type": "string",
            "description": "Notice to show alongside any pack information."
          },
          "shared_package": {
            "type": "object",
            "description": "Services included in every Starter Pack pilot, such as the 30-day platform access.",
            "additionalProperties": true
          },
          "account_offer": {
            "type": "object",
            "description": "Proposed platform trial without new hardware.",
            "additionalProperties": true
          },
          "products": {
            "type": "array",
            "description": "The Starter Packs.",
            "items": {
              "$ref": "#/components/schemas/StarterPack"
            }
          }
        }
      },
      "StarterPack": {
        "type": "object",
        "required": [
          "id",
          "name",
          "customer_outcome",
          "pilot_scope",
          "product_url",
          "purchase_ready"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/StarterPackId"
          },
          "name": {
            "type": "string",
            "examples": [
              "Level Monitoring Starter Pack"
            ]
          },
          "status": {
            "type": "string",
            "examples": [
              "proposed"
            ]
          },
          "customer_outcome": {
            "type": "string",
            "description": "What the customer learns or achieves in the pilot."
          },
          "best_for": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Typical assets or situations the pack fits."
          },
          "pilot_scope": {
            "type": "string",
            "description": "Proposed size of the pilot."
          },
          "hardware": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "quantity": {
                  "type": "integer"
                },
                "item": {
                  "type": "string"
                },
                "model": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Null until a model is confirmed."
                }
              }
            }
          },
          "connectivity": {
            "type": "string"
          },
          "software": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "ai": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "AI functions within the pilot."
          },
          "first_result": {
            "type": "string",
            "description": "The first tangible result of the pilot."
          },
          "requirements": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Prerequisites the customer site must meet."
          },
          "not_included": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Explicit exclusions. Never promise these."
          },
          "candidate_interfaces_not_confirmed": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Interfaces that may work but are not confirmed."
          },
          "verified_supported_interfaces": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "expansion": {
            "type": "string"
          },
          "price": {
            "type": [
              "number",
              "null"
            ],
            "description": "Null means the price is agreed with sales; it never means free."
          },
          "product_url": {
            "type": "string",
            "description": "Detail page, relative to https://intellithings.io/.",
            "examples": [
              "starter-packs/index.html#level-monitoring"
            ]
          },
          "checkout_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "Always null: there is no online checkout."
          },
          "purchase_ready": {
            "type": "boolean",
            "description": "Always false: packs are requested through createSalesEnquiry."
          }
        }
      },
      "StarterPackId": {
        "type": "string",
        "description": "Stable Starter Pack identifier.",
        "enum": [
          "asset-tracking-start",
          "level-monitoring-start",
          "plc-data-start",
          "machine-vision-start"
        ]
      },
      "Language": {
        "type": "string",
        "enum": [
          "en",
          "de"
        ],
        "default": "en",
        "description": "Language of the visitor-facing texts in the result. German results address the reader formally."
      },
      "AnalyzeRequest": {
        "type": "object",
        "required": [
          "url"
        ],
        "additionalProperties": false,
        "properties": {
          "url": {
            "type": "string",
            "maxLength": 2048,
            "description": "Public company website. https:// is added when no scheme is given. Only standard ports, no credentials, no private network addresses.",
            "examples": [
              "https://example.com"
            ]
          },
          "language": {
            "$ref": "#/components/schemas/Language"
          },
          "context": {
            "type": "string",
            "maxLength": 2000,
            "description": "Optional information from the user about their company or goal. It counts as visitor evidence and can enable scores."
          }
        }
      },
      "RefineRequest": {
        "type": "object",
        "required": [
          "analysis_id",
          "answers"
        ],
        "additionalProperties": false,
        "properties": {
          "analysis_id": {
            "type": "string",
            "maxLength": 100,
            "description": "analysis_id from the latest assessment."
          },
          "language": {
            "$ref": "#/components/schemas/Language"
          },
          "answers": {
            "type": "array",
            "minItems": 1,
            "maxItems": 5,
            "description": "Answers to questions of the latest assessment, each question at most once.",
            "items": {
              "type": "object",
              "required": [
                "question_id",
                "text"
              ],
              "additionalProperties": false,
              "properties": {
                "question_id": {
                  "type": "string",
                  "description": "id of a question in the latest assessment.",
                  "examples": [
                    "q1"
                  ]
                },
                "text": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 2000,
                  "description": "The user's answer."
                }
              }
            }
          }
        }
      },
      "Assessment": {
        "type": "object",
        "required": [
          "analysis_id",
          "phase",
          "website_url",
          "summary",
          "overall_score",
          "coverage_percent",
          "dimensions",
          "sources",
          "questions",
          "opportunities"
        ],
        "properties": {
          "analysis_id": {
            "type": "string",
            "description": "Pass to refineWebsiteUseCases within 30 minutes."
          },
          "phase": {
            "type": "string",
            "enum": [
              "initial",
              "refined"
            ]
          },
          "website_url": {
            "type": "string",
            "description": "Normalised address of the analysed website."
          },
          "summary": {
            "type": "string",
            "description": "At most three sentences."
          },
          "overall_score": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 100,
            "description": "Weighted score over the scored areas; null until at least two areas are scored."
          },
          "coverage_percent": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100,
            "description": "Share of the weighting covered by scored areas."
          },
          "dimensions": {
            "type": "array",
            "minItems": 5,
            "maxItems": 5,
            "items": {
              "$ref": "#/components/schemas/Dimension"
            }
          },
          "sources": {
            "type": "array",
            "description": "Pages that were fetched and may be quoted.",
            "items": {
              "type": "object",
              "required": [
                "id",
                "url",
                "title"
              ],
              "properties": {
                "id": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "title": {
                  "type": "string"
                }
              }
            }
          },
          "questions": {
            "type": "array",
            "maxItems": 5,
            "description": "Open follow-up questions to answer with refineWebsiteUseCases.",
            "items": {
              "type": "object",
              "required": [
                "id",
                "dimension",
                "text",
                "reason"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "examples": [
                    "q1"
                  ]
                },
                "dimension": {
                  "$ref": "#/components/schemas/DimensionId"
                },
                "text": {
                  "type": "string"
                },
                "reason": {
                  "type": "string",
                  "description": "Why the answer changes the assessment."
                }
              }
            }
          },
          "opportunities": {
            "type": "array",
            "maxItems": 3,
            "items": {
              "type": "object",
              "required": [
                "title",
                "description"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "description": {
                  "type": "string",
                  "description": "Includes the prerequisite or condition."
                }
              }
            }
          }
        }
      },
      "DimensionId": {
        "type": "string",
        "enum": [
          "connectivity",
          "data",
          "processes",
          "monetisation",
          "organisation"
        ],
        "description": "Assessment area. Weights: connectivity 25, data 20, processes 20, monetisation 20, organisation 15."
      },
      "Dimension": {
        "type": "object",
        "required": [
          "id",
          "score",
          "reason",
          "evidence",
          "unknowns"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/DimensionId"
          },
          "score": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 100,
            "description": "Null when the user has not provided information for this area."
          },
          "reason": {
            "type": "string"
          },
          "evidence": {
            "type": "array",
            "maxItems": 2,
            "description": "Exact quotes from a fetched page (source_id) or from the user's answers (answer_id).",
            "items": {
              "type": "object",
              "required": [
                "quote"
              ],
              "properties": {
                "source_id": {
                  "type": "string"
                },
                "answer_id": {
                  "type": "string"
                },
                "quote": {
                  "type": "string"
                }
              }
            }
          },
          "unknowns": {
            "type": "array",
            "maxItems": 2,
            "items": {
              "type": "string"
            }
          }
        }
      },
      "SalesEnquiry": {
        "type": "object",
        "required": [
          "request_id",
          "name",
          "email",
          "phone",
          "message"
        ],
        "properties": {
          "request_id": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{16,100}$",
            "description": "Client-generated idempotency key, for example a random UUID without separators. Reuse it only to retry the identical enquiry."
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "Full name of the person to call back."
          },
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 254,
            "description": "Work email address; used as Reply-To."
          },
          "phone": {
            "type": "string",
            "pattern": "^[0-9+()./\\- ]{5,40}$",
            "description": "Phone number for the call back, international format preferred.",
            "examples": [
              "+49 30 123456"
            ]
          },
          "company": {
            "type": "string",
            "maxLength": 180,
            "description": "Company name of the person."
          },
          "message": {
            "type": "string",
            "minLength": 1,
            "maxLength": 4000,
            "description": "What the person wants to achieve or discuss."
          },
          "product_id": {
            "$ref": "#/components/schemas/StarterPackId",
            "description": "Starter Pack the enquiry is about. Omit for a general enquiry."
          },
          "summary": {
            "type": "string",
            "maxLength": 4000,
            "description": "Optional summary of the conversation so far, reviewed by the user."
          },
          "source": {
            "type": "string",
            "maxLength": 300,
            "description": "Where the enquiry came from, for example the name of the agent or app."
          },
          "attribution": {
            "type": "object",
            "description": "Campaign details collected by the website itself (UTM values as quelle, medium, kampagne, inhalt, begriff; landingpage; tracking = consent to advertising; click IDs gclid, li_fat_id only with tracking true). Agents omit it; malformed values are ignored."
          }
        }
      },
      "SalesConfirmation": {
        "type": "object",
        "required": [
          "ok",
          "request_id",
          "message"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "request_id": {
            "type": "string"
          },
          "message": {
            "type": "string",
            "examples": [
              "Your enquiry has been sent to our sales team. We will call you back."
            ]
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "The request body or a field is invalid. The error code names the problem (for example invalid_url, invalid_field, invalid_email, invalid_product, invalid_answers, invalid_content_type).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "The request came from a browser on another website (origin_denied) or used an unknown host name (host_denied).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Rate limit reached for this IP address. Wait several minutes before retrying.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "WebsiteNotAnalysable": {
        "description": "The website could not be analysed: website_unreachable (address not reachable), website_blocked (the site refuses automated requests), website_no_content (too little readable text), website_not_html or website_redirect.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "AIFailed": {
        "description": "The AI result was incomplete or invalid. Retrying once is reasonable.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unavailable": {
        "description": "The service is busy or not configured. Check getServiceHealth and retry later.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Timeout": {
        "description": "website_timeout: the analysed website did not respond within about 70 seconds; ai_timeout: the AI did not finish in time.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  }
}
