The @phosra/sdk package provides a typed client for the Phosra API, with support for Node.js and browser environments.
Verified against the published package. Everything on this page was checked against @phosra/[email protected] (the current npm release) and the code was run against the live sandbox — see Verified live at the bottom.
Installation
bash
npm install @phosra/sdk
Latest published version: 0.1.0. Pin the exact version in package.json — do not
float on a caret range while the package is pre-1.0, since a 0.xminor may break
(see SDK versioning):
json
{ "dependencies": { "@phosra/sdk": "0.1.0" } }
Quick Start
Every tab below leads with the open sandbox — https://phosra-api-sandbox-production.up.railway.app — so your first copy-paste returns 201 with no API key. Paste any tab as-is and run it; nothing you create in the sandbox touches production. The Get a production key step (a real dashboard link) comes after you've seen it work.
typescript
import { PhosraClient } from '@phosra/sdk';// Sandbox-first: no API key required. Point baseUrl at the open sandbox.const phosra = new PhosraClient({ baseUrl: 'https://phosra-api-sandbox-production.up.railway.app/api/v1',});// One-call setupconst result = await phosra.setup.quick({ child_name: 'Emma', birth_date: '2016-03-15', strictness: 'recommended',});console.log(`Created family: ${result.family.name}`); // Emma's Familyconsole.log(`Age group: ${result.age_group}`); // preteenconsole.log(`Rules enabled: ${result.rule_summary.total_rules_enabled}`); // 20
Ran the TypeScript tab above verbatim against the sandbox and it printed Emma's Family preteen 20 — see Verified live for the exact command and output.
Beyond sandbox evaluation
Always pass the documented sandbox baseUrl during public evaluation. Production access is not
enabled by omitting it or adding a phosra_live_… key. A design-partner pilot provisions the
approved host and credentials explicitly.
Authentication
PhosraClientConfig accepts exactly these fields (verified against @phosra/[email protected]):
Field
Header sent
Use for
apiKey
X-Api-Key: phosra_…
Server-to-server / B2B / agents
accessToken
Authorization: Bearer <jwt>
Parent-app user sessions (WorkOS token)
deviceKey
X-Device-Key: phosra_dev_…
On-device enforcement
baseUrl
—
Override the API host (defaults to https://prodapi.phosra.com/api/v1)
onTokenExpired
—
() => Promise<string> — called on 401; return a fresh access token to retry
The SDK sends apiKey as the X-Api-Key header internally; the raw curl / Python / Go tabs above
send the same key as Authorization: Bearer (the canonical header the rest of the docs use). The API
accepts both interchangeably — pick either. See Authentication.
typescript
// API key (server-to-server)const phosra = new PhosraClient({ apiKey: process.env.PHOSRA_API_KEY, // provisioned sandbox key});// Bearer token (user sessions)const phosra = new PhosraClient({ accessToken: 'eyJhbGciOi...',});// Device key — either via config or the static helper:const device = new PhosraClient({ deviceKey: 'phosra_dev_...' });const device2 = PhosraClient.forDevice({ deviceKey: 'phosra_dev_...' });
Precedence when more than one credential is set: deviceKey > apiKey > accessToken.
Refreshing an expired token
There is nomaxRetries/retryDelay option in @phosra/[email protected]. Token refresh is handled by the onTokenExpired callback — the client calls it on a 401, then retries the request once with the returned token:
typescript
const phosra = new PhosraClient({ accessToken: currentAccessToken, onTokenExpired: async () => { const { access_token } = await refreshMySession(); // your refresh logic return access_token; // the request is retried with this token },});// After a manual login/refresh you can also set it directly:phosra.setAccessToken(newAccessToken);
Resource Namespaces
The client organizes endpoints into resource namespaces:
The auth namespace ships register/login/refresh methods, but those three routes do not exist on the Go API — POST /auth/register, /auth/login, and /auth/refresh all return 404 (identity lives in WorkOS AuthKit; see Authentication). Only auth.me() and auth.logout() are backed by live endpoints (logout is a documented no-op). Treat the other three as dead surface in @phosra/[email protected].
typescript
// Authphosra.auth.register({ email, password, name }) // 404 — no such route; use WorkOS AuthKitphosra.auth.login({ email, password }) // 404 — no such route; use WorkOS AuthKitphosra.auth.refresh(refreshToken) // 404 — no such route; use onTokenExpiredphosra.auth.logout()phosra.auth.me()// Familiesphosra.families.list()phosra.families.create({ name })phosra.families.get(familyId)phosra.families.update(familyId, { name })phosra.families.delete(familyId)// Childrenphosra.children.create(familyId, { name, birth_date })phosra.children.list(familyId)phosra.children.get(childId)phosra.children.update(childId, { name })phosra.children.delete(childId)phosra.children.ageRatings(childId)// Policiesphosra.policies.create(childId, { name })phosra.policies.list(childId)phosra.policies.get(policyId)phosra.policies.update(policyId, { name, priority })phosra.policies.delete(policyId)phosra.policies.activate(policyId)phosra.policies.pause(policyId)phosra.policies.generateFromAge(policyId)// Rulesphosra.rules.list(policyId)phosra.rules.create(policyId, { category, enabled, config })phosra.rules.update(ruleId, { enabled, config })phosra.rules.delete(ruleId)phosra.rules.bulkUpsert(policyId, rules)// Enforcementphosra.enforcement.trigger(childId, { platform_ids? })phosra.enforcement.listJobs(childId)phosra.enforcement.getJob(jobId)phosra.enforcement.getResults(jobId)phosra.enforcement.retry(jobId)// Compliancephosra.compliance.create({ family_id, platform_id, credentials })phosra.compliance.list(familyId)phosra.compliance.verify(linkId)phosra.compliance.delete(linkId)// Webhooksphosra.webhooks.create({ family_id, url, events })phosra.webhooks.list(familyId)phosra.webhooks.get(webhookId)phosra.webhooks.update(webhookId, { url, events, active })phosra.webhooks.delete(webhookId)phosra.webhooks.test(webhookId)phosra.webhooks.deliveries(webhookId)// Platformsphosra.platforms.list()phosra.platforms.get(platformId)phosra.platforms.byCategory(category)phosra.platforms.byCapability(capability)// Setupphosra.setup.quick({ child_name, birth_date, strictness? })
Error Handling
The SDK throws typed errors. PhosraError is the base class (it carries only message); API failures throw a PhosraApiError (or one of its subclasses) which adds statusCode, code, and details. For the full wire-level status/class matrix (every 4xx/5xx, its cause, and its fix) see the Errors reference:
typescript
import { PhosraClient, PhosraApiError, PhosraNotFoundError,} from '@phosra/sdk';try { // A well-formed UUID that does not exist → 404 (PhosraNotFoundError). // A malformed id (e.g. 'nonexistent-id') returns 400 (invalid UUID) and // surfaces as the base PhosraApiError, not PhosraNotFoundError. const family = await phosra.families.get('00000000-0000-4000-8000-000000000000');} catch (err) { if (err instanceof PhosraNotFoundError) { // 404 — dedicated subclass console.log('not found'); } else if (err instanceof PhosraApiError) { console.log(err.statusCode); // e.g. 404, 422, 429 console.log(err.code); // machine-readable code, if the API sent one console.log(err.details); // extra fields from the response body console.log(err.message); }}
The full exported error hierarchy (all extend PhosraError):
The Quick Start code was run against the open sandbox census (https://phosra-api-sandbox-production.up.railway.app/api/v1, no key required) with @phosra/[email protected]:
typescript
import { PhosraClient } from '@phosra/sdk';const phosra = new PhosraClient({ baseUrl: 'https://phosra-api-sandbox-production.up.railway.app/api/v1',});const r = await phosra.setup.quick({ child_name: 'Emma', birth_date: '2016-03-15', strictness: 'recommended',});console.log(r.family.name, r.age_group, r.rule_summary.total_rules_enabled);const rules = await phosra.rules.list(r.policy.id);console.log('rules:', rules.length);
Actual output:
text
Emma's Family preteen 20rules: 20
Point baseUrl at the sandbox to try these snippets with synthetic data. Do not rely on the
client's default host for production access.