REST API reference
Every endpoint below is callable from any language over plain HTTP, and the complete spec is served as OpenAPI 3.1 for client generators and ChatGPT Custom GPT Actions. The server runs no solver: solve and check-beam validate the model and return a link that solves it in a browser. Model quantities are SI base units.
The machine-readable spec
The full specification lives at ferscloud.com/api/openapi.json in OpenAPI 3.1. It is served with permissive CORS and is explicitly allowed in robots.txt, so tools and agents can fetch it directly.
Authenticate with either an X-API-Key header (a permanent key from your Profile page) or a Bearer token from POST /api/sdk/token. The key form works for every sign-in method; the token form only for email-and-password accounts.
# The complete machine-readable spec (OpenAPI 3.1).
# Import it into ChatGPT Custom GPT Actions, Postman, or a client generator.
curl https://ferscloud.com/api/openapi.jsonAuthentication and API keys
Create a key once, store it server-side, and send it as X-API-Key on every request. Keys are shown once at creation and can be revoked at any time.
POST /api/sdk/token — Get a short-lived JWT
{
"description": "Exchange email + password for a 1-hour Bearer token. Requires an email/password account. Google/GitHub users should use an API key (X-API-Key header) instead.",
"request": {
"method": "POST",
"url": "https://ferscloud.com/api/sdk/token",
"body": {
"email": "you@example.com",
"password": "your_password"
}
},
"response": {
"token": "<jwt>",
"expires_at": "2025-01-01T01:00:00Z",
"is_premium": false,
"user": {
"id": "<uuid>",
"email": "you@example.com"
}
}
}GET /api/sdk/me — Verify a key and get user info
{
"description": "Check the currently authenticated SDK user. Works with both X-API-Key and Bearer token.",
"request": {
"method": "GET",
"url": "https://ferscloud.com/api/sdk/me",
"headers": {
"X-API-Key": "<keyId>.<secret>"
}
},
"response": {
"user_id": "<uuid>",
"email": "you@example.com",
"is_premium": false,
"token_expires_at": "2026-01-01T01:00:00.000Z"
}
}POST /api/sdk/api-keys — Create a persistent API key
{
"description": "Create a named API key (returned once, store it securely). Optionally set expiresInDays for auto-expiry.",
"request": {
"method": "POST",
"url": "https://ferscloud.com/api/sdk/api-keys",
"headers": {
"X-API-Key": "<keyId>.<secret>"
},
"body": {
"name": "My laptop",
"scopes": [],
"expiresInDays": null
}
},
"response": {
"id": "<keyId>",
"name": "My laptop",
"key": "<keyId>.<secret>",
"scopes": [],
"created_at": "2026-01-01T00:00:00.000Z",
"expires_at": null,
"message": "Store this key securely. It will not be shown again."
}
}GET /api/sdk/api-keys — List API keys
{
"description": "List all active (non-revoked) API keys. Secrets are never returned.",
"request": {
"method": "GET",
"url": "https://ferscloud.com/api/sdk/api-keys",
"headers": {
"X-API-Key": "<keyId>.<secret>"
}
},
"response": {
"keys": [
{
"id": "<keyId>",
"name": "My laptop",
"scopes": [],
"createdAt": "2026-01-01T00:00:00.000Z",
"lastUsedAt": null,
"expiresAt": null
}
]
}
}DELETE /api/sdk/api-keys?id=<keyId> — Revoke an API key
{
"description": "Revoke an API key by its ID. Pass the key ID as a query parameter, not a path segment.",
"request": {
"method": "DELETE",
"url": "https://ferscloud.com/api/sdk/api-keys?id=<keyId>",
"headers": {
"X-API-Key": "<keyId>.<secret>"
}
},
"response": {
"message": "API key revoked"
}
}Solving
FERS Cloud runs no solver. solve validates the model, parks it as a public draft and answers HTTP 200 with status: "solve_in_browser" and an open_url. Opening that link solves the model in the browser, at the tier of the account that made it: Free for non-commercial use up to 100 members, Pro up to 10,000. The link stays valid for 24 hours. A model that fails validation gets 422 invalid_model with the errors instead of a link.
check-beam builds a beam and its EN 1993-1-1 check and answers the same way, and also returns the built model. To get results in your own code, solve where the code runs: the Python package (fers solve model.json), the JavaScript package, or the local MCP server.
None of these endpoints is metered. They are the REST twins of the hosted MCP server's solve_model and check_beam tools and return the same answer.
POST /api/sdk/solve — Get a link that solves a model in the browser
{
"description": "Validate a FERS model JSON (SI units: metres, newtons, pascals) and get a link that solves it in a browser. FERS Cloud runs no solver: the model is parked as a public draft for 24 hours and solved by whoever opens open_url, at that account's tier. Not metered. A model that fails validation gets 422 { code: \"invalid_model\", errors, warnings } instead. REST twin of the hosted MCP solve_model tool.",
"request": {
"method": "POST",
"url": "https://ferscloud.com/api/sdk/solve",
"headers": {
"X-API-Key": "<keyId>.<secret>"
},
"body": {
"model": "<FERS model JSON object or string>"
}
},
"response": {
"status": "solve_in_browser",
"open_url": "https://ferscloud.com/share/<slug>",
"expires_at": "2026-01-02T00:00:00.000Z",
"model_ref": "draft_<id>",
"members": 1,
"tier": {
"free_member_limit": 100,
"free_use": "non-commercial use only",
"pro_member_limit": 10000,
"pro_use": "commercial and non-commercial use",
"solves_at": "the tier of the account that made the link, for this model only"
},
"validation": {
"valid": true,
"warnings": []
},
"in_chat_sandbox": {
"when": "You can run Python with internet access (Claude's code execution, a terminal): solve there and quote the numbers in the chat.",
"steps": [
"pip install \"FERS>=0.2\"",
"Save the model JSON you passed to this tool as model.json.",
"fers solve model.json --out results.json, then read results.loadcases / results.loadcombinations by name. Exit 4: do not use the numbers."
]
},
"local_server": {
"npx": "npx -y @ferscloud/fers-mcp",
"claude_code": "claude mcp add --transport stdio fers --env FERS_API_KEY=<your key> -- npx -y @ferscloud/fers-mcp",
"claude_desktop": "https://ferscloud.com/mcp#local",
"docs": "https://ferscloud.com/mcp#local"
},
"python": "pip install FERS && fers solve model.json",
"note": "This server does not run solves. Open open_url: the model solves in the browser and shows its results there; they are not returned to this chat. For results in the chat, solve in your code sandbox (in_chat_sandbox) or install the local FERS MCP server (local_server). The link lives as long as the draft: 24 hours, or until 50 newer drafts exist."
}
}POST /api/sdk/validate — Check a model without solving
{
"description": "Validate a FERS model JSON's structure (required keys, basic integrity) without running the solver. Free and unmetered.",
"request": {
"method": "POST",
"url": "https://ferscloud.com/api/sdk/validate",
"headers": {
"X-API-Key": "<keyId>.<secret>"
},
"body": {
"model": "<FERS model JSON object or string>"
}
},
"response": {
"valid": false,
"errors": [
"Missing required key: \"model.materials\""
]
}
}POST /api/sdk/check-beam — Build an EN 1993-1-1 beam check and get its solve link
{
"description": "Build a single-span steel beam with a ULS combination and its EN 1993-1-1 member check (bending, shear, N+M, lateral-torsional buckling), and get a link that evaluates it in a browser, plus the built model so a caller with the FERS engine can solve it directly. Units: span_m in metres, udl in kN/m, point_load in kN (downward). Not metered. REST twin of the hosted MCP check_beam tool. The request below is the published IPE 400 worked example.",
"request": {
"method": "POST",
"url": "https://ferscloud.com/api/sdk/check-beam",
"headers": {
"X-API-Key": "<keyId>.<secret>"
},
"body": {
"span_m": 7.5,
"section": "IPE400",
"material": "steel_S355",
"udl": 13,
"uls_factor": 1.35,
"restrained": false
}
},
"response": {
"status": "solve_in_browser",
"open_url": "https://ferscloud.com/share/<slug>",
"expires_at": "2026-01-02T00:00:00.000Z",
"model_ref": "draft_<id>",
"members": 1,
"tier": {
"free_member_limit": 100,
"free_use": "non-commercial use only",
"pro_member_limit": 10000,
"pro_use": "commercial and non-commercial use",
"solves_at": "the tier of the account that made the link, for this model only"
},
"in_chat_sandbox": {
"when": "You can run Python with internet access (Claude's code execution, a terminal): solve there and quote the numbers in the chat.",
"steps": [
"pip install \"FERS>=0.2\"",
"Save the model JSON you passed to this tool as model.json.",
"fers solve model.json --out results.json, then read results.loadcases / results.loadcombinations by name. Exit 4: do not use the numbers."
]
},
"local_server": {
"npx": "npx -y @ferscloud/fers-mcp",
"claude_code": "claude mcp add --transport stdio fers --env FERS_API_KEY=<your key> -- npx -y @ferscloud/fers-mcp",
"claude_desktop": "https://ferscloud.com/mcp#local",
"docs": "https://ferscloud.com/mcp#local"
},
"python": "pip install FERS && fers solve model.json",
"note": "This server does not run solves. Open open_url: the model solves in the browser and shows its results there; they are not returned to this chat. For results in the chat, solve in your code sandbox (in_chat_sandbox) or install the local FERS MCP server (local_server). The link lives as long as the draft: 24 hours, or until 50 newer drafts exist.",
"model": "<the built FERS model: the beam, its ULS combination and the EC3 check>"
}
}A complete call
The beam check is the fastest way to confirm your key works: one request builds a single span with its EN 1993-1-1 check and returns the link that evaluates it, together with the built model.
curl -X POST https://ferscloud.com/api/sdk/check-beam \
-H "X-API-Key: <keyId>.<secret>" \
-H "Content-Type: application/json" \
-d '{
"span_m": 7.5,
"section": "IPE400",
"material": "steel_S355",
"udl": 13,
"uls_factor": 1.35,
"restrained": false
}'Units for this endpoint are pragmatic rather than SI: span_m in metres, udl in kN/m and point_load in kN downward. The model endpoints take SI throughout.
Saved models
Cloud model storage is a Pro feature. On the free tier, keep models as local JSON — the same format these endpoints accept and return.
GET /api/sdk/models — List saved models
{
"description": "List all models saved to the authenticated account",
"request": {
"method": "GET",
"url": "https://ferscloud.com/api/sdk/models",
"headers": {
"X-API-Key": "<keyId>.<secret>"
}
},
"response": {
"models": [
{
"id": "<modelId>",
"name": "My cantilever",
"description": null,
"createdAt": "2025-01-01T00:00:00Z",
"updatedAt": "2025-01-01T00:00:00Z"
}
]
}
}POST /api/sdk/models — Save a new model
{
"description": "Save a structural model JSON to your account",
"request": {
"method": "POST",
"url": "https://ferscloud.com/api/sdk/models",
"headers": {
"X-API-Key": "<keyId>.<secret>"
},
"body": {
"name": "My cantilever",
"description": "5 m IPE 180 cantilever with −1 kN tip load",
"model": "<model JSON object>"
}
},
"response": {
"id": "<modelId>",
"name": "My cantilever",
"description": "5 m IPE 180 cantilever with −1 kN tip load",
"createdAt": "2025-01-01T00:00:00Z",
"updatedAt": "2025-01-01T00:00:00Z"
}
}GET /api/sdk/models/{id} — Download a model
{
"description": "Retrieve the full model JSON for a saved model",
"request": {
"method": "GET",
"url": "https://ferscloud.com/api/sdk/models/<modelId>",
"headers": {
"X-API-Key": "<keyId>.<secret>"
}
},
"response": {
"id": "<modelId>",
"name": "My cantilever",
"description": null,
"model": "<full model JSON object>",
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T00:00:00Z"
}
}PUT /api/sdk/models/{id} — Update a model
{
"description": "Update the name, description, and/or JSON of an existing model. Send only the fields you want to change.",
"request": {
"method": "PUT",
"url": "https://ferscloud.com/api/sdk/models/<modelId>",
"headers": {
"X-API-Key": "<keyId>.<secret>"
},
"body": {
"name": "Updated name"
}
},
"response": {
"id": "<modelId>",
"name": "Updated name",
"description": null,
"createdAt": "2025-01-01T00:00:00Z",
"updatedAt": "2025-01-02T00:00:00Z"
}
}DELETE /api/sdk/models/{id} — Delete a model
{
"description": "Permanently delete a saved model",
"request": {
"method": "DELETE",
"url": "https://ferscloud.com/api/sdk/models/<modelId>",
"headers": {
"X-API-Key": "<keyId>.<secret>"
}
},
"response": "204 No Content"
}Error responses
The status code tells a client why a call returned no link, so it can fix the input rather than retry blindly.
| Status | Code | Means |
|---|---|---|
| 400 | — | A parameter is missing or conflicting, or a section, material or support is unknown. |
| 401 | — | Missing or invalid credentials. |
| 413 | — | The model JSON is larger than the size limit. |
| 422 | invalid_model | solve only: the model fails validation. errors and warnings say where. |
Nothing is metered and there is no solve quota, so there are no credit or quota errors, and a retry costs nothing.
Other ways in
- MCP server — the same tools for AI agents, with a local server that solves on your machine.
- JavaScript package — solve in the browser with no HTTP call at all.
- Python package — build models with objects rather than raw JSON, and solve them locally.
Related pages
See also
Frequently asked questions
Which authentication should I use?
X-API-Key) for anything automated. The POST /api/sdk/token route only works for accounts created with an email and password, and its token expires after an hour.What units does the model JSON use?
check-beam convenience endpoint is the exception — it takes metres, kN/m and kN.How much does a call cost?
solve and check-beam return a link, and the solve runs in the browser that opens it. The tier sets the model size and the use: Free up to 100 members for non-commercial use, Pro up to 10,000 at €19.95/month, commercial use included.Can I import this into ChatGPT or Postman?
How do I get the results back in my code?
solve returns a link rather than results. Solve where your code runs instead — the Python package (pip install FERS, then fers solve model.json), the JavaScript package in a browser or Node, or the local MCP server @ferscloud/fers-mcp for an agent. check-beam also returns the built model, ready for any of them.