openapi: 3.1.1
info:
  title: Rosuii Agent API
  version: 1.0.0
  summary: Provider-neutral product facts for AI agents
  description: |
    Read-only, machine-readable information about Rosuii restaurant management
    software. Public catalog routes never expose restaurant orders, customers,
    staff, credentials or analytics. The authenticated account-context route
    derives its restaurant from the verified API key and accepts no tenant id.
  contact:
    name: Rosuii support
    url: https://rosuii.com/contact
  license:
    name: Proprietary
servers:
  - url: https://api.rosuii.com/api/agent/v1
    description: Production
tags:
  - name: Discovery
  - name: Product catalog
  - name: Attribution
  - name: Authenticated context
  - name: MCP
paths:
  /:
    get:
      tags: [Discovery]
      operationId: getAgentApiIndex
      summary: Discover the agent API
      responses:
        '200':
          description: Endpoint index and private-data boundary
          content:
            application/json:
              schema: {$ref: '#/components/schemas/AgentApiIndex'}
        '429': {$ref: '#/components/responses/RateLimited'}
  /rosuii:
    get:
      tags: [Product catalog]
      operationId: getRosuii
      summary: Get Rosuii product facts
      responses:
        '200':
          description: Public product identity, positioning, URLs and limitations
          content:
            application/json:
              schema: {$ref: '#/components/schemas/ProductCatalog'}
        '429': {$ref: '#/components/responses/RateLimited'}
  /features:
    get:
      tags: [Product catalog]
      operationId: searchFeatures
      summary: List or search verified features
      parameters:
        - name: q
          in: query
          required: false
          description: English or Bangla feature search text
          schema: {type: string, maxLength: 100}
      responses:
        '200':
          description: Matching verified features
          content:
            application/json:
              schema:
                type: object
                required: [features, count, factsLastReviewed]
                properties:
                  features: {type: array, items: {$ref: '#/components/schemas/Feature'}}
                  count: {type: integer, minimum: 0}
                  factsLastReviewed: {type: string, format: date}
        '429': {$ref: '#/components/responses/RateLimited'}
  /features/{slug}:
    get:
      tags: [Product catalog]
      operationId: getFeature
      summary: Get one verified feature
      parameters:
        - name: slug
          in: path
          required: true
          schema: {type: string, maxLength: 80}
      responses:
        '200':
          description: Feature, capabilities, limitations and eligible plans
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Feature'}
        '404': {$ref: '#/components/responses/NotFound'}
        '429': {$ref: '#/components/responses/RateLimited'}
  /pricing:
    get:
      tags: [Product catalog]
      operationId: getPricing
      summary: Get current active plans in BDT
      description: Values come from the central plan catalog and are cached for no more than five minutes.
      responses:
        '200':
          description: Current plan prices, ISO 8601 billing periods, limits and feature keys
          content:
            application/json:
              schema: {$ref: '#/components/schemas/PricingCatalog'}
        '429': {$ref: '#/components/responses/RateLimited'}
  /restaurant-types:
    get:
      tags: [Product catalog]
      operationId: getRestaurantTypes
      summary: Get supported restaurant segments
      responses:
        '200':
          description: Segments with meaningful published Rosuii solutions
          content:
            application/json:
              schema:
                type: object
                required: [restaurantTypes]
                properties:
                  restaurantTypes: {type: array, items: {$ref: '#/components/schemas/RestaurantType'}}
        '429': {$ref: '#/components/responses/RateLimited'}
  /integrations:
    get:
      tags: [Product catalog]
      operationId: getIntegrations
      summary: Get verified integration status
      responses:
        '200':
          description: Integrations and explicit limitations
          content:
            application/json:
              schema:
                type: object
                required: [integrations]
                properties:
                  integrations: {type: array, items: {$ref: '#/components/schemas/Integration'}}
        '429': {$ref: '#/components/responses/RateLimited'}
  /faq:
    get:
      tags: [Product catalog]
      operationId: getFaq
      summary: Get concise verified answers
      responses:
        '200':
          description: Product, pricing, capability and privacy answers
          content:
            application/json:
              schema:
                type: object
                required: [faqs, factsLastReviewed]
                properties:
                  factsLastReviewed: {type: string, format: date}
                  faqs:
                    type: array
                    items:
                      type: object
                      required: [question, answer]
                      properties:
                        question: {type: string}
                        answer: {type: string}
        '429': {$ref: '#/components/responses/RateLimited'}
  /events:
    post:
      tags: [Attribution]
      operationId: recordAiReferralEvent
      summary: Record a privacy-minimized AI referral event
      description: |
        Accepts anonymous first-party referral/page/demo events only. Signup and
        conversion events are generated by the server after verified lifecycle
        transitions. Raw IP, full referrer URL, email, phone and user agent are
        not stored. Repeated eventId values are idempotent.
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/AiAttributionEvent'}
      responses:
        '202':
          description: Accepted or already accepted
          content:
            application/json:
              schema:
                type: object
                required: [accepted, duplicate]
                properties:
                  accepted: {type: boolean, const: true}
                  duplicate: {type: boolean}
        '403': {$ref: '#/components/responses/Forbidden'}
        '422': {$ref: '#/components/responses/ValidationError'}
        '429': {$ref: '#/components/responses/RateLimited'}
  /account:
    get:
      tags: [Authenticated context]
      operationId: getAccountContext
      summary: Verify an API key and return its restaurant context
      description: The key selects the tenant. This operation has no tenant-id parameter.
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      responses:
        '200':
          description: Key-derived account context; no restaurant operational data
          content:
            application/json:
              schema: {$ref: '#/components/schemas/AccountContext'}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '429': {$ref: '#/components/responses/RateLimited'}
  /openapi.yaml:
    get:
      tags: [Discovery]
      operationId: getOpenApiDescription
      summary: Get this OpenAPI 3.1 description
      responses:
        '200':
          description: OpenAPI YAML
          content:
            application/yaml:
              schema: {type: string}
        '429': {$ref: '#/components/responses/RateLimited'}
  /mcp:
    get:
      tags: [MCP]
      operationId: explainMcpTransport
      summary: Explain that MCP uses POST
      responses:
        '405':
          description: Use POST for this stateless Streamable HTTP endpoint
          headers:
            Allow: {schema: {type: string}, example: POST}
    post:
      tags: [MCP]
      operationId: callMcp
      summary: Call the public read-only MCP server
      description: |
        Stateless JSON-RPC 2.0 MCP endpoint using protocol version 2025-11-25.
        Clients must advertise both application/json and text/event-stream in
        Accept. Implemented methods: initialize, ping, tools/list and tools/call.
        The tools call application services and never connect directly to a
        restaurant tenant database.
      parameters:
        - name: Origin
          in: header
          required: false
          description: Validated when sent by browser clients
          schema: {type: string, format: uri}
        - name: Accept
          in: header
          required: true
          schema: {type: string}
          example: application/json, text/event-stream
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/JsonRpcRequest'}
      responses:
        '200':
          description: JSON-RPC response
          content:
            application/json:
              schema: {$ref: '#/components/schemas/JsonRpcResponse'}
        '202': {description: Notification accepted}
        '403': {$ref: '#/components/responses/Forbidden'}
        '406': {description: Required response media types were not advertised}
        '429': {$ref: '#/components/responses/RateLimited'}
  /mcp/server-card:
    get:
      tags: [Discovery, MCP]
      operationId: getMcpServerCard
      summary: Get the current MCP Server Card representation
      description: Public pre-connection identity and Streamable HTTP endpoint metadata; live initialize and tools/list remain authoritative.
      responses:
        '200':
          description: MCP Server Card
          headers:
            ETag: {schema: {type: string}}
          content:
            application/mcp-server-card+json:
              schema: {type: object, additionalProperties: true}
        '304': {description: Card has not changed}
        '429': {$ref: '#/components/responses/RateLimited'}
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
      description: Tenant-owned key beginning with rsk_. Only its SHA-256 hash is stored.
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: rsk_
  schemas:
    LocalizedText:
      type: object
      required: [en, bn]
      properties:
        en: {type: string}
        bn: {type: string}
    Feature:
      type: object
      required: [slug, name, summary, status, capabilities, canonicalUrl, related, availableOnPlans]
      properties:
        slug: {type: string}
        name: {$ref: '#/components/schemas/LocalizedText'}
        summary: {$ref: '#/components/schemas/LocalizedText'}
        status: {type: string, const: available}
        capabilities: {type: array, items: {type: string}}
        limitations: {type: array, items: {type: string}}
        canonicalUrl: {type: string, format: uri}
        related: {type: array, items: {type: string}}
        availableOnPlans: {type: array, items: {type: string}}
    BillingPrice:
      type: object
      required: [period, price, priceCurrency]
      properties:
        period: {type: string, enum: [P1M, P1Y]}
        price: {type: number, minimum: 0}
        priceCurrency: {type: string, const: BDT}
    Plan:
      type: object
      required: [id, key, name, billing, limits, enabledFeatures, recommended]
      properties:
        id: {type: integer}
        key: {type: string}
        name: {type: string}
        billing: {type: array, items: {$ref: '#/components/schemas/BillingPrice'}}
        limits:
          type: object
          additionalProperties: true
        enabledFeatures: {type: array, items: {type: string}}
        recommended: {type: boolean}
        updatedAt: {type: [string, 'null'], format: date-time}
    PricingCatalog:
      type: object
      required: [plans, priceCurrency, source, freshness]
      properties:
        plans: {type: array, items: {$ref: '#/components/schemas/Plan'}}
        lastUpdated: {type: [string, 'null'], format: date-time}
        priceCurrency: {type: string, const: BDT}
        source: {type: string, format: uri}
        freshness: {type: string}
    ProductCatalog:
      type: object
      required: [schemaVersion, product, factsLastReviewed, limitations]
      properties:
        schemaVersion: {type: string}
        product: {type: object, additionalProperties: true}
        factsLastReviewed: {type: string, format: date}
        limitations: {type: array, items: {type: string}}
    RestaurantType:
      type: object
      required: [slug, name, url]
      properties:
        slug: {type: string}
        name: {$ref: '#/components/schemas/LocalizedText'}
        url: {type: string, format: uri}
    Integration:
      type: object
      required: [slug, status, kind, notes]
      properties:
        slug: {type: string}
        status: {type: string}
        kind: {type: string}
        notes: {type: string}
    AiAttributionEvent:
      type: object
      required: [eventId, attributionId, eventName, source]
      additionalProperties: false
      properties:
        eventId: {type: string, minLength: 16, maxLength: 64, pattern: '^[a-zA-Z0-9_-]+$'}
        attributionId: {type: string, minLength: 16, maxLength: 64, pattern: '^[a-zA-Z0-9_-]+$'}
        eventName:
          type: string
          enum: [ai_referral_visit, ai_product_page_view, ai_feature_page_view, ai_pricing_view, ai_demo_click]
        source: {type: string, enum: [chatgpt, gemini, claude, perplexity, ai_agent, other_ai]}
        medium: {type: string, maxLength: 32, default: ai}
        campaign: {type: [string, 'null'], maxLength: 120}
        content: {type: [string, 'null'], maxLength: 120}
        referrerHost: {type: [string, 'null'], maxLength: 255}
        landingPath: {type: [string, 'null'], maxLength: 500}
        pagePath: {type: [string, 'null'], maxLength: 500}
        metadata:
          type: [object, 'null']
          additionalProperties: false
          properties:
            attributionWindowDays: {type: integer, minimum: 1, maximum: 90}
            targetKind: {type: string, const: demo}
    AccountContext:
      type: object
      required: [restaurant, apiKey, tenantIsolation, privateDataBaseUrl]
      properties:
        restaurant:
          type: object
          required: [id, name, subdomain]
          properties:
            id: {type: string}
            name: {type: string}
            subdomain: {type: string}
        apiKey:
          type: object
          required: [id, name, scope]
          properties:
            id: {type: integer}
            name: {type: string}
            scope: {type: string, enum: [full, demo]}
        tenantIsolation: {type: string}
        privateDataBaseUrl: {type: string, format: uri}
    AgentApiIndex:
      type: object
      additionalProperties: true
    JsonRpcRequest:
      type: object
      required: [jsonrpc, method]
      properties:
        jsonrpc: {type: string, const: '2.0'}
        id: {type: [string, integer, 'null']}
        method: {type: string}
        params: {type: [object, 'null'], additionalProperties: true}
    JsonRpcResponse:
      type: object
      required: [jsonrpc, id]
      properties:
        jsonrpc: {type: string, const: '2.0'}
        id: {type: [string, integer, 'null']}
        result: {}
        error:
          type: object
          required: [code, message]
          properties:
            code: {type: integer}
            message: {type: string}
            data: {}
    ErrorEnvelope:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message]
          properties:
            code: {type: string}
            message: {type: string}
  responses:
    RateLimited:
      description: Per-IP rate limit exceeded
      headers:
        Retry-After: {schema: {type: integer}, description: Seconds before retrying}
      content:
        application/json:
          schema: {$ref: '#/components/schemas/ErrorEnvelope'}
    ValidationError:
      description: Request validation failed
      content:
        application/json:
          schema: {$ref: '#/components/schemas/ErrorEnvelope'}
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema: {$ref: '#/components/schemas/ErrorEnvelope'}
    Forbidden:
      description: Operation or browser origin is not allowed
      content:
        application/json:
          schema: {$ref: '#/components/schemas/ErrorEnvelope'}
    Unauthorized:
      description: Missing, invalid, expired or revoked restaurant API key
      headers:
        WWW-Authenticate: {schema: {type: string}}
      content:
        application/json:
          schema: {$ref: '#/components/schemas/ErrorEnvelope'}
