Try The Extensions Developer API
Use this guide if you want to build and test a Chastify extension and call the Developer API yourself.
This page is the starting point: what mode to choose, what to call first, and where to go next.
What This API Is For
The Extensions Developer API lets you build third-party extension experiences that run inside Chastify lock sessions.
With it, you can:
- Read session and lock context (
session.get//api/apps/v1/session) - Store extension-owned data per lock session (
state.*) - Add extension UI actions on lock cards (
metadata.homeActions) - Gate unlock flow with extension-owned unlock blockers (
metadata.unlockBlockers) - Trigger lock actions (add/remove time, freeze/unfreeze, settings patch)
- Trigger task and hygiene actions (
task.assign,task.start_timer,task.complete,hygienic_unlock.start) - Submit regular actions with counters/cadence support
- Send supported device commands when available
- Write custom extension log entries to lock history
What You Can Build
Common extension types:
- Task or habit systems with unlock conditions
- Dice/game mechanics that reward or penalize lock time
- Fitness or routine trackers with regular action submissions
- Rule engines that surface quick actions via home action intents
- Device-control companion flows that call supported commands
- Lightweight “no-backend” tools hosted on Cloudflare Pages using
state.*as storage
Proof-of-concept ideas:
- Daily checklist extension: block unlock until all checklist items are complete
- Roll-to-decide extension: use dice rolls to add/freeze time with logged history
- Challenge extension: track streaks and expose one-click home actions
Choose Your Mode
You can use the API in two ways:
Iframe mode(fastest to try): your extension iframe sends bridge messages (postMessage) and Chastify calls/api/extensions/*.Backend mode(recommended for production automation): your backend uses a bearer token with/api/apps/v1/*.
If you are testing quickly, start with iframe mode.
First 10 Minutes (Iframe Mode)
- Read
location.hashpayload from Chastify iframe open. - Create a bridge request for
session.get. - Confirm response with
type: "chastify:ext:resp"andok: true. - Test state persistence with
state.putthenstate.get. - Add auto-resize + theme support so the iframe behaves correctly in UI.
Required payload values:
bridge.noncebridge.parentOriginsessionIdlockId
Example bridge request:
{
"type": "chastify:ext:req",
"v": 1,
"id": "request-id", // unique id per request
"nonce": "nonce-from-hash",
"action": "session.get",
"payload": {}
}
Example bridge response:
{
"type": "chastify:ext:resp",
"v": 1,
"id": "request-id",
"ok": true,
"data": {}
}
Core Actions To Learn First
session.getstate.get,state.put,state.patch
Use state as extension-owned JSON storage for the lock session (useful even with no backend, e.g. static hosting on Cloudflare Pages).metadata.patch
Use for lock-session unlock blockers and extension-card home actions/intents.lock.applyTime,lock.freeze,lock.unfreeze,lock.settings.patchregularActions.get,regularActions.submitdevice.commandlogs.customnotifications.custom
Full API URLs (Supported)
Base domain: https://chastify.net
Iframe bridge/session APIs (/api/extensions/*)
These are called by Chastify parent after your iframe bridge requests, or directly by authenticated first-party clients.
GET https://chastify.net/api/extensions/:lockId/enabledGET https://chastify.net/api/extensions/sessions/:sessionId/authGET https://chastify.net/api/extensions/sessions/:sessionIdGET https://chastify.net/api/extensions/sessions/:sessionId/statePUT https://chastify.net/api/extensions/sessions/:sessionId/statePATCH https://chastify.net/api/extensions/sessions/:sessionId/stateGET https://chastify.net/api/extensions/sessions/:sessionId/metadataPATCH https://chastify.net/api/extensions/sessions/:sessionId/metadataPATCH https://chastify.net/api/extensions/sessions/:sessionId/regular-actions/configGET https://chastify.net/api/extensions/sessions/:sessionId/regular-actionsPOST https://chastify.net/api/extensions/sessions/:sessionId/regular-actionsPOST https://chastify.net/api/extensions/sessions/:sessionId/logs/customPOST https://chastify.net/api/extensions/sessions/:sessionId/notifications/customPOST https://chastify.net/api/extensions/sessions/:sessionId/device-commandPOST https://chastify.net/api/extensions/sessions/:sessionId/action
Compat routes:
GET https://chastify.net/api/extensions/:lockId/session?appId=...(orslug=...)GET https://chastify.net/api/extensions/:lockId/state?appId=...(orslug=...)PUT https://chastify.net/api/extensions/:lockId/state?appId=...(orslug=...)PATCH https://chastify.net/api/extensions/:lockId/state?appId=...(orslug=...)POST https://chastify.net/api/extensions/:lockId/action?appId=...(orslug=...)GET https://chastify.net/api/extensions/:lockId/auth?appId=...(orslug=...)
Backend token APIs (/api/apps/v1/*)
Use Authorization: Bearer <token> from session auth issuance.
GET https://chastify.net/api/apps/v1/sessionGET https://chastify.net/api/apps/v1/statePUT https://chastify.net/api/apps/v1/statePATCH https://chastify.net/api/apps/v1/stateGET https://chastify.net/api/apps/v1/metadataPATCH https://chastify.net/api/apps/v1/metadataPOST https://chastify.net/api/apps/v1/actionPOST https://chastify.net/api/apps/v1/lock/apply-timePOST https://chastify.net/api/apps/v1/lock/freezePOST https://chastify.net/api/apps/v1/lock/unfreezePOST https://chastify.net/api/apps/v1/logs/custom
Bridge Commands -> Full URL Mapping
Bridge command payloads are sent by iframe (chastify:ext:req) and routed by parent:
session.get->GET https://chastify.net/api/extensions/sessions/:sessionIdstate.get->GET https://chastify.net/api/extensions/sessions/:sessionId/statestate.put->PUT https://chastify.net/api/extensions/sessions/:sessionId/statestate.patch->PATCH https://chastify.net/api/extensions/sessions/:sessionId/statemetadata.get->GET https://chastify.net/api/extensions/sessions/:sessionId/metadatametadata.patch->PATCH https://chastify.net/api/extensions/sessions/:sessionId/metadataregularActions.get->GET https://chastify.net/api/extensions/sessions/:sessionId/regular-actionsregularActions.submit->POST https://chastify.net/api/extensions/sessions/:sessionId/regular-actionsregularActions.configure->PATCH https://chastify.net/api/extensions/sessions/:sessionId/regular-actions/configlock.applyTime->POST https://chastify.net/api/extensions/sessions/:sessionId/actionwith{ "name": "add_time", "params": <deltaSeconds> }lock.freeze->POST https://chastify.net/api/extensions/sessions/:sessionId/actionwith{ "name": "freeze", "params": { "durationSeconds": ... } }lock.unfreeze->POST https://chastify.net/api/extensions/sessions/:sessionId/actionwith{ "name": "unfreeze" }lock.settings.patch->POST https://chastify.net/api/extensions/:lockId/action?appId=...with{ "name": "settings.patch", "params": ... }task.assign->POST https://chastify.net/api/extensions/sessions/:sessionId/actionwith{ "name": "task.assign", "params": { "taskText": "Drink water", "points": 5, "verificationRequired": false, "durationSeconds": 900 } }task.start_timer->POST https://chastify.net/api/extensions/sessions/:sessionId/actionwith{ "name": "task.start_timer", "params": {} }task.complete->POST https://chastify.net/api/extensions/sessions/:sessionId/actionwith{ "name": "task.complete", "params": { "successful": true } }hygienic_unlock.start->POST https://chastify.net/api/extensions/sessions/:sessionId/actionwith{ "name": "hygienic_unlock.start", "params": { "durationSeconds": 900 } }lock.action->POST https://chastify.net/api/extensions/sessions/:sessionId/actionwith{ "name": "pillory.end", "params": {} }to end active pillorydevice.command->POST https://chastify.net/api/extensions/sessions/:sessionId/device-commandlogs.custom->POST https://chastify.net/api/extensions/sessions/:sessionId/logs/customnotifications.custom->POST https://chastify.net/api/extensions/sessions/:sessionId/notifications/custom
notifications.custom payload:
{
"title": "Reminder",
"message": "Finish your next task.",
"showPageOverlay": false,
"target": "wearer"
}
Supported Command Values
/api/extensions/sessions/:sessionId/action and /api/apps/v1/action
name supports:
add_timeremove_timefreezepilloryunfreezetoggle_freezesettings.patchtask.assigntask.start_timertask.completehygienic_unlock.startpillory.end
Action constraints:
- Task actions require Tasks extension/module enabled on the lock.
hygienic_unlock.startrequires Hygienic Opening enabled and no active hygiene session.
session.get lock data helpers
session.get / GET /api/apps/v1/session also includes lockData with runtime-friendly booleans, numbers, and strings for rule engines.
Examples:
- booleans:
frozen,unlockable,trusted,taskAssigned - numbers:
timeLockedSeconds,timeRemainingSeconds,maxTimeRemainingSeconds,taskPoints - strings:
lockTitle, wearer/keyholder profile fields
Privacy:
wearerLastSeenTimestampandkeyholderLastSeenTimestamparenullwhen that user disabled online status (showOnlineStatus === false).
/api/extensions/sessions/:sessionId/device-command
command supports:
shock.startshock.stopvibration.startvibration.stopall.stopshock.random.setshock.berserk.set
Setup Page (Optional, Recommended)
If your extension has setup/config UI:
- Parent sends
chastify:ext:setup:init(saved config + context). - Your setup iframe returns updates with
chastify:ext:setup:config. - Parent can request current config with
chastify:ext:setup:get_config.
Backend Token Flow (When You Need Server-Side Calls)
Default flow in extension iframe mode:
- Chastify automatically issues the token for the active extension session.
- Token is embedded into iframe hash payload as
mainToken. - Your extension can forward that token to your backend.
- Your backend calls
https://chastify.net/api/apps/v1/*usingAuthorization: Bearer <token>.
Manual fallback:
- If you need to fetch/rotate explicitly, call
GET https://chastify.net/api/extensions/sessions/:sessionId/auth.
Use backend mode if you need scheduled jobs, webhooks, or automation while the Chastify page is not open.
Backend vs Cloudflare Pages (No Server)
Use Cloudflare Pages (no backend server) when:
- You want the easiest and cheapest setup (can be hosted for free).
- You only need UI-driven actions while the user is actively on your extension page.
- You can store extension data with
state.get/state.put/state.patchinstead of your own database. - You are prototyping or building lightweight extensions quickly.
Local testing example (PowerShell):
cloudflared tunnel --url http://localhost:5174
Use the generated public URL as your iframe URL while testing.
Use a backend server when:
- You need scheduled jobs (cron-like behavior).
- You need webhooks or external integrations.
- You need automation/background processing when no one is on the extension page.
- You need server-controlled workflows that must run continuously.
Important limitation without a backend:
- No background execution.
Your extension can only take actions while a user currently has the extension iframe open and is interacting with it.
Common Issues
403 extension_not_enabled: extension is not enabled for this lock.409 lock_ended: lock is no longer active.429: rate limit reached.- No responses in iframe: check
nonce,targetOrigin(parentOrigin), and allowed origins.
Working Code References
If you want a working example you can copy from:
apps/extension/src/pages/MainPage.tsxapps/extension/src/pages/SetupPage.tsxapps/extension/src/lib/ChastifyBridge.ts
For full endpoint details and payload references:
docs/EXTENSIONS_API.md