{
  "openapi": "3.1.0",
  "info": {
    "title": "ParkOS Reporting API",
    "version": "1.0.0",
    "description": "Read-only financial reporting endpoints for pulling session and permit transaction data (quantities and amounts) into a third-party / sister application. All endpoints are tenant-scoped via the X-Api-Key header and filter on the canonical transaction date (paid_at) — the moment money actually moved. Only settled rows are returned."
  },
  "servers": [
    {
      "url": "https://{parkosHost}/functions",
      "description": "ParkOS backend functions base URL",
      "variables": {
        "parkosHost": {
          "default": "your-parkos-host",
          "description": "Your ParkOS host, provided by the operator."
        }
      }
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "paths": {
    "/apiPing": {
      "post": {
        "operationId": "apiPing",
        "summary": "Connectivity check",
        "description": "Call this first to confirm your API key works and to discover the tenant it is scoped to.",
        "responses": {
          "200": {
            "description": "Key is valid",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/PingResponse" }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, or revoked API key",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/apiListTransactions": {
      "post": {
        "operationId": "apiListTransactions",
        "summary": "List settled transactions",
        "description": "Cursor-paginated, newest-first list of settled session and permit transactions. Each row carries full client (client_id/client_ref/client_name) and location (location_id/location_ref/location_name) identity so consumers can slice by client (e.g. Sudima) or site. Optionally filter to one client or location. To page, pass the returned next_cursor as cursor on the next call. has_more=false means you have reached the end. net_cents = gross_cents − refund_amount_cents.",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ListTransactionsRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Page of transactions",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ListTransactionsResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/apiFinancialSummary": {
      "post": {
        "operationId": "apiFinancialSummary",
        "summary": "Aggregated financial summary",
        "description": "Aggregated session and permit totals (counts and amounts) over a transaction-date window, optionally filtered to one client or location, and optionally grouped by client, location, payment account, or currency.",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/FinancialSummaryRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Summary totals",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/FinancialSummaryResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Api-Key",
        "description": "Tenant-scoped API key issued by the ParkOS operator. Requests can only access data belonging to the tenant that owns the key."
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid parameters (e.g. malformed from/to timestamp)",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Unauthorized": {
        "description": "Missing, invalid, or revoked API key",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "RateLimited": {
        "description": "Rate limit exceeded. Includes a Retry-After header (seconds).",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": { "type": "string", "example": "invalid_api_key" },
              "message": { "type": "string", "example": "API key is invalid" }
            }
          }
        }
      },
      "PingResponse": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean", "example": true },
          "tenant_id": { "type": "string" },
          "tenant_name": { "type": "string", "example": "Acme Parking" },
          "key_name": { "type": "string", "example": "Sister App Production" },
          "key_prefix": { "type": "string", "example": "pk_a1b2c3d4" },
          "consumer": { "type": "string", "example": "SisterApp" },
          "server_time": { "type": "string", "format": "date-time" }
        }
      },
      "ListTransactionsRequest": {
        "type": "object",
        "properties": {
          "include": {
            "type": "string",
            "enum": ["all", "sessions", "permits"],
            "default": "all",
            "description": "Which transaction types to include."
          },
          "client_ref": {
            "type": "string",
            "description": "Optional. Isolate one client's transactions, e.g. 'CLT-00001' (the client's internal_ref) or its id. Matches transactions explicitly tagged to the client AND any at the client's primary location."
          },
          "location_ref": {
            "type": "string",
            "description": "Optional. Isolate one location's transactions by external_ref / internal_ref (e.g. 'L-0001') or id."
          },
          "from": {
            "type": "string",
            "format": "date-time",
            "description": "Optional transaction-date window start (ISO 8601 UTC)."
          },
          "to": {
            "type": "string",
            "format": "date-time",
            "description": "Optional transaction-date window end (ISO 8601 UTC)."
          },
          "limit": {
            "type": "integer",
            "default": 100,
            "maximum": 500,
            "description": "Page size. Default 100, max 500."
          },
          "cursor": {
            "type": ["string", "null"],
            "description": "Pass the previous response's next_cursor to fetch the next page."
          }
        }
      },
      "Transaction": {
        "type": "object",
        "properties": {
          "type": { "type": "string", "enum": ["session", "permit"] },
          "id": { "type": "string" },
          "reference": { "type": "string", "example": "PKS-00347" },
          "location_id": { "type": "string" },
          "location_ref": { "type": "string", "example": "L-0001" },
          "location_name": { "type": "string", "example": "Sudima Auckland Airport" },
          "client_id": { "type": "string", "description": "Owning client id. Resolved from the transaction's own client_id, else the location's primary client." },
          "client_ref": { "type": "string", "example": "CLT-00001", "description": "Owning client internal_ref (e.g. Sudima)." },
          "client_name": { "type": "string", "example": "Sudima Auckland Airport", "description": "Owning client display name." },
          "plate": { "type": "string", "example": "ABC123" },
          "holder_name": { "type": "string", "description": "Permits only.", "example": "Acme Co" },
          "payment_status": { "type": "string", "example": "paid" },
          "payment_method": { "type": "string", "example": "card" },
          "currency": { "type": "string", "example": "NZD" },
          "gross_cents": { "type": "integer", "example": 1200 },
          "discount_cents": { "type": "integer", "example": 0 },
          "extensions_total_cents": { "type": "integer", "description": "Sessions only.", "example": 0 },
          "refund_amount_cents": { "type": "integer", "example": 0 },
          "net_cents": { "type": "integer", "description": "gross_cents − refund_amount_cents.", "example": 1200 },
          "transaction_date": { "type": "string", "format": "date-time", "description": "Canonical settlement date (paid_at)." },
          "started_at": { "type": "string", "format": "date-time", "description": "Sessions only." },
          "expires_at": { "type": "string", "format": "date-time", "description": "Sessions only." },
          "valid_from": { "type": "string", "format": "date-time", "description": "Permits only." },
          "valid_until": { "type": "string", "format": "date-time", "description": "Permits only." }
        }
      },
      "ListTransactionsResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/Transaction" }
          },
          "count": { "type": "integer", "example": 2 },
          "has_more": { "type": "boolean", "example": true },
          "next_cursor": { "type": ["string", "null"], "example": "2026-06-10T09:15:00Z" }
        }
      },
      "FinancialSummaryRequest": {
        "type": "object",
        "properties": {
          "from": {
            "type": "string",
            "format": "date-time",
            "description": "Optional transaction-date window start (ISO 8601 UTC)."
          },
          "to": {
            "type": "string",
            "format": "date-time",
            "description": "Optional transaction-date window end (ISO 8601 UTC)."
          },
          "client_ref": {
            "type": "string",
            "description": "Optional. Limit the summary to one client, e.g. 'CLT-00001' (internal_ref) or its id. Includes transactions at the client's primary location."
          },
          "location_ref": {
            "type": "string",
            "description": "Optional. Limit the summary to one location by external_ref / internal_ref or id."
          },
          "group_by": {
            "type": "string",
            "enum": ["none", "client", "location", "payment_account", "currency"],
            "default": "none",
            "description": "How to break down the totals. 'client' keys by the client internal_ref. When 'none', the groups array is omitted."
          }
        }
      },
      "Totals": {
        "type": "object",
        "properties": {
          "session_count": { "type": "integer", "example": 128 },
          "permit_count": { "type": "integer", "example": 14 },
          "gross_cents": { "type": "integer", "example": 184500 },
          "discount_cents": { "type": "integer", "example": 3200 },
          "refund_cents": { "type": "integer", "example": 1200 },
          "net_cents": { "type": "integer", "example": 183300 }
        }
      },
      "SummaryGroup": {
        "allOf": [
          {
            "type": "object",
            "properties": {
              "key": {
                "type": "string",
                "description": "Location ref, payment account id, or currency. Rows with no value bucket under 'unknown'.",
                "example": "L-0001"
              }
            }
          },
          { "$ref": "#/components/schemas/Totals" }
        ]
      },
      "FinancialSummaryResponse": {
        "type": "object",
        "properties": {
          "from": { "type": ["string", "null"], "format": "date-time" },
          "to": { "type": ["string", "null"], "format": "date-time" },
          "group_by": { "type": "string", "example": "location" },
          "totals": { "$ref": "#/components/schemas/Totals" },
          "groups": {
            "type": "array",
            "description": "Omitted when group_by is 'none'.",
            "items": { "$ref": "#/components/schemas/SummaryGroup" }
          }
        }
      }
    }
  }
}
