{
  "openapi": "3.0.3",
  "info": {
    "title": "TimeX ⇄ Boom-in Dispatch API",
    "version": "1.0.0",
    "description": "Real-time dispatch verification integration between TimeX (timexapp.co.za) and Boom-in.\n\nTwo endpoints:\n1. TimeX pushes the published fleet schedule (drivers + vehicles + trailers + containers) to Boom-in.\n2. Boom-in pushes each gate scan transaction back to TimeX for authoritative comparison & alerting.\n\nAll requests are HMAC-SHA256 signed with a shared secret. Both sides implement the same signing scheme.",
    "contact": { "name": "TimeX Support", "url": "https://timexapp.co.za" }
  },
  "servers": [
    { "url": "https://timexapp.co.za", "description": "TimeX production (custom domain)" }
  ],
  "security": [
    { "HmacSignature": [] }
  ],
  "components": {
    "securitySchemes": {
      "HmacSignature": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Timex-Signature",
        "description": "sha256=<hex> — HMAC-SHA256 of the raw request body using the shared secret."
      }
    },
    "schemas": {
      "DriverRef": {
        "type": "object",
        "properties": {
          "employee_number": { "type": "string" },
          "first_name": { "type": "string" },
          "last_name": { "type": "string" },
          "licence_number": { "type": "string", "description": "RSA driver's licence number — the match key Boom-in scans from the driver's licence card." }
        }
      },
      "VehicleRef": {
        "type": "object",
        "properties": {
          "fleet_number": { "type": "string" },
          "registration": { "type": "string", "description": "Vehicle registration / plate — the match key Boom-in scans from the vehicle disc." }
        }
      },
      "TrailerRef": {
        "type": "object",
        "properties": {
          "fleet_number": { "type": "string" },
          "registration": { "type": "string" }
        }
      },
      "ContainerRef": {
        "type": "object",
        "properties": {
          "container_number": { "type": "string" },
          "seal_number": { "type": "string", "description": "Cargo seal number (optional per container)" },
          "linked_trailer_reg": { "type": "string" }
        }
      },
      "ScheduleAllocation": {
        "type": "object",
        "required": ["allocation_number", "allocation_date", "driver", "vehicle"],
        "properties": {
          "allocation_number": { "type": "string", "example": "AL-1028" },
          "allocation_date": { "type": "string", "format": "date", "example": "2026-09-06" },
          "shift": { "type": "string", "example": "Morning" },
          "depot": { "type": "string" },
          "exit_gate": { "type": "string", "example": "Gate 1" },
          "route_job_reference": { "type": "string" },
          "expected_destination": { "type": "string" },
          "planned_departure": { "type": "string", "format": "date-time" },
          "driver": { "$ref": "#/components/schemas/DriverRef" },
          "vehicle": { "$ref": "#/components/schemas/VehicleRef" },
          "trailers": { "type": "array", "items": { "$ref": "#/components/schemas/TrailerRef" } },
          "containers": { "type": "array", "items": { "$ref": "#/components/schemas/ContainerRef" } },
          "version": { "type": "integer", "minimum": 1, "description": "Incremented on each amendment" },
          "status": { "type": "string", "enum": ["published", "amended", "cancelled"] },
          "published_at": { "type": "string", "format": "date-time" }
        }
      },
      "ScheduleBatch": {
        "type": "object",
        "required": ["batch_id", "pushed_at", "action", "allocations"],
        "properties": {
          "batch_id": { "type": "string", "example": "B-1694000000000" },
          "pushed_at": { "type": "string", "format": "date-time" },
          "action": { "type": "string", "enum": ["publish", "amend", "cancel"] },
          "allocations": { "type": "array", "items": { "$ref": "#/components/schemas/ScheduleAllocation" } }
        }
      },
      "ScannedDriver": {
        "type": "object",
        "properties": {
          "licence_number": { "type": "string", "description": "From scanned driver's licence" },
          "employee_number": { "type": "string", "description": "Optional manual entry" }
        }
      },
      "ScannedVehicle": {
        "type": "object",
        "properties": {
          "registration": { "type": "string", "description": "From scanned vehicle disc" }
        }
      },
      "ScannedTrailer": {
        "type": "object",
        "properties": { "registration": { "type": "string" } }
      },
      "ScannedContainer": {
        "type": "object",
        "properties": {
          "container_number": { "type": "string" },
          "seal_number": { "type": "string" }
        }
      },
      "ScanTransaction": {
        "type": "object",
        "required": ["scan_session_id", "scan_timestamp", "scan_type", "gate"],
        "properties": {
          "scan_session_id": { "type": "string", "description": "Unique per gate transaction — used for idempotency" },
          "scan_timestamp": { "type": "string", "format": "date-time", "description": "UTC ISO-8601" },
          "gate": { "type": "string" },
          "scan_type": { "type": "string", "enum": ["outbound", "checkpoint", "return"] },
          "checkpoint_label": { "type": "string", "description": "Required when scan_type = checkpoint" },
          "scanned_driver": { "$ref": "#/components/schemas/ScannedDriver" },
          "scanned_vehicle": { "$ref": "#/components/schemas/ScannedVehicle" },
          "scanned_trailers": { "type": "array", "items": { "$ref": "#/components/schemas/ScannedTrailer" } },
          "scanned_containers": { "type": "array", "items": { "$ref": "#/components/schemas/ScannedContainer" } },
          "scanner_user": { "type": "string", "description": "Boom-in operator id (optional)" },
          "boomin_match_summary": {
            "type": "object",
            "properties": {
              "result": { "type": "string", "enum": ["matched", "mismatch", "blocked"] },
              "mismatch_items": { "type": "array", "items": { "type": "string" } }
            }
          }
        }
      },
      "ScanAck": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean" },
          "result": { "type": "string", "enum": ["matched", "mismatch", "blocked", "unscheduled", "duplicate", "skipped"] },
          "allocation_number": { "type": "string", "nullable": true },
          "mismatch_items": { "type": "array", "items": { "type": "string" } },
          "scan_id": { "type": "string" }
        }
      },
      "ScheduleAck": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean" },
          "acknowledged": { "type": "array", "items": { "type": "string" }, "description": "allocation_numbers received" }
        }
      },
      "Error": {
        "type": "object",
        "properties": { "error": { "type": "string" } }
      }
    }
  },
  "paths": {
    "/functions/boominScanWebhook": {
      "post": {
        "summary": "Receive a gate scan transaction (Boom-in → TimeX)",
        "description": "Boom-in POSTs one composite payload per gate transaction (driver + vehicle + all trailers + all containers). TimeX matches it to the scheduled allocation and raises alerts on mismatch. Idempotent on scan_session_id.",
        "security": [{ "HmacSignature": [] }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ScanTransaction" } } }
        },
        "responses": {
          "200": { "description": "Processed", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ScanAck" } } } },
          "401": { "description": "Invalid HMAC signature", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "400": { "description": "Invalid payload", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/timex/schedules": {
      "post": {
        "summary": "Receive the published schedule batch (TimeX → Boom-in)",
        "description": "CONTRACT FOR BOOM-IN TO IMPLEMENT. TimeX POSTs a batch of allocations on publish / amend / cancel. One call per event (a full-day publish is a single batch). Boom-in must verify the X-Timex-Signature header, persist the latest version per allocation_number, and return the acknowledged allocation_numbers.",
        "security": [{ "HmacSignature": [] }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ScheduleBatch" } } }
        },
        "responses": {
          "200": { "description": "Acknowledged", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ScheduleAck" } } } },
          "401": { "description": "Invalid HMAC signature", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    }
  }
}
