API Reference

Every action available in the web interface is also available via the HTTP API. The base URL is https://api.solver-arena.com. All endpoints accept and return JSON unless otherwise noted.

Introduction

Using the API follows a simple flow. Here are the main steps:

  1. List available solvers with GET /api/problems/solvers. Pass your file's extension to filter to compatible solvers only, for example ?format=.mps. Note the solver IDs you want to include in your run.
  2. Check the credit cost with GET /api/problems/credit-cost?preset=standard before submitting. The quick preset is always free. All other presets cost 1 credit per run, regardless of file size or solver count.
  3. Submit your problem with POST /api/problems/submit. Upload your file as multipart form data along with the solver list and a preset. The response contains a job_id.
  4. Poll status with GET /api/problems/{job_id}/status every 1 to 2 seconds until the state is SUCCESS, FAILURE, or INTERRUPTED.
  5. Fetch results with GET /api/problems/{job_id}/results. You can also call this during a run to get partial results as solvers finish.
  6. Download a ZIP with GET /api/problems/{job_id}/results/{solver}/download for each solver. The archive contains the full solution, solver log, and the exact parameters used, suitable for archiving and reproducibility.

All benchmark submissions require authentication. Sign in at /settings to generate an API key, then include it in every request to /submit and /history.

Authentication

Two methods are accepted. The server checks them in order and uses the first that matches.

API key (recommended for scripts and automation)

  1. Sign in to your account and go to /settings.
  2. Click Generate API key. The key is shown exactly once: copy it immediately.
  3. Pass it as a Bearer token in the Authorization header of every authenticated request.
# Authenticate with an API key
$ curl -X POST "https://api.solver-arena.com/api/problems/submit" \
    -H "Authorization: Bearer sa_<your_key>" \
    -F "[email protected]" \
    -F 'solvers=["highs","cbc"]' \
    -F "preset=standard"

Session cookie (browser use)

When you sign in through the web interface, the server sets an auth_token cookie. Browsers send it automatically. For fetch calls from your own frontend, the cookie is sufficient and no extra header is needed.

EndpointAuth required
POST /api/problems/submitYes
GET /api/problems/historyYes
GET /api/problems/{job_id}/statusNo
GET /api/problems/{job_id}/resultsNo
GET /api/problems/{job_id}/results/{solver}/downloadNo
POST /api/problems/{job_id}/cancelNo
All other read endpointsNo
GET /api/problems/solvers

Returns solvers currently installed and available. Filter by file format with the format query parameter.

ParameterTypeDescription
formatquery, optionalFile extension including dot, e.g. .mps, .lp, .nl. Omit to return all solvers.
$ curl "https://api.solver-arena.com/api/problems/solvers?format=.mps"

{
  "solvers": [
    { "id": "highs", "name": "HiGHS", "version": "1.13.1", "supported_formats": [".lp", ".mps"] },
    { "id": "cbc",   "name": "CBC",   "version": "2.10.11", "supported_formats": [".lp", ".mps"] },
    { "id": "glpk",  "name": "GLPK",  "version": "5.0",    "supported_formats": [".lp", ".mps"] },
    { "id": "scip",  "name": "SCIP",  "version": "9.1.0",  "supported_formats": [".lp", ".mps"] }
  ]
}
GET /api/problems/presets

Returns all available presets with labels and descriptions.

$ curl "https://api.solver-arena.com/api/problems/presets"
GET /api/problems/params-template

Returns a JSON parameter template pre-filled with a preset's values for the requested solvers. Download it, edit it, and pass it as custom_params to /submit.

ParameterTypeDescription
solversquery, requiredComma-separated solver IDs, e.g. highs,cbc
presetquery, optionalPreset to pre-fill values from. Defaults to standard.
$ curl "https://api.solver-arena.com/api/problems/params-template?solvers=highs,cbc&preset=standard" \
    -o params_template.json
POST /api/problems/analyze

Parse an optimization problem file and return model statistics and suggested solver parameters. Stateless: nothing is written to the database. Only .lp and .mps files are supported; .nl files return supported: false.

FieldTypeDescription
fileform, requiredProblem file to analyze (.mps or .lp), max 50 MB.
$ curl -X POST "https://api.solver-arena.com/api/problems/analyze" \
    -F "[email protected]"

{
  "supported": true,
  "problem_type": "MIP",
  "num_variables": 240,
  "num_continuous": 120,
  "num_integer": 100,
  "num_binary": 20,
  "num_constraints": 180,
  "num_nonzeros": 1440,
  "sparsity": 0.9667,
  "hints": ["MIP detected: SCIP or HiGHS recommended"],
  "suggested_params": {
    "highs": { "mip_rel_gap": 0.001 },
    "scip":  { "limits/gap": 0.001 }
  }
}
GET /api/problems/credit-cost

Returns the credit cost for a given run configuration before submitting. The quick preset always returns cost: 0. All other presets cost 1 credit per run, regardless of file size or solver count.

ParameterTypeDescription
presetquery, optionalPreset ID: quick, standard, or thorough. Defaults to standard.
$ curl "https://api.solver-arena.com/api/problems/credit-cost?preset=standard"

{ "cost": 1, "free": false }
POST /api/problems/submit

Upload a problem file and queue it for benchmarking. Requires authentication. Returns a job_id to poll. Credits are deducted immediately for files 100 KB and above (1 credit per solver). Returns HTTP 402 if the user does not have enough credits, and HTTP 413 if the file exceeds 50 MB.

