{
  "openapi": "3.1.0",
  "info": {
    "title": "FERS Cloud API",
    "version": "1.0.0",
    "description": "Run the FERS finite-element structural solver over HTTP. Authenticate with a FersCloud API key (create one at ferscloud.com → Profile → API Keys) sent as the `X-API-Key` header. `POST /api/sdk/solve` returns displacements, internal forces, reactions, and code (unity) checks. Free tier: 100 solves/week, then €0.01/solve from prepaid credits."
  },
  "servers": [
    {
      "url": "https://ferscloud.com"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "paths": {
    "/api/sdk/solve": {
      "post": {
        "operationId": "solveModel",
        "summary": "Run the FERS FEM structural solver on a model",
        "description": "Solves a structural model and returns results (nodal displacements, member internal forces, support reactions, and any unity/code-check results). Counts as one solve. Pass a stable `idempotency_key` and reuse it on retries to avoid being charged twice.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "model"
                ],
                "properties": {
                  "model": {
                    "type": "object",
                    "description": "A full FERS structural model (schema_version, settings, model, analysis). Build one with the MCP create_beam/create_frame tools or start from a template; may also be sent as a JSON string.",
                    "additionalProperties": true
                  },
                  "idempotency_key": {
                    "type": "string",
                    "description": "Optional. Reuse the same value when retrying to avoid a double charge."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Solved. `result` is the full solver output.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "result": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "funding": {
                          "type": "string",
                          "enum": [
                            "FREE",
                            "PAID",
                            "PREMIUM"
                          ]
                        },
                        "cost_cents": {
                          "type": "integer"
                        },
                        "balance_cents": {
                          "type": "integer"
                        },
                        "replayed": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "402": {
            "description": "Out of credits and weekly free solves",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "409": {
            "description": "A solve with this idempotency_key is still running (code: in_flight) — retry with a fresh key",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Solver rejected the model",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Rate or daily limit reached",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/sdk/validate": {
      "post": {
        "operationId": "validateModel",
        "summary": "Validate a model's structure without solving (free)",
        "description": "Checks the model for the required keys and basic structural integrity. Does not count as a solve.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "model"
                ],
                "properties": {
                  "model": {
                    "type": "object",
                    "description": "A full FERS structural model (schema_version, settings, model, analysis). Build one with the MCP create_beam/create_frame tools or start from a template; may also be sent as a JSON string.",
                    "additionalProperties": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Validation result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "valid": {
                      "type": "boolean"
                    },
                    "errors": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/sdk/create-beam": {
      "post": {
        "operationId": "createBeam",
        "summary": "Generate a solver-ready beam model (named or rectangular section)",
        "description": "Build a single-span beam model JSON from simple inputs — a named European section (IPE300, HEA200, …) or a rectangle, plus span, support and load; pass the returned `model` straight to solveModel. Units: metres, mm, kN.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "span_m"
                ],
                "properties": {
                  "span_m": {
                    "type": "number",
                    "description": "Beam span in metres. (The server also forgives the alias names span/length, case-insensitively.)"
                  },
                  "support": {
                    "type": "string",
                    "enum": [
                      "simply_supported",
                      "cantilever"
                    ],
                    "description": "Support type (default simply_supported)"
                  },
                  "section": {
                    "type": "string",
                    "description": "European section name, e.g. IPE300, HEA200, 'RHS 100x50x5'. Overrides height_mm/width_mm."
                  },
                  "height_mm": {
                    "type": "number",
                    "description": "Rectangular section depth in mm (default 200)"
                  },
                  "width_mm": {
                    "type": "number",
                    "description": "Rectangular section width in mm (default 100)"
                  },
                  "material": {
                    "type": "string",
                    "enum": [
                      "steel_S235",
                      "steel_S355",
                      "aluminium_6061",
                      "concrete_C30"
                    ],
                    "description": "Material preset (default steel_S235; shorthand S235/S355 is recognized)"
                  },
                  "load_type": {
                    "type": "string",
                    "enum": [
                      "udl",
                      "point"
                    ],
                    "description": "Load type (default udl)"
                  },
                  "load_kn": {
                    "type": "number",
                    "description": "UDL in kN/m or point load in kN, downward (default 10). (The server also forgives the alias names load/udl/uniform_load, and point_load — which implies load_type 'point'.)"
                  },
                  "load_position_frac": {
                    "type": "number",
                    "description": "Point-load position along the span, 0..1 (default 0.5)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The generated model (pass `model` to solveModel)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "model": {
                      "type": "object",
                      "additionalProperties": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid parameters (missing span, conflicting alias values, unknown section/material/support/load_type)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/sdk/check-beam": {
      "post": {
        "operationId": "checkBeam",
        "summary": "Check a steel beam to EN 1993-1-1 (Eurocode 3)",
        "description": "Builds a single-span beam with a ULS load combination and an EC3 member check, solves it, and returns the governing utilization (<= 1.0 = OK) with the per-rule breakdown (bending, shear, combined N+M, and lateral-torsional buckling), the governing rule name, and a short advice string. Common parameter aliases are accepted case-insensitively (span/length, load_kN_per_m/uniform_load, laterally_restrained). Counts as one solve. A named European section is required. Units: metres, kN, kN/m.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "span_m",
                  "section"
                ],
                "properties": {
                  "span_m": {
                    "type": "number",
                    "description": "Beam span in metres. (The server also forgives the alias names span/length, case-insensitively.)"
                  },
                  "section": {
                    "type": "string",
                    "description": "European section name, e.g. IPE300, HEA200, 'RHS 100x50x5'"
                  },
                  "support": {
                    "type": "string",
                    "enum": [
                      "simply_supported",
                      "cantilever",
                      "fixed"
                    ],
                    "description": "Support type (default simply_supported)"
                  },
                  "material": {
                    "type": "string",
                    "enum": [
                      "steel_S235",
                      "steel_S355"
                    ],
                    "description": "Steel grade (default steel_S235; shorthand S235/S355 is recognized)"
                  },
                  "udl": {
                    "type": "number",
                    "description": "Uniformly distributed load in kN/m, downward. (The server also forgives the alias names load_kN_per_m/uniform_load.)"
                  },
                  "point_load": {
                    "type": "number",
                    "description": "Point load in kN, downward"
                  },
                  "point_position": {
                    "type": "number",
                    "description": "Point-load position along the span, 0..1 (default mid-span)"
                  },
                  "uls_factor": {
                    "type": "number",
                    "description": "ULS load factor applied to the load (default 1.35)"
                  },
                  "restrained": {
                    "type": "boolean",
                    "description": "Set true if the compression flange is continuously laterally restrained (e.g. by decking or a slab) — the LTB check is then skipped. Alias: laterally_restrained. Default false."
                  },
                  "idempotency_key": {
                    "type": "string",
                    "description": "Optional. Reuse the same value when retrying to avoid a double charge."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The EC3 member-check result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "check": {
                      "type": "object",
                      "properties": {
                        "section": {
                          "type": "string"
                        },
                        "span_m": {
                          "type": "number"
                        },
                        "status": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "governing_utilization": {
                          "type": [
                            "number",
                            "null"
                          ]
                        },
                        "governing_check": {
                          "type": "string",
                          "description": "Name of the EC3 rule that governs, e.g. 'LTB (6.3.2)'"
                        },
                        "passes": {
                          "type": [
                            "boolean",
                            "null"
                          ]
                        },
                        "checks": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "number"
                          }
                        },
                        "advice": {
                          "type": "string",
                          "description": "Short engineering explanation of what governs and which inputs could change the outcome"
                        },
                        "note": {
                          "type": "string"
                        }
                      }
                    },
                    "meta": {
                      "type": "object",
                      "additionalProperties": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid parameters (missing span/section, conflicting alias values, unknown section/material/support)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "402": {
            "description": "Out of credits and weekly free solves",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "409": {
            "description": "A solve with this idempotency_key is still running (code: in_flight) — retry with a fresh key",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Solver rejected the model",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Rate or daily limit reached",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/sdk/me": {
      "get": {
        "operationId": "getMe",
        "summary": "Get the authenticated account (id, email, premium status)",
        "responses": {
          "200": {
            "description": "Account info",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "user_id": {
                      "type": "string"
                    },
                    "email": {
                      "type": "string"
                    },
                    "is_premium": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/sdk/models": {
      "get": {
        "operationId": "listModels",
        "summary": "List saved models",
        "responses": {
          "200": {
            "description": "Model list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "models": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "description": {
                            "type": "string"
                          },
                          "updatedAt": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "saveModel",
        "summary": "Save a model to the account (results are never stored)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "model"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "description": {
                    "type": "string"
                  },
                  "model": {
                    "type": "object",
                    "description": "A full FERS structural model (schema_version, settings, model, analysis). Build one with the MCP create_beam/create_frame tools or start from a template; may also be sent as a JSON string.",
                    "additionalProperties": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Saved",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          }
        }
      }
    },
    "/api/sdk/models/{id}": {
      "get": {
        "operationId": "getModel",
        "summary": "Download a saved model's full JSON by id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The model JSON",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "A FersCloud API key in the form `keyId.secret` (create at ferscloud.com → Profile → API Keys). A `Authorization: Bearer <sdk-jwt>` token is also accepted."
      }
    }
  }
}