Skip to main content

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:

  1. Iframe mode (fastest to try): your extension iframe sends bridge messages (postMessage) and Chastify calls /api/extensions/*.
  2. 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)

  1. Read location.hash payload from Chastify iframe open.
  2. Create a bridge request for session.get.
  3. Confirm response with type: "chastify:ext:resp" and ok: true.
  4. Test state persistence with state.put then state.get.
  5. Add auto-resize + theme support so the iframe behaves correctly in UI.

Required payload values:

  • bridge.nonce
  • bridge.parentOrigin
  • sessionId
  • lockId

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.get
  • state.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.patch
  • regularActions.get, regularActions.submit
  • device.command
  • logs.custom
  • notifications.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/enabled
  • GET https://chastify.net/api/extensions/sessions/:sessionId/auth
  • GET https://chastify.net/api/extensions/sessions/:sessionId
  • GET https://chastify.net/api/extensions/sessions/:sessionId/state
  • PUT https://chastify.net/api/extensions/sessions/:sessionId/state
  • PATCH https://chastify.net/api/extensions/sessions/:sessionId/state
  • GET https://chastify.net/api/extensions/sessions/:sessionId/metadata
  • PATCH https://chastify.net/api/extensions/sessions/:sessionId/metadata
  • PATCH https://chastify.net/api/extensions/sessions/:sessionId/regular-actions/config
  • GET https://chastify.net/api/extensions/sessions/:sessionId/regular-actions
  • POST https://chastify.net/api/extensions/sessions/:sessionId/regular-actions
  • POST https://chastify.net/api/extensions/sessions/:sessionId/logs/custom
  • POST https://chastify.net/api/extensions/sessions/:sessionId/notifications/custom
  • POST https://chastify.net/api/extensions/sessions/:sessionId/device-command
  • POST https://chastify.net/api/extensions/sessions/:sessionId/action

Compat routes:

  • GET https://chastify.net/api/extensions/:lockId/session?appId=... (or slug=...)
  • GET https://chastify.net/api/extensions/:lockId/state?appId=... (or slug=...)
  • PUT https://chastify.net/api/extensions/:lockId/state?appId=... (or slug=...)
  • PATCH https://chastify.net/api/extensions/:lockId/state?appId=... (or slug=...)
  • POST https://chastify.net/api/extensions/:lockId/action?appId=... (or slug=...)
  • GET https://chastify.net/api/extensions/:lockId/auth?appId=... (or slug=...)

Backend token APIs (/api/apps/v1/*)

Use Authorization: Bearer <token> from session auth issuance.

  • GET https://chastify.net/api/apps/v1/session
  • GET https://chastify.net/api/apps/v1/state
  • PUT https://chastify.net/api/apps/v1/state
  • PATCH https://chastify.net/api/apps/v1/state
  • GET https://chastify.net/api/apps/v1/metadata
  • PATCH https://chastify.net/api/apps/v1/metadata
  • POST https://chastify.net/api/apps/v1/action
  • POST https://chastify.net/api/apps/v1/lock/apply-time
  • POST https://chastify.net/api/apps/v1/lock/freeze
  • POST https://chastify.net/api/apps/v1/lock/unfreeze
  • POST 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/:sessionId
  • state.get -> GET https://chastify.net/api/extensions/sessions/:sessionId/state
  • state.put -> PUT https://chastify.net/api/extensions/sessions/:sessionId/state
  • state.patch -> PATCH https://chastify.net/api/extensions/sessions/:sessionId/state
  • metadata.get -> GET https://chastify.net/api/extensions/sessions/:sessionId/metadata
  • metadata.patch -> PATCH https://chastify.net/api/extensions/sessions/:sessionId/metadata
  • regularActions.get -> GET https://chastify.net/api/extensions/sessions/:sessionId/regular-actions
  • regularActions.submit -> POST https://chastify.net/api/extensions/sessions/:sessionId/regular-actions
  • regularActions.configure -> PATCH https://chastify.net/api/extensions/sessions/:sessionId/regular-actions/config
  • lock.applyTime -> POST https://chastify.net/api/extensions/sessions/:sessionId/action with { "name": "add_time", "params": <deltaSeconds> }
  • lock.freeze -> POST https://chastify.net/api/extensions/sessions/:sessionId/action with { "name": "freeze", "params": { "durationSeconds": ... } }
  • lock.unfreeze -> POST https://chastify.net/api/extensions/sessions/:sessionId/action with { "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/action with { "name": "task.assign", "params": { "taskText": "Drink water", "points": 5, "verificationRequired": false, "durationSeconds": 900 } }
  • task.start_timer -> POST https://chastify.net/api/extensions/sessions/:sessionId/action with { "name": "task.start_timer", "params": {} }
  • task.complete -> POST https://chastify.net/api/extensions/sessions/:sessionId/action with { "name": "task.complete", "params": { "successful": true } }
  • hygienic_unlock.start -> POST https://chastify.net/api/extensions/sessions/:sessionId/action with { "name": "hygienic_unlock.start", "params": { "durationSeconds": 900 } }
  • lock.action -> POST https://chastify.net/api/extensions/sessions/:sessionId/action with { "name": "pillory.end", "params": {} } to end active pillory
  • device.command -> POST https://chastify.net/api/extensions/sessions/:sessionId/device-command
  • logs.custom -> POST https://chastify.net/api/extensions/sessions/:sessionId/logs/custom
  • notifications.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_time
  • remove_time
  • freeze
  • pillory
  • unfreeze
  • toggle_freeze
  • settings.patch
  • task.assign
  • task.start_timer
  • task.complete
  • hygienic_unlock.start
  • pillory.end

Action constraints:

  • Task actions require Tasks extension/module enabled on the lock.
  • hygienic_unlock.start requires 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:

  • wearerLastSeenTimestamp and keyholderLastSeenTimestamp are null when that user disabled online status (showOnlineStatus === false).

/api/extensions/sessions/:sessionId/device-command

command supports:

  • shock.start
  • shock.stop
  • vibration.start
  • vibration.stop
  • all.stop
  • shock.random.set
  • shock.berserk.set

If your extension has setup/config UI:

  1. Parent sends chastify:ext:setup:init (saved config + context).
  2. Your setup iframe returns updates with chastify:ext:setup:config.
  3. 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:

  1. Chastify automatically issues the token for the active extension session.
  2. Token is embedded into iframe hash payload as mainToken.
  3. Your extension can forward that token to your backend.
  4. Your backend calls https://chastify.net/api/apps/v1/* using Authorization: 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.patch instead 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.tsx
  • apps/extension/src/pages/SetupPage.tsx
  • apps/extension/src/lib/ChastifyBridge.ts

For full endpoint details and payload references:

  • docs/EXTENSIONS_API.md

Next Guides