Self-host

Run the Apache-2.0 HITLy server yourself.

This repository is the open-source HITLy server. Self-hosting includes the inbox, projects, plugins, audit log, API keys, and team membership. There are no usage caps.

Run locally

yarn install
cp apps/app/.env.example apps/app/.env.local
yarn db:up
yarn db:migrate
yarn dev:app

The marketing/docs site is optional (yarn dev:web). The Expo reviewer app is yarn dev:mobile — see Mobile.

Database

Local Compose runs Postgres 16 (postgres://USER:PASSWORD@localhost:5432/hitly; apps/app/.env.example ships the local dev defaults). OSS does not enable row-level security or envelope encryption; those are Cloud overlay concerns.

Older self-host installs used MariaDB. There is no in-place upgrade: stand up Postgres, run yarn db:migrate on an empty database, and re-create workspaces. Schema was squashed to a single Postgres migration.

What is not in this repo

Stripe billing, hosted plan limits, and SSO ship in a private @hitly/cloud overlay used by hitly.net. The @hitly/cloud package here is a stub: billing API routes return 404, and the nav hides Cloud-only items.

Do not set an env var to "enable Cloud." Cloud features exist only when that private package replaces the stub.

Housekeeping

The app process runs due jobs about once a minute:

JobScheduleWhat it does
expire-overdue-approvalshourlyMarks pending items past expiresAt as expired
purge-completed-approvalsdailyDeletes closed items older than the workspace plan retention
purge-expired-audit-eventsweeklyDeletes project events older than auditRetentionDays

Self-hosted workspaces keep completed items forever. Cloud (invite-only) plans: solo 10 days, pro 30, team 365, enterprise 1000.

Set HITLY_HOUSEKEEPING=0 to disable the in-process timer (for example when a process manager or cron should own it). Then POST /api/v1/housekeeping with Authorization: Bearer $HITLY_CRON_SECRET. Optional JSON { "job": "purge-completed-approvals", "force": true } runs one job immediately.

Outbound destinations

HITLy sends requests to destinations you configure: HTTP and S3 evidence sinks, workspace OTEL endpoints, and plugin URLs (for example a Mastra or LangGraph base URL, or an HTTP resume URL). Every request runs a destination check when it is sent:

  • Only http and https URLs are accepted, without a user name or password in the URL.
  • The host name is resolved once, every address is checked, and the request goes to that checked address.
  • Redirects are not followed; a 3xx answer is reported as an error.
  • The connection must open within 5 seconds and the whole request must finish within 10 seconds (5 seconds for evidence sinks and OTEL, 60 seconds for plugin resume calls). Responses are capped at 1 MiB.
  • Error messages name the status code and host only, never the response body or the full URL.

Private, loopback and link-local addresses (including localhost) are allowed when NODE_ENV is not production, so local development and the quickstart work as they are. In production they are refused unless the server sets:

HITLY_ALLOW_PRIVATE_EVIDENCE_SINKS=true

Only the exact value true turns this on. It is read from the server environment, not from workspace or project settings, and the server logs one line at startup when it is on. Set it on a self-hosted production server whose evidence sink, collector or agents run on the same machine or private network. Multicast and broadcast addresses are always refused.

On this page