Skip to Content
DocsQuickstartAuthentication

Authentication

Pug uses three auth boundaries. Each boundary maps to specific RPC services — using the wrong credential type returns 401 Unauthenticated or 403 Permission Denied.

Quick decision guide

Are you building a browser or mobile app? └─ Yes → SDK API key (write-only, safe for client code) Are you calling from your backend or a trusted server? └─ Yes → Shared API key or JWT Are you building a dashboard UI integration? └─ Yes → JWT (obtained via user sign-in)

Request headers

All authenticated requests require a project scope header:

x-project-id: <project-id>

Dashboard (JWT)

Browser sessions for growth managers. Obtained via sign-in (email/password, magic link, or Google OAuth).

Authorization: Bearer <jwt> x-project-id: <project-id>

Services: OrgsService, ProjectsService, DashboardsService, OrgEmailProvidersService, CustomersService

JWTs are short-lived and refreshed by the dashboard client automatically. Do not hard-code JWTs in scripts — use the SDK API key or shared key for server automation.

SDK (API key)

Client-side SDKs authenticate with the SDK API key from project settings. Write-only and untrusted — suitable for browsers and mobile apps.

Authorization: Bearer <sdk-api-key> x-project-id: <project-id>

Services: EventsService.BatchCreate, ProfilesSDKService.Identify

Never expose a shared/private API key in client-side code. SDK keys cannot read analytics data.

Shared (API key or JWT)

Server-side or trusted integrations. Accepts either a shared API key or a user JWT.

Authorization: Bearer <shared-api-key-or-jwt> x-project-id: <project-id>

Services: InsightsService, ProfilesService, ActivityService

Use a shared key for cron jobs, internal tools, and backend services. Use JWT when acting on behalf of a logged-in dashboard user.

Connect RPC client example

All APIs use Connect RPC  over HTTP/2. Install the client:

npm install @connectrpc/connect @connectrpc/connect-web @bufbuild/protobuf

SDK auth — send events from Node.js

import { createClient } from '@connectrpc/connect' import { createConnectTransport } from '@connectrpc/connect-web' import { EventsService } from './gen/events/v1/events_connect' // generated from proto const transport = createConnectTransport({ baseUrl: 'https://api.pug.sh', interceptors: [ next => async req => { req.header.set('Authorization', `Bearer ${process.env.PUG_SDK_API_KEY}`) req.header.set('x-project-id', process.env.PUG_PROJECT_ID!) return next(req) } ] }) const client = createClient(EventsService, transport) await client.batchCreate({ events: [ { name: 'purchase', properties: { revenue: 29.99, currency: 'USD' }, timestamp: new Date() } ] })

Shared auth — run an insight query

import { createClient } from '@connectrpc/connect' import { createConnectTransport } from '@connectrpc/connect-web' import { InsightsService } from './gen/insights/v1/insights_connect' const transport = createConnectTransport({ baseUrl: 'https://api.pug.sh', interceptors: [ next => async req => { req.header.set('Authorization', `Bearer ${process.env.PUG_SHARED_API_KEY}`) req.header.set('x-project-id', process.env.PUG_PROJECT_ID!) return next(req) } ] }) const client = createClient(InsightsService, transport) const result = await client.query({ /* InsightQuerySpec */ })

Generate TypeScript clients from proto with Buf :

buf generate

Proto definitions: cotton/proto 

API key management

Key typeWhere to findRotate when
SDK API keySettings → API keysKey leaked in client bundle or public repo
Shared API keySettings → API keysEmployee offboarding, suspected compromise

Rotation steps:

  1. Generate a new key in project settings.
  2. Update your SDK init or server env vars.
  3. Deploy.
  4. Revoke the old key.

Security best practices

  • SDK keys in client code are expected — they are write-only. Do not treat obscurity as security.
  • Shared keys stay on the server — never bundle them in frontend JavaScript, mobile apps, or public repos.
  • Use environment variablesNEXT_PUBLIC_* for SDK keys only; shared keys in server-side env only.
  • Proxy pattern — if you need server-side logic before ingesting events, call your own API which forwards to Pug with the SDK key.

Error responses

Connect RPC returns structured errors. Common cases:

CodeMeaningTypical cause
UnauthenticatedMissing or invalid credentialWrong key, expired JWT
PermissionDeniedValid credential, wrong serviceSDK key calling InsightsService
InvalidArgumentPayload failed protovalidateMissing required field, wrong type
NotFoundResource doesn’t existWrong project ID

Invalid payloads include field-level details from protovalidate. Fix the named field and retry.

Further reading

Last updated on