@ophelio/sdk
v0.2.0
Published
Official JavaScript / TypeScript SDK for the Ophel.io membership & entitlement API.
Readme
@ophelio/sdk
Official JavaScript / TypeScript SDK for the Ophel.io membership & entitlement API. Works on Node 20+, Cloudflare Workers, Deno and Bun — zero runtime dependencies, ESM + CJS, fully typed from the platform's OpenAPI spec.
Status: pre-1.0. Minor versions may contain breaking changes.
Install
npm install @ophelio/sdkQuickstart
import { Ophelio } from '@ophelio/sdk'
const ophelio = new Ophelio({ apiKey: process.env.OPHELIO_API_KEY })
// Gate admission check
const result = await ophelio.admit.check({ card: 'M-123456' })
if (result.admitted) {
console.log(result.pricing_group) // e.g. 'member_adult'
}
// Offline gate cache
const { members, generated_at } = await ophelio.members.sync()
// later, incremental:
await ophelio.members.sync({ since: generated_at })Authentication
Pass your project API key (o_live_…); the project is implied by the key:
new Ophelio({ apiKey: 'o_live_…' })Errors
Non-2xx responses throw a typed subclass of OphelioError mapped from the
API's AIP-193 envelope:
import { NotFoundError, OphelioError } from '@ophelio/sdk'
try {
await ophelio.memberships.get(id)
} catch (error) {
if (error instanceof NotFoundError) {
// error.status === 404, error.code === 'NOT_FOUND', error.details — raw ErrorInfo
} else if (error instanceof OphelioError) {
// UnauthenticatedError, PermissionDeniedError, AlreadyExistsError,
// ResourceExhaustedError, OphelioTimeoutError, OphelioConnectionError, …
}
}Retries & idempotency
GETs are retried automatically on network errors / 429 / 502 / 503 / 504
(exponential backoff with jitter, Retry-After honoured; configure with
maxRetries, default 2). Plain mutations are never retried.
entitlements.redeem and entitlements.recordUsage are idempotency-keyed:
the SDK generates a key per call and reuses it across its own retries, so
they're safely retryable. If you retry at the application level (offline
gate queues), pass your own key:
await ophelio.entitlements.redeem(
{ member_id, entitlement_code: 'guest_pass' },
{ idempotencyKey: `gate-7-${visitId}` },
)Webhooks
Verify deliveries with the X-Ophelio-Signature header and your
subscription secret. Always pass the raw request body:
import { Ophelio, WebhookSignatureVerificationError } from '@ophelio/sdk'
const event = await Ophelio.webhooks.constructEvent(
rawBody,
request.headers.get('X-Ophelio-Signature'),
process.env.OPHELIO_WEBHOOK_SECRET,
) // throws WebhookSignatureVerificationError on mismatch
switch (event.type) {
case 'membership.payment_failed':
// event.data — see the event reference at https://docs.ophel.io
break
}Unknown event types still verify and parse, so new platform events don't break older SDK versions.
Pagination
The larger list endpoints are paginated. Their list* methods accept an
optional { page_size?, page_token? } and resolve to a Page<T>:
interface Page<T> {
items: T[]
next_page_token: string // '' on the last page
total_size: number // exact count across all pages
}Most of the time, let paginate walk the pages for you — pass a closure that
forwards the pagination params, capturing any ids, filters and options in it:
import { collect, paginate } from '@ophelio/sdk'
for await (const membership of paginate((page) =>
ophelio.memberships.list(page),
)) {
// …
}
// nested + filtered:
for await (const change of paginate((page) =>
ophelio.memberships.listScheduledChanges(id, { ...page, status: 'all' }),
)) {
// …
}
// or drain everything into an array (loads the whole collection into memory):
const customers = await collect((page) => ophelio.customers.list(page))To page manually — e.g. to surface total_size or drive numbered pages — feed
next_page_token back in as page_token until it comes back empty.
page_size defaults to 50 and is capped at 250. Because pagination is
offset-based, a row can be seen twice or skipped across a page boundary if the
collection is written to while you iterate. Paginated methods are marked
(params?) in the table below; the bounded lists (config resources,
listMembers) return a plain array.
API surface
Everything reachable with an API key is covered:
| Namespace | Methods |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| admit | check({ card }) |
| members | sync({ since? }), get(id), listEntitlements(id, { type? }), reissueCard(id, { note? }) |
| memberships | list(params?), get(id), create(...), update(id, ...), cancel(id, ...), uncancel(id), renew(id, ...), upgrade(id, ...), downgrade(id, ...), listMembers(id), listScheduledChanges(id, params?), cancelScheduledChange(id, changeId), listTransactions(id, params?), listTermTransactions(id, termId, params?), createTransaction(id, ...) |
| customers | list(params?), get(id), create(...), update(id, ...), delete(id), listMemberships(id, params?) |
| entitlements | redeem(...), recordUsage(...), list(), get(id), create(...), update(id, ...), delete(id) |
| plans | list(), get(id), create(...), update(id, ...), delete(id), listBillingOptions(id), createBillingOption(id, ...), listEntitlements(id), createEntitlement(id, ...), listPlanLinks(id), createPlanLink(id, ...), listPricingGroupMappings(id), createPricingGroupMapping(id, ...) |
| billingOptions | list(), get(id), update(id, ...), delete(id), listSalesChannels(id), createSalesChannelLink(id, ...) |
| memberRoles | list(), get(id), create(...), update(id, ...), delete(id) |
| pricingGroups | list(), get(id), create(...), update(id, ...), delete(id) |
| salesChannels | list(), get(id), create(...), update(id, ...), delete(id) |
| planEntitlements | get(id), update(id, ...), delete(id) |
| planLinks | delete(id) |
| planPricingGroupMappings | delete(id) |
| billingOptionSalesChannels | delete(id) |
| webhookSubscriptions | list(), get(id), create(...), update(id, ...), delete(id), listDeliveries(id, params?) |
Every method accepts a trailing options argument: { signal?, headers? }
(plus idempotencyKey? on redeem / recordUsage).
Endpoints requiring a user session (API key management, organisation and project admin) are intentionally not in the SDK — API keys cannot call them.
