External API & Programs
Use this page when you want a simple external program, script, local server, or backend service to control your current Chastify lock.
The easiest way is to create a user-wide DEV token, then send it as a bearer token to the /api/apps/v1/* endpoints.
/api/apps/v1/* is only for your own active lock sessions. If you are building a public extension used by other Chastify users, use an app-scoped Developer API key with /api/extensions/sessions/:sessionId/* and pass the iframe mainToken in x-chastify-main-token.
Create A DEV Token
- Open Chastify.
- Go to
Developer API. - Find
User-wide DEV API keys. - Create a key.
- Copy the token immediately. It is only shown once.
Use it in requests like this:
curl https://chastify.net/api/apps/v1/session \
-H "Authorization: Bearer YOUR_DEV_TOKEN"
DEV tokens:
- do not require creating an extension
- do not expire automatically
- work for your current active lock and future active lock sessions
- use your role on the active lock, either wearer or keyholder
- can be revoked from the Developer API page
Treat a DEV token like a password. Anyone with the token can call the Developer API as you.
First Call: Check The Current Lock
Start with:
GET https://chastify.net/api/apps/v1/session
Example:
curl https://chastify.net/api/apps/v1/session \
-H "Authorization: Bearer YOUR_DEV_TOKEN"
This returns your current lock context, role, scopes, remaining time, and lockData.
Lock Data Helpers
GET /api/apps/v1/session includes lockData, which is designed to be easy for programs and rule engines to read.
Common booleans:
frozenunlockabletrustedtaskAssigned:truewhen the active lock has an openTaskRun
Common numbers:
timeLockedSecondstimeRemainingSecondsmaxTimeRemainingSecondstaskPoints
Common strings:
lockTitle- wearer profile fields
- keyholder profile fields
Privacy note:
wearerLastSeenTimestampandkeyholderLastSeenTimestamparenullwhen that user has disabled online status.
Main Lock Action Endpoints
These endpoints are the ones most external programs use to modify a lock.
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
All requests use:
Authorization: Bearer YOUR_DEV_TOKEN
Content-Type: application/json
Add Or Remove Time
Use the simple time endpoint when you only want to change remaining time.
Add 10 minutes:
curl -X POST https://chastify.net/api/apps/v1/lock/apply-time \
-H "Authorization: Bearer YOUR_DEV_TOKEN" \
-H "Content-Type: application/json" \
-d '{"deltaSeconds":600}'
Remove 5 minutes:
curl -X POST https://chastify.net/api/apps/v1/lock/apply-time \
-H "Authorization: Bearer YOUR_DEV_TOKEN" \
-H "Content-Type: application/json" \
-d '{"deltaSeconds":-300}'
Freeze And Unfreeze
Freeze for 30 minutes:
curl -X POST https://chastify.net/api/apps/v1/lock/freeze \
-H "Authorization: Bearer YOUR_DEV_TOKEN" \
-H "Content-Type: application/json" \
-d '{"durationSeconds":1800}'
Unfreeze:
curl -X POST https://chastify.net/api/apps/v1/lock/unfreeze \
-H "Authorization: Bearer YOUR_DEV_TOKEN"
General Action Endpoint
Use:
POST https://chastify.net/api/apps/v1/action
Body shape:
{
"name": "add_time",
"params": 600
}
Example:
curl -X POST https://chastify.net/api/apps/v1/action \
-H "Authorization: Bearer YOUR_DEV_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"add_time","params":600}'
Supported Action Names
name supports:
add_timeremove_timefreezepilloryunfreezetoggle_freezesettings.patchtask.assigntask.start_timertask.completehygienic_unlock.startpillory.end
Action constraints:
- Task actions require the Tasks module to be enabled on the lock.
hygienic_unlock.startrequires Hygienic Opening to be enabled and no active hygiene session.- Device commands require a supported connected device and permissions.
Useful Action Examples
Remove 15 minutes:
{
"name": "remove_time",
"params": 900
}
Freeze for 5 minutes:
{
"name": "freeze",
"params": {
"durationSeconds": 300
}
}
Toggle freeze:
{
"name": "toggle_freeze",
"params": {
"durationSeconds": 300
}
}
Assign a task:
{
"name": "task.assign",
"params": {
"actor": "extension",
"taskText": "Drink water",
"points": 5,
"verificationRequired": false,
"durationSeconds": 900
}
}
This creates a TaskRun for the active lock. If another task run is already open, the current implementation cancels the old open run and creates a new one.
Start the active task timer:
{
"name": "task.start_timer",
"params": {}
}
Complete the active task:
{
"name": "task.complete",
"params": {
"successful": true
}
}
This completes the latest open TaskRun for the active lock.
Start a hygienic unlock:
{
"name": "hygienic_unlock.start",
"params": {
"durationSeconds": 900
}
}
End active pillory:
{
"name": "pillory.end",
"params": {}
}
Custom Lock Log
Use this when your program did something and you want it visible in lock history.
POST https://chastify.net/api/apps/v1/logs/custom
Example:
curl -X POST https://chastify.net/api/apps/v1/logs/custom \
-H "Authorization: Bearer YOUR_DEV_TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"Program check completed","description":"The external rule script ran successfully.","role":"extension"}'
Body fields:
title: requireddescription: optionalrole:extension,wearer, orkeyholdericon: optionalcolor: optional hex color, for example#ffcc00
Device Commands
Device commands let you trigger shocks and vibrations on the wearer's connected device.
POST https://chastify.net/api/apps/v1/device-command
Works with your DEV token — no session ID needed. The server automatically resolves the lock and wearer from your token.
curl -X POST https://chastify.net/api/apps/v1/device-command \
-H "Authorization: Bearer YOUR_DEV_TOKEN" \
-H "Content-Type: application/json" \
-d '{"command":"shock.start","params":{"intensityPct":75,"durationSeconds":10}}'
Request body
{
"command": "<command>",
"params": { ... }
}
command(string, required) — the device command to execute.params(object, optional) — command-specific parameters (see below).
Supported commands and their params
shock.start
Starts a shock on the wearer's device.
| Param | Type | Range | Default | Description |
|---|---|---|---|---|
intensityPct | number | 1–100 | 50 | Shock intensity as a percentage |
durationSeconds | number | 1–300 | 60 | Shock duration in seconds |
message | string | — | — | Optional message shown to the wearer |
The wearer's configured max voltage is always enforced as a hard cap, regardless of what you send. For Lockink devices, this is the per-device voltage limit. For QIUI devices, this is the shockVolt setting (1–4 scale). If you send intensityPct: 80 but the wearer's limit is 50%, the device will only deliver 50%.
Example:
curl -X POST https://chastify.net/api/apps/v1/device-command \
-H "Authorization: Bearer YOUR_DEV_TOKEN" \
-H "Content-Type: application/json" \
-d '{"command":"shock.start","params":{"intensityPct":75,"durationSeconds":10,"message":"Extension shock"}}'
shock.stop
Stops all active shocks on the wearer's device. No params required.
curl -X POST https://chastify.net/api/apps/v1/device-command \
-H "Authorization: Bearer YOUR_DEV_TOKEN" \
-H "Content-Type: application/json" \
-d '{"command":"shock.stop"}'
vibration.start
Starts a vibration on the wearer's device.
| Param | Type | Range | Default | Description |
|---|---|---|---|---|
intensityPct | number | 1–100 | 50 | Vibration intensity as a percentage |
durationSeconds | number | 1–300 | 30 | Vibration duration in seconds |
frequencyPct | number | 1–100 | 50 | Vibration frequency as a percentage |
message | string | — | — | Optional message shown to the wearer |
Example:
curl -X POST https://chastify.net/api/apps/v1/device-command \
-H "Authorization: Bearer YOUR_DEV_TOKEN" \
-H "Content-Type: application/json" \
-d '{"command":"vibration.start","params":{"intensityPct":60,"durationSeconds":15,"frequencyPct":40}}'
vibration.stop
Stops all active vibrations. No params required.
curl -X POST https://chastify.net/api/apps/v1/device-command \
-H "Authorization: Bearer YOUR_DEV_TOKEN" \
-H "Content-Type: application/json" \
-d '{"command":"vibration.stop"}'
all.stop
Stops all device activity (shocks, vibrations, etc.). No params required.
curl -X POST https://chastify.net/api/apps/v1/device-command \
-H "Authorization: Bearer YOUR_DEV_TOKEN" \
-H "Content-Type: application/json" \
-d '{"command":"all.stop"}'
shock.random.set
Enables or disables random shock mode on the wearer's device.
| Param | Type | Range | Default | Description |
|---|---|---|---|---|
enabled | boolean | — | — | Enable (true) or disable (false) random mode |
minIntensityPct | number | 1–100 | 20 | Minimum shock intensity percentage |
maxIntensityPct | number | 1–100 | 80 | Maximum shock intensity percentage |
message | string | — | — | Optional message shown to the wearer |
The wearer's configured max voltage is always the hard cap. If you set maxIntensityPct: 80 but the wearer's limit is 50, the actual maximum will be 50%.
Example:
curl -X POST https://chastify.net/api/apps/v1/device-command \
-H "Authorization: Bearer YOUR_DEV_TOKEN" \
-H "Content-Type: application/json" \
-d '{"command":"shock.random.set","params":{"enabled":true,"minIntensityPct":25,"maxIntensityPct":75}}'
shock.berserk.set
Enables or disables berserk shock mode on the wearer's device.
| Param | Type | Range | Default | Description |
|---|---|---|---|---|
enabled | boolean | — | — | Enable (true) or disable (false) berserk mode |
message | string | — | — | Optional message shown to the wearer |
Example:
curl -X POST https://chastify.net/api/apps/v1/device-command \
-H "Authorization: Bearer YOUR_DEV_TOKEN" \
-H "Content-Type: application/json" \
-d '{"command":"shock.berserk.set","params":{"enabled":true}}'
Device commands depend on the active lock, device availability, and command policy.
Device selection
You do not need to specify a device ID or device type. Each wearer can only have one active device at a time — the server targets it automatically.
The deviceType is returned in the response so you know which device received the command.
Responses
Success (200)
{
"ok": true,
"command": "shock.start",
"result": {
"success": true,
"message": "Shock command sent (10s)",
"deviceType": "lockink-aa-a1012"
},
"active": {
"shock": true,
"vibration": false
}
}
| Field | Description |
|---|---|
ok | true when the command was accepted |
command | The command that was executed |
result.success | true if the device confirmed the command |
result.message | Human-readable status message |
result.deviceType | The wearer's device type (e.g. lockink-aa-a1012, cellmate-pro-3) |
active.shock | Whether a shock is currently active on the wearer's device |
active.vibration | Whether a vibration is currently active on the wearer's device |
Note: shock.start and vibration.start wait up to 25 seconds for the device to confirm receipt. Stop commands (shock.stop, vibration.stop, all.stop) return immediately.
Failure
All failures return HTTP 4xx/5xx with a JSON body:
{
"success": false,
"error": "no_device",
"message": "No shock-capable device found for user"
}
| Scenario | HTTP | error | message |
|---|---|---|---|
| Lock not found or no active lock | 404 | lock_not_found | No active lock found |
| Lock is no longer active | 409 | lock_ended | The lock is no longer active |
| Missing wearer session | 400 | no_wearer | Missing wearer session |
Missing or invalid durationSeconds | 400 | invalid_params | durationSeconds is required for server-initiated shocks |
| Random/berserk mode on unsupported device | 400 | unsupported_device | Random mode only supported on Lockink AA-A1012 |
| User has not granted shock consent | 403 | not_authorized | User not eligible for shock commands (consent or device check failed) |
| No shock/vibration-capable device paired | 404 | no_device | No shock-capable device found for user |
| Device is offline (no socket connection) | 404 | device_offline | No active device socket found for user |
| Device didn't confirm in time (25s) | 504 | device_timeout | Device verification timeout |
| Unrecognized or unhandled failure | 400 | command_failed | command_failed |
When available, deviceType is included in the response.
Common failure scenarios explained
Device must be connected via the mobile app
Shocks and vibrations require the wearer to have a supported Bluetooth device paired and the Chastify mobile app actively running on their phone. The app maintains the real-time socket connection that delivers commands to the device. If the app is closed or the phone has no internet, commands will fail.
no_device — The wearer has not paired a shock-capable device (e.g. CellMate Pro 3, Cagink Metal, Lockink AA-A1012) in the Chastify app. Devices must be paired, active, and fully configured before the API can target them.
device_offline — The wearer's device is paired, but the Native Shock Service is not enabled or not running on the Android app. This is the most common failure — the wearer must have the Native Shock Service enabled in the Android app's settings for commands to be delivered reliably.
device_timeout — The command was sent to the mobile app, but the app did not confirm that the Bluetooth device received it within 25 seconds. This usually means the wearer's Bluetooth is off, the device is out of range, or the device is turned off. Lockink devices go to sleep after only 3 minutes of inactivity unless the keep-alive function is enabled — and even then, OEM battery optimizations on the phone may restrict background Bluetooth and prevent keep-alive from working reliably.
not_authorized — The wearer has not granted explicit shock consent in their device settings. This is a safety requirement — even with a paired device, the wearer must opt in to allowing remote shock/vibration commands.
unsupported_device — Random and berserk modes are only available on Lockink AA-A1012 (Beesting). Other devices like CellMate Pro 3 or Cagink Metal do not support these modes.
Common Errors
401 missing_token: sendAuthorization: Bearer YOUR_DEV_TOKEN.401 invalid_token: the token is wrong or was copied incorrectly.401 revoked_token: the DEV key was revoked.403 insufficient_scope: the key does not have the required scope.409 no_active_lock_session: the user does not currently have an active lock.409 lock_ended: the resolved lock is no longer active.
Understanding device_timeout (504)
The device_timeout error means the server sent the shock command to the wearer's Android app, but the app did not confirm delivery to the Bluetooth device within 25 seconds. This is the most nuanced error — here's what happens on the Android side:
- Server → App: The command travels via Socket.IO to the Native Shock Service running on the wearer's phone.
- App → Device: The app connects to the device via Bluetooth Low Energy (BLE) and sends the shock command. For QIUI devices, this involves fetching a token from the device manufacturer's API first, then writing the BLE command. For Lockink devices, the BLE command is sent directly.
- App → Server: The app emits a confirmation back to the server.
The 25-second timeout starts at step 1. A timeout at step 2 can happen because:
- Bluetooth is off or the device is out of range on the wearer's phone.
- The cage is asleep. Lockink devices enter deep sleep after only 3 minutes of BLE inactivity. The keep-alive feature can prevent this, but OEM battery optimizations (Samsung, Xiaomi, Huawei, etc.) may kill background Bluetooth connections, making keep-alive unreliable.
- BLE connection failed. The app retries the BLE connection, but if the device doesn't respond, it times out.
- Safety block. The app has a built-in safety system that blocks shocks if the wearer is detected to be moving (e.g. driving, cycling) based on activity recognition and GPS speed. If the wearer is in motion, the shock is silently blocked and reported as failed.
- QIUI token fetch failed. For CellMate Pro 3 and Cagink devices, the app must fetch a command token from QIUI's cloud API before sending the BLE command. If the wearer's phone has no internet, or QIUI's API is slow/unreachable, this step can consume most of the 25-second window.
Practical Pattern
Most external programs follow this flow:
- Call
GET /api/apps/v1/session. - Read
lockData. - Decide what should happen.
- Call
/api/apps/v1/actionor one of the simpler lock endpoints. - Optionally write a custom log with
/api/apps/v1/logs/custom.
For example, a script can check timeRemainingSeconds, add time when a rule fails, then write a custom log explaining what happened.