Authentication

The four credential types the Voidhash API accepts, the session each one resolves to, and which endpoints expect which credential.

Every endpoint under /api/v1 goes through a single shared authentication middleware. It accepts four credential types, resolves each to a session that carries the organizations and projects the credential can access, and rejects everything else with a 401.

CredentialHeader(s)Typical caller
User API keyx-api-keyCLI, scripts acting as a dashboard user
Project secret keyx-secret-keyYour backend (server-to-server)
Publishable keyx-publishable-key + x-distinct-idDevice SDKs (React Native, web)
Dashboard session cookieCookie: wos-session=...The Voidhash dashboard itself

The middleware checks credentials in a fixed order: x-api-key first, then the session cookie, then x-secret-key, then x-publishable-key. If a request carries several, the first match wins.

User API key (x-api-key)

A user API key authenticates as a specific dashboard user. The resulting session has method: "user" and includes the full user object plus every organization and project that user belongs to, each with its own permissions array.

curl https://api.voidhash.com/api/v1/auth/session \
  -H "x-api-key: $VOIDHASH_API_KEY"

Use this for tooling that acts on your behalf across projects — the CLI authenticates this way. See API Keys for creating and rotating keys.

Project secret key (x-secret-key)

A secret key authenticates as a single project with full project access. The resulting session has method: "secret-key", an empty organizations array, no user, and exactly one entry in projects carrying the project:all permission.

curl https://api.voidhash.com/api/v1/persons \
  -H "x-secret-key: $VOIDHASH_SECRET_KEY"

Never ship a secret key in a client

A secret key grants full access to its project — persons, catalog, webhooks, notifications, all of it. Keep it on your server only. Never embed it in a mobile app, web bundle, or anything else you distribute; use the publishable key for device-side calls instead. If a secret key leaks, rotate it immediately via the API Keys endpoints.

Publishable key + distinct ID (device SDKs)

The publishable key is safe to embed in clients. It must be paired with an x-distinct-id header identifying the current person; sending x-publishable-key without x-distinct-id fails with Api/AuthenticationError ("Missing distinct-id header").

curl https://api.voidhash.com/api/v1/sdk/schema \
  -H "x-publishable-key: $VOIDHASH_PUBLISHABLE_KEY" \
  -H "x-distinct-id: device-8f2c1a"

The resulting session has method: "publishable-key", a single project entry with an empty permissions array, and a person object carrying the distinctId. It only unlocks the SDK endpoint group, which additionally requires a set of client telemetry headers (x-sdk, x-platform, x-client-bundle-id, ...) documented on that page. The React Native SDK sends all of this for you.

Requests carrying a WorkOS session cookie (wos-session=) authenticate as the logged-in dashboard user. This is what the Voidhash dashboard uses; it resolves to the same method: "user" session shape as an API key. You will normally never construct this yourself — use an API key for programmatic access.

Session shapes

Whatever the credential, the middleware provides a session with a common core:

type Session = {
  method: "user" | "secret-key" | "publishable-key";
  name: string;
  organizations: Array<{
    id: string;
    name: string;
    slug: string;
    logo: string | null;
    workosOrganizationId: string | null;
    permissions: string[];
  }>;
  projects: Array<{
    id: string;
    name: string;
    slug: string;
    logo: string | null;
    organizationId: string;
    permissions: string[];
  }>;
  user: User | null; // only on method: "user"
  person: { distinctId: string } | null; // only on method: "publishable-key"
};
  • User sessions list every organization and project the user can access; permissions on each entry reflects the user's role there.
  • Secret-key sessions contain exactly one project with permissions: ["project:all"] and no organizations.
  • Publishable-key sessions contain exactly one project with no permissions, plus the person identity from x-distinct-id.

Per-endpoint authorization is enforced against these permissions arrays — a valid credential that lacks the required project permission gets a 403 Api/ActionForbiddenError.

Inspecting your credential

GET /api/v1/auth/session echoes back what the API resolved your credential to. It is the quickest way to verify a key works and to discover the project and organization IDs it can see.

curl https://api.voidhash.com/api/v1/auth/session \
  -H "x-secret-key: $VOIDHASH_SECRET_KEY"
{
  "method": "secret-key",
  "name": "My App API Key",
  "organizations": [],
  "projects": [
    {
      "id": "proj_123",
      "name": "My App",
      "organizationId": "org_123",
      "slug": "my-app"
    }
  ]
}

The method field is one of "api-key", "secret-key", or "publishable-key" — user sessions (API key or dashboard cookie) both report "api-key" here.

Authentication errors

Errors are tagged JSON objects; the _tag identifies the error type.

Status_tagBodyWhen
401Api/NotAuthenticatedError{ message }No credential sent, or the API key / cookie is invalid
500Api/AuthenticationError{ cause, message }Invalid or expired secret / publishable key, missing x-distinct-id, or an internal failure while authenticating
{
  "_tag": "Api/NotAuthenticatedError",
  "message": "You are not authenticated"
}

Both can occur on any /api/v1 endpoint since they come from the shared middleware, not the endpoint itself.

Which credential each endpoint group expects

Any project-scoped endpoint accepts any session that covers the target project with the required permission, but the groups have clear intents:

Endpoint groupExpected credential
/sdkPublishable key + distinct ID only
/notificationsSecret key only (server-to-server)
/auth, /users, /organizations, /projectsUser session (API key or dashboard cookie)
Everything else — persons, catalog, schema, webhooks, paywall deploys, API keysUser session or secret key with the required project permission

The event capture surface at /i/v1 is separate: it authenticates with a publishable project token in the request body, not through this middleware.