Hogsend
API Reference

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 # production

Interactive Docs

In non-production environments, the API serves an auto-generated OpenAPI spec and an interactive Scalar UI:

PathDescription
GET /openapi.jsonOpenAPI 3.1 spec (JSON)
GET /docsScalar 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/email returns 400 EMAIL_PASSWORD_SIGN_UP_DISABLED for everyone, including the in-process auth.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:

  1. Legacy key -- A static key from the ADMIN_API_KEY environment variable (full admin access; implies ingest)
  2. Database-backed keys -- Generated via POST /v1/admin/api-keys with scoped permissions
curl -H "Authorization: Bearer your-api-key" \
  http://localhost:3002/v1/admin/contacts

Keys are SHA-256 hashed before storage -- the raw key is only shown once at creation time. Scopes come in two kinds:

ScopeKindAccess
readhierarchicalGET admin endpoints only
journey-adminhierarchicalRead + journey management
full-adminhierarchicalAll admin operations. Also implies ingest
ingestorthogonalThe 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:

  1. Container injection -- Sets the DI container on the Hono context
  2. Secure headers -- Adds security-related HTTP headers
  3. CORS -- Cross-origin resource sharing (permissive by default)
  4. Compression -- gzip/brotli response compression
  5. Request ID -- Generates a unique X-Request-Id header
  6. 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 authentication
  • 404 -- Resource not found
  • 409 -- Conflict (e.g., duplicate externalId)
  • 429 -- Rate limit exceeded
  • 500 -- 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.

VariableRequiredDefaultDescription
NODE_ENVNodevelopmentdevelopment, production, or test
PORTNo3002HTTP server port
LOG_LEVELNoinfoerror, warn, info, http, debug
DATABASE_URLYes--TimescaleDB/Postgres connection string
REDIS_URLNoredis://localhost:6379Redis connection (PostHog cache + Better Auth secondaryStorage). Required in production — secondaryStorage gates on the raw process.env.REDIS_URL, not the zod default
BETTER_AUTH_SECRETYes--Session signing secret
BETTER_AUTH_URLNohttp://localhost:3002Auth base URL
BETTER_AUTH_TRUSTED_ORIGINSNo--csv of extra trusted origins beyond BETTER_AUTH_URL + API_PUBLIC_URL
HATCHET_CLIENT_TOKENYes--Hatchet authentication token (the engine will not boot without it)
STUDIO_ADMIN_EMAILNo--Set it to env-bootstrap the first Studio admin on a zero-user DB (see Authentication)
STUDIO_ADMIN_PASSWORDNo--First-admin password (min 8). If omitted, an auto-generated one is printed once to the server log
EMAIL_PROVIDERNoresendActive email provider id (resend, postmark, …). The active provider also resolves from email.defaultProvider
EMAIL_FROMNo--Neutral default sender (mailer defaultFrom = EMAIL_FROM ?? RESEND_FROM_EMAIL)
RESEND_API_KEYNo--Resend API key. Optional now — a Postmark-only (or other-provider) deploy boots without it
RESEND_FROM_EMAILNonoreply@hogsend.comDefault Resend sender email
RESEND_WEBHOOK_SECRETNo--Enables Resend email-webhook signature verification
POSTMARK_SERVER_TOKENNo--Builds the Postmark provider preset (never changes the active provider on its own)
POSTMARK_MESSAGE_STREAMNo--Postmark message stream
POSTMARK_WEBHOOK_USERNo--HTTP Basic user for the Postmark webhook (fails closed when unset)
POSTMARK_WEBHOOK_PASSNo--HTTP Basic password for the Postmark webhook
HATCHET_CLIENT_HOST_PORTNolocalhost:7077Hatchet engine gRPC host:port
HATCHET_CLIENT_TLS_STRATEGYNotlsnone, tls, or mtls
HATCHET_CLIENT_NAMESPACENo--Optional Hatchet namespace prefix
POSTHOG_API_KEYNo--PostHog project API key
POSTHOG_HOSTNo--PostHog instance URL
POSTHOG_WEBHOOK_SECRETNo--PostHog webhook source authentication
ADMIN_API_KEYNo--Bearer token for admin endpoints
API_PUBLIC_URLNohttp://localhost:3002Public URL for generating links (unsubscribe, preferences)
HOGSEND_API_KEYNo--Ingest-scoped data-plane key used by the public API + @hogsend/client SDK (not by the engine factories)
ENABLED_JOURNEYSNo*Comma-separated journey IDs or * for all
ENABLED_BUCKETSNo*Comma-separated bucket IDs or * for all
ENABLED_LISTSNo*Comma-separated defineList() ids or * for all
ENABLED_WEBHOOK_PRESETSNo--csv of inbound integration-preset ids (clerk, supabase, stripe, segment), * (all), or none
ENABLED_DESTINATION_PRESETSNo--csv of destination preset ids, * (all), or none; absent → the webhook + posthog default set. webhook and posthog are always registered
ENABLE_POSTHOG_DESTINATIONNofalseWhen true (and POSTHOG_API_KEY is set), auto-seeds one durable PostHog destination for the email lifecycle
SKIP_SCHEMA_CHECKNofalseBypass the boot-time engine schema guard
CLIENT_MIGRATIONS_FOLDERNo--Folder for your client-track Drizzle migrations
BUCKET_RECONCILE_CRONNo*/5 * * * *Cadence for the engine-owned bucket reconcile cron (time-based leaves)
OUTBOUND_WEBHOOK_REAPER_CRONNo--Cadence for the outbound-delivery reaper cron (retry scheduler + stuck-row recovery)
OUTBOUND_WEBHOOK_MAX_ATTEMPTSNo8Max delivery attempts before an outbound webhook goes to the DLQ
OUTBOUND_WEBHOOK_TIMEOUT_MSNo15000Per-attempt HTTP timeout for outbound webhook delivery
OUTBOUND_WEBHOOK_BASE_DELAY_MSNo5000Base backoff delay between outbound webhook retries
OUTBOUND_WEBHOOK_MAX_DELAY_MSNo21600000Max backoff delay (6h) between outbound webhook retries
OUTBOUND_WEBHOOK_STUCK_AFTER_MSNo300000How long (5min) before a sending row is treated as stuck and recovered

API Sub-pages

PageDescription
Ingestion & WebhooksEvent ingestion, provider email webhooks, generic webhook sources, health endpoint
Contacts APIContact CRUD, email preferences, contact timeline
Journeys APIJourney listing, detail, enable/disable, instances, enrollment, journey logs
Buckets APIBucket listing with counts, detail with criteria and fed journeys, member listing, enable/disable
Emails APIEmail send history, delivery details, unsubscribe and preference center
Events APIEvent browsing, detail, and replay
Metrics APISystem overview, journey performance, email metrics, event volume
Operations APIBulk import/export, batch enrollment, API keys, audit logs, alerts, DLQ

On this page