{
  "openapi": "3.1.0",
  "info": {
    "title": "Modara Statistics API",
    "version": "1.0.0",
    "description": "Read-only, guild-scoped statistics. Enable the developer API and issue a stats:read key in the server dashboard. Use HTTPS and a backend proxy; never expose keys to browsers. Days use UTC and include the partial current day. No member identities or message content. This specification describes implemented behavior, not deployment status."
  },
  "servers": [
    {
      "url": "/discord",
      "description": "Default Modara base path. Override the server URL for deployments using a different base path or domain."
    }
  ],
  "security": [
    {
      "BearerApiKey": []
    }
  ],
  "paths": {
    "/api/v1/guilds/{guildId}/stats": {
      "get": {
        "operationId": "getGuildStats",
        "summary": "Read aggregate guild statistics",
        "description": "60 requests per 60-second window per key and 120 per trusted client IP. Without trusted proxy IPs, the latter is a shared bucket. No CORS for direct browser usage. No additional query parameters are accepted.",
        "parameters": [
          {
            "name": "guildId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{15,22}$"
            }
          },
          {
            "name": "days",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 90,
              "default": 30
            },
            "description": "UTC calendar days ending today, inclusive. Must be an integer written without leading zeroes."
          }
        ],
        "responses": {
          "200": {
            "description": "Recorded aggregate statistics; empty history returns an empty daily array, recordedDays=0, and zero totals. Zero totals with no data do not prove inactivity.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string"
                },
                "description": "no-store, private"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer",
                  "const": 60
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer",
                  "minimum": 0
                }
              },
              "X-RateLimit-Reset-After": {
                "schema": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 60
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Statistics"
                }
              }
            }
          },
          "400": {
            "description": "Invalid or repeated days parameter, unsupported query parameters.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string"
                },
                "description": "no-store, private"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired, revoked, or wrong-tenant API key.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string"
                },
                "description": "no-store, private"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "API disabled or bot no longer installed.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string"
                },
                "description": "no-store, private"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Invalid or deleted resource.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string"
                },
                "description": "no-store, private"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Per-IP or per-key fixed-window rate limit exceeded.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string"
                },
                "description": "no-store, private"
              },
              "Retry-After": {
                "schema": {
                  "type": "integer",
                  "minimum": 1
                },
                "description": "Wait this many seconds before retrying."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Database or Redis temporarily unavailable. Requests fail closed.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string"
                },
                "description": "no-store, private"
              },
              "Retry-After": {
                "schema": {
                  "type": "integer",
                  "minimum": 1
                },
                "description": "Wait this many seconds before retrying."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "BearerApiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "mdr_<public-id>_<32-byte-secret>",
        "description": "Guild-scoped API key with the fixed stats:read scope. Issued once in the owner/admin dashboard."
      }
    },
    "schemas": {
      "Statistics": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "apiVersion",
          "guildId",
          "generatedAt",
          "window",
          "coverage",
          "memberCount",
          "totals",
          "daily"
        ],
        "properties": {
          "apiVersion": {
            "type": "string",
            "const": "1"
          },
          "guildId": {
            "type": "string",
            "pattern": "^[0-9]{15,22}$"
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "window": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "days",
              "from",
              "to",
              "timezone",
              "includesPartialToday"
            ],
            "properties": {
              "days": {
                "type": "integer",
                "minimum": 1,
                "maximum": 90
              },
              "from": {
                "type": "string",
                "format": "date-time"
              },
              "to": {
                "type": "string",
                "format": "date-time"
              },
              "timezone": {
                "type": "string",
                "const": "UTC"
              },
              "includesPartialToday": {
                "type": "boolean",
                "const": true
              }
            }
          },
          "coverage": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "recordedDays",
              "requestedDays",
              "missingDaysAreZero"
            ],
            "properties": {
              "recordedDays": {
                "type": "integer",
                "minimum": 0,
                "maximum": 90
              },
              "requestedDays": {
                "type": "integer",
                "minimum": 1,
                "maximum": 90
              },
              "missingDaysAreZero": {
                "type": "boolean",
                "const": false
              }
            }
          },
          "memberCount": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "description": "Latest known server member count including bots; not a historical count."
          },
          "totals": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "messageCount",
              "joinCount",
              "leaveCount",
              "voiceMinutes",
              "moderationCount",
              "activeMemberDays"
            ],
            "properties": {
              "messageCount": {
                "type": "integer",
                "minimum": 0
              },
              "joinCount": {
                "type": "integer",
                "minimum": 0
              },
              "leaveCount": {
                "type": "integer",
                "minimum": 0
              },
              "voiceMinutes": {
                "type": "integer",
                "minimum": 0
              },
              "moderationCount": {
                "type": "integer",
                "minimum": 0
              },
              "activeMemberDays": {
                "type": "integer",
                "minimum": 0,
                "description": "Sum of daily distinct message authors; NOT distinct users over the complete window."
              }
            }
          },
          "daily": {
            "type": "array",
            "maxItems": 90,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "date",
                "messageCount",
                "joinCount",
                "leaveCount",
                "voiceMinutes",
                "moderationCount",
                "activeMemberCount"
              ],
              "properties": {
                "date": {
                  "type": "string",
                  "format": "date"
                },
                "messageCount": {
                  "type": "integer",
                  "minimum": 0
                },
                "joinCount": {
                  "type": "integer",
                  "minimum": 0
                },
                "leaveCount": {
                  "type": "integer",
                  "minimum": 0
                },
                "voiceMinutes": {
                  "type": "integer",
                  "minimum": 0
                },
                "moderationCount": {
                  "type": "integer",
                  "minimum": 0
                },
                "activeMemberCount": {
                  "type": "integer",
                  "minimum": 0
                }
              }
            },
            "description": "Ascending UTC dates with recorded statistics. Missing dates are omitted; present dates may still have gaps."
          }
        }
      },
      "Error": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "INVALID_QUERY",
                  "UNAUTHORIZED",
                  "API_DISABLED",
                  "BOT_NOT_INSTALLED",
                  "NOT_FOUND",
                  "RATE_LIMITED",
                  "SERVICE_UNAVAILABLE"
                ]
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      }
    }
  }
}
