openapi: "3.1.0"
info:
  title: "Clarm Public API"
  version: "1.0.0"
  summary: "Ask the Clarm agent, record widget analytics, and join Clarm launch programmes."
  description: |-
    Public, same-origin HTTP endpoints served from https://clarm.com by Cloudflare Pages Functions.

    Clarm Atlas lets non-technical teams build AI coworker agents using building blocks pre-approved
    by IT and compliance, giving teams the freedom to deploy complete agents straight into production.
    This API is the machine-readable front door to that product: `POST /api/agent/chat` answers
    questions about Clarm with citations, and the remaining endpoints cover widget analytics and
    commercial sign-up flows.

    Companion machine-readable resources:

    - `https://clarm.com/llms.txt` - curated site summary for language models
    - `https://clarm.com/llms-full.txt` - extended reference including case studies and pricing
    - `https://clarm.com/sitemap.xml` - full URL inventory
    - `https://clarm.com/developers/` - human-readable developer hub

    Authenticated product APIs (workspaces, agents, conversations, API keys) live on
    https://app.clarm.com and require a dashboard session or an org-scoped API key. They are not
    part of this public spec.
  termsOfService: "https://clarm.com/tos/"
  contact:
    name: "Clarm developer support"
    email: "support@clarm.com"
    url: "https://clarm.com/contact/"
  license:
    name: "Clarm API terms of service"
    url: "https://clarm.com/tos/"
externalDocs:
  description: "Clarm developer hub"
  url: "https://clarm.com/developers/"
servers:
  - url: "https://clarm.com"
    description: "Production"
tags:
  - name: "Agent"
    description: "Ask Clarm questions in natural language and get an answer with source citations."
  - name: "Analytics"
    description: "Widget and site analytics ingestion plus the region an analytics client should use."
  - name: "Signup"
    description: "Waitlist and demo-booking entry points for the commercial motion."
  - name: "Discovery"
    description: "Machine-readable descriptions of the site and this API."
