Skip to Content
DocsQuickstartAuthentication

Authentication

Pug has three auth boundaries. Each maps to a set of RPC services — using the wrong credential returns Unauthenticated or PermissionDenied.

The three credentials

CredentialFormatUsed byHeaderReaches
Public keypub_…Browser / app SDKx-api-keysdk.events.v1.EventsService, sdk.profiles.v1.ProfilesSDKService
Private keyprv_…Your serverx-api-keyshared.* — insights, profiles, activity
JWTsession tokenDashboardAuthorization: Bearerdashboard.* (and shared.*)

The project is resolved from the API key, so SDK and server requests don’t send a project ID. Dashboard JWT requests optionally include an x-project-id header to scope to a project.

Where keys live

Keys live under Settings → API Keys. A project holds any number of them, so you can issue one per app or service and revoke it on its own. Your project Overview also shows the public key next to the SDK setup snippets.

  • Public key is displayed in plain text — it’s safe in client code. It can write events and identify users, but cannot read analytics.
  • Private key is shown once, when you create it — copy it then. Pug stores only a digest of the key, so it cannot be revealed afterwards; the list shows a mask (prv_…3f9c) to tell your private keys apart. Treat it like a password. It can read insights, profiles, and activity.

There is no rotate button. To replace a key, create its replacement, update your SDK apiKey / server env var, deploy, then revoke the old one.

Public key — client SDKs

The Web and Flutter SDKs take the public key as apiKey and send it as x-api-key on every request:

import { init } from '@pug-sh/browser' init('YOUR_PROJECT_ID', { apiKey: 'pub_YOUR_PUBLIC_KEY' })

The public key is write-scoped: BatchCreate (events) and Identify (profiles). It cannot reach the shared.* read APIs, so it’s safe to ship in a browser bundle or mobile binary.

Private key — your servers

Use the private key from trusted server-side code to read analytics. The Node SDK wraps these reads — construct a client with your private key and call pug.insights, pug.profiles, or pug.activity:

import { Pug } from '@pug-sh/node' const pug = new Pug({ apiKey: process.env.PUG_PRIVATE_KEY! }) const result = await pug.insights.query({ /* spec, timeRange, granularity */ })

From any other language, send the private key as x-api-key against the shared.* services (which accept a private key or a dashboard JWT):

curl -X POST https://api.pugs.dev/shared.insights.v1.InsightsService/Query \ -H "x-api-key: prv_YOUR_PRIVATE_KEY" \ -H "Connect-Protocol-Version: 1" \ -H "Content-Type: application/json" \ -d '{ /* InsightQuerySpec */ }'

Replace the body with a real InsightQuerySpec — see Insights API for the field-by-field shape and a complete example.

Never ship a private key to a browser, mobile app, or public repo.

JWT — the dashboard

The dashboard obtains a short-lived JWT when a user signs in, and sends it as Authorization: Bearer <jwt> (plus x-project-id to scope to a project). JWTs reach the dashboard.* management services and the shared.* read services. They’re refreshed automatically by the dashboard client — don’t hard-code them in scripts; use a private key for automation instead.

Connect RPC

All APIs speak Connect RPC  over HTTP/2 and accept the Connect, gRPC, and gRPC-Web protocols. The request path is /<package>.<Service>/<Method>, e.g. /sdk.events.v1.EventsService/BatchCreate. For JSON requests, include Connect-Protocol-Version: 1.

To send events without an SDK, see Integrate over HTTP. To generate a typed client from the protobuf definitions, see the API overview.

Error responses

Connect returns structured errors:

CodeMeaningTypical cause
UnauthenticatedMissing or invalid credentialWrong key, missing x-api-key
PermissionDeniedValid credential, wrong scopePublic key calling a shared.* read API
InvalidArgumentPayload failed validationA rejected field — see below
NotFoundResource doesn’t existWrong project or profile ID

Common InvalidArgument causes:

  • A required field is missing.
  • A custom property key is prefixed with $ (reserved for auto-properties).
  • kind (the event name) starts with pug. (a reserved prefix).

Responses include field-level details from server-side validation — fix the named field and retry.

Advanced — proxying

If you need server-side logic before events reach Pug (enrichment, filtering, auth of your own), run your own endpoint that forwards requests to Pug with the appropriate key. This keeps the credential on your backend instead of in the client.

Further reading

Last updated on