VoidhashDocs
DevelopersHelp centerGuidesAPI reference
Dashboard
Essentials
API overview
Authentication
Event capture
Push device API
Webhooks
Webhook delivery
Webhook events
Projects & keys
Projects
API keys
Audit log
Customers & access
Persons
Products
Perks
Sandbox
Paywalls
Paywalls
Paywall locations
Components
Component registry
Assets
Experiments & flags
Experiments
Feature flags
Analytics & events
Analytics
Analytics alerts
Cohorts
Group types
Groups
Events
Ingest policy
VoidQL
Tracking domains
Ads
Ads
Ad accounts
Meta measurement
Payments & stores
Payment providers
Payment provider products
Purchase history imports
Payment sources
Webhooks & notifications
Webhooks
Push notifications
Push configurations
Automations
Organization & team
Organizations
Members
Invitations
Billing
Your account
Profile
Session
Notices
Connected apps
Terms of Service
Developer tools
Schema
SDK

Authentication

The headers each Voidhash credential goes in, the endpoints that take it, and the errors it gets.

Every request to the Voidhash API carries one credential. This page covers how to send it. To create a secret key, restrict what it can do, roll it or make it expire, see API keys.

CredentialLooks likeHeaderWorks with
Project secret keyvh_sk_…, or vh_sk_test_… for testAuthorization: BearerThe management API and event capture
Sign-in tokenvh_oat_…, or vh_cli_… from older CLI versionsAuthorization: BearerThe management API, as you
Publishable keyvh_pk_…, or vh_pk_test_… for testx-publishable-key, with x-distinct-idThe client API the SDKs call, event capture
Never expose a secret key

A secret key grants access to its project. Send it only from a server you control, never from an app, a web page or a public repository.

Management API

The management API, under /api/v1, takes a project secret key or a sign-in token as a bearer token:

curl https://api.voidhash.com/api/v1/projects/$VOIDHASH_PROJECT_ID/persons \
  -H "Authorization: Bearer $VOIDHASH_SECRET_KEY"

A secret key works in its own project only: a path naming another project answers 404, like a project that doesn't exist. It reads and writes the environment it was created for, production or test. A restricted key gets 403 for anything its access doesn't include.

A sign-in token acts as you, with your role in every project you can use. The CLI gets one when you run voidhash login; an AI agent gets its own for the MCP server, which works only there. A token reads production unless the request names another environment in the x-environment header: production, test or all.

Some endpoints act for a person, such as /api/v1/me and renaming a project, and refuse a secret key. Publishable keys don't work here.

Client API

The client API, under /api/v1/sdk, serves the SDKs in your end users' apps. It takes the project's publishable key in x-publishable-key and the end user's distinct ID in x-distinct-id, and every request also carries the headers that describe the app and the SDK, which the SDKs send for you. Register devices explains each one.

curl https://api.voidhash.com/api/v1/sdk/person \
  -H "x-publishable-key: vh_pk_..." \
  -H "x-distinct-id: user_123" \
  -H "x-client-bundle-id: com.example.app" \
  -H "x-platform: ios" \
  -H "x-platform-flavor: native" \
  -H "x-sdk: ios" \
  -H "x-sdk-version: 0.0.1-alpha.1" \
  -H "x-is-debug-build: false" \
  -H "x-is-backgrounded: false" \
  -H "x-observer-mode: false"

Use an SDK rather than calling the client API yourself. Sign-in tokens don't work here.

Distinct IDs outside printable ASCII

HTTP header values are limited to printable ASCII. When a distinct ID contains any other character, such as an accented letter or an emoji, send its UTF-8 bytes percent-encoded and add x-distinct-id-encoding: percent:

curl https://api.voidhash.com/api/v1/sdk/person \
  -H "x-publishable-key: vh_pk_..." \
  -H "x-distinct-id: Zo%C3%AB" \
  -H "x-distinct-id-encoding: percent" \
  -H "x-client-bundle-id: com.example.app" \
  -H "x-platform: ios" \
  -H "x-platform-flavor: native" \
  -H "x-sdk: ios" \
  -H "x-sdk-version: 0.0.1-alpha.1" \
  -H "x-is-debug-build: false" \
  -H "x-is-backgrounded: false" \
  -H "x-observer-mode: false"

Voidhash decodes the header only when that marker is present. Without it, the value is used exactly as sent, so an ID that contains a literal % keeps its identity. A distinct ID is at most 255 characters and must not be blank or contain a NUL character.

Event capture

The event capture endpoints, /i/v1/capture and /i/v1/batch, take a credential in one of two places:

  • From your server, send the secret key in the x-secret-key header, not as a bearer token. A restricted key needs Events: Write.
  • From an app or web page, send the publishable key as a token field in the JSON body. The SDKs may send it in x-publishable-key instead.

When both are present, the body token wins. A secret key in token is refused. See Event capture for request examples.

Verify a credential

Call GET /api/v1/session with a secret key or a sign-in token. The response shows what kind of credential it is, the person it acts for, and the organizations and projects it can use:

curl https://api.voidhash.com/api/v1/session \
  -H "Authorization: Bearer $VOIDHASH_SECRET_KEY"

Errors

The management API answers a refused credential with 401 and an unauthenticated error whose code says why:

codeMeaning
missing_credentialNo credential was sent.
malformed_authorizationThe Authorization header isn't Bearer followed by a credential.
invalid_api_keyThe key is unknown or was deleted.
expired_api_keyThe key has expired; the message says when.
invalid_token, session_endedThe sign-in token is invalid, has expired or was ended. Sign in again.
publishable_key_not_allowedA publishable key was sent; use a secret key.
credential_not_acceptedThis kind of credential doesn't work on this endpoint.

A valid credential that can't do something gets 403 with permission_denied. Its code is permission_denied when a key lacks the access, person_credential_required when only a person can do it, and dashboard_session_required when only Studio can, as for disconnecting an app.

The client API answers a refused key with 401 and an Api/AuthenticationError body whose cause holds the same codes, and a request with no key at all with Api/NotAuthenticatedError. A missing, blank or malformed x-distinct-id is a 400 with an Api/SdkValidationError body, not a 401. Event capture answers 401 with the code unauthorized. See Error codes for every code.

Next steps

  • API keys: create a secret key, choose its access, roll it and set its expiry.
  • API overview: base URL, conventions, errors and limits.
  • Event capture: send events with a secret key or a publishable key.
  • CLI: sign in once and call the API as yourself.

Something wrong or missing on this page? Tell us by email or on Discord.

PreviousAPI overviewNextEvent capture

On this page

Management APIClient APIDistinct IDs outside printable ASCIIEvent captureVerify a credentialErrorsNext steps