{
  "openapi": "3.1.0",
  "info": {
    "title": "Energirör Public Service API",
    "version": "2.0.0",
    "description": "Publikt, rate-limitat API för AI-agenter. Tjänster och routing hämtas från Energirörs service_catalog. API:t kan klassificera ärenden, göra säker värmepumpstriage, föreslå sammanhängande lediga tider och skapa en pending bokningsförfrågan. Kundregister och rå kalenderdata exponeras aldrig. En chatt utan skrivverktyg ger kunden en förifylld länk till https://energiror.se/boka-via-ai. En AI med HTTP-stöd måste inhämta kundens godkännande innan POST /booking-request. MCP är valfritt."
  },
  "servers": [
    {
      "url": "https://mcp.energiror.se/functions/v1/agent-service-api",
      "description": "Production API"
    }
  ],
  "paths": {
    "/services": {
      "get": {
        "operationId": "listServices",
        "summary": "Lista levande bokningsbara tjänster",
        "responses": {
          "200": {
            "description": "Tjänstekatalog"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/resolve-service": {
      "post": {
        "operationId": "resolveService",
        "summary": "Matcha kundens problem mot rätt service_code",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ResolveServiceRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Matchande tjänster"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        }
      }
    },
    "/slots": {
      "post": {
        "operationId": "suggestServiceSlots",
        "summary": "Föreslå sammanhängande lediga servicetider",
        "description": "Använder tjänstens duration och service_technician_assignments. Teknikernamn och kalenderdetaljer exponeras inte.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SlotRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tidsförslag"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/availability": {
      "post": {
        "operationId": "checkAvailability",
        "summary": "Kontrollera ett exakt tidsfönster",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AvailabilityRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Aggregerad free/busy"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/triage": {
      "post": {
        "operationId": "triageHeatPumpIssue",
        "summary": "Säker teknisk triage av värmepumpsfel",
        "description": "Använder Kjells manualbank, felsökningsmotor och valfri verifierad produkt/reservdelssökning.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TriageRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Säkra felsökningssteg och verifierade kandidater"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/booking-request": {
      "post": {
        "operationId": "createBookingRequest",
        "summary": "Skapa pending bokningsförfrågan",
        "description": "Kontrollerar vald tid igen, väljer intern resurs enligt service_catalog, verifierar adress och berikar ärendet privat. Ingen kalender reserveras innan Energirör bekräftar bokningen. Visa först en sammanfattning och inhämta kundens godkännande. En länk eller ett tidsförslag är inte en skapad bokningsförfrågan.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128
            }
          },
          {
            "name": "X-Agent-Client",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 80
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BookingRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Pending bokningsförfrågan skapad"
          },
          "200": {
            "description": "Idempotent replay"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "409": {
            "description": "Vald tid är inte längre ledig eller idempotenskonflikt"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/resolve-heat-pump": {
      "post": {
        "operationId": "resolveHeatPump",
        "summary": "Identifiera värmepumpsmodell via serienummer eller modellnamn",
        "description": "Integritetssäker resolver. Returnerar endast modell, fabrikat, sista 4 tecken av serienumret och om pumpen är registrerad, samt upp till 5 publika modellträffar. Inga kund-, adress-, ägar- eller orderuppgifter exponeras. 20/min och 200/timme per klient.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ResolveHeatPumpRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sanerade modellträffar"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ServiceRef": {
        "type": "string",
        "description": "Använd service_code från GET /services, t.ex. VP-REPARATION eller VVS-SERVICE. Äldre tjänstenamn accepteras för bakåtkompatibilitet."
      },
      "ResolveServiceRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "issue"
        ],
        "properties": {
          "issue": {
            "type": "string",
            "minLength": 3,
            "maxLength": 2000
          }
        }
      },
      "SlotRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "service_code",
          "from_date"
        ],
        "properties": {
          "service_code": {
            "$ref": "#/components/schemas/ServiceRef"
          },
          "from_date": {
            "type": "string",
            "format": "date"
          },
          "to_date": {
            "type": "string",
            "format": "date",
            "description": "Valfri. Högst 14 kalenderdagar från from_date."
          },
          "daypart": {
            "type": "string",
            "enum": [
              "any",
              "morning",
              "afternoon"
            ],
            "default": "any"
          },
          "city": {
            "type": "string",
            "maxLength": 100
          },
          "max_results": {
            "type": "integer",
            "minimum": 1,
            "maximum": 6,
            "default": 3
          }
        }
      },
      "AvailabilityRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "service_code",
          "start_datetime",
          "end_datetime"
        ],
        "properties": {
          "service_code": {
            "$ref": "#/components/schemas/ServiceRef"
          },
          "start_datetime": {
            "type": "string",
            "description": "Lokal Europe/Stockholm-tid utan zon, t.ex. 2026-10-07T08:00."
          },
          "end_datetime": {
            "type": "string"
          },
          "city": {
            "type": "string",
            "maxLength": 100
          }
        }
      },
      "TriageRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "model",
          "symptom"
        ],
        "properties": {
          "model": {
            "type": "string",
            "maxLength": 200
          },
          "symptom": {
            "type": "string",
            "maxLength": 2000
          },
          "error_code": {
            "type": "string",
            "maxLength": 120
          },
          "part_query": {
            "type": "string",
            "maxLength": 180
          }
        }
      },
      "BookingRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "service_code",
          "customer_name",
          "customer_email",
          "customer_phone",
          "address",
          "postal_code",
          "city",
          "problem_description",
          "service_date",
          "service_time"
        ],
        "properties": {
          "service_code": {
            "$ref": "#/components/schemas/ServiceRef"
          },
          "customer_name": {
            "type": "string",
            "minLength": 2,
            "maxLength": 120
          },
          "customer_email": {
            "type": "string",
            "format": "email",
            "maxLength": 254
          },
          "customer_phone": {
            "type": "string",
            "minLength": 5,
            "maxLength": 30
          },
          "address": {
            "type": "string",
            "minLength": 2,
            "maxLength": 200
          },
          "postal_code": {
            "type": "string",
            "pattern": "^\\d{3}\\s?\\d{2}$"
          },
          "city": {
            "type": "string",
            "minLength": 2,
            "maxLength": 100
          },
          "problem_description": {
            "type": "string",
            "minLength": 2,
            "maxLength": 2000
          },
          "service_date": {
            "type": "string",
            "format": "date"
          },
          "service_time": {
            "type": "string",
            "pattern": "^\\d{2}:(00|30)$"
          },
          "heat_pump_brand_model": {
            "type": "string",
            "maxLength": 200
          },
          "heat_pump_serial_number": {
            "type": "string",
            "maxLength": 120
          },
          "heat_pump_error_code": {
            "type": "string",
            "maxLength": 120
          },
          "urgency": {
            "type": "string",
            "enum": [
              "low",
              "normal",
              "high",
              "urgent"
            ],
            "default": "normal"
          },
          "rot_avdrag": {
            "type": "boolean",
            "default": false
          },
          "diagnostic_summary": {
            "type": "string",
            "maxLength": 1200
          },
          "steps_tried": {
            "type": "array",
            "maxItems": 10,
            "items": {
              "type": "string",
              "maxLength": 240
            }
          },
          "part_article_numbers": {
            "type": "array",
            "maxItems": 10,
            "items": {
              "type": "string",
              "maxLength": 80
            }
          },
          "agent_id": {
            "type": "string",
            "maxLength": 80
          }
        }
      },
      "ResolveHeatPumpRequest": {
        "type": "object",
        "description": "Minst ett av serial_number eller model krävs.",
        "properties": {
          "serial_number": {
            "type": "string",
            "maxLength": 60,
            "description": "Serie-/artikelnummer, minst 3 siffror."
          },
          "model": {
            "type": "string",
            "minLength": 2,
            "maxLength": 120
          }
        },
        "anyOf": [
          {
            "required": [
              "serial_number"
            ]
          },
          {
            "required": [
              "model"
            ]
          }
        ]
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Ogiltig förfrågan",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "success": {
                  "type": "boolean"
                },
                "error": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "RateLimited": {
        "description": "För många förfrågningar",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "string"
            }
          }
        }
      }
    }
  },
  "externalDocs": {
    "description": "Bokningsguide och fallback utan plugin",
    "url": "https://energiror.se/ai/booking-guide.json"
  }
}
