Models
A model in JAOT is a model project: a first-class, versioned entity your organization owns. You create it blank, from a template, by importing a file, or by forking a marketplace model — then edit its draft, commit versions, and solve it.
The marketplace is a facet of the same entity: publishing a project attaches a public listing to it. There is no separate "catalog model" object to activate — using a marketplace model means forking it into your own project.
Two endpoint families cover this:
/api/v2/models/catalog— the public marketplace catalog (browse, detail, input schema). No authentication required./api/v2/projects— your organization's model projects (create, draft, versions, datasets, solve, publish). Authenticated.
GET /api/v2/models/catalog
Browse the public marketplace. This endpoint is public and does not require authentication.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
category | string | -- | Filter by category (e.g. "logistics", "finance", "combinatorial") |
search | string | -- | Search in model name, display name, and description |
is_official | boolean | -- | Only official (JAOT-published) models |
min_rating | number | -- | Minimum average rating (0--5) |
sort_by | string | popular | popular, newest, or rating |
page | integer | 1 | Page number |
page_size | integer | 20 | Items per page (max 100) |
Response
| Field | Type | Description |
|---|---|---|
items | array | List of published listings |
items[].id | string | Model ID — also the project ID you pass to from-marketplace |
items[].name | string | Machine-readable model name |
items[].display_name | string | Human-readable display name |
items[].description | string | Model description |
items[].category | string | Model category |
items[].tags | array | Searchable tags |
items[].avg_rating | number | Average review rating |
items[].is_official | boolean | Published by JAOT |
total | integer | Total matching models |
page | integer | Current page number |
page_size | integer | Page size |
Examples
import httpx
API_URL = "https://jaot.io/api/v2"
# List all logistics models (no auth needed)
response = httpx.get(
f"{API_URL}/models/catalog",
params={"category": "logistics", "page_size": 10},
)
data = response.json()
for model in data["items"]:
print(f"{model['display_name']}: {model['description']}")GET /api/v2/models/catalog/{model_id}
Get the full detail of a published marketplace model (description, sections, rating, adoption counters). Public.
GET /api/v2/models/catalog/{model_id}/schema
Get the input schema and example input of a generator-backed (official) model — the fields you can customize when forking it with user_input. Public; published models only.
POST /api/v2/projects/from-marketplace/{model_id}
Fork a published marketplace model into a new model project of your organization. This is the API equivalent of the Use in studio button, and the only "use a marketplace model" path.
- A generator-backed (official) model is materialized from your optional
user_input— or from its example input if you send none. - A static community model copies the version its author pinned when publishing.
Either way you get a fresh, fully editable project — born with a v1 commit — that you can modify, version, and solve like any other.
Authentication: Requires API key or JWT token.
Request Body (optional)
| Field | Type | Required | Description |
|---|---|---|---|
user_input | object | No | Custom input for a generator-backed model (see its /schema). Ignored for static models. |
Examples
import httpx
response = httpx.post(
"https://jaot.io/api/v2/projects/from-marketplace/b9c1d2e3-abcd-1234-5678-f0a1b2c3d4e5",
headers={"Authorization": "Bearer ok_live_your_key_here"},
json={"user_input": {"capacity": 80}}, # optional
)
project = response.json()
print(f"Forked into project: {project['id']}")Errors
| HTTP Code | Description |
|---|---|
| 404 | Model not found or not published |
| 422 | The generator could not build a model from this input |
POST /api/v2/projects
Create a new blank model project.
Authentication: Requires API key or JWT token.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Project name |
description | string | No | Project description |
workspace_id | string | No | Workspace to attach the project to |
Returns 201 with the project (id prefixed mp_).
POST /api/v2/projects/from-template/{template_id}
Seed a project from one of the curated templates — the one-click "Use template" path. The template is materialized from its example input and the project is born with a v1 commit.
GET /api/v2/projects
List your organization's model projects (newest-updated first). The list is org-wide; pass mine=true to narrow it to your own.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
status | string | active | active or archived |
workspace_id | string | -- | Filter by workspace |
q | string | -- | Search in name/description |
mine | boolean | false | Only projects created by the current user |
skip | integer | 0 | Pagination offset |
limit | integer | 50 | Page size |
GET /api/v2/projects/{project_id}
Get a single project: metadata, the mutable draft model, and the committed HEAD.
GET /api/v2/projects/{project_id}/stats
Live structural statistics for the draft: variable/constraint counts by type, density, detected problem class (LP, MIP, QP, ...), and the health score.
GET /api/v2/projects/{project_id}/executions
The project's solve runs (newest first). Supports status and limit (max 100). This is the same server-side history the workspace uses to re-attach a running solve.
PATCH /api/v2/projects/{project_id}
Update project metadata (name, description, status).
PUT /api/v2/projects/{project_id}/draft
Replace the draft model. This is the agent/programmatic authoring endpoint: send model_json (an OptimizationProblem), and optionally canvas_json or dsl_source (JModel).
Concurrency is optimistic: pass the draft lock version in the If-Match header (an integer); a stale value returns 409 Conflict so two writers never silently overwrite each other.
import httpx
headers = {"Authorization": "Bearer ok_live_your_key_here"}
project = httpx.get(
"https://jaot.io/api/v2/projects/mp_abc123", headers=headers
).json()
response = httpx.put(
"https://jaot.io/api/v2/projects/mp_abc123/draft",
headers={**headers, "If-Match": str(project["draft_lock_version"])},
json={"model_json": {
"name": "production_plan",
"variables": [{"name": "x", "type": "integer", "lower_bound": 0}],
"constraints": [{"name": "cap", "expression": "x <= 10"}],
"objective": {"sense": "maximize", "expression": "3*x"},
}},
)
print(response.status_code) # 200, or 409 if someone committed in betweenDELETE /api/v2/projects/{project_id}
Archive the project (soft delete, reversible from the trash view). With ?permanent=true the project and its versions are hard-deleted — only allowed once the project is already archived (409 otherwise), so a permanent delete is always a deliberate two-step action.
An archived project is read-only. Every write to it answers 409 — the draft, commits, version restores, datasets, publish, rename and solve alike. Reads keep working, so the trash view can show what is in it. The one write that passes is the restore itself, PATCH {"status": "active"}; do that first and the rest opens up again.
POST /api/v2/projects/{project_id}/publish
Publish the project to the marketplace. Publishing attaches (or updates) the project's public listing and pins your latest committed version — the marketplace never serves your dirty draft. Requires at least one committed version (400 otherwise).
The request body carries the presentation: display_name, description, short_description, category, tags, and optional documentation sections. See Publishing Models for the full flow.
Versions, datasets, and solving
| Concern | Endpoints | Docs |
|---|---|---|
| Commit / list / diff / restore versions | POST /projects/{id}/commit, GET /projects/{id}/versions, GET /projects/{id}/versions/{a}/diff/{b}, POST /projects/{id}/versions/{vid}/restore | Versions API |
| Datasets & scenarios | GET/POST /projects/{id}/datasets, POST /projects/{id}/datasets/import, POST /projects/{id}/datasets/{did}/solve | Datasets & Scenarios |
| Solve the draft or a version | POST /projects/{id}/solve?version_id=&solver_name=&solution_filter=nonzero | Solve API |
| Execution results | GET /api/v2/models/executions/all, GET /api/v2/models/executions/{id} | Executions API |
POST /projects/{id}/solve rides the same async pipeline as POST /solve: it waits briefly for the result and returns the classic OptimizationResult, or 202 with a task envelope (poll_url, ws_url) if the solve is still running. solution_filter=nonzero returns a compact solution that omits near-zero variables (variables_omitted reports the count); the stored execution always keeps the full solution.
Its options go in the query string, not the body.
POST /solvetakessolver_nameinside the JSON problem; this endpoint takes it as a query parameter, because the model comes from the project and there is no request body at all. A body sent here is ignored without an error, so{"solver_name": "glpk"}silently gets you the default solver. Checksolver_usedin the response if you are not sure which one ran.