Skip to main content

API Examples

Use these actions inside iframe bridge requests (action + payload).

Request and Response Format

Every action below is sent inside this bridge request envelope:

{
"type": "chastify:ext:req", // required
"v": 1, // protocol version
"id": "req-123", // your unique request id
"nonce": "from-iframe-hash",
"action": "session.get",
"payload": {}
}

And you receive:

{
"type": "chastify:ext:resp",
"v": 1,
"id": "req-123", // same id you sent
"ok": true,
"data": {}
}

If ok is false, check error.code and error.message.

Session and Context

session.get

Use this first in almost every extension flow.

What it does:

  • Verifies your bridge connection is working.
  • Returns session context (lock state, role, extension config, capabilities).
  • Returns device capability information used by device.command.

What it is used for:

  • Bootstrapping your UI.
  • Enabling/disabling features based on role and permissions.
  • Checking supported device commands before rendering device controls.

Example action payload:

{
"action": "session.get",
"payload": {}
}

Example session.get response excerpt (runtime lock data):

{
"ok": true,
"data": {
"lockData": {
"frozen": false,
"unlockable": false,
"trusted": true,
"taskAssigned": true,
"timeLockedSeconds": 1420,
"timeRemainingSeconds": 27800,
"maxTimeRemainingSeconds": 86400,
"taskPoints": 12,
"taskPointsRequired": 20,
"lockTitle": "Weekend Challenge",
"wearerUsername": "alice",
"keyholderUsername": "kh_bob",
"wearerLastSeenTimestamp": 1739640505123,
"keyholderLastSeenTimestamp": null
}
}
}

Privacy notes:

  • wearerLastSeenTimestamp / keyholderLastSeenTimestamp are returned only if that user has showOnlineStatus !== false.
  • If online status visibility is disabled, these fields are null.

Notifications

notifications.custom

Use this to send a custom extension notification to wearer, keyholder, or both.

Example action payload:

{
"action": "notifications.custom",
"payload": {
"title": "Extension Reminder",
"message": "Your next challenge is ready.",
"showPageOverlay": false,
"target": "both"
}
}

Notes:

  • showPageOverlay defaults to false.
  • target defaults to wearer.
  • API creates notification type extension_app_message.

Extension State

Extension state is your extension-owned JSON data for the current lock session. You can use it like a lightweight per-session database when you do not run your own backend, which is especially useful for static/serverless extension hosting such as Cloudflare Pages.

state.put

What it does:

  • Replaces the entire state object with the new data object.

When to use:

  • Initial save.
  • Full overwrite when you already have the complete new state.

Example:

{
"action": "state.put",
"payload": {
"data": {
"counter": 1,
"notes": "first test"
}
}
}

Field notes:

  • payload.data: any valid JSON value/object your extension needs.
  • Avoid Mongo-unsafe key names ($ prefix or keys containing .).

state.patch

What it does:

  • Applies a JSON merge patch to existing state.
  • Only changed keys need to be sent.

When to use:

  • Incremental updates from user interactions.
  • Updating one field without resending everything.

Example:

{
"action": "state.patch",
"payload": {
"data": {
"counter": 2
}
}
}

state.get

What it does:

  • Reads the current extension state.

When to use:

  • On iframe load.
  • After writes, if you want to re-sync local UI.

Example:

{
"action": "state.get",
"payload": {}
}

Lock Actions

Add time

Action: lock.applyTime

What it does:

  • Adds or removes time from the lock countdown depending on deltaSeconds.

When to use:

  • Reward/penalty buttons.
  • Game outcomes (win adds time, lose removes time).

Example:

{
"action": "lock.applyTime",
"payload": {
"deltaSeconds": 300 // +300 sec = +5 minutes
}
}

Field notes:

  • Positive value adds time.
  • Negative value removes time (if permitted by server rules).

Freeze

Action: lock.freeze

What it does:

  • Freezes lock progression for a duration.

When to use:

  • Cooldown mechanics.
  • Reward checkpoints.

Example:

{
"action": "lock.freeze",
"payload": {
"durationSeconds": 120
}
}

You can also call it without durationSeconds:

{
"action": "lock.freeze",
"payload": {}
}

