Quickstart

Postgres, Redis and MinIO in Docker; three Node processes; an account and a project you create in the browser. About five minutes, most of it pulling images.

1. Clone and install

git clone https://github.com/Adithya-Adi/syncline
cd syncline
pnpm install

The install runs prisma generate, which needs no database. Node 22 or newer, and pnpm — the version is pinned in package.json.

2. Configure

cp .env.example .env

The defaults match the Docker services. Two things to set yourself:BETTER_AUTH_SECRET, which signs session cookies, and nothing else — API keys are created in the app rather than in a file.

node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))"

Note the ports: 5442 for Postgres and 6399 for Redis, not the standard ones. If you already run either natively, the standard port is taken, and on some systems a host connection will silently reach your server instead of the container with nothing to say so.

3. Start the infrastructure

pnpm infra:up
pnpm db:migrate

4. Run it

Three processes, three terminals:

# terminal 1 — ingest and read API on :4000
pnpm nx build api && node apps/api/dist/main.js

# terminal 2 — queue consumers
pnpm nx build worker && node apps/worker/dist/main.js

# terminal 3 — the web app on :3000
pnpm nx dev web

The API logs database connected and bucket "syncline" ready at startup. If it does not, fix that before going further — curl localhost:4000/v1/health names the failing dependency.

5. Create an account

Open http://localhost:3000/sign-up. Registration is open, and every account is provisioned its own organization — a new sign-up sees an empty dashboard, never anyone else’s recordings. To share a project, invite someone into your organization.

6. Create a project

A project owns a pair of API keys and the list of origins allowed to send recordings to it. Create one at /projects/new, listing the origins your app is served from.

  • The public key (pk_) ships in your browser bundle. It is public by design — the origin allowlist is what protects it.
  • The secret key (sk_) is shown once, because only its hash is stored. It is for your OpenTelemetry exporter. Lost it? Rotate.

7. Record something

The project page shows this snippet with your real key already in it. The traceOrigins list decides which requests get a traceparent; it defaults to the page’s own origin.

import { startRecording } from 'syncline-browser';

startRecording({
  key: 'pk_...',
  endpoint: 'http://localhost:4000',
  traceOrigins: ['http://localhost:3000'],
});

The page’s origin has to be on the project’s allowlist, or ingest answers 403 naming the origin it rejected.

8. Watch it arrive

Open /dashboard. The first chunk lands within a few seconds of the page loading — the SDK flushes every five seconds or 64 KB, whichever comes first. Click a recording, then click a bar in any lane to zoom the timeline to that request.

No backend traces yet? That is expected until your services export OTLP. Backend tracing is two environment variables and one CORS header.

Troubleshooting

SymptomCause
Ingest returns 403The page origin is not on the project’s allowlist
Ingest returns 401Wrong key kind — recordings need pk_, OTLP needs sk_
Recordings list is empty but ingest returned 202The worker is not running; the queue has the job waiting
Requests fail after adding the SDKYour API must allow the traceparent header in Access-Control-Allow-Headers
New account sees no projectsExpected — each account gets its own organization. Ask an existing member for an invitation to theirs
Backend lane stays emptyTraces are arriving on a different trace id, or not arriving at all