Skip to main content

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

  1. Open Chastify.
  2. Go to Developer API.
  3. Find User-wide DEV API keys.
  4. Create a key.
  5. 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:

  • frozen
  • unlockable
  • trusted
  • taskAssigned: true when the active lock has an open TaskRun

Common numbers:

  • timeLockedSeconds
  • timeRemainingSeconds
  • maxTimeRemainingSeconds
  • taskPoints

Common strings:

  • lockTitle
  • wearer profile fields
  • keyholder profile fields

Privacy note:

  • wearerLastSeenTimestamp and keyholderLastSeenTimestamp are null when 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_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 the Tasks module to be enabled on the lock.
  • hygienic_unlock.start requires 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: required
  • description: optional
  • role: extension, wearer, or keyholder
  • icon: optional
  • color: 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.

ParamTypeRangeDefaultDescription
intensityPctnumber1–10050Shock intensity as a percentage
durationSecondsnumber1–30060Shock duration in seconds
messagestringOptional message shown to the wearer
note

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.

ParamTypeRangeDefaultDescription
intensityPctnumber1–10050Vibration intensity as a percentage
durationSecondsnumber1–30030Vibration duration in seconds
frequencyPctnumber1–10050Vibration frequency as a percentage
messagestringOptional 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.

ParamTypeRangeDefaultDescription
enabledbooleanEnable (true) or disable (false) random mode
minIntensityPctnumber1–10020Minimum shock intensity percentage
maxIntensityPctnumber1–10080Maximum shock intensity percentage
messagestringOptional message shown to the wearer
note

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.

ParamTypeRangeDefaultDescription
enabledbooleanEnable (true) or disable (false) berserk mode
messagestringOptional 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
}
}
FieldDescription
oktrue when the command was accepted
commandThe command that was executed
result.successtrue if the device confirmed the command
result.messageHuman-readable status message
result.deviceTypeThe wearer's device type (e.g. lockink-aa-a1012, cellmate-pro-3)
active.shockWhether a shock is currently active on the wearer's device
active.vibrationWhether 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"
}
ScenarioHTTPerrormessage
Lock not found or no active lock404lock_not_foundNo active lock found
Lock is no longer active409lock_endedThe lock is no longer active
Missing wearer session400no_wearerMissing wearer session
Missing or invalid durationSeconds400invalid_paramsdurationSeconds is required for server-initiated shocks
Random/berserk mode on unsupported device400unsupported_deviceRandom mode only supported on Lockink AA-A1012
User has not granted shock consent403not_authorizedUser not eligible for shock commands (consent or device check failed)
No shock/vibration-capable device paired404no_deviceNo shock-capable device found for user
Device is offline (no socket connection)404device_offlineNo active device socket found for user
Device didn't confirm in time (25s)504device_timeoutDevice verification timeout
Unrecognized or unhandled failure400command_failedcommand_failed

When available, deviceType is included in the response.

Common failure scenarios explained

caution

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: send Authorization: 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.
info

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:

  1. Server → App: The command travels via Socket.IO to the Native Shock Service running on the wearer's phone.
  2. 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.
  3. 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:

  1. Call GET /api/apps/v1/session.
  2. Read lockData.
  3. Decide what should happen.
  4. Call /api/apps/v1/action or one of the simpler lock endpoints.
  5. 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.