Tracking external runs¶
Info
Learn the concepts and fundamentals of external runs in the Explanation page.
It is possible to execute runs outside of the Nextmv Cloud environment and just send the results back to the platform. This can be useful if you want to do production runs on your own infrastructure while still using the Nextmv Cloud platform for monitoring and collaboration on non-production models.
You can track an external run by submitting its already-computed result to the
POST /v1/applications/{application_id}/runs endpoint, using
the result field of the request payload.
Consider the following files and directories you can place in a known location that we are going to use as part of the run information.
Place the following files in the inputs directory:
{
"assets": [
{
"name": "Plotly example",
"content": [
{
"data": [
{
"marker": {
"color": "red"
},
"name": "Radius (km)",
"opacity": 0.5,
"x": [
"Patches"
],
"y": [
6378
],
"type": "bar"
},
{
"marker": {
"color": "blue"
},
"name": "Distance (Millions km)",
"opacity": 0.5,
"x": [
"Patches"
],
"y": [
147.6
],
"type": "bar"
}
],
"layout": {
"template": {
"data": {
"histogram2dcontour": [
{
"type": "histogram2dcontour",
"colorbar": {
"outlinewidth": 0,
"ticks": ""
},
"colorscale": [
[
0.0,
"#0d0887"
],
[
0.1111111111111111,
"#46039f"
],
[
0.2222222222222222,
"#7201a8"
],
[
0.3333333333333333,
"#9c179e"
],
[
0.4444444444444444,
"#bd3786"
],
[
0.5555555555555556,
"#d8576b"
],
[
0.6666666666666666,
"#ed7953"
],
[
0.7777777777777778,
"#fb9f3a"
],
[
0.8888888888888888,
"#fdca26"
],
[
1.0,
"#f0f921"
]
]
}
],
"choropleth": [
{
"type": "choropleth",
"colorbar": {
"outlinewidth": 0,
"ticks": ""
}
}
],
"histogram2d": [
{
"type": "histogram2d",
"colorbar": {
"outlinewidth": 0,
"ticks": ""
},
"colorscale": [
[
0.0,
"#0d0887"
],
[
0.1111111111111111,
"#46039f"
],
[
0.2222222222222222,
"#7201a8"
],
[
0.3333333333333333,
"#9c179e"
],
[
0.4444444444444444,
"#bd3786"
],
[
0.5555555555555556,
"#d8576b"
],
[
0.6666666666666666,
"#ed7953"
],
[
0.7777777777777778,
"#fb9f3a"
],
[
0.8888888888888888,
"#fdca26"
],
[
1.0,
"#f0f921"
]
]
}
],
"heatmap": [
{
"type": "heatmap",
"colorbar": {
"outlinewidth": 0,
"ticks": ""
},
"colorscale": [
[
0.0,
"#0d0887"
],
[
0.1111111111111111,
"#46039f"
],
[
0.2222222222222222,
"#7201a8"
],
[
0.3333333333333333,
"#9c179e"
],
[
0.4444444444444444,
"#bd3786"
],
[
0.5555555555555556,
"#d8576b"
],
[
0.6666666666666666,
"#ed7953"
],
[
0.7777777777777778,
"#fb9f3a"
],
[
0.8888888888888888,
"#fdca26"
],
[
1.0,
"#f0f921"
]
]
}
],
"contourcarpet": [
{
"type": "contourcarpet",
"colorbar": {
"outlinewidth": 0,
"ticks": ""
}
}
],
"contour": [
{
"type": "contour",
"colorbar": {
"outlinewidth": 0,
"ticks": ""
},
"colorscale": [
[
0.0,
"#0d0887"
],
[
0.1111111111111111,
"#46039f"
],
[
0.2222222222222222,
"#7201a8"
],
[
0.3333333333333333,
"#9c179e"
],
[
0.4444444444444444,
"#bd3786"
],
[
0.5555555555555556,
"#d8576b"
],
[
0.6666666666666666,
"#ed7953"
],
[
0.7777777777777778,
"#fb9f3a"
],
[
0.8888888888888888,
"#fdca26"
],
[
1.0,
"#f0f921"
]
]
}
],
"surface": [
{
"type": "surface",
"colorbar": {
"outlinewidth": 0,
"ticks": ""
},
"colorscale": [
[
0.0,
"#0d0887"
],
[
0.1111111111111111,
"#46039f"
],
[
0.2222222222222222,
"#7201a8"
],
[
0.3333333333333333,
"#9c179e"
],
[
0.4444444444444444,
"#bd3786"
],
[
0.5555555555555556,
"#d8576b"
],
[
0.6666666666666666,
"#ed7953"
],
[
0.7777777777777778,
"#fb9f3a"
],
[
0.8888888888888888,
"#fdca26"
],
[
1.0,
"#f0f921"
]
]
}
],
"mesh3d": [
{
"type": "mesh3d",
"colorbar": {
"outlinewidth": 0,
"ticks": ""
}
}
],
"scatter": [
{
"fillpattern": {
"fillmode": "overlay",
"size": 10,
"solidity": 0.2
},
"type": "scatter"
}
],
"parcoords": [
{
"type": "parcoords",
"line": {
"colorbar": {
"outlinewidth": 0,
"ticks": ""
}
}
}
],
"scatterpolargl": [
{
"type": "scatterpolargl",
"marker": {
"colorbar": {
"outlinewidth": 0,
"ticks": ""
}
}
}
],
"bar": [
{
"error_x": {
"color": "#2a3f5f"
},
"error_y": {
"color": "#2a3f5f"
},
"marker": {
"line": {
"color": "#E5ECF6",
"width": 0.5
},
"pattern": {
"fillmode": "overlay",
"size": 10,
"solidity": 0.2
}
},
"type": "bar"
}
],
"scattergeo": [
{
"type": "scattergeo",
"marker": {
"colorbar": {
"outlinewidth": 0,
"ticks": ""
}
}
}
],
"scatterpolar": [
{
"type": "scatterpolar",
"marker": {
"colorbar": {
"outlinewidth": 0,
"ticks": ""
}
}
}
],
"histogram": [
{
"marker": {
"pattern": {
"fillmode": "overlay",
"size": 10,
"solidity": 0.2
}
},
"type": "histogram"
}
],
"scattergl": [
{
"type": "scattergl",
"marker": {
"colorbar": {
"outlinewidth": 0,
"ticks": ""
}
}
}
],
"scatter3d": [
{
"type": "scatter3d",
"line": {
"colorbar": {
"outlinewidth": 0,
"ticks": ""
}
},
"marker": {
"colorbar": {
"outlinewidth": 0,
"ticks": ""
}
}
}
],
"scattermap": [
{
"type": "scattermap",
"marker": {
"colorbar": {
"outlinewidth": 0,
"ticks": ""
}
}
}
],
"scattermapbox": [
{
"type": "scattermapbox",
"marker": {
"colorbar": {
"outlinewidth": 0,
"ticks": ""
}
}
}
],
"scatterternary": [
{
"type": "scatterternary",
"marker": {
"colorbar": {
"outlinewidth": 0,
"ticks": ""
}
}
}
],
"scattercarpet": [
{
"type": "scattercarpet",
"marker": {
"colorbar": {
"outlinewidth": 0,
"ticks": ""
}
}
}
],
"carpet": [
{
"aaxis": {
"endlinecolor": "#2a3f5f",
"gridcolor": "white",
"linecolor": "white",
"minorgridcolor": "white",
"startlinecolor": "#2a3f5f"
},
"baxis": {
"endlinecolor": "#2a3f5f",
"gridcolor": "white",
"linecolor": "white",
"minorgridcolor": "white",
"startlinecolor": "#2a3f5f"
},
"type": "carpet"
}
],
"table": [
{
"cells": {
"fill": {
"color": "#EBF0F8"
},
"line": {
"color": "white"
}
},
"header": {
"fill": {
"color": "#C8D4E3"
},
"line": {
"color": "white"
}
},
"type": "table"
}
],
"barpolar": [
{
"marker": {
"line": {
"color": "#E5ECF6",
"width": 0.5
},
"pattern": {
"fillmode": "overlay",
"size": 10,
"solidity": 0.2
}
},
"type": "barpolar"
}
],
"pie": [
{
"automargin": true,
"type": "pie"
}
]
},
"layout": {
"autotypenumbers": "strict",
"colorway": [
"#636efa",
"#EF553B",
"#00cc96",
"#ab63fa",
"#FFA15A",
"#19d3f3",
"#FF6692",
"#B6E880",
"#FF97FF",
"#FECB52"
],
"font": {
"color": "#2a3f5f"
},
"hovermode": "closest",
"hoverlabel": {
"align": "left"
},
"paper_bgcolor": "white",
"plot_bgcolor": "#E5ECF6",
"polar": {
"bgcolor": "#E5ECF6",
"angularaxis": {
"gridcolor": "white",
"linecolor": "white",
"ticks": ""
},
"radialaxis": {
"gridcolor": "white",
"linecolor": "white",
"ticks": ""
}
},
"ternary": {
"bgcolor": "#E5ECF6",
"aaxis": {
"gridcolor": "white",
"linecolor": "white",
"ticks": ""
},
"baxis": {
"gridcolor": "white",
"linecolor": "white",
"ticks": ""
},
"caxis": {
"gridcolor": "white",
"linecolor": "white",
"ticks": ""
}
},
"coloraxis": {
"colorbar": {
"outlinewidth": 0,
"ticks": ""
}
},
"colorscale": {
"sequential": [
[
0.0,
"#0d0887"
],
[
0.1111111111111111,
"#46039f"
],
[
0.2222222222222222,
"#7201a8"
],
[
0.3333333333333333,
"#9c179e"
],
[
0.4444444444444444,
"#bd3786"
],
[
0.5555555555555556,
"#d8576b"
],
[
0.6666666666666666,
"#ed7953"
],
[
0.7777777777777778,
"#fb9f3a"
],
[
0.8888888888888888,
"#fdca26"
],
[
1.0,
"#f0f921"
]
],
"sequentialminus": [
[
0.0,
"#0d0887"
],
[
0.1111111111111111,
"#46039f"
],
[
0.2222222222222222,
"#7201a8"
],
[
0.3333333333333333,
"#9c179e"
],
[
0.4444444444444444,
"#bd3786"
],
[
0.5555555555555556,
"#d8576b"
],
[
0.6666666666666666,
"#ed7953"
],
[
0.7777777777777778,
"#fb9f3a"
],
[
0.8888888888888888,
"#fdca26"
],
[
1.0,
"#f0f921"
]
],
"diverging": [
[
0,
"#8e0152"
],
[
0.1,
"#c51b7d"
],
[
0.2,
"#de77ae"
],
[
0.3,
"#f1b6da"
],
[
0.4,
"#fde0ef"
],
[
0.5,
"#f7f7f7"
],
[
0.6,
"#e6f5d0"
],
[
0.7,
"#b8e186"
],
[
0.8,
"#7fbc41"
],
[
0.9,
"#4d9221"
],
[
1,
"#276419"
]
]
},
"xaxis": {
"gridcolor": "white",
"linecolor": "white",
"ticks": "",
"title": {
"standoff": 15
},
"zerolinecolor": "white",
"automargin": true,
"zerolinewidth": 2
},
"yaxis": {
"gridcolor": "white",
"linecolor": "white",
"ticks": "",
"title": {
"standoff": 15
},
"zerolinecolor": "white",
"automargin": true,
"zerolinewidth": 2
},
"scene": {
"xaxis": {
"backgroundcolor": "#E5ECF6",
"gridcolor": "white",
"linecolor": "white",
"showbackground": true,
"ticks": "",
"zerolinecolor": "white",
"gridwidth": 2
},
"yaxis": {
"backgroundcolor": "#E5ECF6",
"gridcolor": "white",
"linecolor": "white",
"showbackground": true,
"ticks": "",
"zerolinecolor": "white",
"gridwidth": 2
},
"zaxis": {
"backgroundcolor": "#E5ECF6",
"gridcolor": "white",
"linecolor": "white",
"showbackground": true,
"ticks": "",
"zerolinecolor": "white",
"gridwidth": 2
}
},
"shapedefaults": {
"line": {
"color": "#2a3f5f"
}
},
"annotationdefaults": {
"arrowcolor": "#2a3f5f",
"arrowhead": 0,
"arrowwidth": 1
},
"geo": {
"bgcolor": "white",
"landcolor": "#E5ECF6",
"subunitcolor": "white",
"showland": true,
"showlakes": true,
"lakecolor": "white"
},
"title": {
"x": 0.05
},
"mapbox": {
"style": "light"
}
}
},
"title": {
"text": "Radius and Distance by Planet"
},
"xaxis": {
"title": {
"text": "Planet"
}
},
"yaxis": {
"title": {
"text": "Values"
}
},
"barmode": "group"
}
}
],
"content_type": "json",
"visual": {
"schema": "plotly",
"label": "Charts",
"type": "custom-tab"
}
}
]
}
We can track an external run for both content formats: json and
multi-file. In both cases, the input and output are always
submitted through an upload URL. Request one from the
POST /v1/applications/{application_id}/runs/uploadurl
endpoint, upload the file directly to the returned upload_url, and keep
track of the returned upload_id. Repeat this for the logs, metrics, and
assets files as well, since these are also submitted as uploads. Finally,
create the run by passing the input's upload_id, together with a result
object that references the status and the other upload IDs, to the
POST /v1/applications/{application_id}/runs endpoint.
BASE="https://api.cloud.nextmv.io/v1/applications/uncanny-rodent"
get_upload_id() {
curl -s -X POST "${BASE}/runs/uploadurl" \
-H "Authorization: Bearer ${NEXTMV_API_KEY}"
}
INPUT_UPLOAD=$(get_upload_id)
INPUT_UPLOAD_URL=$(echo "$INPUT_UPLOAD" | jq -r '.upload_url')
INPUT_UPLOAD_ID=$(echo "$INPUT_UPLOAD" | jq -r '.upload_id')
curl -s -X PUT "$INPUT_UPLOAD_URL" --data-binary @inputs/input.json
OUTPUT_UPLOAD=$(get_upload_id)
OUTPUT_UPLOAD_URL=$(echo "$OUTPUT_UPLOAD" | jq -r '.upload_url')
OUTPUT_UPLOAD_ID=$(echo "$OUTPUT_UPLOAD" | jq -r '.upload_id')
curl -s -X PUT "$OUTPUT_UPLOAD_URL" --data-binary @outputs/output.json
LOGS_UPLOAD=$(get_upload_id)
LOGS_UPLOAD_URL=$(echo "$LOGS_UPLOAD" | jq -r '.upload_url')
LOGS_UPLOAD_ID=$(echo "$LOGS_UPLOAD" | jq -r '.upload_id')
curl -s -X PUT "$LOGS_UPLOAD_URL" --data-binary @logs.log
METRICS_UPLOAD=$(get_upload_id)
METRICS_UPLOAD_URL=$(echo "$METRICS_UPLOAD" | jq -r '.upload_url')
METRICS_UPLOAD_ID=$(echo "$METRICS_UPLOAD" | jq -r '.upload_id')
curl -s -X PUT "$METRICS_UPLOAD_URL" --data-binary @metrics.json
ASSETS_UPLOAD=$(get_upload_id)
ASSETS_UPLOAD_URL=$(echo "$ASSETS_UPLOAD" | jq -r '.upload_url')
ASSETS_UPLOAD_ID=$(echo "$ASSETS_UPLOAD" | jq -r '.upload_id')
curl -s -X PUT "$ASSETS_UPLOAD_URL" --data-binary @assets.json
curl -s -X POST "${BASE}/runs" \
-H "Authorization: Bearer ${NEXTMV_API_KEY}" \
-H "Content-Type: application/json" \
-d "{
\"upload_id\": \"${INPUT_UPLOAD_ID}\",
\"result\": {
\"status\": \"succeeded\",
\"execution_duration\": 200,
\"output_upload_id\": \"${OUTPUT_UPLOAD_ID}\",
\"error_upload_id\": \"${LOGS_UPLOAD_ID}\",
\"metrics_upload_id\": \"${METRICS_UPLOAD_ID}\",
\"assets_upload_id\": \"${ASSETS_UPLOAD_ID}\"
},
\"configuration\": {
\"format\": {
\"input\": {\"type\": \"json\"},
\"output\": {\"type\": \"json\"}
}
}
}" \
| jq '.'
BASE="https://api.cloud.nextmv.io/v1/applications/uncanny-rodent"
tar -czf inputs.tar.gz -C inputs .
tar -czf outputs.tar.gz -C outputs .
get_upload_id() {
curl -s -X POST "${BASE}/runs/uploadurl" \
-H "Authorization: Bearer ${NEXTMV_API_KEY}"
}
INPUT_UPLOAD=$(get_upload_id)
INPUT_UPLOAD_URL=$(echo "$INPUT_UPLOAD" | jq -r '.upload_url')
INPUT_UPLOAD_ID=$(echo "$INPUT_UPLOAD" | jq -r '.upload_id')
curl -s -X PUT "$INPUT_UPLOAD_URL" --data-binary @inputs.tar.gz
OUTPUT_UPLOAD=$(get_upload_id)
OUTPUT_UPLOAD_URL=$(echo "$OUTPUT_UPLOAD" | jq -r '.upload_url')
OUTPUT_UPLOAD_ID=$(echo "$OUTPUT_UPLOAD" | jq -r '.upload_id')
curl -s -X PUT "$OUTPUT_UPLOAD_URL" --data-binary @outputs.tar.gz
LOGS_UPLOAD=$(get_upload_id)
LOGS_UPLOAD_URL=$(echo "$LOGS_UPLOAD" | jq -r '.upload_url')
LOGS_UPLOAD_ID=$(echo "$LOGS_UPLOAD" | jq -r '.upload_id')
curl -s -X PUT "$LOGS_UPLOAD_URL" --data-binary @logs.log
METRICS_UPLOAD=$(get_upload_id)
METRICS_UPLOAD_URL=$(echo "$METRICS_UPLOAD" | jq -r '.upload_url')
METRICS_UPLOAD_ID=$(echo "$METRICS_UPLOAD" | jq -r '.upload_id')
curl -s -X PUT "$METRICS_UPLOAD_URL" --data-binary @metrics.json
ASSETS_UPLOAD=$(get_upload_id)
ASSETS_UPLOAD_URL=$(echo "$ASSETS_UPLOAD" | jq -r '.upload_url')
ASSETS_UPLOAD_ID=$(echo "$ASSETS_UPLOAD" | jq -r '.upload_id')
curl -s -X PUT "$ASSETS_UPLOAD_URL" --data-binary @assets.json
curl -s -X POST "${BASE}/runs" \
-H "Authorization: Bearer ${NEXTMV_API_KEY}" \
-H "Content-Type: application/json" \
-d "{
\"upload_id\": \"${INPUT_UPLOAD_ID}\",
\"result\": {
\"status\": \"succeeded\",
\"execution_duration\": 200,
\"output_upload_id\": \"${OUTPUT_UPLOAD_ID}\",
\"error_upload_id\": \"${LOGS_UPLOAD_ID}\",
\"metrics_upload_id\": \"${METRICS_UPLOAD_ID}\",
\"assets_upload_id\": \"${ASSETS_UPLOAD_ID}\"
},
\"configuration\": {
\"format\": {
\"input\": {\"type\": \"multi-file\"}
}
}
}" \
| jq '.'
Tip
Because the snippets above submit the input through an upload_id
(obtained from the upload URL endpoint) instead of the input field
directly, the Cloud API cannot infer the content format on its own. Always
set the configuration field explicitly: use json for json runs, and
multi-file for multi-file runs, as shown above.
Once you have the run_id of the external run, you can use it like you would
a normal run, with endpoints such as:
GET /v1/applications/{application_id}/runs/{run_id}/metadataGET /v1/applications/{application_id}/runs/{run_id}GET /v1/applications/{application_id}/runs/{run_id}/logsGET /v1/applications/{application_id}/runs/{run_id}/input
Please note that specifying the execution_duration field is optional. If you
want to track a failed run, you would set the status field to failed and
optionally provide an error message with the error_message field.