paths:
  "/api/agent/chat":
    get:
      operationId: "getAgentChatServiceInfo"
      tags:
        - "Agent"
      summary: "Describe the agent chat endpoint"
      description: "Returns a self-describing document for the agent chat endpoint: the HTTP method, request shape, a copy-pasteable curl example, and links to the llms.txt resources. Useful as a zero-cost probe before sending a real question."
      responses:
        "200":
          description: "Usage document for POST /api/agent/chat."
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/AgentChatServiceInfo"
    post:
      operationId: "askClarmAgent"
      tags:
        - "Agent"
      summary: "Ask the Clarm agent a question"
      description: "Sends one natural-language question to the Clarm agent and returns the complete answer with source citations. No authentication required. The agent answers questions about the Clarm product, pricing, integrations, compliance posture, and use cases, grounded in the approved content indexed for the public clarm.com agent. Rate limited at the edge: 20 requests per minute per client IP and 200 requests per hour across the endpoint. Over-limit requests return 429 with a Retry-After header carrying the number of seconds to wait."
      requestBody:
        required: true
        description: "The question to ask, as JSON."
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/AgentChatRequest"
            examples:
              product:
                summary: "Product and pricing question"
                value:
                  message: "What does Clarm do and how much does it cost?"
              compliance:
                summary: "Compliance question"
                value:
                  message: "Can Clarm be deployed on premises for a regulated bank?"
      responses:
        "200":
          description: "The agent answered."
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/AgentChatResponse"
        "400":
          description: "The body was not JSON, carried no question, or the question exceeded 2000 characters."
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        "413":
          description: "The request body exceeded the 8 KiB limit."
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        "429":
          description: "Rate limit exceeded for this client IP or for the endpoint as a whole."
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
          headers:
            Retry-After:
              description: "Seconds to wait before retrying."
              schema:
                type: "integer"
                minimum: 1
                examples:
                  - 30
        "500":
          description: "The agent produced no answer, or the request failed unexpectedly."
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        "502":
          description: "The upstream agent could not be reached."
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
    options:
      operationId: "preflightAgentChat"
      tags:
        - "Agent"
      summary: "CORS preflight for the agent chat endpoint"
      description: "Returns the CORS policy for browser clients on clarm.com and its subdomains."
      responses:
        "204":
          description: "Preflight accepted. No body."
  "/api/track":
    post:
      operationId: "trackVisitorEvent"
      tags:
        - "Analytics"
      summary: "Record a widget or site analytics event"
      description: "Records one visitor event (page view, widget open, message sent, identify, purchase) against a Clarm widget. Requests are proxied same-origin to the Clarm analytics service, which authenticates the caller from the widget token or widget id in the payload. Events for a visitor who has opted out should carry `tracking_disabled: true`, which is accepted and discarded."
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/TrackEventRequest"
            examples:
              pageView:
                summary: "Page view"
                value:
                  event: "page_view"
                  visitor_id: "v_8f2c19a4"
                  widget_id: "1CwmcOQ75KJq"
                  page_url: "https://example.com/pricing"
                  page_title: "Pricing"
              identify:
                summary: "Identify a known visitor"
                value:
                  event: "identify"
                  visitor_id: "v_8f2c19a4"
                  widget_id: "1CwmcOQ75KJq"
                  data:
                    email: "operator@example.com"
                    company: "Example GmbH"
      responses:
        "200":
          description: "Event accepted, or discarded because the visitor opted out."
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/TrackEventResponse"
        "400":
          description: "The payload was missing `event` or `visitor_id`, or the widget id was malformed."
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        "401":
          description: "No usable widget credential was supplied, or the widget token failed verification."
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        "403":
          description: "The widget is not permitted to record this event."
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        "429":
          description: "Too many events for this widget and visitor."
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
          headers:
            Retry-After:
              description: "Seconds to wait before retrying."
              schema:
                type: "integer"
                minimum: 1
                examples:
                  - 30
        "500":
          description: "The event could not be recorded."
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        "502":
          description: "The analytics service could not be reached."
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
    get:
      operationId: "pingTrackingEndpoint"
      tags:
        - "Analytics"
      summary: "Liveness probe for the tracking endpoint"
      description: "Returns 204 with the CORS policy applied. Used by the widget loader to confirm the same-origin tracking path is reachable before it starts batching events."
      responses:
        "204":
          description: "The tracking endpoint is reachable. No body."
    options:
      operationId: "preflightTrackingEndpoint"
      tags:
        - "Analytics"
      summary: "CORS preflight for the tracking endpoint"
      description: "Returns the CORS policy for browser clients posting analytics events."
      responses:
        "204":
          description: "Preflight accepted. No body."
  "/api/analytics-region":
    get:
      operationId: "getAnalyticsRegion"
      tags:
        - "Analytics"
      summary: "Resolve the analytics region for the caller"
      description: "Returns the analytics region a client should send events to, derived from the edge geo headers on the request (`cf-ipcountry`, falling back to `x-vercel-ip-country`). `eu` is returned for callers in the EEA and the United Kingdom, where consent gating applies; `non_eu` is returned otherwise, including when the country is unknown. The response is never cached."
      responses:
        "200":
          description: "The region resolved for this caller."
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/AnalyticsRegionResponse"
  "/api/waitlist":
    post:
      operationId: "joinLaunchWaitlist"
      tags:
        - "Signup"
      summary: "Join the Clarm launch waitlist"
      description: "Adds a work email to the Clarm launch waitlist. The body is form-encoded to match the HTML form that posts to it. The email is validated at the edge before it is forwarded to the mailing provider."
      requestBody:
        required: true
        content:
          "application/x-www-form-urlencoded":
            schema:
              "$ref": "#/components/schemas/WaitlistRequest"
            examples:
              signup:
                summary: "Waitlist signup"
                value: "email=operator%40example.com&userGroup=waitlist"
      responses:
        "200":
          description: "The address was accepted by the mailing provider."
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/WaitlistResponse"
        "400":
          description: "The email address was missing or malformed."
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/WaitlistResponse"
        "502":
          description: "The mailing provider could not be reached."
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/WaitlistResponse"
    options:
      operationId: "preflightWaitlist"
      tags:
        - "Signup"
      summary: "CORS preflight for the waitlist endpoint"
      description: "Returns the CORS policy for browser clients submitting the waitlist form."
      responses:
        "204":
          description: "Preflight accepted. No body."
  "/api/demo-booking":
    post:
      operationId: "bookProductDemo"
      tags:
        - "Signup"
      summary: "Submit a demo booking from a Clarm agent conversation"
      description: "Submits the fields a Clarm agent collected during a booking conversation. The edge validates that a `fields` object with a well-formed email and a numeric `agentId` are present before forwarding the booking to the Clarm booking service."
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/DemoBookingRequest"
            examples:
              booking:
                summary: "Booking collected by an agent"
                value:
                  prospect: "example-gmbh"
                  agentId: 142
                  fields:
                    email: "operator@example.com"
                    name: "Anuschka Weber"
                    company: "Example GmbH"
                    role: "Head of Operations"
                  timestamp: "2026-08-23T09:30:00.000Z"
      responses:
        "200":
          description: "The booking was accepted."
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/DemoBookingResponse"
        "400":
          description: "`fields` was missing, the email was malformed, or `agentId` was not a number."
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
        "500":
          description: "The booking could not be processed."
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
    options:
      operationId: "preflightDemoBooking"
      tags:
        - "Signup"
      summary: "CORS preflight for the demo booking endpoint"
      description: "Returns the CORS policy for browser clients submitting a booking."
      responses:
        "204":
          description: "Preflight accepted. No body."
  "/openapi.json":
    get:
      operationId: "getOpenApiSpecJson"
      tags:
        - "Discovery"
      summary: "Fetch this OpenAPI document as JSON"
      description: "Returns the machine-readable description of every endpoint above. Agents that build tool definitions at runtime should read this document rather than scraping the documentation pages."
      responses:
        "200":
          description: "The OpenAPI document."
          content:
            "application/json":
              schema:
                type: "object"
                description: "An OpenAPI 3.1 document."
  "/llms.txt":
    get:
      operationId: "getLlmsTxt"
      tags:
        - "Discovery"
      summary: "Fetch the curated site summary for language models"
      description: "Returns the llms.txt summary of clarm.com: what Clarm does, the commercial model, the contact routes, and the highest-value pages. `https://clarm.com/llms-full.txt` carries the extended reference including case studies and the full article inventory."
      responses:
        "200":
          description: "The llms.txt document."
          content:
            "text/plain":
              schema:
                type: "string"
                description: "Markdown-formatted plain text."
