{
  "openapi": "3.1.0",
  "info": {
    "title": "Meter Flow API",
    "version": "0.3.0",
    "description": "Contract for the live Meter Flow demo. Public reads expose demo state. Mutating operations require a server-side session cookie and X-CSRF-Token."
  },
  "servers": [
    {
      "url": "https://meters.zagfsa.ru/api/v1"
    }
  ],
  "paths": {
    "/auth/login": {
      "post": {
        "operationId": "login",
        "summary": "Create demo session",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LoginRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Authenticated session",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Session"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/auth/me": {
      "get": {
        "operationId": "getCurrentSession",
        "summary": "Get current authenticated user",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "Active session",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Session"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/auth/logout": {
      "post": {
        "operationId": "logout",
        "summary": "Revoke current session",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/CsrfToken"
          }
        ],
        "responses": {
          "204": {
            "description": "Session revoked"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/demo/reset": {
      "post": {
        "operationId": "resetDemo",
        "summary": "Reset demo period",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/CsrfToken"
          }
        ],
        "responses": {
          "200": {
            "description": "Reset period",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PeriodSnapshot"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/periods/{periodId}": {
      "get": {
        "operationId": "getPeriod",
        "summary": "Get live period snapshot",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/PeriodId"
          }
        ],
        "responses": {
          "200": {
            "description": "Period snapshot",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PeriodSnapshot"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/periods/{periodId}/events": {
      "get": {
        "operationId": "listPeriodEvents",
        "summary": "Get append-only audit trail",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/PeriodId"
          }
        ],
        "responses": {
          "200": {
            "description": "Audit events",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/AuditEvent"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/periods/{periodId}/reading-requests": {
      "post": {
        "operationId": "createReadingRequest",
        "summary": "OWNER requests readings",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PeriodId"
          },
          {
            "$ref": "#/components/parameters/CsrfToken"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "201": {
            "description": "Reading request created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReadingRequest"
                }
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/reading-requests/{requestId}/submissions": {
      "post": {
        "operationId": "submitManualReadings",
        "summary": "TENANT submits manually verified readings",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/RequestId"
          },
          {
            "$ref": "#/components/parameters/CsrfToken"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ManualSubmissionRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Manual submission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReadingSubmission"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/reading-requests/{requestId}/demo-ocr": {
      "post": {
        "operationId": "createDemoOcrSubmission",
        "summary": "TENANT uploads demo meter photos and runs demo OCR adapter",
        "description": "Creates three photo references, ReadingValue rows and OCR candidates. HOT_WATER intentionally has low confidence for human-review demonstration.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/RequestId"
          },
          {
            "$ref": "#/components/parameters/CsrfToken"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "201": {
            "description": "OCR submission requires human review",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReadingSubmission"
                }
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/reading-values/{readingId}/review": {
      "post": {
        "operationId": "reviewOcrCandidate",
        "summary": "TENANT accepts or corrects an OCR candidate",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ReadingId"
          },
          {
            "$ref": "#/components/parameters/CsrfToken"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OcrReviewRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated submission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReadingSubmission"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/reading-submissions/{submissionId}/confirm": {
      "post": {
        "operationId": "confirmReadings",
        "summary": "TENANT confirms fully reviewed submission",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SubmissionId"
          },
          {
            "$ref": "#/components/parameters/CsrfToken"
          }
        ],
        "responses": {
          "200": {
            "description": "Confirmed submission",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReadingSubmission"
                }
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/periods/{periodId}/bills": {
      "post": {
        "operationId": "calculateBill",
        "summary": "OWNER calculates bill draft",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PeriodId"
          },
          {
            "$ref": "#/components/parameters/CsrfToken"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "201": {
            "description": "Bill draft",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Bill"
                }
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/bills/{billId}/issue": {
      "post": {
        "operationId": "issueBill",
        "summary": "OWNER issues bill",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/BillId"
          },
          {
            "$ref": "#/components/parameters/CsrfToken"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "Issued bill",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Bill"
                }
              }
            }
          }
        }
      }
    },
    "/periods/{periodId}/payment-claims": {
      "post": {
        "operationId": "claimPayment",
        "summary": "TENANT reports payment",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PeriodId"
          },
          {
            "$ref": "#/components/parameters/CsrfToken"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "amountMinor"
                ],
                "properties": {
                  "amountMinor": {
                    "type": "integer",
                    "minimum": 1
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Payment claim; balance is unchanged",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentClaim"
                }
              }
            }
          }
        }
      }
    },
    "/payment-claims/{claimId}/confirm": {
      "post": {
        "operationId": "confirmPayment",
        "summary": "OWNER confirms money received",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClaimId"
          },
          {
            "$ref": "#/components/parameters/CsrfToken"
          }
        ],
        "responses": {
          "201": {
            "description": "Payment recorded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payment"
                }
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/payment-claims/{claimId}/reject": {
      "post": {
        "operationId": "rejectPaymentClaim",
        "summary": "OWNER rejects unreceived payment claim",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClaimId"
          },
          {
            "$ref": "#/components/parameters/CsrfToken"
          }
        ],
        "responses": {
          "204": {
            "description": "Claim rejected"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "sessionCookie": {
        "type": "apiKey",
        "in": "cookie",
        "name": "mf_session"
      }
    },
    "parameters": {
      "PeriodId": {
        "name": "periodId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "RequestId": {
        "name": "requestId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "ReadingId": {
        "name": "readingId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "SubmissionId": {
        "name": "submissionId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "BillId": {
        "name": "billId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "ClaimId": {
        "name": "claimId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "CsrfToken": {
        "name": "X-CSRF-Token",
        "in": "header",
        "required": true,
        "schema": {
          "type": "string",
          "minLength": 16
        }
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": true,
        "schema": {
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Authentication required or invalid credentials",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "Role or CSRF check failed",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Conflict": {
        "description": "State or idempotency conflict",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "ValidationError": {
        "description": "Validation error",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "LoginRequest": {
        "type": "object",
        "required": [
          "login",
          "password"
        ],
        "properties": {
          "login": {
            "type": "string"
          },
          "password": {
            "type": "string",
            "format": "password"
          }
        }
      },
      "Session": {
        "type": "object",
        "required": [
          "userId",
          "login",
          "role",
          "displayName",
          "csrfToken",
          "expiresAt"
        ],
        "properties": {
          "userId": {
            "type": "string"
          },
          "login": {
            "type": "string"
          },
          "role": {
            "type": "string",
            "enum": [
              "OWNER",
              "TENANT"
            ]
          },
          "displayName": {
            "type": "string"
          },
          "csrfToken": {
            "type": "string"
          },
          "expiresAt": {
            "type": "integer"
          }
        }
      },
      "ReadingRequest": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "period_id": {
            "type": "string"
          },
          "created_by": {
            "type": "string"
          },
          "due_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string"
          },
          "created_at": {
            "type": "string"
          }
        }
      },
      "ManualSubmissionRequest": {
        "type": "object",
        "required": [
          "readings"
        ],
        "properties": {
          "readings": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "object",
              "required": [
                "meterId",
                "actualValue",
                "reportedValue"
              ],
              "properties": {
                "meterId": {
                  "type": "string"
                },
                "actualValue": {
                  "type": "string"
                },
                "reportedValue": {
                  "type": "integer"
                },
                "photoObjectKey": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              }
            }
          }
        }
      },
      "OcrReviewRequest": {
        "type": "object",
        "required": [
          "actualValue",
          "reportedValue",
          "decision"
        ],
        "properties": {
          "actualValue": {
            "type": "string"
          },
          "reportedValue": {
            "type": "integer"
          },
          "decision": {
            "type": "string",
            "enum": [
              "ACCEPTED",
              "CORRECTED"
            ]
          }
        }
      },
      "OcrCandidate": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "reading_value_id": {
            "type": "string"
          },
          "candidate_value": {
            "type": "string"
          },
          "confidence": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0,
            "maximum": 1
          },
          "model": {
            "type": [
              "string",
              "null"
            ]
          },
          "decision": {
            "type": "string",
            "enum": [
              "PENDING",
              "ACCEPTED",
              "CORRECTED",
              "REJECTED"
            ]
          },
          "created_at": {
            "type": "string"
          }
        }
      },
      "ReadingValue": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "submission_id": {
            "type": "string"
          },
          "meter_id": {
            "type": "string"
          },
          "meter_type": {
            "type": "string"
          },
          "unit": {
            "type": "string"
          },
          "previous_reported_value": {
            "type": "integer"
          },
          "actual_value": {
            "type": "string"
          },
          "reported_value": {
            "type": "integer"
          },
          "verification_status": {
            "type": "string",
            "enum": [
              "PENDING",
              "VERIFIED",
              "CORRECTED",
              "REJECTED"
            ]
          },
          "verified_by": {
            "type": [
              "string",
              "null"
            ]
          },
          "photo_object_key": {
            "type": [
              "string",
              "null"
            ]
          },
          "ocrCandidates": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OcrCandidate"
            }
          }
        }
      },
      "ReadingSubmission": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "request_id": {
            "type": "string"
          },
          "submitted_by": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "submitted_at": {
            "type": "string"
          },
          "confirmed_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "readings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ReadingValue"
            }
          }
        }
      },
      "Bill": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "period_id": {
            "type": "string"
          },
          "version": {
            "type": "integer"
          },
          "status": {
            "type": "string"
          },
          "utilities_amount_minor": {
            "type": "integer"
          },
          "internet_amount_minor": {
            "type": "integer"
          },
          "total_amount_minor": {
            "type": "integer"
          },
          "opening_balance_minor": {
            "type": "integer"
          },
          "amount_due_minor": {
            "type": "integer"
          },
          "issued_at": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "PaymentClaim": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "period_id": {
            "type": "string"
          },
          "claimed_by": {
            "type": "string"
          },
          "amount_minor": {
            "type": "integer"
          },
          "status": {
            "type": "string"
          },
          "created_at": {
            "type": "string"
          },
          "resolved_at": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "Payment": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "period_id": {
            "type": "string"
          },
          "payment_claim_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "source": {
            "type": "string"
          },
          "provider_event_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "amount_minor": {
            "type": "integer"
          },
          "received_at": {
            "type": "string"
          },
          "confirmed_by": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "PeriodSnapshot": {
        "type": "object",
        "required": [
          "id",
          "period_code",
          "status",
          "opening_balance_minor",
          "paidMinor",
          "balanceMinor"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "property_id": {
            "type": "string"
          },
          "lease_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "period_code": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "WAITING_REQUEST",
              "WAITING_READINGS",
              "READINGS_REVIEW",
              "READINGS_CONFIRMED",
              "BILL_DRAFT",
              "BILL_ISSUED",
              "PAYMENT_CLAIMED",
              "PARTIALLY_PAID",
              "SETTLED",
              "DISPUTED"
            ]
          },
          "opening_balance_minor": {
            "type": "integer"
          },
          "paidMinor": {
            "type": "integer"
          },
          "balanceMinor": {
            "type": "integer"
          },
          "latestRequest": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/ReadingRequest"
              },
              {
                "type": "null"
              }
            ]
          },
          "latestSubmission": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/ReadingSubmission"
              },
              {
                "type": "null"
              }
            ]
          },
          "latestBill": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Bill"
              },
              {
                "type": "null"
              }
            ]
          },
          "pendingPaymentClaim": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/PaymentClaim"
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "AuditEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "period_id": {
            "type": "string"
          },
          "actor_user_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "actor_role": {
            "type": "string"
          },
          "entity_type": {
            "type": "string"
          },
          "entity_id": {
            "type": "string"
          },
          "event_type": {
            "type": "string"
          },
          "payload": {
            "type": "object",
            "additionalProperties": true
          },
          "correlation_id": {
            "type": "string"
          },
          "occurred_at": {
            "type": "string"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "code",
          "message"
        ],
        "properties": {
          "code": {
            "type": "string"
          },
          "message": {
            "type": "string"
          }
        }
      }
    }
  }
}
