---
title: "Read a bounded slice of a job's output log"
method: GET
path: "/api/v1/jobs/{id}/logs"
---

# Read a bounded slice of a job's output log

`GET /api/v1/jobs/{id}/logs`

## Request

- Base URL: `https://aicommander.dev`
- URL: `https://aicommander.dev/api/v1/jobs/{id}/logs`
- Auth: one of:
  - none
  - HTTP bearer

Returns the job's output — stdout and stderr interleaved exactly as a terminal would show them. With no range parameters you get the TAIL of the log (default 200 lines), which is what you want for 'how is it going?'. To follow a growing log without re-reading it, feed the previous reply's `nextOffsetBytes` back as `offset_bytes`. Every reply is capped at 262144 bytes (256 KiB) decoded, so a huge log is paged rather than dumped; `chunk` is base64 because process output is arbitrary bytes, not necessarily valid UTF-8. `eof: true` means you have read to the current end of the file, NOT that the job finished. Unlike /api/v1/exec the job is never killed for producing too much output; `truncated: true` means the 256 MiB log file itself hit its cap and the agent stopped recording while the job kept running.

## Path parameters

- `id` string, required

## Query parameters

- `code` string, required
- `tail_lines` integer
- `offset_bytes` integer
- `max_bytes` integer

## Response `200`

One slice of the job's output log.

- object
  - `ok` true, required
  - `logs` JobLogs, required: One bounded slice of a job's output log.
    - `jobId` string, required
    - `chunk` string, required: Base64 of the raw slice (process output is arbitrary bytes). Decoded length is at most 262144.
    - `offsetBytes` integer, required: Byte offset in the log this slice starts at.
    - `nextOffsetBytes` integer, required: Offset to pass as `offset_bytes` next; equals offsetBytes + the decoded chunk length.
    - `eof` boolean, required: True when nextOffsetBytes is the current end of the file — not a statement about whether the job finished.
    - `truncated` boolean, required: True when the log FILE itself is truncated (it hit the 256 MiB cap); the job kept running.

## Other responses

- `400`: Missing/invalid fields. On the job endpoints this is also how the MACHINE reports a request it can never run (`reason: "invalid_request"` — an empty command, a relative `cwd`, a `gpu_index` that names no card): the request itself is at fault, so correct the field the message names rather than retrying it.
- `401`: A token was supplied but is not a valid API key/admin/OAuth token.
- `403`: Resolved but refused. Causes include expired anonymous access; Free/anonymous file transfer; `reason: plan_device_limit`; independent operator block/approval; or `error: reactivation_required`. For plan_device_limit, delete an older saved machine to promote the next or upgrade to Pro. For file transfer, use Pro. Do not conflate either with operator approval.
- `404`: Session code or alias not found (anonymous callers get a unified message that does not confirm existence).
- `429`: Per-IP or global tool-call rate limit exceeded.
- `503`: The session exists but the agent is offline or disconnected mid-command.

---

[API](https://skmtc.dev/aicommander/apis/ai-commander-remote-shell-and-long-running-jobs-api.md) · [All operations](https://skmtc.dev/aicommander/apis/ai-commander-remote-shell-and-long-running-jobs-api/llms.txt) · [OpenAPI document](https://skmtc.dev/aicommander/apis/ai-commander-remote-shell-and-long-running-jobs-api/revisions/a6bb89f779a1?raw)