components:
  schemas:
    ErrorResponse:
      type: "object"
      title: "ErrorResponse"
      description: "The shape every failing endpoint returns."
      required:
        - "error"
      additionalProperties: true
      properties:
        error:
          type: "string"
          description: "Human-readable explanation of what went wrong."
          examples:
            - "Invalid request body. Send JSON: {\"message\": \"your question about Clarm\"}"
        example:
          type: "object"
          description: "A valid request body, returned on 400 so a caller can self-correct."
          additionalProperties: true
    AgentChatRequest:
      type: "object"
      title: "AgentChatRequest"
      description: "One question for the Clarm agent. Supply exactly one of the four accepted keys; `message` is canonical and the others are accepted aliases."
      additionalProperties: false
      properties:
        message:
          type: "string"
          description: "The question to ask, 1 to 2000 characters."
          minLength: 1
          maxLength: 2000
          examples:
            - "What does Clarm do and how much does it cost?"
        query:
          type: "string"
          description: "Alias for `message`."
          minLength: 1
          maxLength: 2000
        question:
          type: "string"
          description: "Alias for `message`."
          minLength: 1
          maxLength: 2000
        text:
          type: "string"
          description: "Alias for `message`."
          minLength: 1
          maxLength: 2000
      anyOf:
        - required:
            - "message"
        - required:
            - "query"
        - required:
            - "question"
        - required:
            - "text"
    AgentChatResponse:
      type: "object"
      title: "AgentChatResponse"
      description: "The agent answer plus the sources it drew on."
      required:
        - "answer"
        - "meta"
      additionalProperties: false
      properties:
        answer:
          type: "string"
          description: "The complete answer text."
          examples:
            - "Clarm Atlas lets non-technical teams build AI coworker agents from building blocks pre-approved by IT and compliance."
        citations:
          type: "array"
          description: "Sources the answer drew on. Omitted when the agent cited nothing."
          items:
            "$ref": "#/components/schemas/Citation"
        meta:
          "$ref": "#/components/schemas/AgentChatMeta"
    Citation:
      type: "object"
      title: "Citation"
      description: "One source document behind an answer."
      required:
        - "title"
        - "url"
      additionalProperties: false
      properties:
        title:
          type: "string"
          description: "Human-readable title of the source document."
          examples:
            - "Clarm pricing"
        url:
          type: "string"
          format: "uri"
          description: "Canonical URL of the source document."
          examples:
            - "https://clarm.com/pricing/"
    AgentChatMeta:
      type: "object"
      title: "AgentChatMeta"
      description: "Provenance for the answer and pointers to further machine-readable context."
      required:
        - "model"
        - "note"
        - "llms_txt"
        - "llms_full_txt"
      additionalProperties: false
      properties:
        model:
          type: "string"
          description: "Identifier of the answering agent."
          examples:
            - "clarm-agent"
        note:
          type: "string"
          description: "Short provenance note naming Clarm as the answering party."
        llms_txt:
          type: "string"
          format: "uri"
          description: "URL of the curated site summary."
          examples:
            - "https://clarm.com/llms.txt"
        llms_full_txt:
          type: "string"
          format: "uri"
          description: "URL of the extended reference."
          examples:
            - "https://clarm.com/llms-full.txt"
    AgentChatServiceInfo:
      type: "object"
      title: "AgentChatServiceInfo"
      description: "Self-description returned by GET /api/agent/chat."
      required:
        - "service"
        - "description"
        - "usage"
        - "example_curl"
        - "llms_txt"
      additionalProperties: true
      properties:
        service:
          type: "string"
          description: "Name of the service."
        description:
          type: "string"
          description: "What the endpoint does."
        usage:
          type: "object"
          description: "Method, URL, body shape, and content type for a real call."
          additionalProperties: true
          properties:
            method:
              type: "string"
              description: "HTTP method to use."
              examples:
                - "POST"
            url:
              type: "string"
              format: "uri"
              description: "Absolute endpoint URL."
            body:
              type: "object"
              description: "Example request body."
              additionalProperties: true
            content_type:
              type: "string"
              description: "Content type to send."
        example_curl:
          type: "string"
          description: "A copy-pasteable curl invocation."
        llms_txt:
          type: "string"
          format: "uri"
          description: "URL of the curated site summary."
    TrackEventRequest:
      type: "object"
      title: "TrackEventRequest"
      description: "One analytics event from a Clarm widget or a site using the Clarm tracking helper."
      required:
        - "event"
        - "visitor_id"
      additionalProperties: true
      properties:
        event:
          type: "string"
          description: "Event name, for example `page_view`, `widget_open`, `message_sent`, `identify`, or `purchase`."
          examples:
            - "page_view"
        visitor_id:
          type: "string"
          description: "Stable pseudonymous identifier for the visitor."
          examples:
            - "v_8f2c19a4"
        widget_id:
          type: "string"
          description: "Public widget identifier that authenticates the event when no token is supplied."
          examples:
            - "1CwmcOQ75KJq"
        organization_id:
          type: "string"
          description: "Workspace the widget belongs to. Resolved server-side when omitted."
        page_url:
          type: "string"
          format: "uri"
          description: "URL the event happened on."
        page_title:
          type: "string"
          description: "Document title of that page."
        referrer:
          type: "string"
          description: "Referrer of that page view."
        timestamp:
          type: "string"
          format: "date-time"
          description: "When the event happened. Defaults to receipt time."
        user_agent:
          type: "string"
          description: "Client user agent. Defaults to the request header."
        widget_session:
          type: "string"
          description: "Widget session identifier, when a widget is open."
        chat_session_id:
          type: "string"
          description: "Conversation identifier, when a chat is running."
        has_used_widget:
          type: "boolean"
          description: "Whether this visitor has interacted with the widget before."
        tracking_disabled:
          type: "boolean"
          description: "Set when the visitor opted out. The event is accepted and discarded."
        utm_params:
          "$ref": "#/components/schemas/UtmParams"
        data:
          type: "object"
          description: "Event-specific payload, for example identify traits or purchase value."
          additionalProperties: true
    UtmParams:
      type: "object"
      title: "UtmParams"
      description: "Campaign attribution recorded alongside the event."
      additionalProperties: false
      properties:
        utm_source:
          type: "string"
          description: "Campaign source."
        utm_medium:
          type: "string"
          description: "Campaign medium."
        utm_campaign:
          type: "string"
          description: "Campaign name."
        utm_content:
          type: "string"
          description: "Campaign content variant."
        utm_term:
          type: "string"
          description: "Campaign term."
    TrackEventResponse:
      type: "object"
      title: "TrackEventResponse"
      description: "Confirmation that an event was recorded."
      required:
        - "success"
      additionalProperties: true
      properties:
        success:
          type: "boolean"
          description: "True when the event was accepted."
        event_id:
          type: "string"
          description: "Identifier of the stored event."
        goals_achieved:
          type: "integer"
          minimum: 0
          description: "How many configured goal rules this event satisfied."
        tracking_authenticated:
          type: "boolean"
          description: "Whether the caller presented a verified widget token."
        side_effects_suppressed:
          type: "boolean"
          description: "Whether downstream integrations were skipped for this event."
        message:
          type: "string"
          description: "Human-readable confirmation."
    AnalyticsRegionResponse:
      type: "object"
      title: "AnalyticsRegionResponse"
      description: "The analytics region resolved for the caller."
      required:
        - "analyticsRegion"
      additionalProperties: false
      properties:
        analyticsRegion:
          type: "string"
          enum:
            - "eu"
            - "non_eu"
          description: "Region an analytics client should send events to."
          examples:
            - "eu"
    WaitlistRequest:
      type: "object"
      title: "WaitlistRequest"
      description: "Form-encoded waitlist signup."
      required:
        - "email"
      additionalProperties: false
      properties:
        email:
          type: "string"
          format: "email"
          description: "Work email address to add to the waitlist."
          examples:
            - "operator@example.com"
        userGroup:
          type: "string"
          description: "Which signup surface the address came from."
          default: "waitlist"
          examples:
            - "waitlist"
    WaitlistResponse:
      type: "object"
      title: "WaitlistResponse"
      description: "Result of a waitlist signup."
      required:
        - "success"
      additionalProperties: true
      properties:
        success:
          type: "boolean"
          description: "True when the address was accepted."
        message:
          type: "string"
          description: "Human-readable result to show the visitor."
    DemoBookingRequest:
      type: "object"
      title: "DemoBookingRequest"
      description: "A booking collected by a Clarm agent during a conversation."
      required:
        - "agentId"
        - "fields"
      additionalProperties: false
      properties:
        agentId:
          type: "integer"
          description: "Numeric identifier of the agent that collected the booking."
          examples:
            - 142
        prospect:
          type: "string"
          description: "Slug identifying the prospect or demo the booking came from."
          default: "unknown"
          examples:
            - "example-gmbh"
        fields:
          "$ref": "#/components/schemas/DemoBookingFields"
        timestamp:
          type: "string"
          format: "date-time"
          description: "When the booking was collected. Defaults to receipt time."
    DemoBookingFields:
      type: "object"
      title: "DemoBookingFields"
      description: "The answers the agent collected. `email` is required; other keys vary by agent."
      required:
        - "email"
      additionalProperties:
        type: "string"
      properties:
        email:
          type: "string"
          format: "email"
          description: "Contact email for the booking."
          examples:
            - "operator@example.com"
        name:
          type: "string"
          description: "Contact name."
        company:
          type: "string"
          description: "Company name."
    DemoBookingResponse:
      type: "object"
      title: "DemoBookingResponse"
      description: "Result of a demo booking submission."
      additionalProperties: true
      properties:
        success:
          type: "boolean"
          description: "True when the booking was accepted."
        message:
          type: "string"
          description: "Human-readable result."
