# API reference

> Every endpoint a Main-track team can call, with its request, response and errors.

Source: <https://frontieror-challenge.com/docs/main/api>

Download problems and submit from a script. The [starter repository](https://frontieror-challenge.com/docs/main/example-code) 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](https://frontieror-challenge.com/docs/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](https://www.python-httpx.org), which the starter uses too. Create the client once:

`client.py`

```python
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

`GET /me` · Token

Who the token belongs to, their team, the current stage and round, and the submissions left today. Use it to check a token.

**Responses**

**200 OK** (application/json)

- `user` (object)
  - `id` (string)
  - `name` (string)
  - `email` (string)
- `team` (object · null): `null` until you join or create a team.
  - `id` (string)
  - `name` (string)
  - `track` (enum): `main` or `self-hosted`
- `stage` (object · null): The running stage, else the next one, else the last one.
  - `id` (string)
  - `name` (string)
  - `status` (enum): `upcoming`, `active` or `ended`.
  - `start_date` (string · date-time)
  - `end_date` (string · date-time)
- `round` (object · null): The current round of the stage: the open one, else the next one, else the last one. `null` if the stage has no rounds.
  - `number` (integer): 1-based.
  - `of` (integer): How many rounds the stage has.
  - `status` (enum): `upcoming`, `open` or `closed`. You can submit only while it is `open`.
  - `start_date` (string · date-time)
  - `end_date` (string · date-time)
  - `self_hosted_window_s` (integer · null): How long a Self-hosted submission in this round stays open, in seconds. See [Submission flow](https://frontieror-challenge.com/docs/self-hosted/submission-flow#1-open-a-submission). `null` until the round opens.
- `submissions_left_today` (integer · null): Resets at 00:00 UTC. `null` without a team or a stage.

**Python example**

```python
me = api.get("/me").json()
print(me["team"], me["submissions_left_today"])
```

**Example response: 200 OK**

```json
{
  "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
}
```

**Example response: 401 Unauthorized**

```json
{ "error": "Missing or invalid API token." }
```

## List problems

`GET /problems` · Token

The 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**

- `details` (flag, optional): Add `?details=1` to include each problem's description, schemas and checker, as in [Get a problem](#get-a-problem).

**Responses**

**200 OK** (application/json)

An array of problems, empty before the round opens.

- `slug` (string)
- `name` (string)
- `category` (enum): The leaderboard category: `routing`, `production_inventory`, `scheduling`, `packing_partitioning`, `graph_quadratic` or
  `network_design_location`
- `formulation_type` (enum): The leaderboard formulation: `combinatorial`, `mixed_integer`, `quadratic`, `stochastic` or `robust_bilevel`
- `objective` (enum): `minimize` or `maximize`

**Python example**

```python
problems = api.get("/problems").json()
slugs = [p["slug"] for p in problems]
```

**Example response: 200 OK**

```json
[
  {
    "slug": "barnhart2000",
    "name": "Origin-Destination Integer Multicommodity Flow (ODIMCF)",
    "category": "network_design_location",
    "formulation_type": "combinatorial",
    "objective": "minimize"
  }
]
```

## Get a problem

`GET /problems/{slug}` · Token

One problem of the current stage with its statement and schemas: the content of its problem page.

**Path parameters**

- `slug` (string, required)

**Responses**

**200 OK** (application/json)

- `slug` (string)
- `name` (string)
- `category` (enum): The leaderboard category: `routing`, `production_inventory`, `scheduling`, `packing_partitioning`, `graph_quadratic` or
  `network_design_location`
- `formulation_type` (enum): The leaderboard formulation: `combinatorial`, `mixed_integer`, `quadratic`, `stochastic` or `robust_bilevel`
- `objective` (enum): `minimize` or `maximize`
- `description` (string): The statement in Markdown, with TeX math.
- `instance_schema` (object): An annotated example of an instance file. See [Reading the schemas](https://frontieror-challenge.com/docs/problems#reading-the-schemas).
- `solution_schema` (object): An annotated example of a solution file.
- `solution_json_schema` (object): The solution schema as a [JSON Schema](https://json-schema.org), to validate solutions with any JSON Schema library. It
  checks the format only; feasibility is up to the checker.
- `checker` (string · null): The source of the checker our evaluation runs, `feasibility_check.py`. `null` when the problem's checker is not
  published. See [Checkers](https://frontieror-challenge.com/docs/problems#checkers).

**404 Not found**

No problem with this slug in the current stage.

**Python example**

```python
problem = api.get("/problems/barnhart2000").json()
print(problem["solution_schema"])
```

**Example response: 200 OK**

```json
{
  "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": "…"
}
```

**Example response: 404 Not found**

```json
{ "error": "No problem with this slug in the current stage." }
```

## List public instances

`GET /problems/{slug}/instances` · Token

The problem's public instances, for development. Private instances are never listed.

**Path parameters**

- `slug` (string, required)

**Responses**

**200 OK** (application/json)

An array of instances, ordered by name.

- `id` (string · uuid)
- `name` (string)
- `time_limit_s` (integer): Seconds a solver gets for this instance.

**404 Not found**

No problem with this slug in the current stage.

**Python example**

```python
instances = api.get("/problems/barnhart2000/instances").json()
```

**Example response: 200 OK**

```json
[
  { "id": "0f1e8c2a-…", "name": "tiny_instance", "time_limit_s": 60 }
]
```

## Download a public instance

`GET /problems/{slug}/instances/{id}` · Token

The instance file itself, in the problem's instance schema, sent as an attachment named `<slug>-<name>.json`.

**Path parameters**

- `slug` (string, required)
- `id` (string · uuid, required): From [List public instances](#list-public-instances).

**Responses**

**200 OK** (application/json)

The instance file.

**404 Not found**

This problem has no public instance with this id.

**Python example**

```python
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

`GET /images` · Token

Our images with their packages and full lock files, and the limits on extra packages. See [Environment](https://frontieror-challenge.com/docs/main/submission-format#environment).

**Responses**

**200 OK** (application/json)

- `default_image` (string): Used when a submission names no image: `python-3.12`.
- `images` (object\[])
  - `id` (string): The value for `docker_image`, such as `python-3.14`.
  - `label` (string)
  - `python_version` (string)
  - `pull` (string · null): The image to `docker pull`, to run solvers locally in the same environment.
  - `packages` (string\[]): The packages installed on purpose.
  - `lock` (string): Every installed package with its exact version, in requirements format.
- `requirements` (object)
  - `max_packages` (integer): 50
  - `max_bytes` (integer): 10240

**Python example**

```python
images = api.get("/images").json()
for image in images["images"]:
    print(image["id"], image["pull"])
```

**Example response: 200 OK**

```json
{
  "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

`POST /main/submissions` · Token · Main team

Checks 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](#get-a-submission).

**Body (multipart/form-data)**

- `file` (binary, required): The archive: zip, tar or tar.gz. The whole body is at most 4 MB. See [Submission format](https://frontieror-challenge.com/docs/main/submission-format).
- `docker_image` (string, optional): `python-3.12` (default) or `python-3.14`.
- `requirements` (string · binary, optional): Extra 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_missing` (flag, optional): Required when the archive has no folder for some problems. Those problems score 0.

**Responses**

**201 Created** (application/json)

- `id` (string · uuid)
- `status` (enum): Always `pending` here.
- `missing_problems` (string\[]): Slugs of the problems without a folder.
- `docker_image` (string): The image it runs in.
- `progress` (object): Where it is in the queue and when its results should arrive. See [Get a submission](#get-a-submission).

**400 Bad request**

The body is empty or cannot be read, or the form has no `file` part.

**403 Forbidden**

You have no team, or your team is on the Self-hosted track.

**409 Conflict**

The submission was refused, and the message says why:

- no round is open;
- the file is not a zip or tar archive, is damaged, or holds a link or an unsafe path;
- no problem has a solver, or some are missing without `acknowledge_missing=1`;
- the image or the extra packages are not valid.

**413 Payload too large**

The body is larger than 4 MB.

**429 Too many requests**

Your team has used today's submissions: one per UTC day, 4 during the testing stage. You can submit again after 00:00 UTC.

**Python example**

```python
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"]
```

**Example response: 201 Created**

```json
{
  "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
  }
}
```

**Example response: 409 Conflict**

```json
{
  "error": "The archive has no solver for barnhart2000. Confirm that those problems may score 0 to submit anyway."
}
```

**Example response: 429 Too many requests**

```json
{
  "error": "Your team has already submitted today. You can submit again after 00:00 UTC."
}
```

## Get a submission

`GET /submissions/{id}` · Token · team member

One of your team's submissions with its status and results. Poll it every few seconds until `status` is `completed` or `failed`.

**Path parameters**

- `id` (string · uuid, required)

**Responses**

**200 OK** (application/json)

- `id` (string · uuid)
- `track` (enum): `main`
- `status` (enum): `pending` (queued), `running`, `completed` or `failed`. See [Statuses](https://frontieror-challenge.com/docs/main/submission-flow#statuses).
- `score` (number · null): The submission's score once `completed`.
- `error` (string · null): Why a `failed` submission could not be evaluated at all.
- `created_at` (string · date-time)
- `updated_at` (string · date-time)
- `progress` (object · null): While the submission is `pending` or `running`, where it is and when its results should arrive; `null` otherwise.
  - `ahead` (integer): Submissions queued before this one.
  - `instances_done` (integer · null): Instances evaluated so far, once evaluation has started.
  - `instances_total` (integer · null): Instances to evaluate, once evaluation has started.
  - `results_in_minutes` (integer): Minutes until the results should arrive, rounded up. Every instance is counted at its full time limit, so results usually come sooner.
- `environment` (object)
  - `image` (string)
  - `requirements` (string · null)
  - `status` (enum · null): `building`, `ready` or `failed` when you listed extra packages.
  - `installed` (string · null): The exact installed versions, once `ready`.
  - `build_log` (string · null): Why the install `failed`.
- `problems` (object\[])
  - `slug` (string)
  - `name` (string)
  - `score` (number · null): The problem's score: the mean over its private instances.
  - `instances` (object\[])
    - `name` (string)
    - `set` (enum): `public` or `private`. Public instances run first, as a check, and don't count towards the score.
    - `score` (number · null)
    - `error` (string · null): Why the instance scored 0, such as “Time limit exceeded (60 s)”. `null` for a feasible solution. See [what can happen to an instance](https://frontieror-challenge.com/docs/main/scoring#what-can-happen-to-an-instance).

**404 Not found**

Your team has no submission with this id.

**Python example**

```python
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"])
```

**Example response: 200 OK**

```json
{
  "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 }
      ]
    }
  ]
}
```

**Example response: 404 Not found**

```json
{ "error": "Your team has no submission with this id." }
```
