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.json

Authentication 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/tokenGet 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/meVerify 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-keysCreate 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-keysList 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/solveRun 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/validateCheck 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-beamTurnkey 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/modelsList 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/modelsSave 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.

StatusCodeMeans
422solver_errorThe model reached the solver and could not be solved.
402insufficient_creditFree quota exhausted and no prepaid credit left.
429daily_limit / rate_limitedQuota or rate limit hit. Back off and retry.
409in_flightA 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?

An API key (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?

SI base units: metres, newtons and pascals; angles in degrees. The check-beam convenience endpoint is the exception — it takes metres, kN/m and kN.

How much does a call cost?

100 successful solves per rolling week are free. Beyond that it is €0.01 per solve from prepaid credit, or unlimited on Pro at €19.95/month. validate and the key endpoints are never metered.

Can I import this into ChatGPT or Postman?

Yes. /api/openapi.json is a complete OpenAPI 3.1 document, designed to be imported as a ChatGPT Custom GPT Action or into any OpenAPI toolkit.

How do I avoid being charged twice for a retry?

Send an 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?

Yes, per account and per minute, to stop a runaway agent loop. Exceeding it returns 429 with rate_limited; back off and retry.