Endpoints
Requests, responses, device selection and idempotency.
All paths are relative to https://apidrop.kinkyraven.com/api/v1. Every route takes the Bearer key from Authentication and can answer 401 unauthorized or 429 rate_limited. Every route except GET /relay/me can answer 403 patreon_required. Those three are not repeated below.
The server forwards routes marked relayed to the user's app. These calls can fail with targeting errors.
Account
GET /relay/me
Returns the key owner, subscription status and current rate-limit counters. This endpoint does not require a supporter subscription.
{
"user": {"id": "8f3d...", "email": "[email protected]"},
"key": {"id": "c1a2...", "label": "puppyclicker2", "created_at": 1752768000},
"patreon": {"active": true},
"rate_limits": {
"per_key_per_min": 120,
"per_account_per_min": 240,
"used_this_window": {"key": 3, "account": 3}
}
}
patreon.active reports supporter status under its historical name. used_this_window includes this call. The response also includes rate_limits.trigger_burst_note, a description of how trigger requests count towards these limits.
GET /relay/online
Reports whether any of the user's computers is connected. The server answers without forwarding a request to the app.
{
"online": true,
"clients": [
{"client_id": "d4e5...", "platform": "windows", "name": "Gaming PC", "online": true, "last_seen": 1752768123}
]
}
Pass a client_id to select a device for your own relayed calls. The list includes offline devices. A connection does not mean an effects session is running.
Effect library
GET /relay/library
Relayed. The user's enabled, renderable effects as {"effects": [...]}. Each entry uses the effect's file format, with id, name and version added. Disabled effects, custom filters and non-renderable entries are excluded.
Optional query: client_id.
GET /relay/effect/{effect_id}
Relayed. One library entry in the same format, or 404 effect_not_found for an unknown, disabled or excluded effect.
Optional query: client_id.
POST /relay/trigger
Relayed. Fire a library effect on the whole screen. Supports idempotency keys.
{
"effect_id": "glow",
"duration_secs": 60,
"client_id": "d4e5...",
"clientEventId": "evt_01J..."
}
effect_idis required, at most 128 characters, with no control characters.duration_secsis an optional non-negative integer. The app clamps it to 1 through 86,400 seconds. Leave it out and the effect stays until the session ends or someone stops it.client_idandclientEventIdare optional.paramsis accepted but ignored.
Success is 200 {"ok": true, "effect_id": "glow"}.
Invalid effect IDs return 400 bad_request for an empty ID or 400 bad_effect for an overlong ID or control characters.
The app can refuse with 403 effect_disabled, 403 remote_disabled, 409 session_off, 404 effect_not_found, 403 premium_required, 403 high_risk_disabled, 403 not_remotely_fireable or 403 unlock_unavailable. Errors explains each.
Your manual triggers
Manual triggers are rules the user can fire by hand. The app applies each rule's effects, durations, cooldowns and chance settings. The last-trigger feed identifies API calls as api:<key label>.
GET /relay/rules
Relayed. The active profile's enabled manual triggers with no target conditions. Remote sequences are excluded if the account lacks supporter status. Listing does not require an active effects session.
{"rules": [{"id": "r_9f2...", "name": "Bubbles"}]}
Optional query: client_id.
App refusals: 403 remote_disabled, 400 or 403 app_outdated.
POST /relay/rules/trigger
Relayed. Fire one trigger. Supports idempotency keys.
{"rule_id": "r_9f2...", "client_id": "d4e5...", "clientEventId": "evt_01J..."}
Only rule_id is required. It must be non-empty, at most 128 characters, with no control characters. Invalid IDs return 400 bad_request or 400 bad_rule.
Success is 200 {"ok": true, "rule_id": "r_9f2...", "fired": true}. fired: false means the rule started nothing, for example because of cooldown or chance. If premium steps cannot run, the response may include reason: "premium" with fired: false, or partial: true when only part of the rule can run. Older apps may omit these fields.
App refusals: 403 remote_disabled, 409 session_off, 404 rule_not_found, 403 premium_required. Disabled triggers and triggers with target conditions return rule_not_found. Remote sequences require supporter status on the target account. Older apps may return app_outdated.
POST /relay/rules/stop
Relayed. Stop the effects started by one trigger.
{"rule_id": "r_9f2...", "client_id": "d4e5..."}
Success is 200 {"ok": true, "rule_id": "r_9f2..."}. Does not use an idempotency key. Repeated stops are safe.
Stopping a trigger with no active effects returns 200, including disabled or deleted triggers. This endpoint does not return session_off or rule_not_found.
App refusals: 403 remote_disabled, app_outdated. Server-side: 400 bad_request for an empty rule_id, 400 bad_rule for over 128 characters or control characters.
POST /relay/rules/clear
Relayed. Stop all active effects, regardless of what started them.
The body is optional. Send nothing, or {"client_id": "d4e5..."}. With a Content-Type: application/json header send at least {}.
Success is 200 {"ok": true}. Does not use an idempotency key.
App refusals: 403 remote_disabled, app_outdated.
Friends
Friend calls fire the manual triggers the friend configured. The friend must allow the key owner to control them.
- The friendship must exist.
- The friend's consent switch toward the key's owner must be on.
- The key owner needs a supporter subscription. The friend does not need one to receive commands.
- Premium effects and remote sequences still require the relevant entitlement on the friend's account.
Friend routes do not accept client_id. The server prefers an online device with the app powered on, using the latest availability report and then the newest connection. Older apps that do not report power remain eligible. If no device qualifies, the server falls back to the oldest-registered online device.
Friend routes have additional rate limits shared with the control page. Each is listed below.
GET /relay/friends
The key owner's friends list. Answered by the server.
{
"friends": [
{"user_id": "7c1d...", "name": "kit", "online": true, "can_control": true}
]
}
name is their public username or a generated display name. It never contains their login email. can_control is their switch toward the key's owner. With it off, the routes below answer 403 or 404, and online always reads false.
GET /relay/friends/{user_id}/rules
Relayed to the friend's computer. Their manual triggers, as they configured them.
{"rules": [{"id": "r_ab3...", "name": "Bubbles"}]}
Rate limits: 20 per minute per caller, 10 per minute per caller-and-friend pair.
Errors: 404 not_friends, 403 friend_control_disabled, 503 device_offline, 504 client_timeout, 502 rules_unavailable.
POST /relay/friends/{user_id}/trigger
Relayed. Fire one of the friend's triggers. Supports idempotency keys.
{"rule_id": "r_ab3...", "clientEventId": "evt_01J..."}
Success is 200 {"ok": true, "rule_id": "r_ab3...", "fired": true}. The fired, reason and partial fields work as described for your own triggers.
Rate limits: 30 per minute per sender, 10 per minute per sender-and-friend pair.
Errors: 400 bad_request, 404 not_friends, 403 friend_trigger_disabled, 400 or 403 app_outdated, 503 device_offline, 504 client_timeout. The friend's app can also return remote_disabled, session_off, rule_not_found and premium_required.
POST /relay/friends/{user_id}/stop
Relayed. Stop the effects started by one of the friend's triggers.
{"rule_id": "r_ab3..."}
Success is 200 {"ok": true, "rule_id": "r_ab3..."}. Stopping a trigger with no active effects returns 200. Does not use an idempotency key.
Rate limits: 30 per minute per sender and 10 per pair, counted separately from trigger calls. The shared API-key and account limits still apply.
Errors: 400 bad_request, 400 bad_rule, 404 not_friends, 403 friend_trigger_disabled, 400 or 403 app_outdated, 503 device_offline, 504 client_timeout and 403 remote_disabled.
POST /relay/friends/{user_id}/clear
Relayed. Stop all active effects on the friend's device. No body.
Success is 200 {"ok": true}. Does not use an idempotency key.
Rate limits: 30 per minute per sender and 10 per pair, counted separately from trigger and stop calls.
Errors: as for the stop, excluding bad_request and bad_rule.
Targeting
Each relayed call to your own account targets one computer. Friend calls use the selection described under Friends. The client_id values come from GET /relay/online.
With a client_id:
- Not on the account:
404 client_not_found. - Offline:
503 device_offline.
Without one, the server auto-routes:
- Exactly one computer online: the call goes there.
- Several online:
409 ambiguous_client, with aclientsarray of the online ones so you can pick. - None online:
503 device_offline.
If the app does not answer within 10 seconds you get 504 client_timeout.
When you omit client_id, handle 409 ambiguous_client by selecting a device from the response.
Idempotency
POST /relay/trigger, POST /relay/rules/trigger and POST /relay/friends/{user_id}/trigger honour an Idempotency-Key header. The body field clientEventId is an alias for it. The header wins when both are present, and they share one key space.
Idempotency-Key: evt_01JXYZ...
- Keys are scoped per account and per endpoint, and per friend on the friend route. Use unique keys across your integration.
- The server stores responses in memory for 5 minutes. A server restart clears them.
- A retry with the same key returns the first execution's response byte for byte, status and body included, with an
Idempotency-Replayed: trueheader. It is not delivered to the device again. This includes mapped failures like503 device_offline. 429 rate_limitedresponses are never stored, because nothing happened. Retrying re-executes.- Empty keys and keys over 128 bytes after trimming are ignored. The call runs without duplicate detection.
- Concurrent requests with the same new key can both reach the app. Wait for the first request to finish before retrying.
Stop and clear routes take no key. Repeating them is harmless.
Generate a unique key for each intended trigger and reuse it for retries. Keep retries within the 5-minute retention period.