Free Β· Source-available Β· Unofficial

TypeScript & PHP SDKs for the Chastify API

Two client libraries for the Chastify Developer API: control your lock session, build extensions and receive webhooks β€” without rewriting auth, retries or error handling.

What this project is

The e-cosplay.fr association builds these SDKs free of charge for the Chastify.net community. The Chastify API is rich (lock, tasks, devices, extensions, files, webhooks), but calling it properly takes a lot of boilerplate. The SDKs handle it, with the same design in both languages.

πŸ”Œ Full API coverage

External API, extension session API, file storage, webhook verification and the iframe bridge.

πŸ” Automatic retries

Exponential backoff on 429 and 5xx, honouring the Retry-After header.

🧯 Typed errors

One class per documented case: invalid token, no active session, device offline…

πŸ—„οΈ Cache & logs

In-memory or Redis cache (off by default) and debug logs with secrets redacted.

Installation

SDKPackageRequirements
TypeScript / JS@ecosplay/chastify-sdk (npm)Node β‰₯ 18 or Bun β‰₯ 1.3
PHPe-cosplay/chastify-sdk (Composer)PHP β‰₯ 8.1, ext-curl, ext-json, ext-mbstring, ext-openssl, ext-fileinfo

The package is published on npm:

npm add @ecosplay/chastify-sdk     # Node
bun add @ecosplay/chastify-sdk     # Bun

Getting a key

KeyUsed forClient option
DEV token (user) Control your own active session (/api/apps/v1/*) devToken
App developer key Public extensions used by others (/api/extensions/*), together with each session's mainToken appKey (+ appId)
πŸ” Keys are backend-only secrets. Never put them in browser code or in a repository β€” pass them through environment variables (CHASTIFY_DEV_TOKEN…).

Quick start

Read the active session, then act on the lock:

import { ChastifyClient } from "@ecosplay/chastify-sdk";

const chastify = new ChastifyClient({ devToken: process.env.CHASTIFY_DEV_TOKEN! });

const session = await chastify.session.get();
console.log(session.lockData?.remainingSeconds);

await chastify.lock.addTime(600);          // +10 min
await chastify.lock.freeze(1800);          // freeze 30 min
await chastify.lock.unfreeze();
await chastify.tasks.assign({ taskText: "10 min meditation", points: 10 });
await chastify.logs.custom({ title: "Check-in", role: "extension" });
await chastify.device.vibrate({ intensityPct: 40, durationSeconds: 5 });

What the external API covers

AreaMethods (JS: chastify.x, PHP: $chastify->x())
sessionget()
lockapplyTime, addTime, removeTime, freeze, unfreeze, toggleFreeze, pillory, endPillory
tasksassign, startTimer, complete
hygienestartUnlock
settingspatch
logscustom
deviceshock, stopShock, vibrate, stopVibration, allStop, setRandomShock, setBerserkShock
actionsrun(name, params) β€” for any action, including future ones

For devices, the wearer's configured intensity cap is always enforced server-side, whatever value you send.

Configuration

import { ChastifyClient, RedisCache, ConsoleLogger } from "@ecosplay/chastify-sdk";
import Redis from "ioredis";

const chastify = new ChastifyClient({
  devToken: process.env.CHASTIFY_DEV_TOKEN!,
  timeoutMs: 30_000,                      // default
  maxRetries: 4,                          // 429 + 5xx + network
  cache: new RedisCache(new Redis(process.env.REDIS_URL!)),
  cacheTtlMs: 5000,                       // 0 = cache OFF (default)
  logger: new ConsoleLogger(),
  logLevel: "debug",                      // secrets redacted
});

await chastify.session.get({ cache: false }); // force refresh

Code generator

Pick a call, fill in its parameters and copy ready-to-run code for your backend: cURL, Node or PHP.

πŸ” Your key never leaves this page. Nothing is sent or stored: the code is built right here in your browser. Developer keys are backend-only secrets, so run the generated code on your server, never in a web page.

Building an extension

Chastify loads your extension in an iframe and passes a context (including sessionId and mainToken) in location.hash. The browser reads it through the bridge; state-changing actions go through your backend, with the app key.

  1. Browser β€” read the context and session through the bridge (read-only, no secret).
  2. Backend β€” receive sessionId + mainToken, then call the API with appKey.

1. Inside the iframe (JS only)

import { parseHashPayload, ChastifyBridgeClient, startAutoResizeToParent }
  from "@ecosplay/chastify-sdk/bridge";

const ctx = parseHashPayload(location.hash);
const bridge = new ChastifyBridgeClient(ctx);
startAutoResizeToParent(ctx);             // resizes the iframe

const session = await bridge.request("session.get");
const state = await bridge.request("state.get");

2. On your backend

const chastify = new ChastifyClient({ appKey: process.env.CHASTIFY_APP_KEY! });
const s = chastify.extensions.session(sessionId, mainToken);

await s.patchState({ wins: 3 });                          // extension state
await s.patchMetadata({ unlockBlockers: ["Win 3 games"] });
await s.action("add_time", { deltaSeconds: 300 });        // reward or penalty
await s.recordProgress({ metric: "memory_win", amount: 1 });
await s.notify({ title: "Well done!", message: "Game won", target: "both" });
await s.files.upload({ data: bytes, filename: "proof.jpg" }, { purpose: "evidence" });
⚠️ The iframe is not a trust boundary: the mainToken is visible to the user. Always validate sensitive actions on the server.

Webhooks

Chastify sends events (lock.time_changed, lock.frozen, task.assigned, task.completed, task.failed, hygiene.started, dice.rolled…) with an x-webhook-token-hash header. The SDK verifies that hash and parses the event. Delivery is at-least-once: dedupe on event.id.

import { ChastifyWebhooks } from "@ecosplay/chastify-sdk";

app.post("/chastify/webhook", express.raw({ type: "*/*" }), (req, res) => {
  const event = ChastifyWebhooks.constructEvent(
    req.body, req.headers, process.env.CHASTIFY_WEBHOOK_TOKEN!,
  ); // throws WebhookVerificationError on hash mismatch

  if (event.event === "task.completed") { /* … */ }
  res.sendStatus(200);
});

Errors & limits

Every non-2xx response throws a typed error (Error suffix in JS, Exception in PHP) carrying status, code and message.

ClassHTTPTypical cases
Authentication401missing_token, invalid_token, revoked_token
Authorization403insufficient_scope, not_authorized
NotFound404lock_not_found, no_device
Conflict409no_active_lock_session, lock_ended
Validation400 / 422invalid_params, unsupported_device
RateLimit429retried automatically
DeviceTimeout504device_timeout, device_offline
Server / Network / Timeout5xx / β€”retried automatically

API limits per minute: read 300, write 120, action 30, upload 10. Don't blindly retry an action that may already have succeeded.

Working on the SDKs

Tests run entirely against a bundled mock server β€” the real API is never called.

git clone https://code.e-cosplay.fr/shoko/chastify-sdk-js.git
cd chastify-sdk-js && bun install && bun test

git clone https://code.e-cosplay.fr/shoko/chastify-sdk-php.git
cd chastify-sdk-php && composer install && composer test

If the Chastify API evolves, the SDKs will be updated on a best-effort basis, as the association's volunteer time allows. Suggestions and reports: contact@e-cosplay.fr.