{
  "openapi": "3.1.0",
  "info": {
    "title": "DYMOL API",
    "description": "Backend-as-a-Service for indie game developers. Ready-to-use APIs for reactions, promo codes, counters, and death telemetry.\n\nAll public endpoints require a DYMOL API key passed in the `Authorization` header as `Bearer <token>`.",
    "version": "1.0.0",
    "contact": {
      "name": "DYMOL Support",
      "url": "https://dymol.dev"
    }
  },
  "servers": [
    {
      "url": "https://api.dymol.dev/v1",
      "description": "Production API"
    }
  ],
  "tags": [
    {
      "name": "Health",
      "description": "Service health and status checks"
    },
    {
      "name": "ReactionAPI",
      "description": "Submit and aggregate player reactions (sentiment-as-a-service)"
    },
    {
      "name": "PromoCodeAPI",
      "description": "Redeem server-validated promo codes"
    },
    {
      "name": "CounterAPI",
      "description": "Track and aggregate in-game events and stats"
    },
    {
      "name": "DeathTelemetryAPI",
      "description": "Record and query player death coordinates for heatmap analysis"
    },
    {
      "name": "Activity",
      "description": "Combined activity feed across APIs"
    }
  ],
  "paths": {
    "/health": {
      "get": {
        "tags": ["Health"],
        "summary": "Health check",
        "description": "Check service and database health. No authentication required.",
        "operationId": "healthCheck",
        "security": [],
        "responses": {
          "200": {
            "description": "Service is healthy",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthResponse"
                }
              }
            }
          }
        }
      }
    },
    "/react": {
      "post": {
        "tags": ["ReactionAPI"],
        "summary": "Submit or update a reaction",
        "description": "Submit a reaction. Uses upsert logic — if the same user reacts again to the same target, the previous reaction is overwritten.",
        "operationId": "submitReaction",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReactionPostRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Reaction recorded successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessMessageResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/reactions": {
      "get": {
        "tags": ["ReactionAPI"],
        "summary": "Get all reactions",
        "description": "Get a global aggregation of all reactions across the project, broken down by target and reaction type.",
        "operationId": "getReactions",
        "responses": {
          "200": {
            "description": "Aggregated reaction data",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReactionsAggregationResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/reactions/daily": {
      "get": {
        "tags": ["ReactionAPI"],
        "summary": "Get daily reaction counts",
        "description": "Get daily aggregated reaction counts for charting. Returns the last N days (default 7) with zero-filled missing days.",
        "operationId": "getReactionsDaily",
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "description": "Number of days to look back (1-365)",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 365,
              "default": 7
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Daily reaction counts",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DailyChartResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/reactions/recent": {
      "get": {
        "tags": ["ReactionAPI"],
        "summary": "Get recent reactions",
        "description": "Get the last 5 reactions for the project.",
        "operationId": "getReactionsRecent",
        "responses": {
          "200": {
            "description": "Recent reactions",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReactionsRecentResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/reactions/{target_id}": {
      "get": {
        "tags": ["ReactionAPI"],
        "summary": "Get reactions for a target",
        "description": "Get reaction aggregation for a specific target.",
        "operationId": "getReactionsByTarget",
        "parameters": [
          {
            "name": "target_id",
            "in": "path",
            "required": true,
            "description": "The target ID (e.g. level, item, post)",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Target reaction aggregation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReactionsTargetResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/activity": {
      "get": {
        "tags": ["Activity"],
        "summary": "Get activity feed",
        "description": "Get a combined activity feed of recent reactions and promo code claims across the project.",
        "operationId": "getActivity",
        "responses": {
          "200": {
            "description": "Activity feed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActivityFeedResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/promo/redeem": {
      "post": {
        "tags": ["PromoCodeAPI"],
        "summary": "Redeem a promo code",
        "description": "Redeem a promo code for a specific user. Each user can only redeem a code once. The code is automatically deactivated when claims reach max_claims.",
        "operationId": "redeemPromoCode",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PromoRedeemRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Redemption result (check `status` field)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PromoRedeemResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/counter/increment": {
      "post": {
        "tags": ["CounterAPI"],
        "summary": "Increment a counter",
        "description": "Increment a counter for a specific target. Uses upsert — if the target doesn't exist, it's created automatically. Each increment is logged for analytics.",
        "operationId": "incrementCounter",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CounterIncrementRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Counter incremented successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CounterIncrementResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/counter": {
      "get": {
        "tags": ["CounterAPI"],
        "summary": "Get all counters",
        "description": "Get aggregated counters for all targets in the project, plus the global total.",
        "operationId": "getCounters",
        "responses": {
          "200": {
            "description": "Aggregated counter data",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CounterAggregationResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/counter/daily": {
      "get": {
        "tags": ["CounterAPI"],
        "summary": "Get daily counter counts",
        "description": "Get daily aggregated counter data for the last N days (default 7). Optionally filter by target_id.",
        "operationId": "getCounterDaily",
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "description": "Number of days to look back (1-365)",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 365,
              "default": 7
            }
          },
          {
            "name": "target_id",
            "in": "query",
            "description": "Optional target ID to filter by",
            "schema": {
              "type": "string",
              "maxLength": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Daily counter counts",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DailyChartResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/counter/recent": {
      "get": {
        "tags": ["CounterAPI"],
        "summary": "Get recent counter increments",
        "description": "Get the last 10 counter increment logs for the project.",
        "operationId": "getCounterRecent",
        "responses": {
          "200": {
            "description": "Recent counter increments",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CounterRecentResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/counter/{target_id}": {
      "get": {
        "tags": ["CounterAPI"],
        "summary": "Get counter for a target",
        "description": "Get the current count for a specific target.",
        "operationId": "getCounterByTarget",
        "parameters": [
          {
            "name": "target_id",
            "in": "path",
            "required": true,
            "description": "The target ID to query",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Counter for the target",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CounterTargetResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/deaths": {
      "post": {
        "tags": ["DeathTelemetryAPI"],
        "summary": "Record a death",
        "description": "Record a player death with 3D coordinates. Use this from your game client every time a player dies.",
        "operationId": "recordDeath",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeathPostRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Death recorded successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeathPostResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/deaths/{level_id}": {
      "get": {
        "tags": ["DeathTelemetryAPI"],
        "summary": "Get deaths for a level",
        "description": "Retrieve death coordinates for a specific level. Returns up to 1000 records ordered by created_at DESC.",
        "operationId": "getDeathsByLevel",
        "parameters": [
          {
            "name": "level_id",
            "in": "path",
            "required": true,
            "description": "The level or map ID",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Death records for the level",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeathsListResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "DYMOL API key prefixed with `dymol_pk_...`. Pass it in the Authorization header as `Bearer <token>`."
      }
    },
    "schemas": {
      "HealthResponse": {
        "type": "object",
        "required": ["status", "version", "db", "timestamp"],
        "properties": {
          "status": {
            "type": "string",
            "example": "ok"
          },
          "version": {
            "type": "string",
            "example": "1.0.0"
          },
          "db": {
            "type": "string",
            "example": "ok",
            "description": "Database connection status"
          },
          "timestamp": {
            "type": "string",
            "format": "date-time",
            "example": "2026-05-25T17:30:00.000Z"
          }
        }
      },
      "ReactionPostRequest": {
        "type": "object",
        "required": ["target_id", "type", "user_id"],
        "properties": {
          "target_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 50,
            "description": "The ID of the target (level, post, item)",
            "example": "level_99"
          },
          "type": {
            "type": "string",
            "minLength": 1,
            "maxLength": 50,
            "description": "Reaction type, e.g. fire, like, heart",
            "example": "fire"
          },
          "user_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "description": "Unique player identifier",
            "example": "player_123"
          }
        }
      },
      "SuccessMessageResponse": {
        "type": "object",
        "required": ["success", "message"],
        "properties": {
          "success": {
            "type": "boolean",
            "example": true
          },
          "message": {
            "type": "string",
            "example": "Reaction recorded successfully"
          }
        }
      },
      "ReactionsAggregationResponse": {
        "type": "object",
        "required": ["success", "data"],
        "properties": {
          "success": {
            "type": "boolean",
            "example": true
          },
          "data": {
            "type": "object",
            "required": ["total_reactions", "global_distribution", "targets"],
            "properties": {
              "total_reactions": {
                "type": "integer",
                "example": 3
              },
              "global_distribution": {
                "type": "object",
                "description": "Map of reaction type to total count",
                "additionalProperties": {
                  "type": "integer"
                },
                "example": {
                  "fire": 2,
                  "heart": 1
                }
              },
              "targets": {
                "type": "object",
                "description": "Map of target_id to reaction type counts",
                "additionalProperties": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "integer"
                  }
                },
                "example": {
                  "level_99": {
                    "fire": 1
                  },
                  "junk_20": {
                    "fire": 1,
                    "heart": 1
                  }
                }
              }
            }
          }
        }
      },
      "DailyChartResponse": {
        "type": "object",
        "required": ["success", "data"],
        "properties": {
          "success": {
            "type": "boolean",
            "example": true
          },
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["day", "count"],
              "properties": {
                "day": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-05-18"
                },
                "count": {
                  "type": "integer",
                  "example": 245
                }
              }
            }
          }
        }
      },
      "ReactionsRecentResponse": {
        "type": "object",
        "required": ["success", "data"],
        "properties": {
          "success": {
            "type": "boolean",
            "example": true
          },
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["reaction_type", "target_id", "user_id", "created_at"],
              "properties": {
                "reaction_type": {
                  "type": "string",
                  "example": "fire"
                },
                "target_id": {
                  "type": "string",
                  "example": "level_99"
                },
                "user_id": {
                  "type": "string",
                  "example": "player_123"
                },
                "created_at": {
                  "type": "string",
                  "format": "date-time",
                  "example": "2026-05-25T17:30:00Z"
                }
              }
            }
          }
        }
      },
      "ReactionsTargetResponse": {
        "type": "object",
        "required": ["success", "data"],
        "properties": {
          "success": {
            "type": "boolean",
            "example": true
          },
          "data": {
            "type": "object",
            "required": ["target_id", "total_reactions", "distribution"],
            "properties": {
              "target_id": {
                "type": "string",
                "example": "level_99"
              },
              "total_reactions": {
                "type": "integer",
                "example": 5
              },
              "distribution": {
                "type": "object",
                "description": "Map of reaction type to count",
                "additionalProperties": {
                  "type": "integer"
                },
                "example": {
                  "fire": 3,
                  "heart": 2
                }
              }
            }
          }
        }
      },
      "ActivityFeedResponse": {
        "type": "object",
        "required": ["success", "data"],
        "properties": {
          "success": {
            "type": "boolean",
            "example": true
          },
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["type", "action", "target_id", "user_id", "created_at"],
              "properties": {
                "type": {
                  "type": "string",
                  "description": "Event type: 'reaction' or 'promo_claim'",
                  "example": "reaction"
                },
                "action": {
                  "type": "string",
                  "description": "For reactions: the reaction_type. For promo claims: the code_id.",
                  "example": "fire"
                },
                "target_id": {
                  "type": "string",
                  "description": "For reactions: the target_id. For promo claims: empty string.",
                  "example": "level_99"
                },
                "user_id": {
                  "type": "string",
                  "example": "player_123"
                },
                "created_at": {
                  "type": "string",
                  "format": "date-time",
                  "example": "2026-05-25T17:30:00Z"
                }
              }
            }
          }
        }
      },
      "PromoRedeemRequest": {
        "type": "object",
        "required": ["code_id", "user_id"],
        "properties": {
          "code_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 50,
            "description": "The promo code to redeem (case-insensitive)",
            "example": "SUMMER2025"
          },
          "user_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "description": "Unique player identifier (prevents duplicate redemptions)",
            "example": "player_123"
          }
        }
      },
      "PromoRedeemResponse": {
        "type": "object",
        "required": ["status"],
        "properties": {
          "status": {
            "type": "string",
            "description": "Redemption status",
            "enum": ["SUCCESS", "INVALID", "EXHAUSTED", "ALREADY_CLAIMED", "ERROR"]
          },
          "message": {
            "type": "string",
            "description": "Present when status is SUCCESS",
            "example": "Promo code redeemed successfully"
          },
          "error": {
            "type": "string",
            "description": "Present when status is not SUCCESS",
            "example": "You have already redeemed this code"
          },
          "data": {
            "type": "object",
            "description": "Present when status is SUCCESS",
            "required": ["code_id", "claims_count", "max_claims"],
            "properties": {
              "code_id": {
                "type": "string",
                "example": "SUMMER2025"
              },
              "claims_count": {
                "type": "integer",
                "example": 42
              },
              "max_claims": {
                "type": "integer",
                "example": 1000
              }
            }
          }
        }
      },
      "CounterIncrementRequest": {
        "type": "object",
        "required": ["target_id"],
        "properties": {
          "target_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 50,
            "description": "The ID of the event to count",
            "example": "boss_dragon_defeated"
          },
          "user_id": {
            "type": "string",
            "maxLength": 128,
            "description": "Unique player identifier (logged for analytics)",
            "example": "player_123"
          },
          "amount": {
            "type": "integer",
            "minimum": 1,
            "maximum": 1000000,
            "description": "Amount to increment by",
            "example": 1,
            "default": 1
          }
        }
      },
      "CounterIncrementResponse": {
        "type": "object",
        "required": ["success", "message", "data"],
        "properties": {
          "success": {
            "type": "boolean",
            "example": true
          },
          "message": {
            "type": "string",
            "example": "Counter incremented successfully"
          },
          "data": {
            "type": "object",
            "required": ["target_id", "count"],
            "properties": {
              "target_id": {
                "type": "string",
                "example": "boss_dragon_defeated"
              },
              "count": {
                "type": "integer",
                "example": 1542
              }
            }
          }
        }
      },
      "CounterAggregationResponse": {
        "type": "object",
        "required": ["success", "data"],
        "properties": {
          "success": {
            "type": "boolean",
            "example": true
          },
          "data": {
            "type": "object",
            "required": ["total_counts", "targets"],
            "properties": {
              "total_counts": {
                "type": "integer",
                "example": 15420
              },
              "targets": {
                "type": "object",
                "description": "Map of target_id to count",
                "additionalProperties": {
                  "type": "integer"
                },
                "example": {
                  "blocks_broken": 8400,
                  "boss_dragon_defeated": 1542,
                  "level_started": 5478
                }
              }
            }
          }
        }
      },
      "CounterTargetResponse": {
        "type": "object",
        "required": ["success", "data"],
        "properties": {
          "success": {
            "type": "boolean",
            "example": true
          },
          "data": {
            "type": "object",
            "required": ["target_id", "count"],
            "properties": {
              "target_id": {
                "type": "string",
                "example": "boss_dragon_defeated"
              },
              "count": {
                "type": "integer",
                "example": 1542
              }
            }
          }
        }
      },
      "CounterRecentResponse": {
        "type": "object",
        "required": ["success", "data"],
        "properties": {
          "success": {
            "type": "boolean",
            "example": true
          },
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["target_id", "amount", "created_at"],
              "properties": {
                "target_id": {
                  "type": "string",
                  "example": "boss_dragon_defeated"
                },
                "user_id": {
                  "type": ["string", "null"],
                  "example": "player_123"
                },
                "amount": {
                  "type": "integer",
                  "example": 1
                },
                "created_at": {
                  "type": "string",
                  "format": "date-time",
                  "example": "2026-05-25T17:30:00Z"
                }
              }
            }
          }
        }
      },
      "DeathPostRequest": {
        "type": "object",
        "required": ["level_id", "x", "y", "z"],
        "properties": {
          "level_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 50,
            "description": "The level or map where the death occurred",
            "example": "level_1"
          },
          "x": {
            "type": "number",
            "description": "X coordinate of the death location",
            "example": 12.5
          },
          "y": {
            "type": "number",
            "description": "Y coordinate of the death location",
            "example": 3.2
          },
          "z": {
            "type": "number",
            "description": "Z coordinate of the death location",
            "example": -7.8
          },
          "cause": {
            "type": "string",
            "maxLength": 100,
            "description": "Death cause (e.g. fall, enemy, lava)",
            "example": "fall"
          }
        }
      },
      "DeathPostResponse": {
        "type": "object",
        "required": ["success", "message", "data"],
        "properties": {
          "success": {
            "type": "boolean",
            "example": true
          },
          "message": {
            "type": "string",
            "example": "Death recorded successfully"
          },
          "data": {
            "type": "object",
            "required": ["id"],
            "properties": {
              "id": {
                "type": "integer",
                "description": "Row ID of the inserted death record",
                "example": 42
              }
            }
          }
        }
      },
      "DeathsListResponse": {
        "type": "object",
        "required": ["success", "data"],
        "properties": {
          "success": {
            "type": "boolean",
            "example": true
          },
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["x", "y", "z", "created_at"],
              "properties": {
                "x": {
                  "type": "number",
                  "example": 12.5
                },
                "y": {
                  "type": "number",
                  "example": 3.2
                },
                "z": {
                  "type": "number",
                  "example": -7.8
                },
                "cause": {
                  "type": ["string", "null"],
                  "example": "fall"
                },
                "created_at": {
                  "type": "string",
                  "format": "date-time",
                  "example": "2026-05-17T10:32:00Z"
                }
              }
            }
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": {
            "type": "string",
            "example": "Missing or malformed Authorization header"
          },
          "code": {
            "type": "string",
            "description": "Error code from the auth provider",
            "example": "RATE_LIMITED"
          },
          "status": {
            "type": "string",
            "description": "Optional status field (e.g. 'ERROR' on some endpoints)",
            "example": "ERROR"
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Bad Request — invalid input",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Unauthorized — missing or invalid API key",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Too Many Requests — rate limit or usage quota exceeded",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "InternalServerError": {
        "description": "Internal Server Error",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      }
    }
  },
  "security": [
    {
      "bearerAuth": []
    }
  ]
}
