{
  "openapi": "3.1.0",
  "info": {
    "title": "nginz-iot panel contract",
    "version": "0.1.0",
    "description": "Backend-owned dashboard and admin contract for nginz-iot-panel. Governed mutations are runtime-enabled only for governed snapshots and trusted-edge principal headers; HTTP 2xx remains accepted-only."
  },
  "paths": {
    "/api/v1/admin/identity": {
      "get": {
        "operationId": "getAdminIdentity",
        "summary": "Stable API identity, role vocabulary, and current trusted-edge role",
        "responses": {
          "200": {
            "description": "Redacted API identity and verified role observation; omits the principal and does not imply broker readiness or command authority",
            "headers": {
              "X-Nginz-Iot-Api-Version": { "schema": { "const": "1" } },
              "X-Nginz-Iot-Contract-Version": { "schema": { "const": "0.1.0" } },
              "Cache-Control": { "schema": { "const": "no-store" } }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "./fixture-schemas/identity.schema.json" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" },
          "500": { "$ref": "#/components/responses/InternalServerError" }
        }
      }
    },
    "/live": {
      "get": {
        "operationId": "getLive",
        "summary": "Process liveness",
        "responses": {
          "200": {
            "description": "Process liveness"
          }
        }
      }
    },
    "/ready": {
      "get": {
        "operationId": "getReady",
        "summary": "Route readiness",
        "responses": {
          "200": {
            "description": "Route readiness"
          }
        }
      }
    },
    "/health/details": {
      "get": {
        "operationId": "getHealthDetails",
        "summary": "Detailed health / HA facts",
        "responses": {
          "200": {
            "description": "Detailed health"
          }
        }
      }
    },
    "/api/v1/admin/dashboard": {
      "get": {
        "operationId": "getAdminDashboard",
        "summary": "Panel-contract dashboard snapshot",
        "responses": {
          "200": {
            "description": "Bounded redacted dashboard projection",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "./fixture-schemas/dashboard.schema.json"
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" },
          "502": { "$ref": "#/components/responses/BadGateway" },
          "503": { "$ref": "#/components/responses/ServiceUnavailable" }
        }
      }
    },
    "/api/v1/admin/clients": {
      "get": {
        "operationId": "getAdminClients",
        "summary": "Paginated connected clients",
        "parameters": [
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Query"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated clients collection",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "./fixture-schemas/collection.schema.json"
                }
              }
            }
          },
          "503": { "$ref": "#/components/responses/ServiceUnavailable" }
        }
      }
    },
    "/api/v1/admin/sessions": {
      "get": {
        "operationId": "getAdminSessions",
        "summary": "Paginated sessions",
        "parameters": [
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Query"
          },
          {
            "$ref": "#/components/parameters/SessionState"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated sessions collection",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "./fixture-schemas/collection.schema.json"
                }
              }
            }
          },
          "503": { "$ref": "#/components/responses/ServiceUnavailable" }
        }
      }
    },
    "/api/v1/admin/subscriptions": {
      "get": {
        "operationId": "getAdminSubscriptions",
        "summary": "Paginated subscriptions",
        "parameters": [
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Query"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated subscriptions collection",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "./fixture-schemas/collection.schema.json"
                }
              }
            }
          },
          "503": { "$ref": "#/components/responses/ServiceUnavailable" }
        }
      }
    },
    "/api/v1/admin/retained": {
      "get": {
        "operationId": "getAdminRetained",
        "summary": "Paginated retained message summaries (payload-free)",
        "parameters": [
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Query"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated retained summaries",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "./fixture-schemas/collection.schema.json"
                }
              }
            }
          },
          "503": { "$ref": "#/components/responses/ServiceUnavailable" }
        }
      }
    },
    "/api/v1/admin/offline-queues": {
      "get": {
        "operationId": "getAdminOfflineQueues",
        "summary": "Paginated offline queue summaries (payload-free)",
        "parameters": [
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Query"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated offline queue summaries",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "./fixture-schemas/collection.schema.json"
                }
              }
            }
          },
          "503": { "$ref": "#/components/responses/ServiceUnavailable" }
        }
      }
    },
    "/api/v1/admin/bridges": {
      "get": {
        "operationId": "getAdminBridges",
        "summary": "Shared guardian summary and paginated isolated bridge worker status rows",
        "parameters": [
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Query"
          },
          {
            "$ref": "#/components/parameters/BridgeState"
          }
        ],
        "responses": {
          "200": {
            "description": "Shared guardian summary plus paginated per-target worker rows",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "./fixture-schemas/collection.schema.json"
                }
              }
            }
          },
          "503": { "$ref": "#/components/responses/ServiceUnavailable" }
        }
      }
    },
    "/api/v1/admin/rules": {
      "get": {
        "operationId": "getAdminRules",
        "summary": "Paginated rules",
        "parameters": [
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Query"
          },
          {
            "$ref": "#/components/parameters/RuleState"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated rules",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "./fixture-schemas/collection.schema.json"
                }
              }
            }
          },
          "503": { "$ref": "#/components/responses/ServiceUnavailable" }
        }
      }
    },
    "/api/v1/admin/schemas": {
      "get": {
        "operationId": "getAdminSchemas",
        "summary": "Paginated schema registry summaries",
        "parameters": [
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Query"
          },
          {
            "$ref": "#/components/parameters/SchemaState"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated schemas",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "./fixture-schemas/collection.schema.json"
                }
              }
            }
          },
          "503": { "$ref": "#/components/responses/ServiceUnavailable" }
        }
      }
    },
    "/api/v1/admin/gateways": {
      "get": {
        "operationId": "getAdminGateways",
        "summary": "Paginated gateway status",
        "parameters": [
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Query"
          },
          {
            "$ref": "#/components/parameters/GatewayState"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated gateways",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "./fixture-schemas/collection.schema.json"
                }
              }
            }
          },
          "503": { "$ref": "#/components/responses/ServiceUnavailable" }
        }
      }
    },
    "/api/v1/admin/audit": {
      "get": {
        "operationId": "getAdminAudit",
        "summary": "Paginated audit records (redacted)",
        "parameters": [
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Query"
          },
          {
            "$ref": "#/components/parameters/AuditState"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated audit records",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "./fixture-schemas/collection.schema.json"
                }
              }
            }
          },
          "503": { "$ref": "#/components/responses/ServiceUnavailable" }
        }
      }
    },
    "/api/v1/admin/support": {
      "get": {
        "operationId": "getAdminSupport",
        "summary": "Support bundle metadata (safe-to-share flags)",
        "responses": {
          "200": {
            "description": "Support bundle metadata"
          }
        }
      }
    },
    "/api/v1/admin/ha": {
      "get": {
        "operationId": "getAdminHa",
        "summary": "HA readiness projection",
        "responses": {
          "200": {
            "description": "HA readiness"
          }
        }
      }
    },
    "/api/v1/admin/metrics": {
      "get": {
        "operationId": "getAdminMetrics",
        "summary": "Bounded metrics summary",
        "responses": {
          "200": {
            "description": "Metrics summary"
          }
        }
      }
    },
    "/api/v1/admin/metrics/history": {
      "get": {
        "operationId": "getAdminMetricHistory",
        "summary": "Backend-owned 30-second metric history at a fixed one-second cadence",
        "responses": {
          "200": {
            "description": "Bounded oldest-to-newest metric samples; clients must not infer retention beyond the response metadata"
          }
        }
      }
    },
    "/api/v1/admin/drain": {
      "post": {
        "operationId": "postControlledDrain",
        "summary": "Request controlled node drain (HTTP fail-closed until RBAC/SSO; never completes on 2xx)",
        "parameters": [
          {
            "name": "X-Iot-Admin-Role",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "operator_admin",
                "config_admin"
              ]
            },
            "description": "Honored only when snapshot rbac_sso_hooks_ready=true"
          },
          {
            "name": "X-Iot-Admin-Principal",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Required with role when SSO hooks are ready; ignored otherwise"
          },
          {
            "name": "idempotency_key",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64
            }
          },
          {
            "name": "expected_generation",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[0-9]+$"
            }
          },
          {
            "$ref": "#/components/parameters/ExpectedWriterTerm"
          },
          {
            "$ref": "#/components/parameters/Target"
          }
        ],
        "responses": {
          "202": {
            "description": "Drain command accepted only; completion requires reconciliation"
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "500": { "$ref": "#/components/responses/InternalServerError" },
          "503": { "$ref": "#/components/responses/ServiceUnavailable" }
        }
      }
    },
    "/api/v1/admin/bridges/{id}/drain": {
      "post": {
        "operationId": "postBridgeDrain",
        "summary": "Request bridge drain (HTTP fail-closed until RBAC/SSO; never completes on 2xx)",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/ExpectedGeneration"
          },
          {
            "$ref": "#/components/parameters/ExpectedWriterTerm"
          },
          {
            "$ref": "#/components/parameters/Target"
          }
        ],
        "responses": {
          "202": {
            "description": "Bridge drain accepted only"
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "500": { "$ref": "#/components/responses/InternalServerError" },
          "503": { "$ref": "#/components/responses/ServiceUnavailable" }
        }
      }
    },
    "/api/v1/admin/config/preflight": {
      "post": {
        "operationId": "postConfigPreflight",
        "summary": "Config preflight (HTTP fail-closed until RBAC/SSO; never completes on 2xx)",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/ExpectedGeneration"
          },
          {
            "$ref": "#/components/parameters/ExpectedWriterTerm"
          },
          {
            "$ref": "#/components/parameters/Target"
          }
        ],
        "responses": {
          "202": {
            "description": "Config preflight accepted only"
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "500": { "$ref": "#/components/responses/InternalServerError" },
          "503": { "$ref": "#/components/responses/ServiceUnavailable" }
        }
      }
    },
    "/api/v1/admin/commands/{id}": {
      "get": {
        "operationId": "getAdminCommand",
        "summary": "Read-only observation of daemon-owned command lifecycle status",
        "x-nginz-iot-read-only": true,
        "x-nginz-iot-reconciles-evidence": false,
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Current daemon-owned command lifecycle record. GET never transitions state or reconciles evidence. HTTP 200 is not completion; only state=reconciled is.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "./fixture-schemas/command-lifecycle.schema.json"
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" },
          "502": { "$ref": "#/components/responses/BadGateway" },
          "503": { "$ref": "#/components/responses/ServiceUnavailable" }
        }
      }
    },
    "/api/v1/admin/events": {
      "get": {
        "operationId": "getAdminEvents",
        "summary": "Admin event frames (live GET via iot_admin_view events; fixtures remain)",
        "parameters": [
          {
            "name": "commandId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Offset"
          },
          {
            "$ref": "#/components/parameters/Limit"
          }
        ],
        "responses": {
          "200": {
            "description": "Daemon-owned event list with explicit freshness and pagination metadata (bounded, redacted)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "./fixture-schemas/admin-events.schema.json"
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" },
          "502": { "$ref": "#/components/responses/BadGateway" },
          "503": { "$ref": "#/components/responses/ServiceUnavailable" }
        }
      }
    },
    "/api/v1/admin/support/bundle": {
      "post": {
        "operationId": "postSupportBundleCreate",
        "summary": "Create support bundle command (support_admin owner; operator_admin API v1 compatibility; accepted only)",
        "parameters": [
          {
            "$ref": "#/components/parameters/AdminRole"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/ExpectedGeneration"
          },
          {
            "$ref": "#/components/parameters/ExpectedWriterTerm"
          },
          {
            "$ref": "#/components/parameters/Target"
          }
        ],
        "responses": {
          "202": {
            "description": "Support bundle command accepted only"
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "500": { "$ref": "#/components/responses/InternalServerError" },
          "503": { "$ref": "#/components/responses/ServiceUnavailable" }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "AdminError": {
        "$ref": "./fixture-schemas/error.schema.json"
      }
    },
    "responses": {
      "BadRequest": {
        "description": "The bounded request contract was invalid.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AdminError" } } }
      },
      "Unauthorized": {
        "description": "An authenticated admin identity is required.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AdminError" } } }
      },
      "Forbidden": {
        "description": "Governance or authorization denied the operation.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AdminError" } } }
      },
      "NotFound": {
        "description": "The requested admin resource was not found.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AdminError" } } }
      },
      "MethodNotAllowed": {
        "description": "The HTTP method is not supported by this admin view.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AdminError" } } }
      },
      "Conflict": {
        "description": "The request conflicts with current daemon authority or fencing state.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AdminError" } } }
      },
      "InternalServerError": {
        "description": "The bounded admin response could not be produced.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AdminError" } } }
      },
      "BadGateway": {
        "description": "The daemon-owned snapshot failed its version, redaction, or shape contract.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AdminError" } } }
      },
      "ServiceUnavailable": {
        "description": "Fresh daemon authority or its command ring is temporarily unavailable.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AdminError" } } }
      }
    },
    "parameters": {
      "AdminRole": {
        "name": "X-Iot-Admin-Role",
        "in": "header",
        "required": false,
        "description": "Injected only by a trusted authentication edge; unknown roles are rejected.",
        "schema": {
          "type": "string",
          "enum": ["viewer", "operator_admin", "config_admin", "support_admin"]
        }
      },
      "Offset": {
        "name": "offset",
        "in": "query",
        "required": false,
        "schema": {
          "type": "integer",
          "minimum": 0,
          "default": 0
        }
      },
      "Limit": {
        "name": "limit",
        "in": "query",
        "required": false,
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 200,
          "default": 50
        }
      },
      "Query": {
        "name": "q",
        "in": "query",
        "required": false,
        "x-max-utf8-bytes": 64,
        "description": "Bounded case-insensitive match across redacted string fields; applied before pagination. Decoded UTF-8 is bounded to 64 bytes at code-point boundaries; response filters echo the normalized value.",
        "schema": {
          "type": "string",
          "maxLength": 64
        }
      },
      "SessionState": {
        "name": "state",
        "in": "query",
        "required": false,
        "x-max-utf8-bytes": 64,
        "description": "Exact case-insensitive match derived from the session connected flag; applied before pagination. Decoded UTF-8 is bounded to 64 bytes at code-point boundaries; response filters echo the normalized value.",
        "schema": {
          "type": "string",
          "enum": ["connected", "disconnected"]
        }
      },
      "BridgeState": {
        "name": "state",
        "in": "query",
        "required": false,
        "x-max-utf8-bytes": 64,
        "description": "Exact case-insensitive bridge target state match; applied before pagination. Decoded UTF-8 is bounded to 64 bytes at code-point boundaries; response filters echo the normalized value.",
        "schema": {
          "type": "string",
          "enum": ["healthy", "degraded", "standby", "failed", "draining"]
        }
      },
      "RuleState": {
        "name": "state",
        "in": "query",
        "required": false,
        "x-max-utf8-bytes": 64,
        "description": "Exact case-insensitive match derived from the rule enabled flag; applied before pagination. Decoded UTF-8 is bounded to 64 bytes at code-point boundaries; response filters echo the normalized value.",
        "schema": {
          "type": "string",
          "enum": ["enabled", "disabled"]
        }
      },
      "SchemaState": {
        "name": "state",
        "in": "query",
        "required": false,
        "x-max-utf8-bytes": 64,
        "description": "Exact case-insensitive match derived from the schema active flag; applied before pagination. Decoded UTF-8 is bounded to 64 bytes at code-point boundaries; response filters echo the normalized value.",
        "schema": {
          "type": "string",
          "enum": ["active", "inactive"]
        }
      },
      "GatewayState": {
        "name": "state",
        "in": "query",
        "required": false,
        "x-max-utf8-bytes": 64,
        "description": "Exact case-insensitive gateway certification status match; applied before pagination. Decoded UTF-8 is bounded to 64 bytes at code-point boundaries; response filters echo the normalized value.",
        "schema": {
          "type": "string",
          "enum": ["installed-runtime-certified"]
        }
      },
      "AuditState": {
        "name": "state",
        "in": "query",
        "required": false,
        "x-max-utf8-bytes": 64,
        "description": "Exact case-insensitive security audit decision match; applied before pagination. Decoded UTF-8 is bounded to 64 bytes at code-point boundaries; response filters echo the normalized value.",
        "schema": {
          "type": "string",
          "enum": ["allow", "deny"]
        }
      },
      "IdempotencyKey": {
        "name": "idempotency_key",
        "in": "query",
        "required": true,
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 64
        }
      },
      "ExpectedGeneration": {
        "name": "expected_generation",
        "in": "query",
        "required": true,
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 20,
          "pattern": "^[0-9]+$"
        }
      },
      "ExpectedWriterTerm": {
        "name": "expected_writer_term",
        "in": "query",
        "required": true,
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 20,
          "pattern": "^[0-9]+$"
        }
      },
      "Target": {
        "name": "target",
        "in": "query",
        "required": true,
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 96,
          "pattern": "^[A-Za-z0-9._:@/-]+$"
        }
      }
    }
  }
}
