Browser usage (publishable keys)
Use @hatched/sdk-js in the browser with a publishable key — read buddies, mint widget sessions, no server round-trip.
For pages where you only need to read buddy state or mint widget sessions, a publishable key lets you talk to Hatched directly from the browser — no server endpoint of your own, no secret-key leak risk.
Decide: is this the right tool?
| You want to... | Use |
|---|---|
| Read buddy state on a static site | Publishable key |
| Show the buddy widget | Mint widget session with a publishable key |
Send events (lesson_completed, etc.) | Secret key on your server |
| Spend coins, equip items via API | Secret key on your server |
| Manage webhook configs | Secret key on your server |
If you need mutations, keep your secret key on the server. See Auth model.
Create a publishable key
Dashboard → Developers → API keys → Create publishable key.
- Pick a label.
- Confirm the scopes (default: all read scopes +
write:widget-sessions). - Copy the
hatch_pk_*value. Unlike secret keys, it's safe to put in client-side config (NEXT_PUBLIC_*,<meta>tags, etc.).
Initialise the client in the browser
import { HatchedClient } from '@hatched/sdk-js';
const hatched = new HatchedClient({
publishableKey: process.env.NEXT_PUBLIC_HATCHED_PK!,
});The server-only runtime guard is disabled for publishable-key clients, so this works in a React client component, a Vite SPA, a static landing page, anywhere.
Read buddy state
'use client';
import { useEffect, useState } from 'react';
import { HatchedClient, type Buddy } from '@hatched/sdk-js';
const hatched = new HatchedClient({
publishableKey: process.env.NEXT_PUBLIC_HATCHED_PK!,
});
export function BuddyBadge({ buddyId }: { buddyId: string }) {
const [buddy, setBuddy] = useState<Buddy | null>(null);
useEffect(() => {
hatched.buddies.get(buddyId).then(setBuddy).catch(console.error);
}, [buddyId]);
if (!buddy) return null;
return (
<div>
<img src={buddy.thumbUrl ?? undefined} alt={buddy.name} />
<div>
Level {buddy.evolutionStage} · {buddy.coins} coins
</div>
</div>
);
}Mint a widget session in the browser
Publishable keys can mint their own widget sessions — no server endpoint needed:
const session = await hatched.widgetSessions.create({
buddyId: 'bdy_abc',
userId: currentUserId,
scopes: ['buddy:read', 'buddy:interact'],
ttlSeconds: 900,
});
document
.querySelector('[data-hatched-widget]')!
.setAttribute('data-hatched-token', session.token);What fails and what you'll see
Attempting a mutation from a publishable-key client fails before the
network call with PublishableKeyScopeError — no latency cost:
import { PublishableKeyScopeError } from '@hatched/sdk-js';
try {
await hatched.events.send({ ... }); // server-only
} catch (err) {
if (err instanceof PublishableKeyScopeError) {
console.error('move this call to your backend with HATCHED_API_KEY');
}
}If you somehow bypass the SDK and hit the API directly, you'll get
403 publishable_key_scope with the same semantic.
Rotation
Publishable keys rotate like secret keys — Dashboard → Developers → API
keys → revoke. Revocation is instant; browser sessions error on their
next call and you deploy a new NEXT_PUBLIC_HATCHED_PK.
Try it
Head to the Playground — paste a publishable key, hit "Get buddy", see the response inline.