---
title: "Get Deploy Log"
method: GET
path: "/apps/{app_id}/channels/{channel_id}/logs"
---

# Get Deploy Log

`GET /apps/{app_id}/channels/{channel_id}/logs`

## Request

- Base URL: `https://api.xhostd.com` (the document declares no server; this is the origin it was published from)
- URL: `https://api.xhostd.com/apps/{app_id}/channels/{channel_id}/logs`
- Auth: none declared

Return one deploy's status plus a byte window of its stored log.

The log bytes come from the deploy's ``deploy_log_chunks`` rows —
contiguous, line-aligned slices with stored ``start_offset`` /
``byte_len`` byte accounting, so every window figure is byte math
over stored lengths, never ``len(str)`` over decoded text.

With no ``offset`` the window is the LAST ``max_bytes`` bytes of the
log, advanced past the first newline inside the window so the log
starts on a whole line. The snap runs only when at least one byte
follows that newline; when the newline ends the window, the raw
window is returned unsnapped so the reply is never emptied. An
explicit ``offset`` reads byte-exactly from that offset with no line
snapping; an offset past the end of the log clamps to the log size
and yields an empty log. ``window_bytes`` is the byte length of the
returned window before utf-8 decoding: the next page starts at
``offset + window_bytes``, and ``offset + window_bytes ==
log_bytes`` means the reply reaches the end of the log. A window
boundary can still split a multibyte character (``errors="replace"``
covers the decode), exactly as the file implementation did. A deploy
with no chunks yet (a queued deploy) answers 200 with an empty log
and ``log_bytes=0`` — 404 stays for a missing or foreign deploy row
only. A ``superseded`` deploy answers 200 too, with
``superseded_by`` naming its successor: its log is empty when a newer
request replaced it in the queue, or partial plus one marker line
when a planned stop superseded it mid-run. A reader paging a deploy
whose chunks the sweeper prunes
mid-pagination sees the remaining pages empty; the race is accepted,
not defended — the log was already past retention.

The route only reads, so it runs on ``run_read_in_thread``, and the
whole answer costs one round trip: the access check, the deploy row,
the log-size lookup, and one indexed range query over the chunks
overlapping the window all run inside the same ``_load`` DB hop.

## Path parameters

- `app_id` string, uuid, required
- `channel_id` string, uuid, required

## Query parameters

- `deploy` string, uuid, required
- `offset` integer, nullable
- `max_bytes` integer

## Headers

- `authorization` string, nullable

## Response `200`

Successful Response

- DeployLogResponse: One deploy's status plus a byte window of its build log.
  - `deploy_id` string, uuid, required
  - `git_sha` string, required
  - `status` string, required
  - `superseded_by` string, uuid, nullable, required
  - `started_at` string, date-time, required
  - `finished_at` string, date-time, nullable, required
  - `log_bytes` integer, required
  - `offset` integer, required
  - `window_bytes` integer, required
  - `log` string, required

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/xhostd/apis/xhostd-public.md) · [All operations](https://skmtc.dev/xhostd/apis/xhostd-public/llms.txt) · [OpenAPI document](https://skmtc.dev/xhostd/apis/xhostd-public/revisions/bf439c4d1789?raw)
