| name | Lookout API Documentation |
|---|---|
| description | Full API docs for the Lookout (formerly Collapse) timelapse recording service — endpoints, auth, rate limits, session states, upload flow |
| type | reference |
Framework: Fastify v5
Base URL: https://lookout.hackclub.com (configurable via PORT and BASE_URL env vars)
Public endpoints use a 64-character hex session token as a path parameter. No header-based auth required.
Internal endpoints require the X-API-Key header matching the INTERNAL_API_KEY environment variable. Uses constant-time comparison.
In-memory sliding window (60-second windows). Rate-limited responses return:
- Status:
429 Too Many Requests - Header:
Retry-After: <seconds> - Body:
{ "error": "Rate limit exceeded" }
| Endpoint | Limit | Key |
|---|---|---|
GET /api/sessions/:token |
60 req/min | per token |
GET /api/sessions/:token/upload-url |
3 req/min (configurable) | per session ID |
POST /api/sessions/:token/screenshots |
10 req/min | per token |
POST /api/sessions/:token/pause |
10 req/min | per token |
POST /api/sessions/:token/resume |
10 req/min | per token |
POST /api/sessions/:token/stop |
10 req/min | per token |
GET /api/sessions/:token/video |
30 req/min | per token |
GET /api/sessions/:token/thumbnail |
30 req/min | per token |
POST /api/sessions/batch |
30 req/min | per IP |
pending → active → paused → active → stopped → compiling → complete
↘ ↗
stopped ──────
↘ failed
Valid states: pending, active, paused, stopped, compiling, complete, failed
State transitions use optimistic locking — concurrent state changes return 409 Conflict.
- Signed URLs (video, thumbnail) expire after 1 hour (
X-Amz-Expires=3600) - Presigned upload URLs expire after 2 minutes
- Batch endpoint accepts max 100 tokens, returns sessions sorted by creation date (newest first)
- Batch response includes
thumbnailUrlandvideoUrlas signed URLs (same 1hr expiry) - Get Session (
GET /api/sessions/:token) also returns signedthumbnailUrlandvideoUrl - Dedicated endpoints
GET /api/sessions/:token/videoandGET /api/sessions/:token/thumbnailreturn fresh signed URLs on demand - The
namefield is set viaPOST /api/sessions/:token/stopbody orPOST /api/internal/sessionsbody
GET /api/sessions/:token → { status, name, trackedSeconds, screenshotCount, startedAt, totalActiveSeconds, createdAt, thumbnailUrl, videoUrl, videoWebmUrl, metadata }
GET /api/sessions/:token/upload-url → { uploadUrl, r2Key, screenshotId, minuteBucket, nextExpectedAt }
POST /api/sessions/:token/screenshots body: { screenshotId, width, height, fileSize } → { confirmed, trackedSeconds, nextExpectedAt }
POST /api/sessions/:token/pause → { status, totalActiveSeconds }
POST /api/sessions/:token/resume → { status, nextExpectedAt }
POST /api/sessions/:token/stop → { status, trackedSeconds, totalActiveSeconds }
GET /api/sessions/:token/status → { status, videoUrl, videoWebmUrl, trackedSeconds }
GET /api/sessions/:token/video[?format=webm] → { videoUrl } (1hr signed URL, only when complete)
GET /api/sessions/:token/thumbnail → { thumbnailUrl } (1hr signed URL)
POST /api/sessions/batch body: { tokens: [...] } → { sessions: [{ token, name, status, trackedSeconds, screenshotCount, startedAt, createdAt, totalActiveSeconds, thumbnailUrl, videoUrl, videoWebmUrl, metadata }] }
POST /api/internal/sessions body: { name?, metadata? } → { token, sessionId, sessionUrl }
GET /api/internal/sessions/:sessionId → full session object with internal fields
POST /api/internal/sessions/:sessionId/stop → { status }
POST /api/internal/sessions/:sessionId/recompile → { status }
- Create session —
POST /api/internal/sessions(server-side) - Get upload URL —
GET /api/sessions/:token/upload-url - Upload JPEG —
PUT <uploadUrl>withContent-Type: image/jpeg(direct to R2) - Confirm upload —
POST /api/sessions/:token/screenshots - Repeat 2-4 every 60 seconds
- Stop session —
POST /api/sessions/:token/stop - Poll status —
GET /api/sessions/:token/statusuntilcomplete - Get video —
GET /api/sessions/:token/video
| Job | Schedule | Description |
|---|---|---|
compile-timelapse |
On demand | Compiles screenshots into MP4 + WebM. Retries 3x with backoff. |
check-timeouts |
Every 1 min | Auto-pauses idle >5 min, auto-stops idle >30 min, resets stuck compilations >60 min. |
cleanup-unconfirmed |
Every 5 min | Deletes unconfirmed screenshots older than 10 min. |