> ## Documentation Index
> Fetch the complete documentation index at: https://snapr.seshuk.im/llms.txt
> Use this file to discover all available pages before exploring further.

# Overview

> How to authenticate against the snapr HTTP API, call it with curl, and stream logs over SSE.

snapr exposes a JSON HTTP API — the same one its web UI uses. Everything lives under the `/api/v1` prefix on the address the server listens on (default `0.0.0.0:8080`, see [server configuration](/configuration/server/overview)):

```
http://<host>:8080/api/v1
```

The endpoints in the left sidebar are generated from snapr's OpenAPI document. A running instance serves the live version of that document at `/api/v1/openapi`, so you can always fetch the spec that matches your exact snapr version.

## Authentication

The API uses session cookies backed by signed JWTs (HS256).

Log in with `POST /api/v1/auth/login`:

```bash theme={null}
curl -c cookies.txt http://localhost:8080/api/v1/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"username": "admin", "password": "your-password"}'
```

A successful login returns `{"success": true}` and a `Set-Cookie` header with the `snapr_session` cookie (`HttpOnly`, `Path=/`). Send that cookie with every subsequent request. Wrong credentials return `401`.

Details worth knowing:

* Sessions expire after `server.auth.tokenExpiration` minutes (default 30). When less than 5 minutes remain, any authenticated request gets a refreshed cookie in its response automatically.
* `POST /api/v1/auth/logout` clears the cookie.
* `GET /api/v1/auth/check` reports the current state: `{"authenticated": true|false, "authEnabled": true|false}`. It never requires a session.
* Cookie attributes (`Secure`, `SameSite`, `Domain`) come from `server.auth.cookies` — see [authentication configuration](/configuration/server/auth).

<Warning>
  If `server.secret` is empty, snapr signs sessions with a random per-process key, so every restart invalidates all
  sessions. Set a stable secret in production.
</Warning>

### When auth is disabled

If `server.auth` is absent or `enabled: false`, the session check is skipped entirely: every endpoint works without a cookie, and no login is needed. The auth endpoints stay reachable so clients can detect this mode — `GET /api/v1/auth/check` then returns `{"authenticated": true, "authEnabled": false}`.

## Worked example

<Steps>
  <Step title="Log in and store the cookie">
    ```bash theme={null}
    curl -c cookies.txt http://localhost:8080/api/v1/auth/login \
      -H 'Content-Type: application/json' \
      -d '{"username": "admin", "password": "your-password"}'
    ```

    Skip this step if auth is disabled.
  </Step>

  <Step title="List jobs">
    ```bash theme={null}
    curl -b cookies.txt http://localhost:8080/api/v1/jobs
    ```
  </Step>

  <Step title="Trigger a run">
    ```bash theme={null}
    curl -b cookies.txt -X POST \
      http://localhost:8080/api/v1/jobs/postgres-nightly/run
    ```

    Returns `202 Accepted` with `{"job": "...", "startedAt": "..."}` — the backup runs in the background. You get `403` if `server.permissions.allowManualRun` is off, and `409` if the job is already running. See [permissions](/configuration/server/permissions).
  </Step>

  <Step title="Download a backup">
    First list the snapshots, then download one by its filename (for split snapshots, use the set ID — see [Splitter](/configuration/splitter)):

    ```bash theme={null}
    curl -b cookies.txt \
      http://localhost:8080/api/v1/jobs/postgres-nightly/backups

    curl -b cookies.txt -L -OJ \
      http://localhost:8080/api/v1/jobs/postgres-nightly/backups/postgres-nightly-20260507-030000.tar.gz.enc/download
    ```

    The response is either the archive bytes (`200`) or a `307` redirect to a signed storage URL (S3 signed mode, bunny.net Pull Zone) — pass `-L` so curl follows it.
  </Step>
</Steps>

## Log streaming over SSE

Two endpoints stream logs as Server-Sent Events:

* `GET /api/v1/jobs/{name}/logs/stream` — one job's log
* `GET /api/v1/logs/system/stream` — the system log

Both accept a `tail` query parameter (0–50,000): that many existing lines are replayed first, then the stream follows the log live. Both require a session cookie when auth is enabled.

Every frame is a `message` event (the SSE default — no explicit `event:` name) whose `data:` field is a JSON object with a single key:

```json theme={null}
{ "line": "2026-08-20T03:00:00Z INF Archive encrypted ..." }
```

`line` is the rendered log line and may contain ANSI color codes. Behavior to expect:

* A heartbeat frame with an empty `line` (`{"line": ""}`) is sent every 15 seconds to keep the connection alive.
* If the requested log is disabled in config (`logs.system: false` or `logs.perJob: false`) or the job name is unknown, the stream sends a single message saying so and closes. See [log configuration](/configuration/logs).

```bash theme={null}
curl -b cookies.txt -N \
  'http://localhost:8080/api/v1/jobs/postgres-nightly/logs/stream?tail=50'
```

<Tip>
  `curl -N` disables output buffering so you see lines as they arrive. In JavaScript, a plain `EventSource` works:
  listen for `message` events and parse `event.data` as JSON.
</Tip>

## Endpoint groups

| Group   | Endpoints                                                                                                              |
| ------- | ---------------------------------------------------------------------------------------------------------------------- |
| Auth    | `POST /auth/login`, `POST /auth/logout`, `GET /auth/check`                                                             |
| Jobs    | `GET /jobs`, `GET /jobs/{name}/status`, `GET /jobs/{name}/config`, `POST /jobs/{name}/run`, `POST /jobs/{name}/cancel` |
| Backups | `GET /jobs/{name}/backups`, `GET /jobs/{name}/backups/{filename}/download`                                             |
| Logs    | `GET /logs/system`, `GET /logs/system/stream`, `GET /jobs/{name}/logs`, `GET /jobs/{name}/logs/stream`                 |
| Misc    | `GET /status`, `GET /settings`, `GET /openapi`                                                                         |

All paths above are relative to `/api/v1`. Full request and response schemas are in the generated pages in the sidebar, or live at `/api/v1/openapi` on your instance.
