API Reference
Complete REST API -- event ingestion, PostHog/Stripe webhooks, contacts, journeys, email delivery, metrics, and admin operations.
Hogsend exposes a REST API built on Hono with Zod OpenAPI for request/response validation. The HTTP server is the @hogsend/engine app your scaffolded src/index.ts builds with createApp(container, { webhookSources }) — the engine ships the routes; your app injects the content. All endpoints are versioned under /v1.
Base URL
http://localhost:3002 # local development
https://api.hogsend.com # productionInteractive Docs
In non-production environments, the API serves an auto-generated OpenAPI spec and an interactive Scalar UI:
| Path | Description |
|---|---|
GET /openapi.json | OpenAPI 3.1 spec (JSON) |
GET /docs | Scalar API reference UI |
These endpoints are disabled when NODE_ENV=production.
Authentication
Hogsend uses two authentication mechanisms depending on the endpoint:
Better Auth (session-based)
Studio and user-facing endpoints use Better Auth with email/password authentication. Auth routes are mounted at /api/auth/*:
- Email and password sign-in (passwords 8–128 chars). Public sign-up is disabled (
disableSignUp) —POST /api/auth/sign-up/emailreturns400 EMAIL_PASSWORD_SIGN_UP_DISABLEDfor everyone, including the in-processauth.api.signUpEmail. There is no setup token and no web create-admin form. - Session-based auth with 7-day expiry (refreshed daily)
- Self-service forgot/reset password (single-use token, 15-min TTL) — live only when a reset mailer is wired
- Organization plugin (up to 5 orgs, 100 members each)
Because public sign-up is closed, the first Studio admin is created from your server — run hogsend studio admin create (needs DATABASE_URL + BETTER_AUTH_SECRET in the environment), or set STUDIO_ADMIN_EMAIL (+ optional STUDIO_ADMIN_PASSWORD) and the API mints it on boot into an empty user table. A zero-user instance renders a read-only info screen, not a create form. See Authentication for the full model.
API Keys (bearer token)
Both the admin plane (/v1/admin/*) and the public data plane (/v1/contacts, /v1/events, /v1/emails, /v1/lists) use bearer-token API keys. The data plane is bearer-authed but it is not under /v1/admin — it's a separate surface gated by a separate scope (see below and the Data API). Hogsend supports two auth modes:
- Legacy key -- A static key from the
ADMIN_API_KEYenvironment variable (full admin access; impliesingest) - Database-backed keys -- Generated via
POST /v1/admin/api-keyswith scoped permissions
curl -H "Authorization: Bearer your-api-key" \
http://localhost:3002/v1/admin/contactsKeys are SHA-256 hashed before storage -- the raw key is only shown once at creation time. Scopes come in two kinds:
| Scope | Kind | Access |
|---|---|---|
read | hierarchical | GET admin endpoints only |
journey-admin | hierarchical | Read + journey management |
full-admin | hierarchical | All admin operations. Also implies ingest |
ingest | orthogonal | The data plane (/v1/contacts, /v1/events, /v1/emails, /v1/lists). No /v1/admin/* access |
The three admin scopes are hierarchical: read < journey-admin < full-admin, each implying the ones below it. ingest is orthogonal — it sits outside that ladder. A read or journey-admin key does not get ingest, and an ingest-only key does not get admin access. The only ways to reach the data plane are an explicit ingest grant or full-admin. Combine scopes (e.g. ["read", "ingest"]) for a key that needs both planes.
Keys can be revoked and have optional expiry dates. Active keys are cached in memory for 60 seconds.
If neither ADMIN_API_KEY nor any database-backed keys are configured, admin endpoints return 503 Service Unavailable.
Middleware Stack
The app is built by createApp(container, opts) (from @hogsend/engine). Every request passes through the following built-in global middleware in order:
- Container injection -- Sets the DI container on the Hono context
- Secure headers -- Adds security-related HTTP headers
- CORS -- Cross-origin resource sharing (permissive by default)
- Compression -- gzip/brotli response compression
- Request ID -- Generates a unique
X-Request-Idheader - Request logger -- Logs method, path, status, and duration
Any custom middleware: MiddlewareHandler[] you pass to createApp is applied after this built-in stack. Thrown errors are caught by a built-in error handler (overridable via the onError option) that returns JSON { error } with the appropriate status code.
The following middleware is scoped to the admin router (/v1/admin/*), not the global stack:
- Rate limiter -- Sliding window (100 req/min per key) with Redis backend and in-memory fallback
- Audit logger -- Records all admin mutations (POST/PUT/PATCH/DELETE) with actor, action, resource, and IP
Error Responses
All errors follow this shape:
{
"error": "Human-readable error message"
}400-- Validation error (Zod schema mismatch)401-- Missing or invalid authentication404-- Resource not found409-- Conflict (e.g., duplicateexternalId)429-- Rate limit exceeded500-- Internal server error (message is generic in production)503-- Service not configured
Environment Variables
The engine validates all environment variables at startup using @t3-oss/env-core with Zod schemas. The server will not start if required variables are missing. In a scaffolded app these live in its own root .env (copied from .env.example), not in any monorepo path.
| Variable | Required | Default | Description |
|---|---|---|---|
NODE_ENV | No | development | development, production, or test |
PORT | No | 3002 | HTTP server port |
LOG_LEVEL | No | info | error, warn, info, http, debug |
DATABASE_URL | Yes | -- | TimescaleDB/Postgres connection string |
REDIS_URL | No | redis://localhost:6379 | Redis connection (PostHog cache + Better Auth secondaryStorage). Required in production — secondaryStorage gates on the raw process.env.REDIS_URL, not the zod default |
BETTER_AUTH_SECRET | Yes | -- | Session signing secret |
BETTER_AUTH_URL | No | http://localhost:3002 | Auth base URL |
BETTER_AUTH_TRUSTED_ORIGINS | No | -- | csv of extra trusted origins beyond BETTER_AUTH_URL + API_PUBLIC_URL |
HATCHET_CLIENT_TOKEN | Yes | -- | Hatchet authentication token (the engine will not boot without it) |
STUDIO_ADMIN_EMAIL | No | -- | Set it to env-bootstrap the first Studio admin on a zero-user DB (see Authentication) |
STUDIO_ADMIN_PASSWORD | No | -- | First-admin password (min 8). If omitted, an auto-generated one is printed once to the server log |
EMAIL_PROVIDER | No | resend | Active email provider id (resend, postmark, …). The active provider also resolves from email.defaultProvider |
EMAIL_FROM | No | -- | Neutral default sender (mailer defaultFrom = EMAIL_FROM ?? RESEND_FROM_EMAIL) |
RESEND_API_KEY | No | -- | Resend API key. Optional now — a Postmark-only (or other-provider) deploy boots without it |
RESEND_FROM_EMAIL | No | noreply@hogsend.com | Default Resend sender email |
RESEND_WEBHOOK_SECRET | No | -- | Enables Resend email-webhook signature verification |
POSTMARK_SERVER_TOKEN | No | -- | Builds the Postmark provider preset (never changes the active provider on its own) |
POSTMARK_MESSAGE_STREAM | No | -- | Postmark message stream |
POSTMARK_WEBHOOK_USER | No | -- | HTTP Basic user for the Postmark webhook (fails closed when unset) |
POSTMARK_WEBHOOK_PASS | No | -- | HTTP Basic password for the Postmark webhook |
HATCHET_CLIENT_HOST_PORT | No | localhost:7077 | Hatchet engine gRPC host:port |
HATCHET_CLIENT_TLS_STRATEGY | No | tls | none, tls, or mtls |
HATCHET_CLIENT_NAMESPACE | No | -- | Optional Hatchet namespace prefix |
POSTHOG_API_KEY | No | -- | PostHog project API key |
POSTHOG_HOST | No | -- | PostHog instance URL |
POSTHOG_WEBHOOK_SECRET | No | -- | PostHog webhook source authentication |
ADMIN_API_KEY | No | -- | Bearer token for admin endpoints |
API_PUBLIC_URL | No | http://localhost:3002 | Public URL for generating links (unsubscribe, preferences) |
HOGSEND_API_KEY | No | -- | Ingest-scoped data-plane key used by the public API + @hogsend/client SDK (not by the engine factories) |
ENABLED_JOURNEYS | No | * | Comma-separated journey IDs or * for all |
ENABLED_BUCKETS | No | * | Comma-separated bucket IDs or * for all |
ENABLED_LISTS | No | * | Comma-separated defineList() ids or * for all |
ENABLED_WEBHOOK_PRESETS | No | -- | csv of inbound integration-preset ids (clerk, supabase, stripe, segment), * (all), or none |
ENABLED_DESTINATION_PRESETS | No | -- | csv of destination preset ids, * (all), or none; absent → the webhook + posthog default set. webhook and posthog are always registered |
ENABLE_POSTHOG_DESTINATION | No | false | When true (and POSTHOG_API_KEY is set), auto-seeds one durable PostHog destination for the email lifecycle |
SKIP_SCHEMA_CHECK | No | false | Bypass the boot-time engine schema guard |
CLIENT_MIGRATIONS_FOLDER | No | -- | Folder for your client-track Drizzle migrations |
BUCKET_RECONCILE_CRON | No | */5 * * * * | Cadence for the engine-owned bucket reconcile cron (time-based leaves) |
OUTBOUND_WEBHOOK_REAPER_CRON | No | -- | Cadence for the outbound-delivery reaper cron (retry scheduler + stuck-row recovery) |
OUTBOUND_WEBHOOK_MAX_ATTEMPTS | No | 8 | Max delivery attempts before an outbound webhook goes to the DLQ |
OUTBOUND_WEBHOOK_TIMEOUT_MS | No | 15000 | Per-attempt HTTP timeout for outbound webhook delivery |
OUTBOUND_WEBHOOK_BASE_DELAY_MS | No | 5000 | Base backoff delay between outbound webhook retries |
OUTBOUND_WEBHOOK_MAX_DELAY_MS | No | 21600000 | Max backoff delay (6h) between outbound webhook retries |
OUTBOUND_WEBHOOK_STUCK_AFTER_MS | No | 300000 | How long (5min) before a sending row is treated as stuck and recovered |
API Sub-pages
| Page | Description |
|---|---|
| Ingestion & Webhooks | Event ingestion, provider email webhooks, generic webhook sources, health endpoint |
| Contacts API | Contact CRUD, email preferences, contact timeline |
| Journeys API | Journey listing, detail, enable/disable, instances, enrollment, journey logs |
| Buckets API | Bucket listing with counts, detail with criteria and fed journeys, member listing, enable/disable |
| Emails API | Email send history, delivery details, unsubscribe and preference center |
| Events API | Event browsing, detail, and replay |
| Metrics API | System overview, journey performance, email metrics, event volume |
| Operations API | Bulk import/export, batch enrollment, API keys, audit logs, alerts, DLQ |