{
  "openapi": "3.1.0",
  "info": {
    "title": "RoboticSwarm API",
    "version": "0.1.0",
    "description": "Prompt-to-choreography for drone light shows. Send a plain-English prompt, get back a portable show script: timed keyframe formations with per-drone positions (ENU meters), colors, and a computed safety envelope. All output is simulation only.",
    "contact": {
      "email": "hello@roboticswarm.com"
    }
  },
  "servers": [
    {
      "url": "https://roboticswarm.com",
      "description": "Production"
    },
    {
      "url": "http://localhost:4820",
      "description": "Local development"
    }
  ],
  "paths": {
    "/api/v1/shows": {
      "post": {
        "summary": "Generate a drone show script from a prompt",
        "operationId": "createShow",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ShowRequest"
              },
              "example": {
                "prompt": "A red heart, spell LOVE, firework finale",
                "drones": 150,
                "duration": 60
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Generated show script",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ShowScript"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "get": {
        "summary": "Generate a show script (query-param convenience form)",
        "operationId": "createShowGet",
        "parameters": [
          {
            "name": "prompt",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "drones",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 12,
              "maximum": 500
            }
          },
          {
            "name": "duration",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 20,
              "maximum": 600
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Generated show script",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ShowScript"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/formations": {
      "get": {
        "summary": "List supported formation keywords",
        "operationId": "listFormations",
        "responses": {
          "200": {
            "description": "Formation vocabulary",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "formations": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/waitlist": {
      "post": {
        "summary": "Join the early-access waitlist",
        "operationId": "joinWaitlist",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Accepted (idempotent)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "already": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        }
      }
    },
    "/api/v1/shows/validate": {
      "post": {
        "summary": "Validate any show script against the safety envelope",
        "description": "Accepts a show script (this engine's or third-party) and returns a full validation report: structure, static + swept separation (before/after collision repair), sampled kinematics vs limits, altitude ceiling and ground clearance, and geofence radius. `valid` means zero errors; every check reports its numbers either way.",
        "operationId": "validateShow",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "show"
                ],
                "properties": {
                  "show": {
                    "$ref": "#/components/schemas/ShowScript"
                  },
                  "min_sep_m": {
                    "type": "number",
                    "description": "Separation bubble (default 1.5)"
                  },
                  "v_max_ms": {
                    "type": "number",
                    "description": "Velocity cap (default 6)"
                  },
                  "max_altitude_m": {
                    "type": "number",
                    "description": "Ceiling (default 120)"
                  },
                  "geofence_radius_m": {
                    "type": "number",
                    "description": "Cylinder radius (default 120)"
                  },
                  "profile": {
                    "type": "object",
                    "description": "Venue/site profile. Overrides the generic envelope: geofence ({type:\"circle\",radius_m} or {type:\"polygon\",points:[[x,y],...]}), ceiling_m, and crowd standoff ({side:\"+y\"|\"-y\"|\"+x\"|\"-x\", min_distance_m, line_offset_m?} — audience front line at line_offset_m on that axis; drones keep min_distance_m beyond it)."
                  },
                  "endurance": {
                    "type": "object",
                    "description": "Drone-class endurance model: {flight_time_min (default 12), reserve_fraction (default 0.25), overhead_s (default 90 — unscripted pre/post-show flight)}. The show must fit inside usable endurance; the report includes the remaining margin."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Validation report (returned for invalid shows too — check `valid`)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "valid": {
                      "type": "boolean"
                    },
                    "checks": {
                      "type": "object"
                    },
                    "summary": {
                      "type": "object",
                      "properties": {
                        "errors": {
                          "type": "integer"
                        },
                        "warnings": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        }
      }
    },
    "/api/v1/shows/waiver-packet": {
      "post": {
        "summary": "Generate a 14 CFR 107.35 waiver-support documentation packet",
        "description": "Assembles a markdown packet for a show + venue profile: operation summary, show volume, containment margins, kinematic envelope, separation analysis (incl. automated path-repair results), endurance, operator-confirmable failure-mode assumptions, and the full validation verdict. Supporting documentation generated from simulation — NOT legal advice and not an FAA filing. Pass format:\"json\" for the machine-readable packet instead of markdown.",
        "operationId": "waiverPacket",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "show"
                ],
                "properties": {
                  "show": {
                    "$ref": "#/components/schemas/ShowScript"
                  },
                  "profile": {
                    "type": "object",
                    "description": "Venue profile (see /api/v1/shows/validate)"
                  },
                  "endurance": {
                    "type": "object",
                    "description": "Drone-class endurance model (see /api/v1/shows/validate)"
                  },
                  "operation": {
                    "type": "object",
                    "properties": {
                      "operator": {
                        "type": "string"
                      },
                      "location": {
                        "type": "string"
                      },
                      "date": {
                        "type": "string"
                      },
                      "drone_model": {
                        "type": "string"
                      },
                      "notes": {
                        "type": "string"
                      }
                    }
                  },
                  "assumptions": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Override the default failure-mode assumption bullets"
                  },
                  "format": {
                    "type": "string",
                    "enum": [
                      "markdown",
                      "json"
                    ],
                    "default": "markdown"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Markdown attachment (or JSON packet when format:\"json\")",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              },
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        }
      }
    },
    "/api/v1/export": {
      "post": {
        "summary": "Export a show script as per-drone CSV, Finale 3D VVIZ, or a Skybrush CSV bundle",
        "description": "Samples the kinematically-limited, collision-repaired trajectory timeline (the exact paths the Studio previews). \"csv\": drone-major `drone,t,x,y,z,r,g,b` rows (PX4/MAVLink-style). \"vviz\": Finale 3D drone-show interchange JSON (delta-encoded positions, RLE color actions) for combined pyro+drone shows. \"skybrush\": ZIP of per-drone CSVs in Skybrush's documented dialect (`Time [msec],x [m],y [m],z [m],Red,Green,Blue`) — feed it to Skybrush's official converter/tools to produce a native .skyc show file.",
        "operationId": "exportShow",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "show"
                ],
                "properties": {
                  "show": {
                    "$ref": "#/components/schemas/ShowScript"
                  },
                  "format": {
                    "type": "string",
                    "enum": [
                      "csv",
                      "vviz",
                      "skybrush"
                    ],
                    "default": "csv"
                  },
                  "hz": {
                    "type": "integer",
                    "description": "Samples per second (default 5)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "CSV attachment",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "413": {
            "description": "Export exceeds the row cap — lower hz or reduce drones/duration",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/share/{id}/export.csv": {
      "get": {
        "summary": "Export a shared show as CSV",
        "operationId": "exportSharedShow",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{6,24}$"
            }
          },
          {
            "name": "hz",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "CSV attachment",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "Unknown share id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/share": {
      "post": {
        "summary": "Store a show script and mint a share permalink",
        "operationId": "shareShow",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ShowScript"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Share created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string",
                      "description": "Site-relative permalink (/s/{id})"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/share/{id}": {
      "get": {
        "summary": "Fetch a shared show script",
        "operationId": "getSharedShow",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{6,24}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The stored show script",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ShowScript"
                }
              }
            }
          },
          "404": {
            "description": "Unknown share id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/keys": {
      "post": {
        "summary": "Issue a free-tier API key (waitlist members)",
        "description": "Self-serve key issuance for waitlist emails. The key is shown once and stored only as a hash; requesting again rotates (revokes) the previous key. Keys raise your rate limit — anonymous access keeps working at the base limit. Pass keys via the X-Api-Key header. Paid tiers are a planned billing integration.",
        "operationId": "issueKey",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "New key (shown once)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "key": {
                      "type": "string"
                    },
                    "tier": {
                      "type": "string"
                    },
                    "rpm": {
                      "type": "integer"
                    },
                    "note": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "403": {
            "description": "Email not on the waitlist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/keys/usage": {
      "get": {
        "summary": "Usage + tier for the calling API key",
        "operationId": "keyUsage",
        "parameters": [
          {
            "name": "X-Api-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tier, rate limit, and usage counters",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "tier": {
                      "type": "string"
                    },
                    "rpm": {
                      "type": "integer"
                    },
                    "uses": {
                      "type": "integer"
                    },
                    "created_at": {
                      "type": "string"
                    },
                    "last_used": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/health": {
      "get": {
        "summary": "Engine status",
        "operationId": "health",
        "responses": {
          "200": {
            "description": "Service is up",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "engine": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ShowRequest": {
        "type": "object",
        "required": [
          "prompt"
        ],
        "properties": {
          "prompt": {
            "type": "string",
            "description": "Show description in plain English. Formations are sequenced in the order they appear; supports colors, \"rainbow\", text via spell/say/quotes, and fleet size via e.g. \"with 200 drones\"."
          },
          "drones": {
            "type": "integer",
            "minimum": 12,
            "maximum": 500,
            "default": 100
          },
          "duration": {
            "type": "integer",
            "minimum": 20,
            "maximum": 600,
            "default": 60,
            "description": "Seconds"
          },
          "planner": {
            "type": "string",
            "enum": [
              "llm",
              "keyword"
            ],
            "description": "Planning engine. \"llm\" (default where configured) has Claude design the formation sequence; \"keyword\" forces the deterministic parser. The response reports the planner actually used in meta.planner."
          },
          "svg": {
            "type": "string",
            "description": "Optional inline SVG markup (max 200KB). Outlines are sampled into the \"svg\" formation — a logo reveal. Supports path/rect/circle/ellipse/line/polyline/polygon; transform attributes are ignored."
          }
        }
      },
      "ShowScript": {
        "type": "object",
        "properties": {
          "version": {
            "type": "string"
          },
          "meta": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "prompt": {
                "type": "string"
              },
              "engine": {
                "type": "string"
              },
              "planner": {
                "type": "string",
                "enum": [
                  "llm",
                  "keyword"
                ]
              }
            }
          },
          "drones": {
            "type": "integer"
          },
          "duration_s": {
            "type": "number"
          },
          "coordinate_system": {
            "type": "string"
          },
          "keyframes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Keyframe"
            }
          },
          "safety": {
            "$ref": "#/components/schemas/Safety"
          }
        }
      },
      "Keyframe": {
        "type": "object",
        "properties": {
          "t": {
            "type": "number",
            "description": "Seconds from show start"
          },
          "formation": {
            "type": "string",
            "description": "Formation name; text formations are \"text:WORD\""
          },
          "color": {
            "type": "string",
            "description": "Hex color, or \"rainbow\" for per-drone hues"
          },
          "positions": {
            "type": "array",
            "description": "One [x, y, z] per drone, ENU meters, origin at show center",
            "items": {
              "type": "array",
              "items": {
                "type": "number"
              },
              "minItems": 3,
              "maxItems": 3
            }
          }
        }
      },
      "Safety": {
        "type": "object",
        "properties": {
          "separation_enforced_m": {
            "type": "number"
          },
          "min_separation_m": {
            "type": "number"
          },
          "max_altitude_m": {
            "type": "number"
          },
          "bounding_box_m": {
            "type": "array",
            "items": {
              "type": "number"
            },
            "minItems": 3,
            "maxItems": 3
          },
          "geofence": {
            "type": "string"
          },
          "note": {
            "type": "string"
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid request",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Rate limit exceeded (60 requests/minute per IP by default)",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "string"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  }
}