Field notes:

  • durationSeconds is optional.
  • If omitted, the current default is 3600 seconds (1 hour).
  • Accepted range is 60 to 86400 seconds.

Unfreeze

Action: lock.unfreeze

What it does:

  • Ends active freeze and resumes normal timer behavior.

When to use:

  • Manual override in extension workflows.
  • “Cancel freeze” controls.

Example:

{
"action": "lock.unfreeze",
"payload": {}
}

Pillory

Action: lock.action

What it does:

  • Starts a pillory period for the active lock session.

When to use:

  • Penalty mechanics after failed tasks/challenges.
  • Escalation flows that temporarily restrict lock interaction.

Example:

{
"action": "lock.action",
"payload": {
"name": "pillory",
"params": {
"durationSeconds": 600,
"reason": "Missed scheduled check-in"
}
}
}

Field notes:

  • name must be pillory.
  • params.durationSeconds is required.
  • params.reason is optional.
  • Requires pillory to be enabled in the session configuration.

End pillory

Action: lock.action

What it does:

  • Ends the current active pillory session immediately.

Example:

{
"action": "lock.action",
"payload": {
"name": "pillory.end",
"params": {}
}
}

Field notes:

  • name must be pillory.end.
  • Fails with pillory_not_active if the lock is not currently in pillory.

Assign task

Action: task.assign

What it does:

  • Creates an active task run for the wearer from extension logic.
  • Can override an already-open task run.

Example:

{
"action": "task.assign",
"payload": {
"taskText": "Clean your room",
"points": 10,
"verificationRequired": true,
"durationSeconds": 1800
}
}

Field notes:

  • taskText is required.
  • points is optional and clamped server-side.
  • verificationRequired defaults to false.
  • durationSeconds is optional (0 means no timer requirement).
  • Requires Tasks module enabled on the lock.

Start task timer

Action: task.start_timer

What it does:

  • Starts or restarts the countdown window for the currently active timed task.

Example:

{
"action": "task.start_timer",
"payload": {}
}

Field notes:

  • Requires an active task run.
  • Fails if the current task has no duration configured.

Complete task

Action: task.complete

What it does:

  • Marks the active task run as completed or failed.

Example (success):

{
"action": "task.complete",
"payload": {
"successful": true
}
}

Example (fail):

{
"action": "task.complete",
"payload": {
"successful": false,
"reason": "Did not finish in time"
}
}

Trigger temporary opening

Action: hygienic_unlock.start

What it does:

  • Starts a temporary hygiene opening window from extension logic.

Example:

{
"action": "hygienic_unlock.start",
"payload": {
"durationSeconds": 900
}
}

Field notes:

  • Requires Hygienic Opening enabled on the lock.
  • Fails if a hygiene opening is already in progress.
  • durationSeconds is optional; lock default is used when omitted.

Metadata and Home Actions

metadata.patch

What it does:

  • Stores extension metadata used by lock-page UI.
  • Supports unlockBlockers and homeActions.
  • Supports homeActions[].intent for deep-link behavior when extension opens.

When to use:

  • Enforce lock-session unlock conditions owned by your extension.
  • Add quick actions on lock page that open your extension with intent.
  • Route users directly to a specific screen/workflow when they click a home action.

Example:

{
"action": "metadata.patch",
"payload": {
"unlockBlockers": ["Finish extension task"],
"homeActions": [
{
"slug": "tasks",
"title": "Tasks",
"description": "Open tasks panel",
"intent": {
"type": "open_panel",
"title": "Tasks",
"message": "Open tasks panel",
"payload": {
"panel": "regular-actions"
}
}
}
]
}
}

Field notes:

  • unlockBlockers: list of active lock-session unlock blockers from your extension.
  • You can include multiple blockers at once (up to 25), one per unmet condition.
  • Unlock remains blocked while any blocker exists across enabled extensions.
  • Chastify aggregates blockers from all extensions for the lock session.
  • Your extension should only add/remove its own blockers in its own metadata.
  • Do not clear blockers from other extensions; clear your array only when your own conditions are satisfied.
  • homeActions: quick action buttons displayed in lock experience.
  • homeActions[].slug: stable id for your action.
  • homeActions[].title: user-facing label.
  • homeActions[].description: optional helper text.
  • homeActions[].intent: optional deep-link instruction passed to your extension when opened.
  • In the extension card UI, these actions are shown as a menu/list by action title (internally keyed by slug).
  • When a user clicks one, Chastify opens the extension and passes:
    • homeActionSlug
    • homeAction (selected action object)
    • intent (normalized intent object) so your extension can immediately route to the correct view/action on load.