FieldTypeDescription
fileform, requiredProblem file (.mps, .lp, or .nl), max 50 MB.
solversform, optionalJSON array of solver IDs to run, e.g. ["highs","cbc"]. Defaults to all compatible solvers.
presetform, optionalPreset ID. Defaults to standard. Ignored if custom_params is provided.
custom_paramsform, optionalJSON object mapping solver ID to parameter dict. Overrides preset entirely.
# Using a preset
$ curl -X POST "https://api.solver-arena.com/api/problems/submit" \
    -H "Authorization: Bearer sa_<your_key>" \
    -F "[email protected]" \
    -F 'solvers=["highs","cbc","scip"]' \
    -F "preset=standard"

{ "job_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6" }

# Using custom parameters
$ curl -X POST "https://api.solver-arena.com/api/problems/submit" \
    -H "Authorization: Bearer sa_<your_key>" \
    -F "[email protected]" \
    -F 'custom_params={"highs":{"time_limit":60,"mip_rel_gap":0.01},"cbc":{"max_seconds":60}}'
GET /api/problems/{job_id}/status

Poll the status of a job. Recommended polling interval: 1 to 2 seconds.

$ curl "https://api.solver-arena.com/api/problems/3fa85f64-.../status"

{ "state": "STARTED" }
StateMeaning
PENDINGJob queued, not yet started
STARTEDSolvers running
SUCCESSAll solvers finished, fetch results
INTERRUPTEDJob was cancelled mid-run, partial results available
FAILUREJob failed unexpectedly
GET /api/problems/{job_id}/results

Retrieve results. Available as soon as at least one solver has finished: you can poll this during STARTED state for live partial results.

$ curl "https://api.solver-arena.com/api/problems/3fa85f64-.../results"

{
  "job_id": "3fa85f64-...",
  "results": {
    "highs": {
      "status": "optimal",
      "objective": 48291.4,
      "wall_time": 0.312,
      "iterations": 1847,
      "gap": 0.0,
      "error": null
    },
    "cbc": {
      "status": "optimal",
      "objective": 48291.4,
      "wall_time": 2.847,
      "iterations": 5102,
      "gap": 0.0,
      "error": null
    }
  }
}
Status valueMeaning
optimalOptimal solution found
infeasibleProblem has no feasible solution
unboundedObjective is unbounded
unknownSolver stopped (time/gap limit) with a feasible point
failedSolver crashed or returned an error
GET /api/problems/{job_id}/results/{solver_name}/download

Download a ZIP archive for one solver containing:

  • results.json: objective, status, variables, constraints
  • metadata.json: preset, exact solver parameters applied, timestamps
  • solver_log.txt: raw solver output
$ curl "https://api.solver-arena.com/api/problems/3fa85f64-.../results/highs/download" \
    -o highs_3fa85f64.zip
POST /api/problems/{job_id}/cancel

Cancel a running job. Solvers that have already finished retain their results.

$ curl -X POST "https://api.solver-arena.com/api/problems/3fa85f64-.../cancel"

{ "cancelled": true }
GET /api/problems/history

Returns the authenticated user's job history, newest first, paginated. Requires authentication via API key or session cookie.

ParameterTypeDescription
pagequery, optionalPage number, starting at 1. Defaults to 1. Page size is 20.
$ curl "https://api.solver-arena.com/api/problems/history?page=1" \
    -H "Authorization: Bearer sa_<your_key>"

{
  "jobs": [
    {
      "id": "3fa85f64-...",
      "original_filename": "problem.mps",
      "status": "finished",
      "preset": "standard",
      "credits_used": 3,
      "created_at": "2025-01-15T10:23:00",
      "finished_at": "2025-01-15T10:23:45",
      "solver_names": ["highs", "cbc", "scip"]
    }
  ],
  "total": 42,
  "page": 1,
  "page_size": 20
}

Python example

A complete end-to-end script using the requests library:

import time
import requests

BASE    = "https://api.solver-arena.com/api/problems"
API_KEY = "sa_<your_key>"  # from /settings

session = requests.Session()
session.headers["Authorization"] = f"Bearer {API_KEY}"

# 1. Submit
with open("problem.mps", "rb") as f:
    resp = session.post(
        f"{BASE}/submit",
        files={"file": f},
        data={"solvers": '["highs","cbc","scip"]', "preset": "standard"},
    )
resp.raise_for_status()
job_id = resp.json()["job_id"]
print(f"Job submitted: {job_id}")

# 2. Poll until done
while True:
    state = session.get(f"{BASE}/{job_id}/status").json()["state"]
    print(f"  {state}")
    if state in ("SUCCESS", "FAILURE", "INTERRUPTED"):
        break
    time.sleep(1.5)

# 3. Print results
results = session.get(f"{BASE}/{job_id}/results").json()["results"]
for solver, r in sorted(results.items(), key=lambda x: x[1]["wall_time"]):
    print(f"{solver:8s}  {r['status']:10s}  obj={r['objective']}  t={r['wall_time']:.3f}s")

# 4. Download ZIP for the fastest solver
winner = min(results, key=lambda s: results[s]["wall_time"])
zip_bytes = session.get(f"{BASE}/{job_id}/results/{winner}/download").content
with open(f"{winner}_result.zip", "wb") as f:
    f.write(zip_bytes)
print(f"Downloaded {winner}_result.zip")