BTick is a generic API-driven scheduler service for recurring HTTP jobs.
It provides:
- Scheduler API (
cmd/scheduler-api):- JSON API under
/api/v1 - server-rendered UI with
templ+templui+tailwindcss+htmx - API key management and API key auth
- JSON API under
- Scheduler Worker (
cmd/scheduler-worker):- Postgres polling for due jobs
- safe multi-worker claim semantics
- HTTP execution with retries and run history persistence
- execution through
task-orchestration
- Control plane: API service
- Execution plane: Worker service
- Source of truth: Postgres (
jobs,job_runs,api_keys) - Scheduling model: persisted
next_run_at+ cron expression - No in-memory timer dependence
cmd/
scheduler-api/
scheduler-worker/
internal/
apikeys/
auth/
authorization/
config/
db/
handlers/
errors/
v1/
httpserver/
jobs/
runs/
scheduler/
worker/
migrations/
queries/
sdk/
go/
scheduler/
views/
Shared:
DATABASE_URL(required)
API:
PORT(default8080)STATIC_ASSETS_PATH(default./assets)UI_SESSION_SECRET(defaultdev-session-secret-change-me)UI_SESSION_TTL_HOURS(default12)
Worker:
WORKER_ID(defaultscheduler-worker-1)WORKER_CONCURRENCY(default4)WORKER_POLL_INTERVAL_MS(default2000)WORKER_STALE_CLAIM_MINUTES(default5)WORKER_MAX_CLAIMS_PER_TICK(default20)WORKER_TASK_LOG_PATH(default./logs/tasks)WORKER_TASK_STATE_DB_PATH(default./data/task_orchestration.db)WORKER_HTTP_TIMEOUT_SECONDS(default90)
- Set
DATABASE_URL. - Start API:
go run ./cmd/scheduler-api- Start worker:
go run ./cmd/scheduler-workerMigrations run automatically on startup.
Build and run Postgres + API + worker:
docker compose up --buildServices:
- Scheduler API:
http://localhost:8080 - Scheduler Worker: separate container (
scheduler-worker) - Adminer:
http://localhost:8010 - Postgres:
localhost:5432(btick/btick/btick)
Notes:
- Both API and worker run migrations on startup.
- Update
UI_SESSION_SECRETincompose.ymlbefore production use.
Build API image:
docker build -f Dockerfile.api -t btick-scheduler-api:local .Build worker image:
docker build -f Dockerfile.worker -t btick-scheduler-worker:local .Health:
GET /api/v1/healthGET /healthzGET /readyz
Jobs:
POST /api/v1/jobsGET /api/v1/jobsGET /api/v1/jobs/{jobId}PATCH /api/v1/jobs/{jobId}DELETE /api/v1/jobs/{jobId}POST /api/v1/jobs/{jobId}/pausePOST /api/v1/jobs/{jobId}/resumePOST /api/v1/jobs/{jobId}/trigger
Runs:
GET /api/v1/jobs/{jobId}/runsGET /api/v1/runs/{runId}
API Keys:
POST /api/v1/api-keysGET /api/v1/api-keysPOST /api/v1/api-keys/{keyId}/revoke
Protected API endpoints expect:
Authorization: Bearer <api-key>Keys are generated once and only key_prefix + secure hash are stored.
/register/login/ui/jobs/ui/jobs/new/ui/jobs/{jobId}/ui/jobs/{jobId}/edit/ui/jobs/{jobId}/runs/ui/runs/{runId}/ui/api-keys/ui/api-keys/new
Go SDK lives at:
sdk/go/scheduler
See sdk/go/scheduler/README.md for examples.
- SQL schema migration files:
migrations/ - Query contracts:
queries/ - SQLC config:
sqlc.yaml - Query implementation package:
internal/db/sqlc