Solve
The Solve endpoints are the core of the JAOT API. Send any optimization problem as JSON and receive the optimal solution. JAOT supports linear programming (LP), integer programming (IP), mixed-integer programming (MIP), and binary problems.
POST /api/v2/solve
Solve an optimization problem synchronously. The request blocks until the solver finishes or the time limit is reached.
Authentication: Requires API key or JWT token.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | A descriptive name for the problem |
objective | object | Yes | Objective function definition |
objective.sense | string | Yes | "minimize" or "maximize" |
objective.expression | string | Yes | Mathematical expression (e.g. "50*chairs + 40*tables") |
variables | array | Yes | List of decision variable definitions |
variables[].name | string | Yes | Variable name used in expressions |
variables[].type | string | Yes | "continuous", "integer", or "binary" |
variables[].lower_bound | number | No | Lower bound (default: 0) |
variables[].upper_bound | number | No | Upper bound (default: infinity) |
constraints | array | Yes | List of constraint definitions |
constraints[].name | string | No | Descriptive constraint name |
constraints[].expression | string | Yes | Constraint expression (e.g. "2*chairs + 3*tables <= 240") |
options | object | No | Solver options |
options.time_limit_seconds | number | No | Maximum solve time in seconds, 1–86400 (default: 300). Long solves should use the async endpoint. |
options.gap_tolerance | number | No | Optimality gap tolerance (default: 0.0001) |
solver_name | string | No | Solver override: "scip", "highs", "cbc", "glpk", "hexaly", or "auto" (default: platform default). See what auto picks. |
warm_start | object | No | Warm-start a solve from a previous execution ({"execution_id": "exe_..."}) |
template | string | No | Template name for template-based solving |
input | object | No | Input data for template-based solving |
Response
| Field | Type | Description |
|---|---|---|
status | string | Solve status (see table below) |
objective_value | number | Optimal objective function value |
variables | array | Variable names and their optimal values |
solution | object | Variable-value map for quick access |
solve_time_seconds | number | Wall-clock solve time |
gap | number | Optimality gap (0.0 = proven optimal) |
iterations | number | Solver iterations performed |
nodes | number | Branch-and-bound nodes explored |
Status values:
| Status | Meaning |
|---|---|
optimal | Globally optimal solution found |
feasible | Solution found, but optimality not proven |
infeasible | No feasible solution exists |
unbounded | Objective is unbounded |
time_limit | Time limit reached; best solution (if any) returned |
error | Solver exception |
Examples
import httpx
API_URL = "https://jaot.io/api/v2"
headers = {"Authorization": "Bearer ok_live_your_key_here"}
response = httpx.post(f"{API_URL}/solve", headers=headers, json={
"name": "furniture_production",
"variables": [
{"name": "chairs", "type": "integer", "lower_bound": 0, "upper_bound": 100},
{"name": "tables", "type": "integer", "lower_bound": 0, "upper_bound": 80}
],
"objective": {"sense": "maximize", "expression": "50*chairs + 40*tables"},
"constraints": [
{"name": "assembly_hours", "expression": "2*chairs + 3*tables <= 240"},
{"name": "finishing_hours", "expression": "4*chairs + 2*tables <= 200"}
],
"options": {"time_limit_seconds": 30}
})
result = response.json()
print(f"Status: {result['status']}")
print(f"Revenue: ${result['objective_value']}")
print(f"Chairs: {result['solution']['chairs']}, Tables: {result['solution']['tables']}")Response
{
"status": "optimal",
"objective_value": 3500.0,
"variables": [
{"name": "chairs", "value": 30, "type": "integer"},
{"name": "tables", "value": 60, "type": "integer"}
],
"solution": {"chairs": 30, "tables": 60},
"solve_time_seconds": 0.045,
"gap": 0.0,
"iterations": 12,
"nodes": 1
}Errors
| HTTP Code | Error | Description |
|---|---|---|
| 400 | bad_request | Invalid problem definition (undefined variable, invalid bounds) |
| 401 | unauthorized | Missing or invalid API key |
| 429 | rate_limited | Too many requests |
POST /api/v2/solve/async
Start an asynchronous solve. Returns immediately with a task ID. Use polling or WebSocket to retrieve the result when it completes.
Authentication: Requires API key or JWT token.
Request Body
Same as POST /api/v2/solve.
Response
| Field | Type | Description |
|---|---|---|
task_id | string | Unique task identifier for polling |
status | string | Always "pending" on creation |
message | string | Confirmation message |
ws_url | string | WebSocket URL for real-time updates |
poll_url | string | Polling URL for status checks |
Examples
import httpx
API_URL = "https://jaot.io/api/v2"
headers = {"Authorization": "Bearer ok_live_your_key_here"}
# For large problems, use async mode
response = httpx.post(f"{API_URL}/solve/async", headers=headers, json={
"name": "warehouse_routing",
"variables": [
{"name": f"route_{i}", "type": "binary"}
for i in range(500)
],
"objective": {
"sense": "minimize",
"expression": " + ".join(f"{cost}*route_{i}" for i, cost in enumerate(costs))
},
"constraints": warehouse_constraints,
"options": {"time_limit_seconds": 120}
})
task = response.json()
print(f"Task ID: {task['task_id']}, Status: {task['status']}")Response
{
"task_id": "b7a3c9e2-1234-5678-abcd-ef0123456789",
"status": "pending",
"message": "Task queued for processing",
"ws_url": "/api/v2/ws/executions/b7a3c9e2-1234-5678-abcd-ef0123456789",
"poll_url": "/api/v2/solve/async/b7a3c9e2-1234-5678-abcd-ef0123456789"
}Tip: Use async mode for problems with more than 1,000 variables or long time limits. Async solves run on dedicated workers and support real-time progress updates via WebSocket.
Polling for Results
Use GET /api/v2/solve/async/{task_id} to check the status of an async solve.
Pending:
{"task_id": "b7a3c9e2-...", "status": "pending", "message": "Task is waiting to be processed"}Running:
{"task_id": "b7a3c9e2-...", "status": "running", "progress": 0.45, "objective_value": 1234.56}Completed:
{"task_id": "b7a3c9e2-...", "status": "completed", "result": {"status": "optimal", "objective_value": 3500.0, "..."}}To retrieve async execution results, see the Executions page.
POST /api/v2/solve/validate
Validate a problem without solving it. Useful for checking problem definitions before committing.
Authentication: Requires API key or JWT token.
Request Body
Same as POST /api/v2/solve.
Response (Valid Problem)
{
"valid": true,
"num_variables": 5,
"num_constraints": 8,
"variable_types": {
"continuous": 2,
"integer": 3,
"binary": 0
}
}Response (Invalid Problem)
{
"valid": false,
"errors": ["Objective references undefined variables: {'unknown_var'}"]
}POST /api/v2/solve/async/{task_id}/cancel
Cancel a running async task.
Authentication: Requires API key or JWT token.
Response
{"task_id": "b7a3c9e2-...", "cancelled": true, "message": "Task cancellation requested"}If the task already completed:
{"task_id": "b7a3c9e2-...", "cancelled": false, "message": "Task already completed, cannot cancel"}GET /api/v2/solvers/available
List the solvers this server can run, and what each one can deliver. Use it to pick a
value for solver_name — and to know what to expect back from that choice.
Authentication: Requires API key or JWT token.
Response
{
"solvers": [
{
"name": "scip",
"available": true,
"description": "Academic MIP solver",
"version": "10.0",
"comparable": true,
"capabilities": {
"sensitivity": true,
"warm_start": true,
"quadratic": true,
"progress": true
}
},
{
"name": "hexaly",
"available": false,
"reason": "maintenance",
"retry_after": null,
"description": "Commercial solver for quadratic / non-convex problems",
"comparable": false,
"not_comparable_reason": "not_available",
"capabilities": {
"sensitivity": false,
"warm_start": true,
"quadratic": true,
"progress": false
}
}
]
}| Capability | What it means for your response |
|---|---|
sensitivity | The solve returns sensitivity (shadow prices, reduced costs). When false, that field is absent — it is not an error. |
warm_start | A re-solve can be seeded from a previous solution. Affects speed, not results. |
quadratic | Models containing quadratic terms can run. A solver without it rejects them explicitly rather than solving a linear relaxation. |
progress | The solve streams per-incumbent progress events. When false, you get the result only when it finishes. |
Notes:
available: falsemeans the solver is registered but temporarily unusable (reasonexplains why). Itscapabilitiesstill describe the solver, so past executions that used it can still be interpreted.capabilitiesmay be absent on an entry. That means "not known", not "supports nothing" — treat it as no information rather than as a denial.versionis the solver's own version string. Absent when the solver will not say. Record it next to any timing you keep: seconds measured against CBC 2.10.12 explain nothing about 2.11.comparablesays whether the solver can take part in a solver comparison on this server. It is a property of the server, not of any one model: Hexaly needs its own image and licence, and the comparison worker runs the base image so that one machine times every column.not_comparable_reasonrides only on afalse. Whether a solver can express a particular model is decided per comparison, against that problem's class.- Multi-objective is not listed: it is available for every solver. Solvers without native support get it through scalarization, so a per-solver flag would be misleading.
solver_name: "auto"is always accepted and is not listed here; the server picks the effective solver per problem and reports it back assolver_used, withauto_route_reasonsaying why.
What auto picks
| The model is | auto runs | auto_route_reason |
|---|---|---|
| Linear, all continuous (LP) | HiGHS | lp_routed_to_highs |
| Anything with quadratic terms | Hexaly | quadratic_routed_to_hexaly |
| Quadratic, Hexaly worker down | SCIP | hexaly_unavailable_fallback (plus a warning) |
| Anything else (MIP, mixed) | SCIP | milp_routed_to_scip |
CBC and GLPK are substitutes, not candidates. auto never picks them while the solver
its rule prefers is installed. It reaches them only on a server built without that solver,
and then it says so: auto_route_reason comes back preferred_solver_not_installed and the
response carries a warning naming the solver that actually ran.
They sit behind SCIP and HiGHS for two reasons that have nothing to do with speed. Neither computes shadow prices or reduced costs, so a caller who chose no solver would silently lose the sensitivity analysis. And GLPK is single-threaded: on a 60-lot burn-in plan (1,342 binaries) HiGHS and CBC finished in 1.5 seconds, SCIP in 12, and GLPK ran a full 60-second limit without finding any feasible answer.
A substitute is never given a model it cannot express, so a quadratic never reaches CBC or GLPK whatever else is missing.
To pick CBC or GLPK, name one — and use the solver comparer to find out which one is right for your model, with numbers, instead of guessing.