{
  "openapi": "3.1.0",
  "info": {
    "title": "FrameAI Eurocode REST API",
    "version": "1.0.0",
    "description": "Public REST API for structural engineering checks — EN 1993-1-8, EN 1993-1-9, EN 1993-1-1, EN 1992-1-1, EN 1995-1-1, EN 1998-1. All endpoints accept JSON and return a standardised check result. Free tier: 10 calls/month. Pro: 1 000/month. Studio: unlimited (fair-use 10k/day).",
    "contact": { "name": "FrameAI", "url": "https://frameai-structural.polsia.app/api/docs", "email": "frameai@polsia.app" },
    "license": { "name": "Proprietary" }
  },
  "servers": [
    { "url": "https://frameai-structural.polsia.app/api/v1", "description": "Production" }
  ],
  "security": [{ "bearerAuth": [] }],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API token from /settings → API Keys. Free tier may omit; calls are IP-keyed against a 10/month quota."
      }
    },
    "schemas": {
      "CheckResponse": {
        "type": "object",
        "properties": {
          "check_id":             { "type": ["string","null"] },
          "endpoint":             { "type": "string" },
          "passed":               { "type": "boolean" },
          "utilisation":          { "type": ["number","null"], "description": "As percentage (0–100)" },
          "summary":              { "type": "string" },
          "result":               { "type": "object" },
          "eurocode_clauses_cited": { "type": "array", "items": { "type": "string" } },
          "api_version":          { "type": "string" }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "error": { "type": "string" }
        }
      }
    }
  },
  "paths": {
    "/check/en-1993-1-8": {
      "post": {
        "summary": "EN 1993-1-8 Bolted connection check",
        "description": "Computes F_v,Rd (shear), F_b,Rd (bearing), F_t,Rd (tension) and utilisation ratio per EN 1993-1-8:2005 §3.5/§3.6.",
        "operationId": "checkEn1993_1_8",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "bolt_grade":             { "type": "string", "enum": ["4.6","8.8","10.9"], "default": "8.8" },
                  "bolt_size":              { "type": "string", "enum": ["M12","M16","M20","M24","M27","M30"], "default": "M20" },
                  "shear_planes":           { "type": "integer", "default": 1 },
                  "plate_fu":               { "type": "number", "description": "Plate ultimate strength (MPa)", "default": 360 },
                  "plate_t":                { "type": "number", "description": "Plate thickness (mm)", "default": 10 },
                  "e1":                     { "type": "number", "description": "End distance (mm)", "default": 60 },
                  "e2":                     { "type": "number", "description": "Edge distance (mm)", "default": 40 },
                  "p1":                     { "type": "number", "description": "Bolt pitch (mm)", "default": 80 },
                  "is_end_bolt":            { "type": "boolean", "default": true },
                  "V_Ed":                   { "type": "number", "description": "Design shear force (N)" },
                  "F_t_Ed":                 { "type": "number", "description": "Design tension force (N)", "default": 0 },
                  "threads_in_shear_plane": { "type": "boolean", "default": false }
                }
              },
              "example": {
                "bolt_grade": "8.8", "bolt_size": "M20", "shear_planes": 1,
                "plate_fu": 360, "plate_t": 10, "e1": 60, "e2": 40, "p1": 80,
                "is_end_bolt": true, "V_Ed": 50000, "F_t_Ed": 0
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Check result", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CheckResponse" } } } },
          "400": { "description": "Invalid input", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
          "402": { "description": "Free quota exceeded — upgrade to Pro", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
          "429": { "description": "Rate limit exceeded", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }
        }
      }
    },
    "/check/en-1993-1-9": {
      "post": {
        "summary": "EN 1993-1-9 Fatigue check",
        "description": "Palmgren-Miner damage sum, λ-method, and combined σ-τ interaction per EN 1993-1-9:2005 §6/§7/§8.",
        "operationId": "checkEn1993_1_9",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "details": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "detail_id":     { "type": "string", "description": "Detail category ID, e.g. 'T8.6-71'" },
                        "stress_history": { "type": "array", "items": { "type": "object", "properties": { "delta_sigma": { "type": "number" }, "n_cycles": { "type": "number" } } } },
                        "method":        { "type": "string", "enum": ["safe_life","damage_tolerant"], "default": "safe_life" },
                        "consequence":   { "type": "string", "enum": ["low","high"], "default": "high" }
                      }
                    }
                  },
                  "na_code": { "type": "string", "description": "National Annex code", "default": "recommended" }
                }
              },
              "example": {
                "details": [{"detail_id": "T8.6-71", "stress_history": [{"delta_sigma": 80, "n_cycles": 500000}], "method": "safe_life", "consequence": "high"}]
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Fatigue check result", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CheckResponse" } } } },
          "400": { "description": "Invalid input" }
        }
      }
    },
    "/check/en-1993-1-1": {
      "post": {
        "summary": "EN 1993-1-1 Steel member check",
        "description": "Flexural buckling, lateral-torsional buckling, and combined N+M interaction per EN 1993-1-1:2005 §6.2/§6.3.",
        "operationId": "checkEn1993_1_1",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "members": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "mark":          { "type": "string" },
                        "profile":       { "type": "string", "description": "e.g. 'IPE 300'" },
                        "length_mm":     { "type": "number" },
                        "N_Ed":          { "type": "number", "description": "Axial force (kN)" },
                        "M_y_Ed":        { "type": "number", "description": "Major axis moment (kNm)" },
                        "M_z_Ed":        { "type": "number", "description": "Minor axis moment (kNm)" },
                        "section_class": { "type": "integer", "enum": [1,2,3,4], "default": 1 }
                      }
                    }
                  },
                  "na_code": { "type": "string", "default": "recommended" }
                }
              },
              "example": {
                "members": [{"mark": "B1", "profile": "IPE 300", "length_mm": 5000, "N_Ed": 0, "M_y_Ed": 50, "M_z_Ed": 0, "section_class": 1}]
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Member check results", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CheckResponse" } } } },
          "400": { "description": "Invalid input" }
        }
      }
    },
    "/check/en-1992-1-1": {
      "post": {
        "summary": "EN 1992-1-1 Concrete column check",
        "description": "N-M interaction, slenderness/2nd-order effects per EN 1992-1-1:2004 §5.8/§6.1.",
        "operationId": "checkEn1992_1_1",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "columns": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "mark": { "type": "string" },
                        "b":    { "type": "number", "description": "Width (mm)" },
                        "h":    { "type": "number", "description": "Depth (mm)" },
                        "fck":  { "type": "number", "description": "Concrete cylinder strength (MPa)" },
                        "fyk":  { "type": "number", "description": "Steel yield strength (MPa)" },
                        "N_Ed": { "type": "number", "description": "Axial force (kN)" },
                        "M_Ed": { "type": "number", "description": "Design moment (kNm)" },
                        "A_s":  { "type": "number", "description": "Provided reinforcement (mm²)" },
                        "l0":   { "type": "number", "description": "Effective length (mm)" }
                      }
                    }
                  },
                  "na_code": { "type": "string", "default": "recommended" }
                }
              },
              "example": {
                "columns": [{"mark": "C1", "b": 300, "h": 300, "fck": 25, "fyk": 500, "N_Ed": 500, "M_Ed": 60, "A_s": 2513, "l0": 3000}]
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Column check results", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CheckResponse" } } } },
          "400": { "description": "Invalid input" }
        }
      }
    },
    "/check/en-1995-1-1": {
      "post": {
        "summary": "EN 1995-1-1 Timber member check",
        "description": "Bending, shear, LTB, and column buckling per EN 1995-1-1:2004 §6.",
        "operationId": "checkEn1995_1_1",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "members": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "mark":        { "type": "string" },
                        "species":     { "type": "string", "description": "e.g. 'C24'" },
                        "b":           { "type": "number" },
                        "h":           { "type": "number" },
                        "length_mm":   { "type": "number" },
                        "M_y_Ed":      { "type": "number" },
                        "V_z_Ed":      { "type": "number" },
                        "N_Ed":        { "type": "number" },
                        "service_class": { "type": "integer", "enum": [1,2,3], "default": 1 },
                        "load_duration": { "type": "string", "default": "medium_term" }
                      }
                    }
                  }
                }
              },
              "example": {
                "members": [{"mark": "T1", "species": "C24", "b": 100, "h": 200, "length_mm": 4000, "M_y_Ed": 5, "V_z_Ed": 6, "N_Ed": 0}]
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Timber check results", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CheckResponse" } } } },
          "400": { "description": "Invalid input" }
        }
      }
    },
    "/check/en-1998-1": {
      "post": {
        "summary": "EN 1998-1 Seismic check",
        "description": "Elastic and design response spectra, lateral force method base shear per EN 1998-1:2004 §3.2/§4.3.3.2.",
        "operationId": "checkEn1998_1",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "agR":              { "type": "number", "description": "Reference peak ground acceleration (g)", "default": 0.1 },
                  "ground_type":      { "type": "string", "enum": ["A","B","C","D","E"], "default": "B" },
                  "spectrum_type":    { "type": "integer", "enum": [1,2], "default": 1 },
                  "q":                { "type": "number", "description": "Behaviour factor", "default": 1.5 },
                  "importance_class": { "type": "integer", "enum": [1,2,3,4], "default": 2 },
                  "T1":               { "type": "number", "description": "Fundamental period T1 (s)", "default": 0.5 },
                  "W":                { "type": "number", "description": "Total seismic weight W (kN)", "default": 1000 },
                  "na_code":          { "type": "string", "default": "recommended" }
                }
              },
              "example": {
                "agR": 0.15, "ground_type": "B", "spectrum_type": 1,
                "q": 2.0, "importance_class": 2, "T1": 0.6, "W": 5000, "na_code": "recommended"
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Seismic actions + base shear", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CheckResponse" } } } },
          "400": { "description": "Invalid input" }
        }
      }
    },
    "/checks/{id}": {
      "get": {
        "summary": "Retrieve a check result by ID",
        "description": "Persistent check storage is coming. Currently returns 404.",
        "operationId": "getCheck",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "404": { "description": "Not yet implemented" }
        }
      }
    },
    "/usage": {
      "get": {
        "summary": "Get current period usage",
        "description": "Returns calls used, remaining quota, and per-endpoint breakdown for the current billing month.",
        "operationId": "getUsage",
        "responses": {
          "200": {
            "description": "Usage summary",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "tier":        { "type": "string" },
                    "limit":       { "type": "integer" },
                    "used":        { "type": "integer" },
                    "remaining":   { "type": "integer" },
                    "reset_at":    { "type": "string", "format": "date-time" },
                    "by_endpoint": { "type": "array" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/jobs": {
      "get": {
        "summary": "List jobs (Studio)",
        "description": "List recent pipeline jobs for the authenticated Studio account. Supports filtering by status and project.",
        "operationId": "listJobs",
        "tags": ["Job Pipeline"],
        "parameters": [
          { "name": "limit",      "in": "query", "schema": { "type": "integer", "default": 20, "maximum": 100 } },
          { "name": "status",     "in": "query", "schema": { "type": "string", "enum": ["pending","processing","done","failed"] } },
          { "name": "project_id", "in": "query", "schema": { "type": "integer" } }
        ],
        "responses": {
          "200": { "description": "Job list", "content": { "application/json": { "schema": { "type": "object", "properties": { "jobs": { "type": "array" }, "count": { "type": "integer" } } } } } },
          "401": { "description": "Missing or invalid bearer token" },
          "402": { "description": "Studio subscription required" }
        }
      },
      "post": {
        "summary": "Upload PDF and create job (Studio)",
        "description": "Submit a structural PDF for processing via multipart upload or JSON URL reference. Returns 202 immediately — poll GET /jobs/:id until status is 'done'.",
        "operationId": "createJob",
        "tags": ["Job Pipeline"],
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "pdf":        { "type": "string", "format": "binary", "description": "PDF file, max 20 MB" },
                  "project_id": { "type": "integer", "description": "Assign to a Studio project" }
                }
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "pdf_url":    { "type": "string", "format": "uri", "description": "Publicly accessible PDF URL" },
                  "project_id": { "type": "integer" }
                }
              }
            }
          }
        },
        "responses": {
          "202": { "description": "Job created — poll poll_url for status", "content": { "application/json": { "schema": { "type": "object", "properties": { "job_id": { "type": "integer" }, "status": { "type": "string" }, "poll_url": { "type": "string" } } } } } },
          "400": { "description": "Bad request" },
          "401": { "description": "Missing or invalid bearer token" },
          "402": { "description": "Studio subscription required" }
        }
      }
    },
    "/jobs/{id}": {
      "get": {
        "summary": "Get job status and results (Studio)",
        "description": "Retrieve full status and results for a single job. When status is 'done', extracted_data, connections_data, and export URLs are included.",
        "operationId": "getJob",
        "tags": ["Job Pipeline"],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }
        ],
        "responses": {
          "200": { "description": "Job details" },
          "401": { "description": "Missing or invalid bearer token" },
          "403": { "description": "Job owned by another account" },
          "404": { "description": "Job not found" }
        }
      }
    },
    "/jobs/{id}/dxf": {
      "get": {
        "summary": "Export DXF shop drawing (Studio)",
        "description": "Returns the fabrication DXF for a completed job. Includes Studio white-label branding if configured.",
        "operationId": "getJobDxf",
        "tags": ["Exports"],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }
        ],
        "responses": {
          "200": { "description": "DXF file", "content": { "application/dxf": { "schema": { "type": "string", "format": "binary" } } } },
          "409": { "description": "Job not complete" }
        }
      }
    },
    "/jobs/{id}/ifc": {
      "get": {
        "summary": "Export IFC 4 BIM model (Studio)",
        "description": "Returns the IFC 4 CoordinationView 2.0 BIM model for a completed job.",
        "operationId": "getJobIfc",
        "tags": ["Exports"],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }
        ],
        "responses": {
          "200": { "description": "IFC file", "content": { "application/x-step": { "schema": { "type": "string", "format": "binary" } } } },
          "409": { "description": "Job not complete" }
        }
      }
    },
    "/jobs/{id}/diff/{previousId}": {
      "get": {
        "summary": "Compare two job revisions (Studio)",
        "description": "Structured diff between two jobs in the same revision chain. Both must be 'done' and owned by the authenticated account.",
        "operationId": "diffJobs",
        "tags": ["Job Pipeline"],
        "parameters": [
          { "name": "id",         "in": "path", "required": true, "schema": { "type": "integer" } },
          { "name": "previousId", "in": "path", "required": true, "schema": { "type": "integer" } }
        ],
        "responses": {
          "200": { "description": "Diff result — members added/removed/changed, connections, welds" },
          "400": { "description": "Jobs not in same revision chain" },
          "409": { "description": "Both jobs must be in 'done' status" }
        }
      }
    },
    "/projects": {
      "get": {
        "summary": "List projects (Studio)",
        "description": "List all project workspaces for the authenticated Studio account.",
        "operationId": "listProjects",
        "tags": ["Projects"],
        "parameters": [
          { "name": "limit",         "in": "query", "schema": { "type": "integer", "default": 50, "maximum": 200 } },
          { "name": "created_after", "in": "query", "schema": { "type": "string", "format": "date-time" } }
        ],
        "responses": {
          "200": { "description": "Project list", "content": { "application/json": { "schema": { "type": "object", "properties": { "projects": { "type": "array" }, "count": { "type": "integer" } } } } } },
          "401": { "description": "Missing or invalid bearer token" },
          "402": { "description": "Studio subscription required" }
        }
      },
      "post": {
        "summary": "Create project (Studio)",
        "description": "Create a new project workspace. Triggers a project.created webhook event. National annex controls Eurocode partial factors for all jobs in this project.",
        "operationId": "createProject",
        "tags": ["Projects"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["name"],
                "properties": {
                  "name":           { "type": "string" },
                  "description":    { "type": "string" },
                  "national_annex": { "type": "string", "enum": ["NL","DE","UK","SE","FR"] }
                }
              },
              "example": { "name": "Warehouse Amersfoort 2026", "national_annex": "NL" }
            }
          }
        },
        "responses": {
          "201": { "description": "Project created" },
          "400": { "description": "name required" },
          "402": { "description": "Studio subscription required" }
        }
      }
    },
    "/projects/{id}": {
      "get": {
        "summary": "Get project (Studio)",
        "description": "Full project record including recent jobs.",
        "operationId": "getProject",
        "tags": ["Projects"],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }
        ],
        "responses": {
          "200": { "description": "Project with jobs" },
          "404": { "description": "Project not found" }
        }
      }
    },
    "/projects/{id}/runs": {
      "post": {
        "summary": "Trigger calc run on a project (Studio)",
        "description": "Submit a PDF to run Eurocode checks within a project workspace. Returns 202 — poll GET /runs/{runId}.",
        "operationId": "createRun",
        "tags": ["Projects"],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }
        ],
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": { "type": "object", "properties": { "pdf": { "type": "string", "format": "binary" } } }
            },
            "application/json": {
              "schema": { "type": "object", "properties": { "pdf_url": { "type": "string", "format": "uri" } } }
            }
          }
        },
        "responses": {
          "202": { "description": "Run created — poll poll_url for status" },
          "404": { "description": "Project not found" }
        }
      }
    },
    "/runs/{runId}": {
      "get": {
        "summary": "Get run status (Studio)",
        "description": "Status + utilisation summary for a specific run. When done, includes export_urls for shop.pdf / bom.xlsx / model.ifc / package.zip.",
        "operationId": "getRun",
        "tags": ["Projects"],
        "parameters": [
          { "name": "runId", "in": "path", "required": true, "schema": { "type": "integer" } }
        ],
        "responses": {
          "200": { "description": "Run status + utilisation summary + export_urls when done" },
          "404": { "description": "Run not found" }
        }
      }
    },
    "/projects/{id}/export/{format}": {
      "get": {
        "summary": "Stream fabrication export (Studio)",
        "description": "Download a fabrication export for the most recent completed run in a project (or specify ?run_id=N). Supported formats: shop.pdf, bom.xlsx, bom.csv, model.ifc, package.zip.",
        "operationId": "getProjectExport",
        "tags": ["Exports"],
        "parameters": [
          { "name": "id",     "in": "path",  "required": true, "schema": { "type": "integer" } },
          { "name": "format", "in": "path",  "required": true, "schema": { "type": "string", "enum": ["shop.pdf","bom.xlsx","bom.csv","model.ifc","package.zip"] } },
          { "name": "run_id", "in": "query", "schema": { "type": "integer" }, "description": "Specific run to export (defaults to latest done run)" }
        ],
        "responses": {
          "200": { "description": "File download" },
          "404": { "description": "Project or run not found" },
          "409": { "description": "Run not complete" }
        }
      }
    },
    "/projects/{id}/revisions": {
      "get": {
        "summary": "Revision history for a project (Studio)",
        "description": "Night Shift revision history — all revision events across all jobs in a project, newest first.",
        "operationId": "getProjectRevisions",
        "tags": ["Projects"],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }
        ],
        "responses": {
          "200": { "description": "Revision list with delta payloads" },
          "404": { "description": "Project not found" }
        }
      }
    }
  }
}
