Saylent

Deploy the app

The full app on infrastructure you own, start to finish.

The CLI is one audit, one folder, no login. The full app adds accounts, stored history, scheduled verifies, multiple users on one deployment, share links, and an admin console with spend controls - all on infrastructure you own. Everything below is Postgres you control (see Your data is plain Postgres) plus a job runner for the audit pipeline; there's no hosted Saylent service in between.

Same product loop everywhere: onboarding (brand + domain) → a run with live progress → the report → a fixes tracker → verify → compare against a rival → a share link → settings. The three targets below only differ in where steps 1 and 5 happen - pick the one that matches how much you want to manage.

Pick where it runs

On my laptopOn my own serverIn the cloud
What it needsDocker DesktopDocker Engine + a domainA Vercel account
Time~10 minutes~20 minutes~5 minutes
Data livesLocal Postgres on diskYour server (hosted or self-hosted Supabase)Your Supabase project
You manageNothing - stop the containers when doneBackups, TLS, upgradesNothing - Vercel + Supabase host it
GuideRun it on your laptopSelf-host on your serverDeploy to the cloud

Every target applies the same migrations (npm run db:migrate), reads the same environment tiers, exposes the same /api/health, and gives the operator role to whoever signs up first.

First sign-up is the operator

There's no separate admin setup step. The first account created on a fresh database becomes the operator (migration 0040) - a trigger, not a manual step. The onboarding shows that account one extra card: "You are the operator. Budget controls and the admin console are in the sidebar." Every account after that is a normal user; nothing else about their experience differs. If you ever need a second admin later, run scripts/grant-admin.ts.

Everything that account can then do is in The operator console.

Users and access

Anyone who can reach your /login page can create an account: there is no invite code and no allowlist in the app. Users and access is who can sign up, the three ways to limit that, how a teammate joins, what a normal user can and cannot do next to an operator, and the per-account brand limit.

Control the questions and the run in the app

The CLI lets you look at the buyer questions before you pay for them (saylent questions), edit the file, and then pick samples, engines, language and skipped stages on the saylent audit command line. The app gives you the same control, per brand, at /app/brand/<id>/questions - reachable from the "Questions and run options" link on a brand's movement page.

The questions editor with the cost estimate card pinned above it: $3.73 to $5.50, 23 questions of which 12 count toward the recommended band, and the first five editable question rows with Type and Samples selects

The page shows the exact set the run will freeze, generated from the brand model you already have, so opening it costs nothing. From there you can:

  • edit, add, remove and retag any question. Only category and problem rows count toward the recommended band; anything you write is tagged as yours and asked, judged and reported without touching the score.
  • set samples per question and per run (1-5), the same as --samples plus a per-row override in questions.json.
  • narrow the answer engines (at least two, so the verdict is still cross-checked) and choose a language, the same as --engines and --locale. A language is applied once, before the set freezes, so every later verify asks the same wording; it has no effect while you have edited rows, because your wording is asked exactly as written.
  • skip stages: the fix drafts (the only skip that saves money), the page-by-page corpus, and the crawler gate checks. Each says what you give up.

The estimated cost, and the arithmetic behind it, sits above the Run button: how many questions, how many of them are scored, how many answers that is across your engines, and what the brand model, judging and fix drafts add. Nothing is spent until you press it.

Changing what gets asked starts a new baseline. Verify compares a run against the frozen set it was baselined on, so editing the questions or the engine set clears that frozen set and bumps its version - the same visible re-baseline that editing a brand's category or competitors already causes. The page says so before you run, and your existing reports are untouched.

The choices are stored on the brand (brands.run_options) and stamped onto each run (runs.run_options), so a finished run always records what it was told to do.

Budget controls

The sidebar's "Budget & limits" (operator-only) holds: a daily spend cap in USD (default $20, DAILY_SPEND_CAP_USD), per-brand run throttles (default 3 audits and 3 verifies per rolling 24-hour window, RUN_THROTTLE_*), a kill switch that refuses every new run the moment you press it, run health, the support inbox, takedown requests, and a "Test providers" button that checks your configured keys with a free call. There are no prices anywhere in a self-hosted deployment - /app/settings/limits shows each user "your limits, set by your operator." Page by page: The operator console. Full reference: Configuration.

By default, the weekly verify and monthly audit crons are off - they spend your keys' money on a schedule you didn't explicitly ask for. Set FLAG_SCHEDULED_RUNS=1 and redeploy when you're comfortable with the cost: every brand with one completed audit then gets a weekly verify and a monthly audit, staggered to each owner's local 09:00.

Why a job runner? (Inngest)

The audit pipeline is nine stages - crawl, brand model, questions, four engines, judge, cited pages, site checks, fixes, score - each of which can individually fail, retry, or take longer than a typical web request allows. Inngest turns each stage into one durable, retried, individually-resumable step instead of one long function that has to restart from the top on any failure. Running it locally (npx inngest-cli dev, no account or keys needed) starts a dev server that shows you every step live; npm run app:local starts it for you alongside the app.

Your data is plain Postgres

Every self-hosted deployment's database is a normal Postgres database on your own Supabase project or your own Supabase self-host - open to psql, DBeaver, pgAdmin, pg_dump, or any other Postgres client, and yours to back up, migrate, or move however you like. Supabase is the login, row-level permission, and live-update layer on top of that Postgres, not a different database - plain Postgres with your own auth system is a much larger rewrite (every row-level-security policy becomes application code, live progress becomes polling) with no evidence it grows adoption faster, so it isn't offered. Want no database at all? The CLI is the answer: a folder of run bundles is your history, and saylent verify on that folder is your tracking.

Upgrades

npm run db:migrate is idempotent - safe to re-run on every deploy. Each release lists the migrations and environment variables it adds; /api/health compares the migrations your database has applied against what the running code expects and shows a banner if any are missing.

On this page