Hogsend
Data API

Data API

The public front door — write contacts, events, transactional emails, and list membership from your app code with a single ingest-scoped API key.

The Data API is Hogsend's public front door. It's the surface your product code calls to push the data that drives lifecycle: contacts, events, transactional emails, and list membership. Everything lives under /v1, is authenticated with a single bearer key, and exposes the clean, predictable shapes you'd want from any messaging SDK — while the journey logic stays in your repo as code.

Two planes, one engine

Hogsend exposes two distinct HTTP surfaces. They share the same contact store and the same engine, but they're authed differently and used by different callers.

Data plane (this section)Admin / ops plane
Paths/v1/contacts, /v1/events, /v1/emails, /v1/lists/v1/admin/*
AuthBearer key with the ingest scopeBearer key with read / journey-admin / full-admin, or Better Auth session
Who calls itYour application code (via the SDK or direct HTTP)Operators, Studio, the CLI, dashboards
PurposeWrite the data that drives journeysObserve and operate — list, inspect, export, replay, manage keys

The data plane is the surface for writing contacts, events, and transactional emails from your app. The admin plane is documented separately under the API Reference.

The data-plane routes are bearer-authed but they are not under /v1/admin. A data key (scope ingest) opens the data plane; it does not open /v1/admin/*. See Authentication for why the ingest scope is orthogonal, not hierarchical.

Base URL

http://localhost:3002   # local development
https://api.hogsend.com # production (your deployed engine)

All data-plane endpoints are versioned under /v1.

The ingest-scoped key

Every data-plane request carries a bearer token:

Authorization: Bearer hsk_...

The key must hold the ingest scope (or full-admin, which implies it). Mint one with POST /v1/admin/api-keys and "scopes": ["ingest"]. Crucially, ingest is orthogonal to the admin tiers: a read or journey-admin key does not get ingest access, and an ingest key does not get admin access. The full reasoning and minting steps are in Authentication.

The endpoints

EndpointMethodWhat it does
/v1/contactsPUTUpsert a contact by email and/or userId, with properties and list membership
/v1/contacts/findGETLook up contacts by email or userId
/v1/contactsDELETESoft-delete a contact
/v1/eventsPOSTIngest an event — the journey trigger and the canonical replacement for the old /v1/ingest
/v1/emailsPOSTSend a transactional email through the tracked mailer
/v1/listsGETEnumerate the code-defined email lists
/v1/lists/:id/subscribePOSTSubscribe a contact to a list
/v1/lists/:id/unsubscribePOSTUnsubscribe a contact from a list
/v1/admin/webhooksPOST GETManage outbound webhook endpoints (admin plane) — subscribe a URL to the lifecycle event stream

Subscribe to events

The Data API isn't just inbound. Hogsend can emit a signed webhook stream of the 13 lifecycle events — contact.created, email.delivered, journey.completed, bucket.entered, and the rest — to any HTTPS URL you register. Each delivery is HMAC-SHA256 signed (Standard Webhooks), retried with backoff, and dead-lettered on exhaustion, so your downstream systems stay in sync without polling.

You register and rotate endpoints through the admin plane (POST /v1/admin/webhooks), since outbound subscriptions are an operator concern, not an ingest one. See Webhooks for the event catalog, the signed envelope, and the management API.

The property model

The single most important data-modelling decision in the Data API is that contact properties and event properties are separate bags.

  • contactProperties are merged onto the durable contact record (contacts.properties). They describe who the person isplan, company, country. Buckets segment on these.
  • eventProperties are stored on the event row (user_events) and are what a journey trigger.where / exitOn rule evaluates. They describe what happenedsource, amount, referrer.
// POST /v1/events
{
  "name": "signup",
  "email": "ada@example.com",
  "eventProperties": { "source": "landing" },  // → the event, → trigger.where
  "contactProperties": { "plan": "pro" }        // → the contact record only
}

Event properties never leak into the contact, and contact properties never end up on the event. This is a deliberate split (and a behavior change from the old single-bag /v1/ingest). See Events and Identity for the full model.

The typed SDK

You don't have to hand-roll HTTP. @hogsend/client is a thin, typed wrapper over the same endpoints:

import { Hogsend } from "@hogsend/client";

const hs = new Hogsend({
  baseUrl: "https://api.hogsend.com",
  apiKey: process.env.HOGSEND_DATA_KEY!, // hsk_… key with the `ingest` scope
});

await hs.contacts.upsert({ email: "ada@example.com", properties: { plan: "pro" } });
await hs.events.send({ name: "signup", email: "ada@example.com", eventProperties: { source: "web" } });
await hs.emails.send({ to: "ada@example.com", template: "welcome", props: { name: "Ada" } });

The SDK gives you typed templates, identity guards, and typed error classes. Read the client SDK reference to get going.

On this page