VoidhashDocs
DocumentationAPI reference
Dashboard
Essentials
API overview
Authentication
Endpoints
Ad_accounts
Analytics
Api_keys
Session
Component_dev_sessions
Component_registry
Components
Sandbox
Events
Experiments
Feature_flags
Ingest_policy
Push_notifications
Organizations
Payment_providers
Payment_provider_products
Payment_sources
Paywall_locations
Paywalls
Perks
Persons
Products
Projects
Push_notification_configurations
Schema
Sdk
Me
Webhooks
Audit_log
Organization_members
Notices
Connected_apps
Tracking_domains
Ads
Payment_provider_history_imports
Feedback
Analytics
Event capture

Authentication

Choose the right credential for server, tool, or device requests.

Every request to the Voidhash API carries a credential. Voidhash has three programmatic credential types, each meant for a different kind of caller. Use the narrowest one that fits.

CredentialHeaderUse
CLI keyAuthorization: Bearer vh_cli_...The CLI and tools acting as you, across your projects.
Project secret keyAuthorization: Bearer vh_sk_...Server-to-server requests for one project.
Publishable keyx-publishable-key + x-distinct-idOfficial SDKs and distributed clients.

Event capture also takes a project secret key, in its own header; see Event capture.

Project secret keys

A project secret key authorizes your backend to act on one project. Send it as a bearer token in the Authorization header, and name the project in the path:

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

To find the project's id, call GET /api/v1/session with the key: its projects list holds the key's one project. A path naming any other project answers 404.

Never expose a secret key

A project secret key grants broad access to the project. Do not put it in a mobile app, web bundle, public repository, or analytics event property. Roll it immediately after exposure.

Limit what a secret key can do

A new secret key is restricted: under Settings → API keys → New secret key, choose None, Read or Write for each part of the project the key needs, such as customers, paywalls or analytics. Write includes Read. A few actions are granted separately, on top of Write: erasing customers, publishing paywalls and sending notifications. Every key can read its own project.

To start quickly, pick a template and adjust it:

TemplateGrants
Backend integrationWrite customers, entitlements, purchases and events; read products.
Read-only analyticsRead analytics reports and ad campaigns with their spend (project:analytics:read, project:ads:read).
Read-onlyRead everything except store credentials.
Paywall publishingWrite and publish paywalls; read products.

Choose Full access instead only for a server you trust: the key can then read and change everything in the project, including store credentials and webhooks.

Keys are created, edited, rolled and deleted in Studio, signed in as yourself. A secret key, the CLI and AI agents can list keys but cannot change them, so no key or agent can give itself more access or outlive its removal.

A key never gets a permission that the person creating or editing it does not have, so the dashboard only offers what your role allows. Listing keys returns each key's scopes, which is null for a full-access key.

Listing raw events (GET /api/v1/projects/{projectId}/events) requires project:events:read (Events: Read), which the read-only analytics template leaves out because events carry data about individual end users. Connecting or disconnecting an ad account requires project:ads:manage (Ads: Write), which that template also leaves out.

A request that needs a permission the key lacks is answered with 403. Event capture rejects a restricted key without project:events:write (Events: Write) as unauthorized.

Change a key's access

Choose Edit access in a key's menu to change what it can do, with the same choices as when you created it. The change applies at once and the secret stays the same, so the integration using the key keeps working with its new access. This is also how you restrict a full-access key.

You can edit only keys whose access you have yourself, and you can give a key only permissions your role holds. Every change is recorded in the audit log with the access before and after.

Expire a key

When you create a key you can choose when it expires: never (the default), after 30, 90 or 365 days, or at the end of a date you pick. From then on every request with the key, including event capture, is answered with 401; the message says the key expired and when. The key list marks it Expired. Create a new key to replace it; an expired key stays listed until you delete it.

Roll a key

Rolling a key issues a new secret and keeps the key's name, access and expiry. Choose Roll key in its menu and say how long the old secret keeps working: not at all, 1 hour, 24 hours or 7 days. During that time both secrets work, so you can deploy the new one without downtime; then the old one stops on its own. Rolling again ends any earlier overlap at once. The new secret is shown once, like a new key's. Each roll is recorded in the audit log.

POST /api/v1/projects/{projectId}/api-keys/{apiKeyId}/rotate does the same, with the overlap in overlapSeconds. Like every change to a key, it needs you signed in to Studio.

See when a key was last used

The key list shows when each key last authenticated, to within a minute, and Never used for a key that hasn't. Use it to find keys nothing depends on any more before you delete them. Listing keys returns the same value as lastUsedAt, next to expiresAt.

CLI keys

A CLI key acts as you, with your role in each organization. It is useful for local tools and automation that need access to more than one project. Send it as a bearer token, like a secret key:

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

The CLI obtains and stores this credential for you when you run voidhash-cli auth login, and it appears under Settings → Connected apps in Studio.

Publishable keys

A publishable key is safe to ship inside an app. It only authorizes the device-facing SDK surface, and it must be paired with a customer identity in the x-distinct-id header:

curl https://api.voidhash.com/api/v1/sdk/person \
  -H "x-publishable-key: vh_pk_..." \
  -H "x-distinct-id: user_123"

The official SDKs also send platform and client metadata that these endpoints require. Prefer the SDK over calling them directly.

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"

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. Send printable ASCII ids as they are.

A distinct id is at most 255 characters and must not be blank or contain a NUL character. A missing, blank or malformed x-distinct-id is answered with 400 and an Api/SdkValidationError body, not 401. A 401 always means the publishable key itself was rejected.

Verify a credential

Call GET /api/v1/session with any credential. 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"

A missing, malformed, unknown or expired key is answered with 401 and an unauthenticated error whose code says which, such as missing_credential or expired_api_key. A publishable key is refused the same way, with publishable_key_not_allowed: it only works with the SDK endpoints.

A valid key can still receive a 403 when it lacks permission for a specific operation.

Event capture

The event capture endpoints, /i/v1/capture and /i/v1/batch, accept either a secret key or a publishable key. Which one you send depends on where the request comes from.

From a backend, send the project secret key in the x-secret-key header, not as a bearer token. No x-distinct-id header is needed, because each event carries its own distinct_id.

From a distributed client that cannot hold a secret, send the publishable key as a token field in the JSON body instead.

Send one or the other. When both are present, the body token wins. See Event Capture for request examples.

PreviousAPI overviewNextList

On this page

Project secret keysLimit what a secret key can doChange a key's accessExpire a keyRoll a keySee when a key was last usedCLI keysPublishable keysDistinct ids outside printable ASCIIVerify a credentialEvent capture