curl -X GET \
"https://bouncer.nunu.ai/runs/lkkg6t5612m-1/artifacts/device_log.log" \
-H "X-Api-Key: YOUR_API_TOKEN" \
-o device_log.log
curl -X GET \
"https://bouncer.nunu.ai/runs/lkkg6t5612m-1/artifacts/recording.mp4" \
-H "X-Api-Key: YOUR_API_TOKEN" \
-H "Range: bytes=0-1048575" \
-o recording-part.mp4
const runId = "lkkg6t5612m-1";
const filename = "device_log.log";
const response = await fetch(
`https://bouncer.nunu.ai/runs/${runId}/artifacts/${filename}`,
{
headers: {
"X-Api-Key": process.env.NUNU_API_TOKEN,
},
}
);
if (!response.ok) {
throw new Error(`Failed to download: ${response.status}`);
}
const buffer = await response.arrayBuffer();
import requests
import os
run_id = "lkkg6t5612m-1"
filename = "device_log.log"
response = requests.get(
f"https://bouncer.nunu.ai/runs/{run_id}/artifacts/{filename}",
headers={"X-Api-Key": os.environ["NUNU_API_TOKEN"]},
stream=True,
)
response.raise_for_status()
with open(filename, "wb") as f:
for chunk in response.iter_content(chunk_size=1 << 16):
f.write(chunk)
"<string>"{
"error": "<string>",
"code": "<string>",
"details": {}
}{
"error": "<string>",
"code": "<string>",
"details": {}
}{
"error": "<string>",
"code": "<string>",
"details": {}
}Artifacts
Get Artifact File
Stream a single artifact file produced by a run. The response body is the raw file bytes (not JSON). Range requests, conditional requests via ETag, and presigned redirects for large media files (e.g. .mp4 recordings) are supported.
GET
/
runs
/
{runId}
/
artifacts
/
{filename}
curl -X GET \
"https://bouncer.nunu.ai/runs/lkkg6t5612m-1/artifacts/device_log.log" \
-H "X-Api-Key: YOUR_API_TOKEN" \
-o device_log.log
curl -X GET \
"https://bouncer.nunu.ai/runs/lkkg6t5612m-1/artifacts/recording.mp4" \
-H "X-Api-Key: YOUR_API_TOKEN" \
-H "Range: bytes=0-1048575" \
-o recording-part.mp4
const runId = "lkkg6t5612m-1";
const filename = "device_log.log";
const response = await fetch(
`https://bouncer.nunu.ai/runs/${runId}/artifacts/${filename}`,
{
headers: {
"X-Api-Key": process.env.NUNU_API_TOKEN,
},
}
);
if (!response.ok) {
throw new Error(`Failed to download: ${response.status}`);
}
const buffer = await response.arrayBuffer();
import requests
import os
run_id = "lkkg6t5612m-1"
filename = "device_log.log"
response = requests.get(
f"https://bouncer.nunu.ai/runs/{run_id}/artifacts/{filename}",
headers={"X-Api-Key": os.environ["NUNU_API_TOKEN"]},
stream=True,
)
response.raise_for_status()
with open(filename, "wb") as f:
for chunk in response.iter_content(chunk_size=1 << 16):
f.write(chunk)
"<string>"{
"error": "<string>",
"code": "<string>",
"details": {}
}{
"error": "<string>",
"code": "<string>",
"details": {}
}{
"error": "<string>",
"code": "<string>",
"details": {}
}Permission Required: any API key scoped to the project that owns the run
Artifact endpoints are served by the bouncer service at
https://bouncer.nunu.ai, not the main nunu.ai/api/v1 host. Use the same
X-Api-Key header for authentication.Content-Type and Content-Length headers,
not a JSON envelope.
The endpoint supports HTTP range requests, conditional requests via ETag, and
serves a presigned redirect for large media files (such as .mp4 recordings)
to allow efficient streaming.
Path Parameters
string
required
Composite run ID, in the format
{multiplayerRunId}-{playerNumber} (for
example lkkg6t5612m-1).string
required
Artifact filename, exactly as returned by
List Run Artifacts. May include
subdirectories (for example
deliverables/report.json).Headers
string
Standard HTTP range header. The endpoint returns
206 Partial Content for
satisfiable ranges and 416 Range Not Satisfiable otherwise.string
Send the ETag from a previous response to receive
304 Not Modified when
the file has not changed.Response
The response body is the raw file content. Common status codes:| Status | Meaning |
|---|---|
200 | Full file body |
206 | Partial content (response to a Range request) |
302 | Redirect to a presigned URL (typically for large media files) |
304 | Not modified (when If-None-Match matches) |
404 | Artifact does not exist or is hidden from the requesting key |
416 | Requested range cannot be satisfied |
When following the URL returned by
List Run Artifacts, you typically
do not need to construct this path manually—just GET that URL with your API
key.
curl -X GET \
"https://bouncer.nunu.ai/runs/lkkg6t5612m-1/artifacts/device_log.log" \
-H "X-Api-Key: YOUR_API_TOKEN" \
-o device_log.log
curl -X GET \
"https://bouncer.nunu.ai/runs/lkkg6t5612m-1/artifacts/recording.mp4" \
-H "X-Api-Key: YOUR_API_TOKEN" \
-H "Range: bytes=0-1048575" \
-o recording-part.mp4
const runId = "lkkg6t5612m-1";
const filename = "device_log.log";
const response = await fetch(
`https://bouncer.nunu.ai/runs/${runId}/artifacts/${filename}`,
{
headers: {
"X-Api-Key": process.env.NUNU_API_TOKEN,
},
}
);
if (!response.ok) {
throw new Error(`Failed to download: ${response.status}`);
}
const buffer = await response.arrayBuffer();
import requests
import os
run_id = "lkkg6t5612m-1"
filename = "device_log.log"
response = requests.get(
f"https://bouncer.nunu.ai/runs/{run_id}/artifacts/{filename}",
headers={"X-Api-Key": os.environ["NUNU_API_TOKEN"]},
stream=True,
)
response.raise_for_status()
with open(filename, "wb") as f:
for chunk in response.iter_content(chunk_size=1 << 16):
f.write(chunk)
Authorizations
API key for authentication
Headers
Standard HTTP range header. Returns 206 for satisfiable ranges and 416 otherwise.
Send the ETag from a previous response to receive 304 Not Modified when the file has not changed.
Path Parameters
Composite run ID, in the format {multiplayerRunId}-{playerNumber}.
Artifact filename, exactly as returned by List Run Artifacts. May include subdirectories (e.g. deliverables/report.json).
Response
Full file body. Content-Type is set based on the artifact's stored type.
The response is of type file.