Skip to main content

Try The Extensions Developer API

Use this guide if you want to build a Chastify extension, host an iframe extension page, or call the Developer API from your own backend.

This page is the starting point: what mode to choose, what to call first, and where to go next.

For concrete extension behavior such as unlock blockers, regular required actions, rewards, and punishments, see Extension API Features.

tip

Just want to control your own lock?

If you don't need to build a public extension, the External API & Programs page is the easiest way to get started. You just create a DEV token and call simple REST endpoints — no extension setup, no iframe, no session management required. It supports add/remove time, freeze, tasks, device commands, and more.

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 for extensions, /api/apps/v1/session for your own lock automation)
  • Read extension-owned data per lock session (state.get) and write it from your backend (PUT/PATCH /state)
  • Store extension-owned image files with Chastify-managed R2 storage (files.*)
  • Add extension UI actions on lock cards (metadata.homeActions)
  • Gate unlock flow with extension-owned unlock blockers (metadata.unlockBlockers)
  • Trigger lock actions from a trusted backend (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

The right feature set depends on where trust lives.

Frontend-only iframe extensions can build UI-first experiences that do not need trusted lock mutation:

  • Setup pages that collect extension configuration
  • Dashboards that read session context
  • Puzzle, checklist, or game UIs that read session state and send verified progress through a backend
  • Media-based flows that read extension files already stored by Chastify
  • Home action entry points that open your iframe with an intent

Server-backed extensions can build lock-affecting features because your backend verifies outcomes before calling privileged APIs:

  • Task or habit systems with unlock requirements
  • Daily or weekly requirements with scheduled missed-window punishments
  • Games that reward success or penalize failure with lock time changes
  • Verification flows that clear unlock blockers after server-side validation
  • Device-control companion flows using supported device commands
  • Webhook/database workflows that keep extension state outside the iframe

External programs are for private automation of your own active lock:

  • Local scripts
  • Personal dashboards
  • Automation tools that use a user-wide DEV key

Choose Your Mode

Choose one of these modes:

  1. Hosted iframe extension: host a static iframe UI on Cloudflare Pages or a similar service. Use the bridge for setup, session context, and safe reads. Do not use this mode alone for state writes, rewards, punishments, unlock completion, or trusted requirement progress.
  2. Server-backed extension: host the iframe UI and run your own backend. The iframe sends its launch mainToken to your backend, and your backend calls the Chastify Extension API with an app-scoped Developer API key plus x-chastify-main-token. Use this mode for privileged actions, unlock blockers, trusted progress, rewards, punishments, webhooks, and external databases.
  3. External API & Programs: use a user-wide DEV key for scripts, local programs, or automations that control your own active lock. This is not the mode for third-party users installing your extension.

If you are testing quickly, start with iframe mode for UI and safe reads. Add a backend before implementing state writes, trusted rewards, time changes, scheduled requirement progress, or unlock-blocker completion.

caution

Iframe code is not a trust boundary. Anything visible to the iframe, including hash payloads and launch tokens, can be inspected and replayed by the user.

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 reads with state.get.
  5. Add auto-resize + theme support so the iframe behaves correctly in UI.

Theme support is part of a production-ready iframe. Chastify passes ui values in the launch hash and sends live theme updates while the iframe is open. See Iframe Theming for light/dark examples and contrast-safe Tailwind patterns.

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
    Read extension-owned JSON storage for the lock session. Write state from your backend with Developer API credentials.
  • files.capabilities, files.list, files.get Use file storage reads for binary media such as puzzle images or generated previews. Store file ids in backend-written state, then refresh signed URLs with files.get.
  • metadata.get Read lock-session unlock blockers and extension-card home actions/intents.
  • regularActions.get

Session mutations such as state writes, regular action submission, runtime file upload/delete, time changes, unlock blocker updates, task completion, hygiene starts, and device commands must be called from your backend with a Developer API key. Browser iframe code is not trusted for these actions.

Full API URLs (Supported)

Base domain: https://chastify.net

Extension session APIs (/api/extensions/*)

These routes have different access modes. Do not treat the whole /api/extensions/* surface as iframe-safe.

Safe iframe bridge routes are routed through Chastify parent after postMessage bridge requests:

  • GET https://chastify.net/api/extensions/sessions/:sessionId
  • GET https://chastify.net/api/extensions/sessions/:sessionId/state
  • GET https://chastify.net/api/extensions/sessions/:sessionId/metadata
  • GET https://chastify.net/api/extensions/sessions/:sessionId/regular-actions
  • GET https://chastify.net/api/extensions/sessions/:sessionId/files/capabilities
  • GET https://chastify.net/api/extensions/sessions/:sessionId/files
  • GET https://chastify.net/api/extensions/sessions/:sessionId/files/:fileId

Backend-only installed-extension routes require an app-scoped Developer API key and the iframe launch token:

Authorization: Bearer YOUR_APP_SCOPED_DEVELOPER_KEY
x-chastify-main-token: MAIN_TOKEN_FROM_IFRAME_HASH
info

This two-token model binds a backend request to both the extension developer (Authorization) and the currently opened extension session (x-chastify-main-token).

  • PATCH https://chastify.net/api/extensions/sessions/:sessionId/metadata
  • PUT https://chastify.net/api/extensions/sessions/:sessionId/state
  • PATCH https://chastify.net/api/extensions/sessions/:sessionId/state
  • PATCH https://chastify.net/api/extensions/sessions/:sessionId/regular-actions/config
  • POST https://chastify.net/api/extensions/sessions/:sessionId/regular-actions
  • POST https://chastify.net/api/extensions/sessions/:sessionId/files
  • DELETE https://chastify.net/api/extensions/sessions/:sessionId/files/:fileId
  • 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
  • POST https://chastify.net/api/extensions/sessions/:sessionId/requirements/progress

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

Use Authorization: Bearer <user-wide DEV token>. These endpoints manage the token owner's own active lock sessions and are intended for External API scripts/programs, not installed third-party extension sessions.

  • 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

Iframe Bridge Commands

Bridge command payloads are sent by iframe (chastify:ext:req) and routed by the Chastify parent. The bridge is intentionally limited to safe/session UI operations.

  • session.get -> GET https://chastify.net/api/extensions/sessions/:sessionId
  • state.get -> GET https://chastify.net/api/extensions/sessions/:sessionId/state
  • files.capabilities -> GET https://chastify.net/api/extensions/sessions/:sessionId/files/capabilities
  • files.list -> GET https://chastify.net/api/extensions/sessions/:sessionId/files
  • files.get -> GET https://chastify.net/api/extensions/sessions/:sessionId/files/:fileId with { "fileId": "file_record_id" }
  • metadata.get -> GET https://chastify.net/api/extensions/sessions/:sessionId/metadata
  • regularActions.get -> GET https://chastify.net/api/extensions/sessions/:sessionId/regular-actions

Session mutation endpoints are direct backend API calls, not iframe bridge commands. This includes state writes, regular action submission, and runtime file upload/delete, because iframe code can be controlled by the user.

Backend Session API Examples

Your backend must send both headers for installed-extension privileged calls:

Authorization: Bearer YOUR_APP_SCOPED_DEVELOPER_KEY
x-chastify-main-token: MAIN_TOKEN_FROM_IFRAME_HASH

Backend action examples:

  • metadata.patch -> PATCH /api/extensions/sessions/:sessionId/metadata
  • regularActions.submit -> POST /api/extensions/sessions/:sessionId/regular-actions
  • files.upload -> POST /api/extensions/sessions/:sessionId/files
  • files.delete -> DELETE /api/extensions/sessions/:sessionId/files/:fileId
  • lock.applyTime -> POST /api/extensions/sessions/:sessionId/action with { "name": "add_time", "params": <deltaSeconds> }
  • lock.freeze -> POST /api/extensions/sessions/:sessionId/action with { "name": "freeze", "params": { "durationSeconds": 900 } }
  • lock.unfreeze -> POST /api/extensions/sessions/:sessionId/action with { "name": "unfreeze", "params": {} }
  • lock.settings.patch -> POST /api/extensions/sessions/:sessionId/action with { "name": "settings.patch", "params": { ... } }
  • task.assign -> POST /api/extensions/sessions/:sessionId/action
  • task.start_timer -> POST /api/extensions/sessions/:sessionId/action with { "name": "task.start_timer", "params": {} }
  • task.complete -> POST /api/extensions/sessions/:sessionId/action with { "name": "task.complete", "params": { "successful": true } }
  • hygienic_unlock.start -> POST /api/extensions/sessions/:sessionId/action with { "name": "hygienic_unlock.start", "params": { "durationSeconds": 900 } }
  • pillory.end -> POST /api/extensions/sessions/:sessionId/action with { "name": "pillory.end", "params": {} }
  • device.command -> POST /api/extensions/sessions/:sessionId/device-command
  • logs.custom -> POST /api/extensions/sessions/:sessionId/logs/custom
  • notifications.custom -> POST /api/extensions/sessions/:sessionId/notifications/custom
  • requirements.progress -> POST /api/extensions/sessions/:sessionId/requirements/progress

Token, Scope, Revocation, and Audit Behavior

Use the right token for the right trust boundary.

warning

Developer API keys are secrets. If one is exposed to browser code, revoke it immediately and rotate the backend environment variable.

Iframe launch token (mainToken)

  • Delivered in the iframe hash when a user opens an installed extension page.
  • Browser-visible by design. It identifies the opened extension session, but it is not a backend secret.
  • Short-lived. Current launch tokens expire after 10 hours; refresh by reopening the extension page.
  • Required as x-chastify-main-token when your backend calls installed-extension session routes, so Chastify can bind the backend request to the user/session that opened the extension.
  • Must not be used by itself to authorize time changes, unlock blocker completion, task completion, device commands, runtime uploads/deletes, custom logs, or custom notifications.

App-scoped Developer API key

  • Created from the Developer UI for one extension app.
  • Backend-only secret. Never put it in iframe JavaScript, mobile app bundles, static hosting config, or browser-readable logs.
  • Used with Authorization: Bearer YOUR_APP_SCOPED_DEVELOPER_KEY and x-chastify-main-token.
  • Can call installed-extension session APIs only for sessions that match the extension app and launch token.
  • Does not expire automatically. Revoke it immediately if exposed, and rotate your backend environment variable.

User-wide Developer API key

  • Created from the Developer UI without selecting an extension app.
  • Backend-only secret for /api/apps/v1/*.
  • Controls the key owner's own current/future active lock sessions.
  • Cannot be used as an installed third-party extension backend credential.

Revocation

  • Revoked Developer API keys stop authorizing new requests.
  • Revoked keys can be permanently deleted from the Developer UI.
  • New iframe launches receive fresh launch tokens. Do not store mainToken as a long-term credential.

Scopes and Roles

  • Extension app scopes describe what the app is allowed to request.
  • Safe iframe bridge calls are limited to UI bootstrap, session reads, extension-owned state, metadata reads, regular action reads, and file reads.
  • Privileged installed-session mutations require backend credentials even when the extension has a matching scope.
  • Role-sensitive actions may still be rejected based on whether the launch belongs to a wearer or keyholder.

Audit and Limits

  • Developer API key last-used metadata is updated when keys are used.
  • Privileged action routes are rate limited and return explicit errors such as server_credentials_required or user_wide_dev_key_required when the wrong credential type is used.
  • Custom logs write visible lock history entries.
  • Custom notifications create Chastify notifications for the requested target.
  • Full audit coverage for every privileged extension mutation is tracked as a production hardening item.

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 (true when an open TaskRun exists)
  • 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).

Device Commands

Extension sessions can use the session-based endpoint:

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

External programs with a DEV token can use the simpler v1 endpoint (no session ID needed):

POST /api/apps/v1/device-command

Both accept the same request body and return the same response. See External API & Programs for full details.

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)

For simple scripts and external programs, use a user-wide DEV token from the Developer API page. See External API & Programs.

Default flow in extension iframe mode:

  1. Chastify issues a short-lived browser-visible launch token for the active extension session.
  2. The launch token is embedded into iframe hash payload as mainToken.
  3. Your iframe forwards mainToken to your backend.
  4. Your backend calls https://chastify.net/api/extensions/sessions/:sessionId/* with Authorization: Bearer <app-scoped Developer API key> and x-chastify-main-token: <mainToken>.

Do not send Developer API keys to iframe/browser code. mainToken identifies the opened extension session; it is not a backend secret and cannot be used by itself for privileged actions.

Manual fallback:

  • If you need to fetch/rotate the iframe launch token explicitly from first-party UI, 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. Current installed-extension session mutations still require a valid 10-hour iframe launch token, so unattended jobs should store pending proof and submit it on the next valid launch unless you are using a first-party/built-in server flow designed for background execution.

info

For fully unattended production behavior, prefer a built-in/first-party server flow or wait for explicit background extension grants. App-scoped session APIs are currently launch-token-bound.

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 do not need server-persisted extension state writes.
  • 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.

Next Guides