REST API reference
Every endpoint below is callable from any language over plain HTTP. Model quantities are SI base units — metres, newtons and pascals. The complete machine-readable spec is served as OpenAPI 3.1 and can be imported straight into a client generator or a ChatGPT Custom GPT Action.
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"
}
}Solver
solve and check-beam are metered: each successful call uses one of your 100 free weekly solves, then prepaid credit at €0.01 per solve. validate is free and unmetered.
These are the REST twins of the MCP solve_model and check_beam tools, so an agent and a script hit exactly the same code path.
POST /api/sdk/solve — Run the FEM solver
{
"description": "Solve a FERS model JSON (SI units: metres, newtons, pascals) and get displacements, member forces and reactions. Metered: uses your weekly free solves or prepaid credits (Pro: unlimited). REST twin of the MCP solve_model tool. Errors: 422 solver_error, 402 insufficient_credit, 429 daily_limit / rate_limited, 409 in_flight (same idempotency_key already running).",
"request": {
"method": "POST",
"url": "https://ferscloud.com/api/sdk/solve",
"headers": {
"X-API-Key": "<keyId>.<secret>"
},
"body": {
"model": "<FERS model JSON object or string>",
"idempotency_key": "optional-unique-id"
}
},
"response": {
"result": "<solver results: displacements, member forces, reactions, unity checks>",
"meta": {
"funding": "free_tier",
"cost_cents": 0,
"balance_cents": 0,
"replayed": false
}
}
}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 — Turnkey EN 1993-1-1 steel member check
{
"description": "Build a single-span steel beam, solve it, and return the EN 1993-1-1 utilizations (bending, shear, N+M, lateral-torsional buckling). Units: span_m in metres, udl in kN/m, point_load in kN (downward). Counts as one solve. REST twin of the MCP check_beam tool. The example below reproduces 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": {
"check": {
"section": "IPE400",
"span_m": 7.5,
"status": "Yellow",
"governing_utilization": 0.822,
"governing_check": "LTB (6.3.2)",
"passes": true,
"checks": {
"Bending z (6.2.5)": 0.266,
"Shear y (6.2.6)": 0.071,
"Combined N+M (6.2.1)": 0.266,
"LTB (6.3.2)": 0.822
},
"advice": "Passes (UC 0.822) with lateral-torsional buckling governing. …"
},
"meta": {
"funding": "free_tier",
"cost_cents": 0,
"balance_cents": 0,
"replayed": false
}
}
}A complete call
The turnkey beam check is the fastest way to confirm your key works — it builds, solves and checks a single span in one request.
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
Metered endpoints use HTTP status codes to distinguish the reasons a call did not run, so a client can retry intelligently rather than treating every failure alike.
| Status | Code | Means |
|---|---|---|
| 422 | solver_error | The model reached the solver and could not be solved. |
| 402 | insufficient_credit | Free quota exhausted and no prepaid credit left. |
| 429 | daily_limit / rate_limited | Quota or rate limit hit. Back off and retry. |
| 409 | in_flight | A request with the same idempotency_key is already running. |
Pass an idempotency_key on solve so a retried request is replayed rather than charged twice; the response meta.replayed tells you which happened.
Other ways in
- MCP server — the same capabilities as tools an AI agent can call directly.
- JavaScript package — solve in the browser with no HTTP call at all.
- Python package — build models with objects rather than raw JSON.
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?
validate and the key endpoints are never metered.Can I import this into ChatGPT or Postman?
How do I avoid being charged twice for a retry?
idempotency_key with the solve request. A repeat of the same key returns the stored result with meta.replayed: true instead of solving again, and a concurrent repeat returns 409.Is there a rate limit?
rate_limited; back off and retry.