# Read job log lines (/api/logs/get-logs)

`GET /api/logs/{id}`

Ingested job-log lines for a completed attempt (sling logs), filtered SERVER-SIDE so an agent reads only what matters: grep (RE2), since (the log tail), job (narrow a run to one job). A run id reads all its jobs; a job/attempt id reads one attempt. Keyset-paginated via an opaque cursor. Authenticated; a run/job in another org 404s (opaque).

Base URL: `https://runners.starsling.dev`

## Authentication

Requires a bearer token (`Authorization: Bearer`).

## Parameters

| Name | In | Type | Required | Notes |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Prefixed log target: run_<id>, job_<id>, or att_<id>.<n>. A malformed shape is a 400. e.g. `run_1234567890` |
| `org` | query | string | yes | GitHub organization login. Scopes the request to your membership. e.g. `acme` |
| `job` | query | string | no | Narrow a run to one job, by job name. e.g. `check` |
| `grep` | query | string | no | RE2 regular expression. Only matching lines are returned. e.g. `(?i)error` |
| `since` | query | string | no | Only the last N of the log, e.g. 5m or 2h. e.g. `5m` |
| `timestamps` | query | boolean | no | Include a per-line timestamp. Off by default. |
| `cursor` | query | string | no | Opaque keyset cursor from a prior page's `next_cursor`. |
| `limit` | query | string \| integer | no | Lines per page. Page with `cursor`. 1–5000; e.g. `1000` |

## Responses

| Status | Description |
| --- | --- |
| `200` | One page of log lines, in order. `next_cursor` is present exactly when there is another page. |
| `400` | The target id isn't a `run_`/`job_`/`att_` shape, `since` isn't a duration, the cursor is stale, or `grep` isn't a compilable RE2 expression. |
| `401` | No credential, or a GitHub grant too old to read org membership — re-run `sling login`. |
| `403` | You are not a member of the org named in `?org=`. |
| `404` | No such run or job in that org. A target in an org you can't see answers the same way — existence is never confirmed across a tenant boundary. |
| `422` | A path or query parameter failed schema validation. `details` names each offending field. |
| `500` | The control plane failed, or the log store is not configured for this deployment. |

### `200` body

- `lines` — array<object>, required
  - array of object
    - `job_id` — string, required
    - `job_name` — string, required
    - `line_number` — string | integer, required
    - `timestamp` — string
    - `log_data` — string, required
- `has_more` — boolean, required
- `next_cursor` — string

### Example

```json
{
  "lines": [
    {
      "job_id": "85933007091",
      "job_name": "typecheck",
      "line_number": 812,
      "timestamp": "2026-07-22T09:15:40.118Z",
      "log_data": "src/index.ts(42,7): error TS2322: Type 'string' is not assignable to type 'number'."
    },
    {
      "job_id": "85933007091",
      "job_name": "typecheck",
      "line_number": 813,
      "timestamp": "2026-07-22T09:15:40.119Z",
      "log_data": "##[error]Process completed with exit code 2."
    }
  ],
  "has_more": true,
  "next_cursor": "eyJqb2JfaWQiOiI4NTkzMzAwNzA5MSIsImxpbmUiOjgxM30"
}
```