@praxium/sdk
v0.5.91
Published
Official TypeScript SDK for the Praxium platform API
Downloads
1,268
Maintainers
Readme
@praxium/sdk
Official TypeScript SDK for the Praxium platform API. Build tenant websites that display practice data (team, services, FAQ, opening hours) with full locale support.
- Quick Start — Installation and usage examples
- Configuration — Environment variables and tenant routing
- Available Methods — All API endpoints
- Error Handling — Typed error classes
- Webhooks — React to entity changes (ISR revalidation, cache busting, etc.)
- Contributing — Development commands
Quick Start
npm install @praxium/sdkLocale-Specific Mode
Pass a locale to get pre-resolved labels and values in that language:
import { createPraxiumClient } from '@praxium/sdk'
const client = createPraxiumClient({
baseUrl: process.env.PRAXIUM_API_URL!,
apiKey: process.env.PRAXIUM_API_KEY!,
locale: 'nl', // 'nl' | 'en' | '*' (see Multilingual Mode)
})
const hours = await client.getOpeningHours()
const team = await client.getTeamMembers()
const faq = await client.getFaq()All methods return data directly or throw a typed PraxiumError on failure (see Error Handling).
Multilingual Mode
Pass locale: '*' to receive all translations as objects. Resolve them at render time with localizeText():
import { createPraxiumClient, localizeText } from '@praxium/sdk'
const client = createPraxiumClient({
baseUrl: process.env.PRAXIUM_API_URL!,
apiKey: process.env.PRAXIUM_API_KEY!,
locale: '*',
})
const team = await client.getTeamMembers()
// Returns MultilingualTeamMember[] — labels and values are multilingual objects:
// {
// name: "Dr. Jan de Vries",
// role: "Fysiotherapeut",
// customFields: [
// { label: { nl: "Specialisatie", en: "Specialization" }, value: { nl: "Sport", en: "Sports" } }
// ]
// }
const label = localizeText(team[0].customFields[0].label, 'nl') // → "Specialisatie"
const value = localizeText(team[0].customFields[0].value, 'nl') // → "Sport"Contact Form
Submit a contact form on behalf of a website visitor:
const result = await client.submitContactForm({
name: 'Jan de Vries',
email: '[email protected]',
phone: '+31612345678',
subject: 'Appointment request',
message: 'I would like to book an appointment.',
acceptTerms: true, // required — must reflect the visitor's explicit consent
})
// → { success: true, emailStatus: 'sent' }Configuration
Your website needs two environment variables to connect to the Praxium platform, plus an optional third for webhook-based cache revalidation:
Required:
| Variable | Purpose | Example |
|----------|---------|---------|
| PRAXIUM_API_URL | Your tenant's admin portal URL. The SDK sends API requests to this host. | https://mypractice.admin.praxium.nl |
| PRAXIUM_API_KEY | HMAC API key for authentication. Generated in the admin portal under API Profiles. The tenant slug AND the API profile slug are both embedded in the key — no need to configure them separately. | praxium_v1_mypractice_public-team_17..._abc... |
Optional (only if using ISR revalidation webhooks):
| Variable | Purpose | Example |
|----------|---------|---------|
| PRAXIUM_WEBHOOK_SECRET | Shared secret for webhook signature verification. Only needed if you want the platform to notify your site when data changes (team, FAQ, opening hours). See Webhooks. | (32+ character random string) |
# .env.local
PRAXIUM_API_URL="https://mypractice.admin.praxium.nl"
PRAXIUM_API_KEY="praxium_v1_mypractice_public-team_1234567890_abcdef..."
PRAXIUM_WEBHOOK_SECRET="your-webhook-secret-from-admin-portal" # optionalHow tenant routing works: Each tenant has its own admin subdomain (
{slug}.admin.praxium.nl). The platform identifies your tenant from both the hostname AND the API key's embedded slug, cross-validating them for security. You don't need to configure the tenant slug separately — it's derived from your API key automatically.
Available Methods
| Method | Description |
|--------|-------------|
| getOpeningHours() | Weekly opening schedule |
| getTeamMembers() | Staff members with photos and custom fields |
| getContactDetails() | Practice contact information |
| getLocation() | Address and map coordinates |
| getBusinessName() | Practice display name |
| getSocialLinks() | Social media URLs |
| getFaq() | FAQ grouped by category |
| getServiceVariants() | Service pricing and variants |
| getInsuranceInfo() | Accepted insurance providers |
| getFeatures() | Practice features and amenities |
| getPaymentMethods() | Accepted payment methods |
| getPolicyInfo() | Practice policies |
| getBookableServices() | Services available for online booking |
| submitContactForm(body) | Submit a contact form |
Error Handling
All methods throw typed errors that you can catch individually:
import { PraxiumNotFoundError, PraxiumAuthError } from '@praxium/sdk'
try {
const team = await client.getTeamMembers()
} catch (error) {
if (error instanceof PraxiumNotFoundError) {
// 404 — resource not found
} else if (error instanceof PraxiumAuthError) {
// 401 — invalid or expired API key
}
}| Error Class | HTTP Status | When |
|-------------|-------------|------|
| PraxiumAuthError | 401 | Invalid API key |
| PraxiumForbiddenError | 403 | Key doesn't match tenant |
| PraxiumNotFoundError | 404 | Resource not found |
| PraxiumValidationError | 400 | Invalid request data |
| PraxiumRateLimitError | 429 | Too many requests |
| PraxiumError | Other | Base class for all errors |
Webhooks
When an admin updates data (team, FAQ, opening hours), the platform sends an HMAC-signed webhook to your registered endpoint. You can use this to react to entity changes in any way your application needs.
Common use cases:
| Use Case | What you do on entity change |
|----------|------------------------------|
| ISR revalidation | Call revalidatePath() to refresh cached pages |
| Search index | Re-index changed entities in your search engine |
| CDN cache | Purge cached API responses or assets |
Prerequisites:
- Register your webhook endpoint URL in the admin portal (Settings → Webhooks)
- Set
PRAXIUM_WEBHOOK_SECRETto the shared secret from the admin portal
All webhook handlers include HMAC-SHA256 signature verification, timestamp-based replay protection (5-minute window), and timing-safe comparison.
Next.js ISR Revalidation
The SDK provides a ready-made handler for Next.js ISR revalidation. It verifies the webhook signature and calls revalidatePath() for the affected pages.
The pathMap is fully customizable — you define which pages to revalidate for each entity based on your website's routing structure:
// app/api/revalidate/route.ts
import { createRevalidationHandler } from '@praxium/sdk/webhooks'
export const POST = createRevalidationHandler({
secret: process.env.PRAXIUM_WEBHOOK_SECRET!,
// Map platform entities to YOUR website's page paths.
// Adjust the paths to match your project's routing structure.
pathMap: {
'team': ['/nl/team', '/en/team'],
'rates': ['/nl/tarieven', '/en/rates'],
'faq': ['/nl/faq', '/en/faq'],
'opening-hours': ['/nl', '/en'], // homepage shows opening hours
'all': ['/nl', '/en'], // full-site revalidation
},
})Requires next as a peer dependency.
Custom Webhook Handler
For non-Next.js use cases or custom logic, use processWebhook() to verify the signature and extract the changed entity:
// Example: invalidate a Redis cache on entity change
import { processWebhook, WEBHOOK_SIGNATURE_HEADER } from '@praxium/sdk/webhooks'
export async function POST(request: Request) {
const body = await request.text()
const result = await processWebhook({
body,
signature: request.headers.get(WEBHOOK_SIGNATURE_HEADER)!,
secret: process.env.PRAXIUM_WEBHOOK_SECRET!,
})
if (!result.valid) {
return new Response(result.error, { status: 401 })
}
// result.entity = 'team' | 'faq' | 'rates' | ...
await redis.del(`cache:${result.entity}`)
return new Response('OK')
}Contributing
npm run generate # Regenerate client from OpenAPI spec
npm run build # Build dist/
npm run test # Run tests
npm run typecheck # Type-check without emitting