AI runs
A run executes an app: you supply the app's input fields, the API queues the work, and the model fills in the answer. Runs are asynchronous: creating one returns at once with the status new, and you read the result back once it has finished.
Endpoints
| Method | Path | Scope |
|---|---|---|
| POST | /v1/ai/run/create | reasoning:write |
| GET | /v1/ai/run/list | reasoning:read |
| GET | /v1/ai/run/detail/<run_id> | reasoning:read |
<run_id> is a run identifier, prefixed runi_.
Note — The path segment is
runbut the scope isreasoning:readandreasoning:write, and the object is areasoning. A key scopedrun:*cannot call these endpoints. Usereasoning:*, or*.
The reasoning object
| Field | Type | Description |
|---|---|---|
id | string | The run identifier, prefixed runi_. |
object | string | Always reasoning. |
app_id | string | The app that was run. |
model | object | The model the run used, or null. |
data | object | The inputs the run was launched with, keyed by field name. |
instruction | object | The instructions that were sent, keyed by instruction identifier. |
response | object | The model's answer, keyed by instruction identifier. Empty until the run finishes. |
tokens | object | Token counts as input, output and total, or null when not measured. |
status | string | See the statuses below. |
duration | number | How long the run took, in seconds to two decimals, or null. |
started_at | integer | When the run was picked up, or null. |
finished_at | integer | When the run completed, or null. |
created_at | integer | When the run was queued, in Unix seconds. |
updated_at | integer | Last change, in Unix seconds. |
The model object names the model and its exact version:
| Field | Type | Description |
|---|---|---|
id | string | The model, prefixed modi_. |
model_library | string | The exact version the run used, prefixed mlyi_. |
name | string | The version's name, such as qwen3:4b. |
parameter | integer | The version's size, in billions of parameters. |
Statuses
| Status | Meaning |
|---|---|
new | Queued, not yet picked up. |
pull | Being prepared. |
processing | Running against the model. |
finished | Complete. response is filled in. |
failed | Ended in an error. |
Poll GET /v1/ai/run/detail/<run_id> until the status is finished or failed. Both are final; every other status changes on its own.
Launch a run
POST /v1/ai/run/create names an app and supplies its inputs.
| Parameter | Type | Required | Description |
|---|---|---|---|
app_id | string | Yes | The app to run. Also accepted as app or app_ident. |
data | object | Yes | One entry per input field, keyed by the field's name as returned by AI apps. |
curl -X POST https://api.ultineo.com/v1/ai/run/create \
-H "Authorization: Bearer sk-project-YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"app_id": "mapp_0ceacb46e5b3a99e91b6",
"data": { "message": "My parcel has not arrived and the tracking has not moved for a week." }
}' {
"code": 200,
"title": "OK",
"response": {
"id": "runi_1b7f4e0c96a2d8531fca",
"object": "reasoning",
"data": { "message": "My parcel has not arrived and the tracking has not moved for a week." },
"instruction": { "1": "Classify the message as low, normal or urgent." },
"status": "new",
"created_at": 1789210200
}
} Keep the returned id and poll it. The creation response carries no response field: nothing has run yet.
You can run your own apps, and any activated public app of another project whose identifier you know. An app that is not activated, has no usable model, or is private to another project is refused with 422 and the general message.
| Code | Message | Cause |
|---|---|---|
| 402 | Your reasoning quota is exhausted. Please wait until it resets or upgrade your plan. | The token allowance of the cycle is used up. |
| 400 | Missing required field: app_id. | No app was named. |
| 404 | App not found. | The app does not exist or was deleted. |
| 422 | Missing required field: <name>. | A required input field was absent or empty. |
| 422 | The parameter '<key>' is not recognized. | data holds a key that is not one of the app's fields. |
The quota is checked before anything else, so a project with no allowance left cannot queue work. Read the remaining allowance from AI quota and token usage.
Tip — A field can also be keyed by its
madi_identifier or by its position, counting from1. Keying bynameis the clearest and is what the app's schema gives you.
Send a document or an image
A field whose type is document or image (see AI apps) does not take text: it takes a file already stored in your project's file library. Send the file's identifier, prefixed file_, or its delivery URL. The API does not upload files itself.
| Code | Message | Cause |
|---|---|---|
| 422 | Field '<name>' expects an uploaded file: send its cdn file id (file_<20 hex>) or its delivery URL. | The value was ordinary text. |
| 422 | This project has no CDN scope, so no file can be attached to a reasoning. | Your project has no file library. |
| 422 | The file sent for '<name>' is not in this project's reasoning image library. | The file belongs to another project, was deleted, or sits in another library. The message reads reasoning document library for a document field. |
| 422 | The file sent for '<name>' cannot be read: accepted document formats are pdf, docx, xlsx, csv, txt. | The document is in a format the model cannot read. |
| 422 | The model resolved for this app cannot read images. Choose a model that accepts image input. | The app has an image field but its model has no image input. |
Launch several runs at once
Send a runs list instead of a single run to queue up to 100 runs in one call. Each item has the same shape as a single run.
curl -X POST https://api.ultineo.com/v1/ai/run/create \
-H "Authorization: Bearer sk-project-YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"runs": [
{ "app_id": "mapp_0ceacb46e5b3a99e91b6", "data": { "message": "Where is my order?" } },
{ "app_id": "mapp_0ceacb46e5b3a99e91b6", "data": {} }
]
}' {
"code": 200,
"title": "OK",
"response": {
"object": "list",
"data": [
{
"id": "runi_8d20c5e4a91f7b3a6c05",
"object": "reasoning",
"data": { "message": "Where is my order?" },
"instruction": { "1": "Classify the message as low, normal or urgent." },
"status": "new",
"created_at": 1789210260
}
],
"created": 1,
"failed": 1,
"errors": [
{
"index": 1,
"code": 422,
"title": "Unprocessable entity",
"response": "Missing required field: message."
}
],
"total": 2
}
} A refused item never blocks the others: each refusal is reported in errors with its position in your runs list, counting from 0. The call answers 200 as soon as one run was queued, and 422 only when every item was refused. A runs value that is not a list, is empty or holds more than 100 items is refused as a whole with 422.
Read one run
GET /v1/ai/run/detail/<run_id> returns the whole run, including the answer once it exists.
curl https://api.ultineo.com/v1/ai/run/detail/runi_1b7f4e0c96a2d8531fca \
-H "Authorization: Bearer sk-project-YOUR_KEY" {
"code": 200,
"title": "OK",
"response": {
"id": "runi_1b7f4e0c96a2d8531fca",
"object": "reasoning",
"app_id": "mapp_0ceacb46e5b3a99e91b6",
"model": {
"id": "modi_c2b7e9a1d3f0a8645c8e",
"model_library": "mlyi_c2a1e7d9b4f30685c3a3",
"name": "qwen3:4b",
"parameter": 4
},
"data": { "message": "My parcel has not arrived and the tracking has not moved for a week." },
"instruction": {
"miid_9a4c07e5b312df8641ca": "Classify the message as low, normal or urgent."
},
"response": {
"miid_9a4c07e5b312df8641ca": "urgent"
},
"tokens": { "input": 412, "output": 3, "total": 415 },
"status": "finished",
"duration": 1.84,
"started_at": 1789210202,
"finished_at": 1789210204,
"created_at": 1789210200,
"updated_at": 1789210204
}
} instruction and response share their keys, so you can pair each answer with the instruction that produced it. A run of another project gives 404 and Reasoning not found.
List runs
GET /v1/ai/run/list returns your project's runs, newest first.
| Parameter | Type | Description |
|---|---|---|
app_id | string | Return only the runs of one app. Also accepted as app or app_ident. |
limit | integer | Rows to return, 1 to 100. Defaults to 50. |
offset | integer | Rows to skip. Defaults to 0. |
curl "https://api.ultineo.com/v1/ai/run/list?app_id=mapp_0ceacb46e5b3a99e91b6&limit=2" \
-H "Authorization: Bearer sk-project-YOUR_KEY" {
"code": 200,
"title": "OK",
"response": {
"object": "list",
"data": [
{
"id": "runi_1b7f4e0c96a2d8531fca",
"object": "reasoning",
"app_id": "mapp_0ceacb46e5b3a99e91b6",
"status": "finished",
"duration": 1.84,
"started_at": 1789210202,
"finished_at": 1789210204,
"created_at": 1789210200,
"updated_at": 1789210204,
"tokens": { "input": 412, "output": 3, "total": 415 }
}
],
"total": 1
}
} The list omits data, instruction, response and model: read a run to get them. Listing never uses your quota, so you can poll a history freely.