Intents: developer app example

Use this pattern in your extension app to react to menu-click intents on load.

import { useEffect, useRef } from "react";
import { parseHashPayload, type IframeHashPayload } from "../lib/ChastifyBridge";

export function useHomeActionIntent(
payload: IframeHashPayload,
routeToPanel: (panel: string) => void,
showToast: (message: string) => void,
) {
const handledRef = useRef<string>("");

useEffect(() => {
const homeActionSlug = payload?.homeActionSlug ?? null;
if (!homeActionSlug) return;

// Prevent duplicate handling if component re-renders.
const key = `${payload.lockId || "lock"}:${homeActionSlug}`;
if (handledRef.current === key) return;
handledRef.current = key;

const intent = payload?.intent ?? payload?.homeAction?.intent ?? null;
if (!intent) return;

if (intent.type === "open_panel") {
const panel = String(intent.payload?.panel || "");
if (panel) routeToPanel(panel);
return;
}

if (intent.message) {
showToast(String(intent.message));
}
}, [payload, routeToPanel, showToast]);
}

// Example app bootstrap
const payload = parseHashPayload();
if (!payload) throw new Error("Missing iframe hash payload");

What this example does:

  • Reads homeActionSlug + intent from iframe hash payload.
  • Handles each click only once per lock/action slug.
  • Routes to a panel when intent is open_panel.
  • Falls back to showing an intent message for custom intent types.

Device Command

device.command

What it does:

  • Sends a device control command (if supported for that session/device).
  • Lets your extension trigger standardized shock/vibration actions through Chastify.

When to use:

  • Triggering supported shock/vibration commands from extension logic.
  • Building interactive extension features (games, punishments, rewards, routines).

Example:

{
"action": "device.command",
"payload": {
"command": "shock.start",
"params": {
"durationSeconds": 30,
"intensityPct": 50
}
}
}

Common commands:

  • shock.start with params: { durationSeconds, intensityPct, message? }
  • shock.stop
  • vibration.start with params: { durationSeconds, intensityPct, frequencyPct?, message? }
  • vibration.stop
  • all.stop
  • shock.random.set with params: { enabled, minIntensityPct?, maxIntensityPct?, message? } (Lockink AA-A1012 only)
  • shock.berserk.set with params: { enabled, message? } (Lockink AA-A1012 only)

Recommended flow:

  1. Call session.get on load.
  2. Read device capabilities from deviceControl.supportedCommands.
  3. Render only controls for supported commands.
  4. Send device.command with validated values.
  5. Refresh or trust response active flags for live UI state.

Parameter guidance:

  • durationSeconds: server clamps to safe limits (current policy max is 300s).
  • intensityPct: expected percentage value (1-100 style input, clamped server-side).
  • frequencyPct: expected percentage value (1-100) for vibration flows (clamped server-side).
  • For random mode, ensure minIntensityPct <= maxIntensityPct.

Safer UI pattern example:

// 1) Get capabilities first
const session = await bridge.request("session.get", {});
const supported = new Set(session?.deviceControl?.supportedCommands ?? []);

// 2) Only call command if supported
if (supported.has("shock.start")) {
await bridge.request("device.command", {
command: "shock.start",
params: { durationSeconds: 30, intensityPct: 50 }
});
}

Important:

  • Call session.get first and read supported commands.
  • Only show controls for commands that are supported in current session.
  • Validate user inputs before sending command params.
  • device.command requires write permission (locks:write) for the extension session.
  • Handle bridge/server errors gracefully (insufficient_scope, unsupported command, validation errors).
  1. Call session.get on startup.
  2. Read state with state.get.
  3. Perform writes with state.patch/state.put.
  4. Run lock/device actions only when supported and visible in UI.
  5. Refresh local view after important writes/actions.