Triggers
Triggers let you fire optimization solves via HTTP requests, authenticated with per-trigger secrets. Each trigger is pinned to a specific model version for reproducible results. Use triggers to integrate JAOT solves into your existing automation pipelines -- CI/CD systems, scheduled jobs, or event-driven architectures.
Overview
A trigger wraps a builder document + version pair behind an HTTP endpoint. When fired, it queues an async solve using the pinned version's model definition, optionally with override data for variable inputs.
Key concepts:
- Per-trigger secret authentication -- each trigger has its own secret, independent of your API key
- Version pinning -- triggers always solve against a specific model version for reproducibility
- Override schema -- define which input fields can be overridden at fire time
- Webhook delivery -- results are POSTed to your webhook URL when the solve completes
Create a trigger
POST /api/v2/triggers
Creates an HTTP event trigger pinned to a specific model version. The plaintext trigger_secret is returned only once in this response -- store it securely.
Authentication: API key (organization scope)
Request body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Human-readable trigger name |
description | string | No | Optional description |
document_id | string | Yes | Builder document to solve |
version_id | string | Yes | Pinned model version |
override_schema | array | No | Fields that can be overridden at fire time |
solver_name | string | No | Solver every run uses (auto, scip, highs, cbc, glpk, jaos). Leave it out to use the solver the pinned version names. An unknown name is refused with 422 |
webhook_url | string | No | URL to receive solve results |
webhook_secret | string | No | Secret for webhook signature verification |
workspace_id | string | No | Workspace scope |
import requests
response = requests.post(
"https://jaot.io/api/v2/triggers",
headers={"Authorization": "Bearer ok_live_..."},
json={
"name": "Daily warehouse restock",
"description": "Runs every morning to optimize warehouse restocking",
"document_id": "doc_abc123",
"version_id": "ver_def456",
"override_schema": [
{"name": "demand_forecast", "type": "array", "required": True}
],
"webhook_url": "https://your-app.com/webhooks/jaot",
"webhook_secret": "whsec_your_signing_secret"
}
)
trigger = response.json()
# Save the trigger_secret -- it will not be shown again
print(f"Trigger ID: {trigger['id']}")
print(f"Secret: {trigger['trigger_secret']}")Response (201 Created)
{
"id": "trg_a1b2c3d4",
"organization_id": "org_b7e4d1a0",
"created_by": "usr_3a9f21c4",
"name": "Daily warehouse restock",
"description": "Runs every morning to optimize warehouse restocking",
"document_id": "doc_abc123",
"version_id": "ver_def456",
"trigger_secret": "a3f8b2c1d4e5f67890abcdef12345678...",
"trigger_secret_prefix": "a3f8b2c1",
"override_schema": [
{"name": "demand_forecast", "type": "array", "required": true}
],
"webhook_url": "https://your-app.com/webhooks/jaot",
"is_enabled": true,
"total_runs": 0,
"created_at": "2026-02-19T08:00:00Z"
}Tip: Pin a specific model version to ensure trigger results are reproducible. If the pinned version is unnamed, it is automatically promoted to a named version to protect it from the retention pruning policy. See the Versions page for version management details.
List triggers
GET /api/v2/triggers
Returns all triggers for your organization, newest first.
Authentication: API key (organization scope)
Query parameters
| Parameter | Type | Description |
|---|---|---|
document_id | string | Filter by builder document |
response = requests.get(
"https://jaot.io/api/v2/triggers",
headers={"Authorization": "Bearer ok_live_..."},
params={"document_id": "doc_abc123"}
)
triggers = response.json()
for t in triggers:
print(f"{t['name']} (enabled={t['is_enabled']}, runs={t['total_runs']})")Response (200)
[
{
"id": "trg_a1b2c3d4",
"name": "Daily warehouse restock",
"document_id": "doc_abc123",
"version_id": "ver_def456",
"trigger_secret_prefix": "a3f8b2c1",
"is_enabled": true,
"total_runs": 47,
"last_fired_at": "2026-02-19T06:00:00Z",
"created_at": "2026-01-15T08:00:00Z"
}
]Get a trigger
GET /api/v2/triggers/{trigger_id}
Returns a single trigger by ID.
Authentication: API key (organization scope)
response = requests.get(
"https://jaot.io/api/v2/triggers/trg_a1b2c3d4",
headers={"Authorization": "Bearer ok_live_..."},
)
trigger = response.json()
print(f"{trigger['name']} (runs={trigger['total_runs']})")Update a trigger
PATCH /api/v2/triggers/{trigger_id}
Partially update a trigger. The version_id is immutable after creation -- create a new trigger if you need to pin a different version.
Authentication: API key (organization scope)
Request body (all fields optional)
| Field | Type | Description |
|---|---|---|
name | string | Updated trigger name |
description | string | Updated description |
override_schema | array | Updated override fields |
solver_name | string or null | Solver every run uses. null goes back to the solver the pinned version names |
webhook_url | string | Updated webhook URL |
webhook_secret | string | Updated webhook secret |
response = requests.patch(
"https://jaot.io/api/v2/triggers/trg_a1b2c3d4",
headers={"Authorization": "Bearer ok_live_...", "Content-Type": "application/json"},
json={"name": "Warehouse restock v2", "webhook_url": "https://new-url.com/webhook"},
)Delete a trigger
DELETE /api/v2/triggers/{trigger_id}
Permanently deletes a trigger and all its run history.
Authentication: API key (organization scope)
response = requests.delete(
"https://jaot.io/api/v2/triggers/trg_a1b2c3d4",
headers={"Authorization": "Bearer ok_live_..."},
)
# Response: 204 No ContentToggle a trigger
POST /api/v2/triggers/{trigger_id}/toggle
Enable or disable a trigger without deleting it. Disabled triggers return 409 Conflict when fired.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
enabled | boolean | Yes | New enabled state |
response = requests.post(
"https://jaot.io/api/v2/triggers/trg_a1b2c3d4/toggle",
headers={"Authorization": "Bearer ok_live_...", "Content-Type": "application/json"},
json={"enabled": False},
)
print(response.json())Fire a trigger
POST /api/v2/triggers/{trigger_id}/fire
Fire a trigger to queue an async solve. Authentication uses the per-trigger secret (not your API key). Provide the secret via the Authorization: Bearer header or the trigger_secret field in the request body.
Authentication: Per-trigger secret
Request body
| Field | Type | Required | Description |
|---|---|---|---|
trigger_secret | string | No | Alternative to Authorization header |
override_data | object | No | Override values for fields defined in override_schema |
A field you leave out of override_data takes its default from the schema, when it has one. A required field with a default does not have to be sent.
The run solves the pinned version, then applies the trigger's solver_name, then the overrides. So a solver named by an override, sent or taken from its default, wins over the trigger's solver.
import requests
# Fire with override data (e.g., today's demand forecast)
response = requests.post(
"https://jaot.io/api/v2/triggers/trg_a1b2c3d4/fire",
headers={"Authorization": "Bearer a3f8b2c1d4e5f6..."},
json={
"override_data": {
"demand_forecast": [120, 85, 200, 150, 90]
}
}
)
result = response.json()
print(f"Run ID: {result['run_id']}, Status: {result['status']}")Response (202 Accepted)
{
"run_id": "run_x1y2z3w4",
"status": "queued"
}Errors
| Status | Description |
|---|---|
| 401 | Missing or invalid trigger secret |
| 404 | Trigger not found |
| 409 | Trigger is disabled |
| 422 | Override validation failed, or the model names a solver this server does not have |
A 422 still records a run with status validation_failed, so the refused call shows in the run history. The body keeps detail as {"error", "run_id"} and adds a code beside it: trigger.override_unknown_fields, trigger.override_missing_required or trigger.solver_unavailable, with the values in params.
Run a trigger now
POST /api/v2/triggers/{trigger_id}/run
Queue one run with the trigger's own settings and override defaults. This is the Run now button on the trigger page. It authenticates with your session or API key, never with the trigger secret, and needs the solver role in the trigger's workspace. It counts against the same per-trigger limit as /fire.
A refused input answers 422 with the same code as /fire, and records no run.
Authentication: API key (organization scope)
Response (202 Accepted)
{
"run_id": "trun_x1y2z3w4",
"status": "pending"
}List trigger runs
GET /api/v2/triggers/{trigger_id}/runs
Returns paginated run history for a trigger, newest first.
Authentication: API key (organization scope)
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | int | 1 | Page number |
page_size | int | 20 | Items per page (max 100) |
response = requests.get(
"https://jaot.io/api/v2/triggers/trg_a1b2c3d4/runs",
headers={"Authorization": "Bearer ok_live_..."},
params={"page": 1, "page_size": 10},
)
runs = response.json()
for run in runs["items"]:
print(f"{run['id']}: {run['status']}")Response (200)
{
"items": [
{
"id": "run_x1y2z3w4",
"trigger_id": "trg_a1b2c3d4",
"status": "completed",
"override_data": {"demand_forecast": [120, 85, 200]},
"result_data": {"objective_value": 4250.0, "status": "optimal"},
"created_at": "2026-02-19T06:00:00Z",
"completed_at": "2026-02-19T06:00:15Z"
}
],
"total": 47,
"page": 1,
"page_size": 10
}Get a single run
GET /api/v2/triggers/{trigger_id}/runs/{run_id}
Returns full details for a run including result data and override data.
Authentication: API key (organization scope)
Rerun a previous run
POST /api/v2/triggers/{trigger_id}/runs/{run_id}/rerun
Queue a new run using the override data from a previous run. Authenticated via API key (not trigger secret), since the user is already authenticated.
The input is checked first. If it would be refused again, for example the run was validation_failed and the schema has not changed, the rerun answers 422 with a code and records no new run.
Authentication: API key (organization scope)
response = requests.post(
"https://jaot.io/api/v2/triggers/trg_a1b2c3d4/runs/run_x1y2z3w4/rerun",
headers={"Authorization": "Bearer ok_live_..."},
)
result = response.json()
print(f"New run: {result['run_id']}")Response (202 Accepted)
{
"run_id": "run_new_abc123",
"status": "queued"
}