Download problems and submit from a script. The starter repository uses these same endpoints. To give this reference to an agent, use Copy page or the Markdown version from the menu next to the title.
Basics
| Base URL | https://frontieror-challenge.com/api/v1 |
| Authentication | Authorization: Bearer fo_… with your personal API token. See Authentication. |
| Format | JSON with snake_case fields. Times are ISO 8601 in UTC. |
| Errors | {"error": "…"}, with a message you can show as it is. |
| Limits | Request bodies up to 4 MB. Submissions count toward your team's daily limit: one per UTC day, 4 during the testing stage. |
The examples use httpx, which the starter uses too. Create the client once:
import os
import httpx
api = httpx.Client(
base_url="https://frontieror-challenge.com/api/v1",
headers={"Authorization": f"Bearer {os.environ['FRONTIEROR_TOKEN']}"},
timeout=60,
)
Errors every endpoint can return
| Status | Message |
|---|---|
| 401 | Missing or invalid API token. |
| 403 | Join or create a team first. (Only endpoints that need a team.) |
| 500 | Something went wrong, try again. |
Get your account
/ meTokenWho the token belongs to, their team, the current stage and round, and the submissions left today. Use it to check a token.
Responses
me = api.get("/me").json()
print(me["team"], me["submissions_left_today"])
{
"user": { "id": "5b0f…", "name": "Ada Lovelace", "email": "ada@example.com" },
"team": { "id": "c2a1…", "name": "Column Generators", "track": "main" },
"stage": {
"id": "91de…",
"name": "Stage 1",
"status": "active",
"start_date": "2027-01-04T05:00:00.000Z",
"end_date": "2027-02-01T05:00:00.000Z"
},
"round": {
"number": 2,
"of": 4,
"status": "open",
"start_date": "2027-01-11T05:00:00.000Z",
"end_date": "2027-01-18T05:00:00.000Z",
"self_hosted_window_s": 5100
},
"submissions_left_today": 3
}
{ "error": "Missing or invalid API token." }
List problems
/ problemsTokenThe problems of the current round, once it has opened: the ones a submission covers. Their slugs are the names your solvers go by.
Query parameters
detailsflagoptionalAdd
?details=1to include each problem's description, schemas and checker, as in Get a problem.
Responses
problems = api.get("/problems").json()
slugs = [p["slug"] for p in problems]
[
{
"slug": "barnhart2000",
"name": "Origin-Destination Integer Multicommodity Flow (ODIMCF)",
"category": "network_design_location",
"formulation_type": "combinatorial",
"objective": "minimize"
}
]
Get a problem
/ problems/ {slug}TokenOne problem of the current stage with its statement and schemas: the content of its problem page.
Path parameters
slugstringrequired
Responses
problem = api.get("/problems/barnhart2000").json()
print(problem["solution_schema"])
{
"slug": "barnhart2000",
"name": "Origin-Destination Integer Multicommodity Flow (ODIMCF)",
"category": "network_design_location",
"formulation_type": "combinatorial",
"objective": "minimize",
"description": "…",
"instance_schema": { "network": { … }, "commodities": { … } },
"solution_schema": {
"objective_value": "<float> Total cost of rejected commodities across the entire routing plan.",
"commodities": [ … ]
},
"checker": "…"
}
{ "error": "No problem with this slug in the current stage." }
List public instances
/ problems/ {slug}/ instancesTokenThe problem's public instances, for development. Private instances are never listed.
Path parameters
slugstringrequired
Responses
instances = api.get("/problems/barnhart2000/instances").json()
[
{ "id": "0f1e8c2a-…", "name": "tiny_instance", "time_limit_s": 60 }
]
Download a public instance
/ problems/ {slug}/ instances/ {id}TokenThe instance file itself, in the problem's instance schema, sent as an attachment named <slug>-<name>.json.
Path parameters
slugstringrequiredidstring · uuidrequiredFrom List public instances.
Responses
from pathlib import Path
for instance in instances:
data = api.get(f"/problems/barnhart2000/instances/{instance['id']}").content
Path(f"{instance['name']}.json").write_bytes(data)
List images
/ imagesTokenOur images with their packages and full lock files, and the limits on extra packages. See Environment.
Responses
images = api.get("/images").json()
for image in images["images"]:
print(image["id"], image["pull"])
{
"default_image": "python-3.12",
"images": [
{
"id": "python-3.14",
"label": "Python 3.14",
"python_version": "3.14",
"pull": "…:3.14",
"packages": ["numpy", "scipy", "pandas", "networkx",
"ortools", "pulp", "highspy", "pyomo"],
"lock": "absl-py==2.5.0\nannotated-types==0.8.0\n…"
}
],
"requirements": { "max_packages": 50, "max_bytes": 10240 }
}
Submit an archive
/ main/ submissionsToken · Main teamChecks the archive straight away, as the dashboard does, and queues it for evaluation. A refused upload does not use one of the day's submissions. Then poll Get a submission.
Bodymultipart/form-data
filebinaryrequiredThe archive: zip, tar or tar.gz. The whole body is at most 4 MB. See Submission format.
docker_imagestringoptionalpython-3.12(default) orpython-3.14.requirementsstring · binaryoptionalExtra packages, one per line, as text or as a file. Only with our images.
A body that is not multipart is taken as the archive itself, run in python-3.12 without extra packages.
Query parameters
acknowledge_missingflagoptionalRequired when the archive has no folder for some problems. Those problems score 0.
Responses
with open("submission.zip", "rb") as f:
r = api.post(
"/main/submissions",
files={"file": ("submission.zip", f, "application/zip")},
data={"docker_image": "python-3.14",
"requirements": "pyscipopt==5.5.0"},
)
if r.is_error:
raise SystemExit(r.json()["error"])
submission_id = r.json()["id"]
{
"id": "3f6c2d1e-8a4b-4c0e-9f7a-2b5d8e1c4a90",
"status": "pending",
"missing_problems": [],
"docker_image": "python-3.14",
"progress": {
"ahead": 2,
"instances_done": null,
"instances_total": null,
"results_in_minutes": 14
}
}
{
"error": "The archive has no solver for barnhart2000. Confirm that those problems may score 0 to submit anyway."
}
{
"error": "Your team has already submitted today. You can submit again after 00:00 UTC."
}
Get a submission
/ submissions/ {id}Token · team memberOne of your team's submissions with its status and results. Poll it every few seconds until status is completed or failed.
Path parameters
idstring · uuidrequired
Responses
import time
while True:
s = api.get(f"/submissions/{submission_id}").json()
if s["status"] in ("completed", "failed"):
break
if s["progress"]:
print(f"{s['status']}: results in about {s['progress']['results_in_minutes']} min")
time.sleep(10)
print(s["score"], s["error"])
{
"id": "3f6c2d1e-8a4b-4c0e-9f7a-2b5d8e1c4a90",
"track": "main",
"status": "completed",
"score": 0.8123,
"error": null,
"created_at": "2027-01-13T09:14:03.512Z",
"updated_at": "2027-01-13T09:31:47.020Z",
"progress": null,
"environment": {
"image": "python-3.14",
"requirements": "pyscipopt==5.5.0",
"status": "ready",
"installed": "absl-py==2.5.0\n…",
"build_log": null
},
"problems": [
{
"slug": "barnhart2000",
"name": "Origin-Destination Integer Multicommodity Flow (ODIMCF)",
"score": 0.8123,
"instances": [
{ "name": "large_instance_1", "set": "private",
"score": 0.8123, "error": null },
{ "name": "tiny_instance", "set": "public",
"score": 1.0, "error": null }
]
}
]
}
{ "error": "Your team has no submission with this id." }