{
  "openapi": "3.1.0",
  "info": {
    "title": "Sync Bridge by Brommaflyg – Hub API",
    "version": "1.0.0",
    "description": "Sync Bridge by Brommaflyg – integrationshub mellan Shopify, Fortnox, WorldTicket, ClickSend och Gmail. Alla anrop kräver `Authorization: Bearer lvh_live_…`. Nycklar och scopes hanteras i Hubens admin under fliken API-nycklar."
  },
  "servers": [
    {
      "url": "https://fortnoxtify-bridge.lovable.app",
      "description": "Produktion"
    }
  ],
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "lvh_live_…"
      },
      "customerSession": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Customer-Session",
        "description": "Opaque kund-session-token (cs_live_…) som returneras av POST /customer/login. Krävs för alla /customer/me, /customer/orders, /customer/addresses och /customer/invoices."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "example": "missing_api_key"
              },
              "message": {
                "type": "string"
              }
            }
          },
          "request_id": {
            "type": "string",
            "format": "uuid"
          }
        }
      },
      "IrrMessage": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "pnr": {
            "type": "string"
          },
          "passenger_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "channel": {
            "type": "string",
            "enum": [
              "email",
              "sms"
            ]
          },
          "recipient": {
            "type": [
              "string",
              "null"
            ],
            "description": "E-post eller telefonnummer"
          },
          "status": {
            "type": "string",
            "enum": [
              "sent",
              "failed",
              "skipped",
              "pending"
            ]
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "template_key": {
            "type": [
              "string",
              "null"
            ]
          },
          "subject": {
            "type": [
              "string",
              "null"
            ],
            "description": "Endast för e-post"
          },
          "body": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "sent_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "sent_by": {
            "type": [
              "string",
              "null"
            ],
            "description": "Handläggaren som gjorde utskicket"
          },
          "flight_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "flight_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          }
        }
      },
      "IrrLookupResponse": {
        "type": "object",
        "properties": {
          "pnr": {
            "type": "string"
          },
          "count": {
            "type": "integer"
          },
          "messages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/IrrMessage"
            }
          }
        }
      },
      "InvoiceCreateRequest": {
        "type": "object",
        "required": [
          "shopify_order_id"
        ],
        "properties": {
          "shopify_order_id": {
            "type": "string",
            "maxLength": 64
          },
          "document_type": {
            "type": "string",
            "enum": [
              "invoice",
              "order"
            ],
            "default": "invoice"
          }
        }
      },
      "InvoiceCreateResponse": {
        "type": "object",
        "properties": {
          "already_existed": {
            "type": "boolean"
          },
          "shopify_order_id": {
            "type": "string"
          },
          "document_type": {
            "type": "string",
            "enum": [
              "invoice",
              "order"
            ]
          },
          "document_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "fortnox_invoice_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "fortnox_order_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "fortnox_customer_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "request_id": {
            "type": "string"
          }
        }
      },
      "FulfillRequest": {
        "type": "object",
        "properties": {
          "notify_customer": {
            "type": "boolean",
            "default": false
          }
        }
      },
      "CustomerLoginRequest": {
        "type": "object",
        "required": [
          "email",
          "password"
        ],
        "properties": {
          "email": {
            "type": "string",
            "format": "email"
          },
          "password": {
            "type": "string"
          }
        }
      },
      "CustomerLoginResponse": {
        "type": "object",
        "properties": {
          "session_token": {
            "type": "string",
            "example": "cs_live_…"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "customer": {
            "type": "object",
            "properties": {
              "id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "email": {
                "type": "string"
              }
            }
          },
          "request_id": {
            "type": "string"
          }
        }
      },
      "MailingAddress": {
        "type": "object",
        "properties": {
          "firstName": {
            "type": "string"
          },
          "lastName": {
            "type": "string"
          },
          "company": {
            "type": "string"
          },
          "address1": {
            "type": "string"
          },
          "address2": {
            "type": "string"
          },
          "city": {
            "type": "string"
          },
          "province": {
            "type": "string"
          },
          "zip": {
            "type": "string"
          },
          "country": {
            "type": "string"
          },
          "phone": {
            "type": "string"
          }
        }
      },
      "AvailabilityClassRow": {
        "type": "object",
        "description": "En bokningsklass (RBD) med antal lediga platser.",
        "properties": {
          "rbd": {
            "type": "string",
            "description": "RBD (klass), t.ex. 'X', 'W', 'Y'."
          },
          "name": {
            "type": "string",
            "description": "Beskrivande namn för klassen."
          },
          "available": {
            "type": "integer",
            "description": "Antal lediga platser i klassen."
          }
        }
      },
      "AvailabilityRow": {
        "type": "object",
        "properties": {
          "scheduled_date": {
            "type": "string",
            "format": "date"
          },
          "flight_designator": {
            "type": "string",
            "description": "Flightnummer utan inledande nolla, t.ex. 'OJ811'."
          },
          "departure_airport": {
            "type": "string",
            "example": "VBY"
          },
          "arrival_airport": {
            "type": "string",
            "example": "BMA"
          },
          "departure_time": {
            "type": "string",
            "example": "08:00"
          },
          "arrival_time": {
            "type": "string",
            "example": "08:35"
          },
          "max_capacity": {
            "type": "integer"
          },
          "total_booked": {
            "type": "integer"
          },
          "classes": {
            "type": "object",
            "description": "Klasser grupperade efter försäljningskanal.",
            "properties": {
              "shopify": {
                "type": "array",
                "description": "Bokas via Shopify: X, W, R, K, Q.",
                "items": {
                  "$ref": "#/components/schemas/AvailabilityClassRow"
                }
              },
              "gds": {
                "type": "array",
                "description": "Bokas via GDS: Y, V, L.",
                "items": {
                  "$ref": "#/components/schemas/AvailabilityClassRow"
                }
              },
              "special": {
                "type": "array",
                "description": "Special/interna klasser: P, U, S, G, O.",
                "items": {
                  "$ref": "#/components/schemas/AvailabilityClassRow"
                }
              }
            }
          }
        }
      },
      "AvailabilityResponse": {
        "type": "object",
        "properties": {
          "count": {
            "type": "integer"
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "flight": {
            "type": [
              "string",
              "null"
            ],
            "description": "Normaliserat flightnummer i form 'OJ<nummer>' utan inledande nolla, eller null om inget flightfilter angavs."
          },
          "class_legend": {
            "type": "object",
            "description": "Förklaringar till alla bokningsklasser, grupperade efter försäljningskanal. Klassbokstaven visas för slutanvändare som 'RBD (klass)'.",
            "properties": {
              "rbd_field_label": {
                "type": "string",
                "example": "RBD (klass)"
              },
              "shopify": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              },
              "gds": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              },
              "special": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              }
            }
          },
          "rows": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AvailabilityRow"
            }
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Felaktig request",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Ogiltig eller saknad API-nyckel / scope",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "Resursen hittades inte",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unprocessable": {
        "description": "Validering misslyckades (t.ex. saknad e-post på order)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "UpstreamError": {
        "description": "Fel från Fortnox eller Shopify",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  },
  "security": [
    {
      "apiKey": []
    }
  ],
  "paths": {
    "/api/v1/fortnox/invoices-from-payload": {
      "post": {
        "summary": "Skapa Fortnox-faktura/order från Agentportalens bokning",
        "description": "Agentportalen bestämmer, Sync Bridge utför. `document_type` i bokningen (`order`, `invoice` eller `invoice_auto`) gäller och vinner över kundregistret. Registrets värde används bara när bokningen saknar dokumenttyp; saknas båda svarar endpointen 400 `document_type_required` (inget tyst standardval). Skickas `customer.fortnox_customer_number` används det numret direkt (422 om det inte finns i Fortnox). Annars söks kunden på organisationsnummer, sedan e-post, och skapas bara om ingen hittas. Idempotent på `external_order_ref`.",
        "security": [
          {
            "apiKey": [
              "fortnox:invoices:create-from-payload"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "external_order_ref",
                  "customer",
                  "lines"
                ],
                "properties": {
                  "external_order_ref": {
                    "type": "string"
                  },
                  "document_type": {
                    "type": "string",
                    "enum": [
                      "order",
                      "invoice",
                      "invoice_auto"
                    ]
                  },
                  "currency": {
                    "type": "string"
                  },
                  "order_date": {
                    "type": "string",
                    "format": "date"
                  },
                  "customer": {
                    "type": "object",
                    "properties": {
                      "fortnox_customer_number": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "organisation_number": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "email": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "agency": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "department": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "company": {
                        "type": [
                          "string",
                          "null"
                        ]
                      }
                    }
                  },
                  "lines": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  },
                  "passengers": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  },
                  "booking_meta": {
                    "type": "object"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Dokument skapat eller fanns redan",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "document_type": {
                      "type": "string",
                      "enum": [
                        "order",
                        "invoice",
                        "contract_append"
                      ]
                    },
                    "document_number": {
                      "type": "string"
                    },
                    "customer_number": {
                      "type": "string"
                    },
                    "customer_created": {
                      "type": "boolean"
                    },
                    "already_existed": {
                      "type": "boolean"
                    },
                    "contract_number": {
                      "type": "string"
                    },
                    "auto_send": {
                      "type": "object"
                    },
                    "request_id": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        }
      }
    },
    "/api/v1/fortnox/customers": {
      "post": {
        "summary": "Hitta eller skapa Fortnox-kund",
        "description": "Letar först på organisationsnummer och sedan på e-post. Skapar kunden bara om ingen finns, så samma organisationsnummer ger alltid samma kundnummer.",
        "security": [
          {
            "apiKey": [
              "fortnox:customers:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "agency": {
                    "type": "string"
                  },
                  "department": {
                    "type": "string"
                  },
                  "organisation_number": {
                    "type": "string"
                  },
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "company": {
                    "type": "string"
                  },
                  "address1": {
                    "type": "string"
                  },
                  "zip": {
                    "type": "string"
                  },
                  "city": {
                    "type": "string"
                  },
                  "country": {
                    "type": "string",
                    "maxLength": 2
                  },
                  "phone": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Kundnummer",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "customer_number": {
                      "type": "string"
                    },
                    "created": {
                      "type": "boolean"
                    },
                    "request_id": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/v1/fortnox/invoices": {
      "get": {
        "summary": "Lista Fortnox-fakturor",
        "description": "Stöder inkrementell sync via `modified_since`.",
        "security": [
          {
            "apiKey": [
              "fortnox:invoices:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "modified_since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Lista av fakturor",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "invoices": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "meta": {
                      "type": [
                        "object",
                        "null"
                      ]
                    },
                    "request_id": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "502": {
            "$ref": "#/components/responses/UpstreamError"
          }
        }
      },
      "post": {
        "summary": "Skapa Fortnox-faktura från Shopify-order",
        "description": "Idempotent: om dokumentet redan skickats returneras 200 med `already_existed=true`. Kräver att ordern har e-post (annars 422 för att undvika dubblettkund).",
        "security": [
          {
            "apiKey": [
              "fortnox:invoices:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InvoiceCreateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Dokumentet fanns redan",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvoiceCreateResponse"
                }
              }
            }
          },
          "201": {
            "description": "Dokument skapat",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvoiceCreateResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "502": {
            "$ref": "#/components/responses/UpstreamError"
          }
        }
      }
    },
    "/api/v1/fortnox/invoices/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "summary": "Hämta en Fortnox-faktura",
        "security": [
          {
            "apiKey": [
              "fortnox:invoices:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Fakturaobjekt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/fortnox/invoices/{id}/cancel": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "put": {
        "summary": "Makulera en Fortnox-faktura (endast obokförd)",
        "description": "Idempotent. Redan makulerad → 200 med `already_cancelled: true`. Bokförd faktura → 409 `invoice_booked` (använd kreditfaktura i stället).",
        "security": [
          {
            "apiKey": [
              "fortnox:invoices:write"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "{ cancelled, already_cancelled, invoice_number }"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Fakturan är bokförd och kan inte makuleras"
          },
          "502": {
            "$ref": "#/components/responses/UpstreamError"
          }
        }
      },
      "post": {
        "summary": "Makulera en Fortnox-faktura (alias för PUT)",
        "security": [
          {
            "apiKey": [
              "fortnox:invoices:write"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Se PUT"
          }
        }
      }
    },
    "/api/v1/fortnox/orders/{id}/cancel": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "put": {
        "summary": "Makulera en Fortnox-order",
        "description": "Idempotent. Redan makulerad → 200 med `already_cancelled: true`.",
        "security": [
          {
            "apiKey": [
              "fortnox:orders:write"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "{ cancelled, already_cancelled, order_number }"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "502": {
            "$ref": "#/components/responses/UpstreamError"
          }
        }
      },
      "post": {
        "summary": "Makulera en Fortnox-order (alias för PUT)",
        "security": [
          {
            "apiKey": [
              "fortnox:orders:write"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Se PUT"
          }
        }
      }
    },
    "/api/v1/fortnox/invoices/{id}/status": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "summary": "Hämta status för en Fortnox-faktura",
        "security": [
          {
            "apiKey": [
              "fortnox:invoices:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Statusobjekt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/fortnox/orders/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "summary": "Hämta en Fortnox-order",
        "security": [
          {
            "apiKey": [
              "fortnox:orders:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Orderobjekt ({ order: {...} })"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "502": {
            "$ref": "#/components/responses/UpstreamError"
          }
        }
      },
      "put": {
        "summary": "Uppdatera en Fortnox-order (partial)",
        "description": "Proxar till Fortnox `PUT /orders/{id}`. Body kan vara antingen `{ Order: {...} }` eller en plain partial-order (t.ex. `{ OrderRows: [...] }`) — bridgen wrapar då automatiskt i `{ Order: ... }`.",
        "security": [
          {
            "apiKey": [
              "fortnox:orders:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Partial Fortnox-order, t.ex. { OrderRows: [...] } eller { Order: { OrderRows: [...] } }."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Uppdaterad order ({ order: {...} })"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "502": {
            "$ref": "#/components/responses/UpstreamError"
          }
        }
      }
    },
    "/api/v1/shopify/orders/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "summary": "Hämta en Shopify-order",
        "security": [
          {
            "apiKey": [
              "shopify:orders:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Orderobjekt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/shopify/orders": {
      "get": {
        "summary": "Slå upp Shopify-order via PNR (alias för /by-pnr)",
        "description": "Identiskt svar som /api/v1/shopify/orders/by-pnr.",
        "security": [
          {
            "apiKey": [
              "shopify:orders:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "pnr",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 3,
              "maxLength": 32
            }
          },
          {
            "name": "first",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 10,
              "maximum": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ pnr, count, orders[] }"
          },
          "400": {
            "description": "Ogiltig query"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/v1/shopify/orders/by-pnr": {
      "get": {
        "summary": "Slå upp Shopify-order via PNR / bokningsreferens",
        "description": "Söker ordrar (status:any) på PNR i line-item properties, note och tags. Exakta PNR-träffar (matches_pnr=true) sorteras först.",
        "security": [
          {
            "apiKey": [
              "shopify:orders:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "pnr",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 3,
              "maxLength": 32
            }
          },
          {
            "name": "first",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 10,
              "maximum": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{ pnr, count, orders[] } med id, name, total_price, total_refunded, line_items[].properties"
          },
          "400": {
            "description": "Ogiltig query"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/v1/shopify/orders/{id}/refund": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "Numeriskt Shopify order-id eller ordernamn (10854 / #10854)."
        }
      ],
      "post": {
        "summary": "Återbetala en Shopify-order (helt eller delvis)",
        "description": "Beräknar refund via Shopify refunds/calculate och skapar sedan refund mot ordinarie betaltransaktion. Ange amount för delåterbetalning (t.ex. retroaktiv Gotlandsrabatt) och/eller line_items.",
        "security": [
          {
            "apiKey": [
              "shopify:orders:refund"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "amount": {
                    "type": "number",
                    "description": "Belopp i orderns valuta. Utelämnas → beräknat belopp för line_items."
                  },
                  "currency": {
                    "type": "string",
                    "example": "SEK"
                  },
                  "reason": {
                    "type": "string",
                    "maxLength": 255,
                    "example": "Gotlandsrabatt retroaktiv"
                  },
                  "note": {
                    "type": "string",
                    "maxLength": 255
                  },
                  "notify": {
                    "type": "boolean",
                    "default": false
                  },
                  "line_items": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "line_item_id"
                      ],
                      "properties": {
                        "line_item_id": {
                          "type": "string"
                        },
                        "quantity": {
                          "type": "integer",
                          "default": 1
                        },
                        "restock_type": {
                          "type": "string",
                          "enum": [
                            "no_restock",
                            "cancel",
                            "return"
                          ],
                          "default": "no_restock"
                        }
                      }
                    }
                  }
                }
              },
              "example": {
                "amount": 495,
                "reason": "Gotlandsrabatt retroaktiv",
                "notify": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{ ok, order_id, order_name, refund_id, amount, currency, refund }"
          },
          "400": {
            "description": "Ogiltig body (varken amount eller line_items)"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "no_refundable_transaction eller amount_exceeds_refundable"
          },
          "502": {
            "$ref": "#/components/responses/UpstreamError"
          }
        }
      }
    },
    "/api/v1/shopify/orders/{id}/fulfill": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "summary": "Markera Shopify-order som levererad",
        "security": [
          {
            "apiKey": [
              "shopify:orders:fulfill"
            ]
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FulfillRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Fulfillment skapad"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "502": {
            "$ref": "#/components/responses/UpstreamError"
          }
        }
      }
    },
    "/api/v1/customer/login": {
      "post": {
        "summary": "Logga in kund (Shopify Storefront)",
        "description": "Returnerar opaque session-token (cs_live_…). Använd denna i `X-Customer-Session` på alla efterföljande /customer/*-anrop.",
        "security": [
          {
            "apiKey": [
              "customer:auth"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CustomerLoginRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Inloggad",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerLoginResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/v1/customer/logout": {
      "post": {
        "summary": "Logga ut kund",
        "security": [
          {
            "apiKey": [
              "customer:auth"
            ],
            "customerSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Utloggad"
          }
        }
      }
    },
    "/api/v1/customer/recover": {
      "post": {
        "summary": "Skicka lösenordsåterställning",
        "security": [
          {
            "apiKey": [
              "customer:auth"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK (returneras alltid för att inte läcka om e-posten finns)"
          }
        }
      }
    },
    "/api/v1/customer/me": {
      "get": {
        "summary": "Hämta inloggad kunds profil",
        "security": [
          {
            "apiKey": [
              "customer:read"
            ],
            "customerSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Kundobjekt"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "patch": {
        "summary": "Uppdatera inloggad kunds profil",
        "security": [
          {
            "apiKey": [
              "customer:write"
            ],
            "customerSession": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "firstName": {
                    "type": "string"
                  },
                  "lastName": {
                    "type": "string"
                  },
                  "email": {
                    "type": "string"
                  },
                  "phone": {
                    "type": "string"
                  },
                  "acceptsMarketing": {
                    "type": "boolean"
                  },
                  "password": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Uppdaterad"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        }
      }
    },
    "/api/v1/customer/orders": {
      "get": {
        "summary": "Lista kundens ordrar",
        "security": [
          {
            "apiKey": [
              "customer:orders:read"
            ],
            "customerSession": []
          }
        ],
        "parameters": [
          {
            "name": "first",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 20,
              "maximum": 100
            }
          },
          {
            "name": "after",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Lista av ordrar"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/v1/customer/orders/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "Shopify GraphQL global id (gid://shopify/Order/123) eller numerisk order-id."
        }
      ],
      "get": {
        "summary": "Hämta en av kundens ordrar",
        "security": [
          {
            "apiKey": [
              "customer:orders:read"
            ],
            "customerSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Orderobjekt"
          },
          "403": {
            "description": "Ordern tillhör inte kunden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/customer/addresses": {
      "get": {
        "summary": "Lista kundens adresser",
        "security": [
          {
            "apiKey": [
              "customer:addresses:read"
            ],
            "customerSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Lista av adresser"
          }
        }
      },
      "post": {
        "summary": "Skapa ny adress",
        "security": [
          {
            "apiKey": [
              "customer:addresses:write"
            ],
            "customerSession": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MailingAddress"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Adress skapad"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        }
      }
    },
    "/api/v1/customer/addresses/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "patch": {
        "summary": "Uppdatera adress",
        "security": [
          {
            "apiKey": [
              "customer:addresses:write"
            ],
            "customerSession": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/MailingAddress"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "set_default": {
                        "type": "boolean"
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Uppdaterad"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        }
      },
      "delete": {
        "summary": "Radera adress",
        "security": [
          {
            "apiKey": [
              "customer:addresses:write"
            ],
            "customerSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Raderad"
          }
        }
      }
    },
    "/api/v1/customer/invoices": {
      "get": {
        "summary": "Lista kundens Fortnox-fakturor",
        "description": "Filtrerade på den inloggade kundens e-post (server-side, kan inte överstyras).",
        "security": [
          {
            "apiKey": [
              "customer:invoices:read"
            ],
            "customerSession": []
          }
        ],
        "parameters": [
          {
            "name": "modified_since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Lista av fakturor"
          },
          "502": {
            "$ref": "#/components/responses/UpstreamError"
          }
        }
      }
    },
    "/api/v1/irr/messages": {
      "get": {
        "summary": "IRR-komm – utskick för ett bokningsnummer",
        "description": "Slår upp exakt vad som skickats till en bokning (IRR-kommunikation): kanal, mottagare, ämne/text, status, tidpunkt och avsändare. Hubben agerar mellanhand mot Brommaflyg IRR-komm; anroparen behöver bara sin lvh_live_-nyckel. Max 200 träffar, nyaste först.",
        "security": [
          {
            "apiKey": [
              "irr:messages:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "pnr",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 5,
              "maxLength": 10
            },
            "description": "Bokningsnummer (PNR), t.ex. 'OBJSN6'."
          }
        ],
        "responses": {
          "200": {
            "description": "Utskick för bokningen",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IrrLookupResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "502": {
            "$ref": "#/components/responses/UpstreamError"
          },
          "503": {
            "description": "IRR_LOOKUP_API_KEY saknas i Hubben"
          }
        }
      },
      "post": {
        "summary": "IRR-komm – utskick för ett bokningsnummer (POST)",
        "security": [
          {
            "apiKey": [
              "irr:messages:read"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "pnr"
                ],
                "properties": {
                  "pnr": {
                    "type": "string",
                    "minLength": 5,
                    "maxLength": 10
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Utskick för bokningen",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IrrLookupResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "502": {
            "$ref": "#/components/responses/UpstreamError"
          }
        }
      }
    },
    "/api/v1/availability": {
      "get": {
        "summary": "Flygtillgänglighet per datum och flight",
        "description": "Returnerar lediga platser per bokningsklass (RBD) för ett givet datum, valfritt filtrerat på flightnummer eller flygplats. Flightnummer accepteras som '811', 'OJ811' eller 'OJ0811' – prefixet 'OJ' antas och inledande nollor ignoreras. Svaret innehåller `class_legend` med namn på alla klasser, grupperade i tre kanaler: **shopify** (X, W, R, K, Q – bokas via webbshop), **gds** (Y, V, L – bokas via GDS) och **special** (P, U, S, G, O – interna/manuella bokningar). Per klass returneras endast antal lediga platser; bokade per klass finns inte i källdata.",
        "security": [
          {
            "apiKey": [
              "availability:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "date",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Schemalagt datum (YYYY-MM-DD)."
          },
          {
            "name": "flight",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 10
            },
            "description": "Flightnummer. Accepteras som '811', 'OJ811' eller 'OJ0811'."
          },
          {
            "name": "origin",
            "in": "query",
            "schema": {
              "type": "string",
              "minLength": 3,
              "maxLength": 3
            },
            "description": "IATA-kod för avgångsflygplats (t.ex. 'VBY')."
          },
          {
            "name": "dest",
            "in": "query",
            "schema": {
              "type": "string",
              "minLength": 3,
              "maxLength": 3
            },
            "description": "IATA-kod för ankomstflygplats (t.ex. 'BMA')."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 500
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tillgänglighet",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AvailabilityResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "description": "Databasfel"
          }
        }
      }
    },
    "/api/v1/worldticket/passengers": {
      "get": {
        "summary": "Passagerardata för en flight (WorldTicket)",
        "description": "Hämtar passagerarmanifest från WorldTicket SMS Gateway för ett givet datum och flightnummer. Flightnummer accepteras som '811', 'OJ811' eller 'OJ0811'. Returnerar normaliserade rader: förnamn, efternamn, pax-typ (ADT/CHD/INF), PNR, SSR-info, e-post, telefon (E.164) och RBD.",
        "security": [
          {
            "apiKey": [
              "worldticket:passengers:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "date",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Avgångsdatum (YYYY-MM-DD)."
          },
          {
            "name": "flight",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 10
            },
            "description": "Flightnummer ('811', 'OJ811' eller 'OJ0811')."
          }
        ],
        "responses": {
          "200": {
            "description": "Passagerarlista"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "502": {
            "description": "WorldTicket-fel (inloggning eller upstream)"
          }
        }
      }
    },
    "/api/v1/worldticket/reservations": {
      "get": {
        "summary": "Lista bokningar per skapandedatum (WorldTicket)",
        "description": "Listar bokningar skapade inom ett datumintervall via `GET /sms-gateway-service/reservations`. Varje rad normaliseras till pnr, created_at, kontaktnamn, kontakt-e-post, telefon, segment och antal passagerare. Datumintervallet kan utelämnas om minst en annan WorldTicket-sökparameter skickas (t.ex. `email`, `ticket_number`, `phone`, `last_name`) – alla extra parametrar skickas vidare oförändrade.",
        "security": [
          {
            "apiKey": [
              "worldticket:reservations:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "reservation_date_start",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Startdatum (YYYY-MM-DD)."
          },
          {
            "name": "reservation_date_end",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Slutdatum (YYYY-MM-DD)."
          },
          {
            "name": "email",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Kontakt-e-post. Övriga WorldTicket-sökparametrar stöds också."
          },
          {
            "name": "exclude_canceled_reservations",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": true
            }
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 0
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 100,
              "maximum": 200
            }
          },
          {
            "name": "sort",
            "in": "query",
            "schema": {
              "type": "string",
              "default": "-recordLocator"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Lista med bokningar"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "502": {
            "description": "WorldTicket-fel (inloggning eller upstream)"
          }
        }
      }
    },
    "/api/v1/checkin/booking": {
      "get": {
        "summary": "Incheckning: hämta bokning via PNR + efternamn",
        "description": "Slår upp bokningen i WorldTicket, verifierar efternamnet (skiftläges- och accenttolerant) och returnerar normaliserade flyg- och passageraruppgifter för IATA BCBP-boardingkort. `flight` är nästa avgång; alla segment finns i `segments`. 404 booking_not_found om PNR saknas eller efternamnet inte matchar.",
        "security": [
          {
            "apiKey": [
              "checkin:booking:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "pnr",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 5,
              "maxLength": 10
            }
          },
          {
            "name": "surname",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Normaliserad bokning"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "booking_not_found"
          },
          "502": {
            "description": "WorldTicket-fel"
          }
        }
      }
    },
    "/api/v1/worldticket/booking": {
      "get": {
        "summary": "Hämta bokning (reservation) via PNR (WorldTicket)",
        "description": "Hämtar en bokning från WorldTicket via record locator (PNR). Anropar https://api.worldticket.net/sms-gateway-service/reservations/{pnr} och returnerar svaret som passthrough.",
        "security": [
          {
            "apiKey": [
              "worldticket:booking:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "pnr",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 5,
              "maxLength": 10
            },
            "description": "Record locator (PNR), 5-10 tecken A-Z0-9. Ex: BE49ME."
          }
        ],
        "responses": {
          "200": {
            "description": "Bokning (passthrough från WorldTicket)"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Bokning hittades inte"
          },
          "502": {
            "description": "WorldTicket-fel (inloggning eller upstream)"
          }
        }
      }
    },
    "/api/v1/worldticket/ssr": {
      "post": {
        "summary": "Lägg till SSR på bokning och markera som betald (WorldTicket)",
        "description": "Hämtar ordern från WorldTicket, lägger till en SSR-nod på matchande passagerare+segment, sparar ordern, läser utestående belopp från `calculable-order` och bokför betalningen om belopp > 0. Stöder t.ex. `PETC` (husdjur i kabin) och `XBAG` (extra bagage) — pris läses alltid från WorldTicket. Flightnummer accepteras som '804', 'OJ804' eller 'OJ0804'. Returnerar 404 om passagerare/segment inte hittas. Vid fel efter steg 'submit_order' innehåller felsvaret `ssr_added: true` så att klienten vet att SSR:en ligger kvar. **Nyckelkrav:** detta scope kräver även `worldticket:booking:read` — UI:t lägger till det automatiskt vid skapande.",
        "security": [
          {
            "apiKey": [
              "worldticket:ssr:write",
              "worldticket:booking:read"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "pnr",
                  "date",
                  "flight",
                  "passenger",
                  "ssr"
                ],
                "properties": {
                  "pnr": {
                    "type": "string",
                    "minLength": 5,
                    "maxLength": 10,
                    "example": "5ZMYA9"
                  },
                  "date": {
                    "type": "string",
                    "format": "date",
                    "example": "2026-05-29"
                  },
                  "flight": {
                    "type": "string",
                    "example": "OJ801",
                    "description": "'811', 'OJ811' eller 'OJ0811'."
                  },
                  "passenger": {
                    "type": "object",
                    "required": [
                      "firstName",
                      "lastName"
                    ],
                    "properties": {
                      "firstName": {
                        "type": "string",
                        "example": "Rikard"
                      },
                      "lastName": {
                        "type": "string",
                        "example": "von Zweigbergk"
                      }
                    }
                  },
                  "ssr": {
                    "type": "object",
                    "required": [
                      "code"
                    ],
                    "properties": {
                      "code": {
                        "type": "string",
                        "pattern": "^[A-Za-z]{3,4}$",
                        "example": "PETC"
                      },
                      "text": {
                        "type": "string",
                        "maxLength": 200,
                        "example": "1 animal in cabin"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "SSR tillagd och betald"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Passagerare/segment hittades inte"
          },
          "502": {
            "description": "WorldTicket-fel (med 'step' + ev. 'ssr_added')"
          }
        }
      }
    },
    "/api/v1/worldticket/booking/refund": {
      "post": {
        "summary": "Avboka bokning med full refund (WorldTicket)",
        "description": "Avbokar en bokning genom att refundera hela `orderInfo.totalAmount` via WorldTicket. Kedjar tre upstream-anrop: reservation → order (direction=REFUND) → payment/refund. Kontakt-email plockas från reservationens `contacts[0].email` om inte `email` skickas i bodyn.",
        "security": [
          {
            "apiKey": [
              "worldticket:booking:refund"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "pnr"
                ],
                "properties": {
                  "pnr": {
                    "type": "string",
                    "minLength": 5,
                    "maxLength": 10,
                    "example": "9FSCBT"
                  },
                  "email": {
                    "type": "string",
                    "format": "email",
                    "description": "Valfri – override för notifierings-email."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Refund genomförd (status PAID)"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Bokning eller order hittades inte"
          },
          "502": {
            "description": "WorldTicket-fel eller refund-status ≠ PAID (med 'step')"
          }
        }
      }
    },
    "/api/v1/worldticket/available-flights-for-change": {
      "get": {
        "summary": "Hitta lediga flighter för datum-ändring (flight picker)",
        "description": "Proxy till `bmaflyg.live2.se/public/available-flights-for-change`. Används av Portalen för att visa flight picker när en kund vill boka om datum i samma bokningsklass (RBD).",
        "security": [
          {
            "apiKey": [
              "worldticket:flights:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 3,
              "maxLength": 3
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 3,
              "maxLength": 3
            }
          },
          {
            "name": "date",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "example": "2026-07-12"
            }
          },
          {
            "name": "bookingClass",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 2,
              "example": "Q"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Lista med lediga flighter i begärd klass"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Inga flighter / okänd rutt"
          },
          "502": {
            "description": "Upstream-fel från bmaflyg"
          }
        }
      }
    },
    "/api/v1/worldticket/booking/create": {
      "post": {
        "summary": "Skapa ny bokning (betalas direkt mot faktura)",
        "description": "Skapar bokningen på Brommaflygs bokningsserver (POST /booking) och biljetterar den (POST /ticket). `dry_run: true` skapar endast en preliminär bokning utan biljett och visar priset. `idempotency_key` gör att samma nyckel inom 24 h ger samma svar utan ny bokning. Ingen tillgänglighetskontroll görs i detta anrop — endast bokning och biljettering. Standard: rbd `W`, fareBasisCode `WEB`. `departureTime`/`arrivalTime` (HH:MM) är valfria.",
        "security": [
          {
            "apiKey": [
              "worldticket:booking:create"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "segments",
                  "passengers",
                  "contact"
                ],
                "properties": {
                  "segments": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "flight",
                        "date",
                        "from",
                        "to"
                      ],
                      "properties": {
                        "flight": {
                          "type": "string",
                          "example": "OJ806"
                        },
                        "date": {
                          "type": "string",
                          "example": "2026-11-12"
                        },
                        "from": {
                          "type": "string",
                          "example": "BMA"
                        },
                        "to": {
                          "type": "string",
                          "example": "VBY"
                        },
                        "departureTime": {
                          "type": "string",
                          "example": "08:30"
                        },
                        "arrivalTime": {
                          "type": "string",
                          "example": "09:05"
                        },
                        "rbd": {
                          "type": "string",
                          "example": "W"
                        },
                        "fareBasisCode": {
                          "type": "string",
                          "example": "WEB"
                        }
                      }
                    }
                  },
                  "passengers": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "firstName",
                        "lastName"
                      ],
                      "properties": {
                        "title": {
                          "type": "string",
                          "example": "Ms"
                        },
                        "firstName": {
                          "type": "string"
                        },
                        "lastName": {
                          "type": "string"
                        },
                        "sex": {
                          "type": "string",
                          "enum": [
                            "M",
                            "F"
                          ]
                        },
                        "type": {
                          "type": "string",
                          "enum": [
                            "ADT",
                            "CHD",
                            "INF"
                          ],
                          "default": "ADT"
                        },
                        "birthDate": {
                          "type": "string",
                          "example": "2020-05-01"
                        },
                        "email": {
                          "type": "string"
                        }
                      }
                    }
                  },
                  "contact": {
                    "type": "object",
                    "required": [
                      "email"
                    ],
                    "properties": {
                      "email": {
                        "type": "string"
                      },
                      "mobile": {
                        "type": "object",
                        "properties": {
                          "country": {
                            "type": "string",
                            "example": "46"
                          },
                          "number": {
                            "type": "string",
                            "example": "701234567"
                          }
                        }
                      },
                      "preferredLanguage": {
                        "type": "string",
                        "default": "sv"
                      }
                    }
                  },
                  "dry_run": {
                    "type": "boolean",
                    "default": false
                  },
                  "idempotency_key": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Bokning skapad och betald: pnr, amount, currency, payment, tickets[]"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "409": {
            "description": "no_seats_available (enligt bokningsservern)"
          },
          "422": {
            "description": "fare_not_found"
          },
          "502": {
            "description": "booking_server_error, med step (create_booking/issue_ticket) och pnr om bokningen hann skapas"
          }
        }
      }
    },
    "/api/v1/worldticket/booking/change-date": {
      "post": {
        "summary": "Ändra datum/flight på ett bokningssegment",
        "description": "Proxy till `bmaflyg.live2.se/public/mybooking/changedate`. `newFlightDetails.flightNumber` är endast siffrorna (t.ex. `811`); `code` är carrier-prefix (t.ex. `OJ`). `segmentRPH` är `rph` från det segment som ska ändras.",
        "security": [
          {
            "apiKey": [
              "worldticket:booking:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "reservationId",
                  "segmentRPH",
                  "newFlightDetails"
                ],
                "properties": {
                  "reservationId": {
                    "type": "string",
                    "minLength": 5,
                    "maxLength": 10,
                    "example": "9FSCBT"
                  },
                  "segmentRPH": {
                    "oneOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "integer"
                      }
                    ],
                    "example": "1"
                  },
                  "newFlightDetails": {
                    "type": "object",
                    "required": [
                      "departureDateTime",
                      "arrivalDateTime",
                      "flightNumber",
                      "code"
                    ],
                    "properties": {
                      "departureDateTime": {
                        "type": "string",
                        "example": "2026-07-12T07:30:00"
                      },
                      "arrivalDateTime": {
                        "type": "string",
                        "example": "2026-07-12T08:25:00"
                      },
                      "flightNumber": {
                        "type": "string",
                        "example": "811"
                      },
                      "code": {
                        "type": "string",
                        "example": "OJ"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Datumändring genomförd"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Bokning hittades inte"
          },
          "502": {
            "description": "Upstream-fel från bmaflyg"
          }
        }
      }
    },
    "/api/v1/worldticket/booking/change-name": {
      "post": {
        "summary": "Ändra namn på passagerare",
        "description": "Proxy till `bmaflyg.live2.se/public/mybooking/changename`.",
        "security": [
          {
            "apiKey": [
              "worldticket:booking:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "reservationId",
                  "travelerIndex",
                  "title",
                  "givenName",
                  "surname"
                ],
                "properties": {
                  "reservationId": {
                    "type": "string",
                    "minLength": 5,
                    "maxLength": 10
                  },
                  "travelerIndex": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "title": {
                    "type": "string",
                    "example": "Mr."
                  },
                  "givenName": {
                    "type": "string"
                  },
                  "surname": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Namnändring genomförd"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Bokning hittades inte"
          },
          "502": {
            "description": "Upstream-fel från bmaflyg"
          }
        }
      }
    },
    "/api/v1/worldticket/booking/add-passengers": {
      "post": {
        "summary": "Lägg till resenär(er) i en bokning",
        "description": "Lägger till en eller flera platser i en befintlig reservation (`POST /sms-gateway-service/reservations/{pnr}/passengers`). Ange `count` för tomma TBA-platser eller `passengers` med namn.",
        "security": [
          {
            "apiKey": [
              "worldticket:booking:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "pnr"
                ],
                "properties": {
                  "pnr": {
                    "type": "string",
                    "minLength": 5,
                    "maxLength": 10,
                    "example": "CMGVDS"
                  },
                  "count": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 20,
                    "example": 2
                  },
                  "passengers": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "title": {
                          "type": "string",
                          "example": "Mr"
                        },
                        "firstName": {
                          "type": "string"
                        },
                        "lastName": {
                          "type": "string"
                        },
                        "sex": {
                          "type": "string",
                          "enum": [
                            "M",
                            "F"
                          ]
                        },
                        "passengerTypeGroup": {
                          "type": "string",
                          "example": "ADT"
                        },
                        "passengerTypeCode": {
                          "type": "string",
                          "example": "1"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Platser tillagda; svaret innehåller uppdaterad passagerarlista"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Bokning hittades inte"
          },
          "409": {
            "description": "`no_seats_available` – bokningsklassen (RBD) är full, fler platser kan inte läggas till. Svaret innehåller `rbd`, `booked`, `max_available`."
          },
          "502": {
            "description": "Upstream-fel från WorldTicket"
          }
        }
      }
    },
    "/api/v1/worldticket/booking/cancel-passenger": {
      "post": {
        "summary": "Ta bort resenär från en bokning",
        "description": "Tar bort en enskild passagerare via rph (`DELETE /sms-gateway-service/reservations/{pnr}/passengers/{rph}`). Sista passageraren kan inte tas bort – avboka hela bokningen i stället.",
        "security": [
          {
            "apiKey": [
              "worldticket:booking:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "pnr",
                  "rph"
                ],
                "properties": {
                  "pnr": {
                    "type": "string",
                    "minLength": 5,
                    "maxLength": 10,
                    "example": "CMGVDS"
                  },
                  "rph": {
                    "type": "integer",
                    "minimum": 1,
                    "example": 7
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Passageraren borttagen"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Bokning eller rph hittades inte"
          },
          "502": {
            "description": "Upstream-fel från WorldTicket"
          }
        }
      }
    },
    "/api/v1/worldticket/booking/resend-confirmation": {
      "post": {
        "summary": "Skicka om bokningsbekräftelse",
        "description": "Skickar om bokningsbekräftelsen via WorldTicket (`POST /sms-gateway-service/notifications/email`). Utelämnas `emails` används bokningens kontakt-e-post. `mailIdentifier` defaultar till `tkt.issue_confirmation`.",
        "security": [
          {
            "apiKey": [
              "worldticket:booking:notify"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "pnr"
                ],
                "properties": {
                  "pnr": {
                    "type": "string",
                    "minLength": 5,
                    "maxLength": 10,
                    "example": "R672VY"
                  },
                  "emails": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "email"
                    }
                  },
                  "ccEmails": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "email"
                    }
                  },
                  "bccEmails": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "email"
                    }
                  },
                  "phones": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "subject": {
                    "type": "string"
                  },
                  "message": {
                    "type": "string"
                  },
                  "mailIdentifier": {
                    "type": "string",
                    "example": "tkt.issue_confirmation"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Bekräftelsen skickad"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Bokning hittades inte"
          },
          "502": {
            "description": "Upstream-fel från WorldTicket"
          }
        }
      }
    },
    "/api/v1/sms/send": {
      "post": {
        "summary": "Skicka SMS via ClickSend",
        "description": "Skickar ett SMS genom ClickSend (`POST https://rest.clicksend.com/v3/sms/send`). Mottagarnumret måste vara i E.164-format, t.ex. `+46701234567`. Utelämnas `from` används avsändaren som är konfigurerad i huben (CLICKSEND_FROM). Svarar 502 om ClickSend nekar meddelandet eller inte returnerar SUCCESS.",
        "security": [
          {
            "apiKey": [
              "sms:send"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "to",
                  "body"
                ],
                "properties": {
                  "to": {
                    "type": "string",
                    "minLength": 8,
                    "maxLength": 20,
                    "description": "Mottagare i E.164-format",
                    "example": "+46701234567"
                  },
                  "body": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 1600,
                    "description": "Meddelandetext (UTF-8, delas automatiskt i flera SMS vid behov)",
                    "example": "Påminnelse: ditt flyg OJ0805 avgår imorgon 16:00 från Bromma."
                  },
                  "from": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 20,
                    "description": "Valfri avsändare (alfanumerisk sender-ID eller nummer)",
                    "example": "Brommaflyg"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "SMS skickat",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "example": true
                    },
                    "status": {
                      "type": "string",
                      "example": "SUCCESS"
                    },
                    "messageId": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "cost": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Kostnad i kontots valuta",
                      "example": "0.0616"
                    },
                    "raw": {
                      "type": "object",
                      "description": "Råsvar från ClickSend"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "502": {
            "description": "ClickSend nekade meddelandet eller svarade med fel",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  }
}