{"openapi":"3.1.0","info":{"title":"fug","version":"2026.07.24.0"},"paths":{"/api/health":{"get":{"summary":"Health Check","description":"Checks the health of a project.\n\nIt returns 204 if the project is healthy.","operationId":"health_check_api_health_get","responses":{"204":{"description":"Successful Response"}}}},"/api/healthz":{"get":{"summary":"Healthz Check","description":"Checks the health of a project.\n\nIt returns 200 if the project is healthy.","operationId":"healthz_check_api_healthz_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Healthz Check Api Healthz Get"}}}}}}},"/api/version":{"get":{"summary":"Api Version","description":"Returns the API version and additional health information for the project.\n\nIf the project is healthy, it returns a JSON response with a status code of 200.\n\nIn development and staging environments, additional platform and build information\nare included in the response.\n\n:return: A JSON response containing the API version and, if applicable, platform and build information.","operationId":"api_version_api_version_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/get_status_of_devices":{"post":{"summary":"Send Command To Devices","operationId":"send_command_to_devices_api_get_status_of_devices_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceStatusSchema"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{},"type":"array","title":"Response Send Command To Devices Api Get Status Of Devices Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"DeviceConnectionKey":[]}]}},"/api/send_command_to_devices":{"post":{"summary":"Send Command To Devices","operationId":"send_command_to_devices_api_send_command_to_devices_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceCommandSchema"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceCommandResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"DeviceConnectionKey":[]}]}},"/api/send_setup_to_device":{"post":{"summary":"Send Setup To Device","operationId":"send_setup_to_device_api_send_setup_to_device_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceSetupSchema"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object","title":"Response Send Setup To Device Api Send Setup To Device Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"DeviceConnectionKey":[]}]}},"/api/send_reprovision_command_to_device":{"post":{"summary":"Send Reprovision Command To Device","operationId":"send_reprovision_command_to_device_api_send_reprovision_command_to_device_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceReprovisionCommandSchema"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceReprovisionCommandResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"DeviceConnectionKey":[]}]}},"/api/scripts":{"get":{"tags":["scripts"],"summary":"List the caller's scripts (metadata only, newest first)","description":"Return metadata for every script owned by the caller.","operationId":"list_scripts_api_scripts_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/ScriptMetadataResponse"},"type":"array","title":"Response List Scripts Api Scripts Get"}}}}},"security":[{"DeviceConnectionKey":[]}]},"post":{"tags":["scripts"],"summary":"Upload a motion script inline","description":"Store a funscript/CSV motion script provided inline in the request body.","operationId":"upload_script_api_scripts_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScriptUploadRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScriptMetadataResponse"}}}},"400":{"description":"Unparseable content"},"409":{"description":"Per-owner script quota exceeded"},"413":{"description":"Inline content exceeds the cap"},"422":{"description":"Limits or field caps exceeded"}},"security":[{"DeviceConnectionKey":[]}]}},"/api/scripts/from-url":{"post":{"tags":["scripts"],"summary":"Upload a motion script fetched from a URL","description":"Fetch a motion script from a URL (SSRF-guarded) and store it.","operationId":"upload_script_from_url_api_scripts_from_url_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScriptFromUrlRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScriptMetadataResponse"}}}},"400":{"description":"Unparseable content or blocked URL"},"409":{"description":"Per-owner script quota exceeded"},"413":{"description":"Fetched body exceeds the cap"},"422":{"description":"Limits or field caps exceeded"},"502":{"description":"Upstream fetch failed"},"504":{"description":"Upstream fetch timed out"}},"security":[{"DeviceConnectionKey":[]}]}},"/api/scripts/{script_id}":{"get":{"tags":["scripts"],"summary":"Get script metadata by id","description":"Return metadata for any script by id; ``source_url`` only for its owner.","operationId":"get_script_api_scripts__script_id__get","security":[{"DeviceConnectionKey":[]}],"parameters":[{"name":"script_id","in":"path","required":true,"schema":{"type":"string","title":"Script Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScriptMetadataResponse"}}}},"404":{"description":"Script not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["scripts"],"summary":"Delete a script (owner only)","description":"Delete a script; only the owner may delete it.","operationId":"delete_script_api_scripts__script_id__delete","security":[{"DeviceConnectionKey":[]}],"parameters":[{"name":"script_id","in":"path","required":true,"schema":{"type":"string","title":"Script Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScriptDeletedResponse"}}}},"403":{"description":"Caller does not own the script"},"404":{"description":"Script not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/playback/play":{"post":{"tags":["playback"],"summary":"Start, resume, or switch playback on the caller's device","description":"Play a script (404 if absent). Busy device: same script → rejection event,\ndifferent script → seamless switch. Otherwise a fresh start request.","operationId":"play_api_playback_play_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlayRequest"}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlaybackAccepted"}}}},"404":{"description":"Script not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"DeviceConnectionKey":[]}]}},"/api/playback/pause":{"post":{"tags":["playback"],"summary":"Pause","description":"Freeze playback; desired=paused.","operationId":"pause_api_playback_pause_post","responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlaybackAccepted"}}}}},"security":[{"DeviceConnectionKey":[]}]}},"/api/playback/seek":{"post":{"tags":["playback"],"summary":"Seek","description":"Scrub to an absolute position; updates desired position.","operationId":"seek_api_playback_seek_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SeekRequest"}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlaybackAccepted"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"DeviceConnectionKey":[]}]}},"/api/playback/sync":{"post":{"tags":["playback"],"summary":"Sync","description":"Drift correction — publish only; never writes desired-state (§3.3).","operationId":"sync_api_playback_sync_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncRequest"}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlaybackAccepted"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"DeviceConnectionKey":[]}]}},"/api/playback/speed":{"post":{"tags":["playback"],"summary":"Set speed","description":"Change the speed scale (clamped 0.5–2.0); desired speed.","operationId":"set_speed_api_playback_speed_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpeedRequest"}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlaybackAccepted"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"DeviceConnectionKey":[]}]}},"/api/playback/offset":{"post":{"tags":["playback"],"summary":"Set offset","description":"Set the absolute timing offset (idempotent); desired offset.","operationId":"set_offset_api_playback_offset_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OffsetRequest"}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlaybackAccepted"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"DeviceConnectionKey":[]}]}},"/api/playback/stop":{"post":{"tags":["playback"],"summary":"Stop","description":"Stop playback; desired=stopped.","operationId":"stop_api_playback_stop_post","responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlaybackAccepted"}}}}},"security":[{"DeviceConnectionKey":[]}]}},"/api/playback/status":{"get":{"tags":["playback"],"summary":"Stream live playback + device status over SSE","description":"Server-Sent Events stream of playback + device_status frames.","operationId":"playback_status_api_playback_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"429":{"description":"Per-instance SSE stream cap reached"}},"security":[{"DeviceConnectionKey":[]}]}}},"components":{"schemas":{"DeviceCommandArgumentsType":{"type":"string","enum":["speed","position","min_position","max_position","raw_data"],"title":"DeviceCommandArgumentsType","description":"Enumeration of argument types used in device commands.\n\nThis enum defines the keys used in the arguments dictionary of device\ncommands, specifying what type of parameter each argument represents.\n\nAttributes:\n    SPEED: Argument specifying movement or operation speed.\n    POSITION: Argument specifying a target position.\n    MIN_POSITION: Argument specifying a minimum position boundary.\n    MAX_POSITION: Argument specifying a maximum position boundary.\n    RAW_DATA: Argument containing raw data bytes or string."},"DeviceCommandResponse":{"properties":{"status":{"type":"string","title":"Status"},"message":{"type":"string","title":"Message"}},"type":"object","required":["status","message"],"title":"DeviceCommandResponse"},"DeviceCommandSchema":{"properties":{"deviceConnectionKey":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Deviceconnectionkey"},"command_type":{"anyOf":[{"$ref":"#/components/schemas/DeviceCommandType"},{"type":"null"}]},"arguments":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Arguments"},"command_duration_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Command Duration Ms"}},"type":"object","required":["command_type","arguments"],"title":"DeviceCommandSchema","description":"Schema for the payload of a device command.\n\nThis model contains the actual command information to be executed by\nthe device, including the command type, associated arguments, and\noptional duration.\n\nAttributes:\n    sid_to: Optional (device connection key)\n    command_type: Type of command to execute (from DeviceCommandType).\n    arguments: Dictionary mapping argument types to their values.\n        Keys should be from DeviceCommandArgumentsType.\n    command_duration_ms: Optional duration for command execution in\n        milliseconds. If not specified, the device uses its default\n        timing.\n\nExample:\n    >>> payload = DeviceCommandPayloadSchema(\n    ...     command_type=DeviceCommandType.MOVEMENT,\n    ...     arguments={DeviceCommandArgumentsType.POSITION: 100},\n    ...     command_duration_ms=5000,\n    ... )"},"DeviceCommandType":{"type":"string","enum":["RAW","MOVEMENT","MOVEMENT_BETWEEN","PAUSE"],"title":"DeviceCommandType","description":"Enumeration of available device command types.\n\nThis enum defines the different categories of commands that can be sent\nto a device, each representing a specific type of operation or instruction.\n\nAttributes:\n    RAW: Command type for sending raw data directly to the device.\n    MOVEMENT: Command type for movement commands to a specific position.\n    MOVEMENT_BETWEEN: Command type for movement between two positions.\n    PAUSE: Command type for pausing device operations."},"DeviceReprovisionCommandResponse":{"properties":{"status":{"type":"string","title":"Status"},"message":{"type":"string","title":"Message"}},"type":"object","required":["status","message"],"title":"DeviceReprovisionCommandResponse"},"DeviceReprovisionCommandSchema":{"properties":{"deviceConnectionKey":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Deviceconnectionkey"},"reprovision_type":{"anyOf":[{"$ref":"#/components/schemas/DeviceReprovisionType"},{"type":"null"}]},"arguments":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Arguments"}},"type":"object","required":["reprovision_type","arguments"],"title":"DeviceReprovisionCommandSchema","description":"Handles device reprovisioning commands."},"DeviceReprovisionType":{"type":"string","enum":["bt_mode","reset_credentials"],"title":"DeviceReprovisionType","description":"Defines commands for device reprovisioning.\n\nAttributes:\nBT_MODE (str): Command to set Bluetooth mode.\nRESET_CREDENTIALS (str): Command to reset device credentials."},"DeviceSetupArgumentsType":{"type":"string","enum":["intensity","interval"],"title":"DeviceSetupArgumentsType","description":"Enum representing the types of arguments for device setup commands.\n\nAttributes:\n    INTENSITY (str): Argument type for intensity."},"DeviceSetupSchema":{"properties":{"deviceConnectionKey":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Deviceconnectionkey"},"setup_type":{"anyOf":[{"$ref":"#/components/schemas/DeviceSetupType"},{"type":"null"}]},"arguments":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Arguments"}},"type":"object","required":["setup_type","arguments"],"title":"DeviceSetupSchema","description":"Represents a device setup command.\n\nAttributes:\n    setup_type (DeviceSetupType): The type of device setup.\n    arguments (dict): A dictionary of arguments related to the setup command."},"DeviceSetupType":{"type":"string","enum":["speed_intensity_adjustment","range_intensity_adjustment","status_update_interval"],"title":"DeviceSetupType","description":"Defines setup commands for device configuration."},"DeviceStatusSchema":{"properties":{"deviceConnectionKey":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Deviceconnectionkey"},"data":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Data"}},"type":"object","title":"DeviceStatusSchema","description":"Schema for the payload of get device status.\n\n\nAttributes:\n    sid_to: Optional (device connection key)\n    data: Dictionary containing the status information of the device.\nExample:\n    >>> payload = DeviceStatusSchema(\n    ...     deviceConnectionKey=\"device_connection_key_123\",\n    ...     data={\"\"},\n    ... )"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"OffsetRequest":{"properties":{"offset_ms":{"type":"integer","title":"Offset Ms"}},"type":"object","required":["offset_ms"],"title":"OffsetRequest","description":"Set the absolute timing offset (± ms)."},"PlayRequest":{"properties":{"script_id":{"type":"string","title":"Script Id"},"start_ms":{"type":"integer","minimum":0.0,"title":"Start Ms","default":0},"speed_scale":{"type":"number","title":"Speed Scale","default":1.0},"profile":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Profile"}},"type":"object","required":["script_id"],"title":"PlayRequest","description":"Start (or switch/resume) playback of a script on the caller's device."},"PlaybackAccepted":{"properties":{"status":{"type":"string","title":"Status"},"request_id":{"type":"string","title":"Request Id"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"}},"type":"object","required":["status","request_id"],"title":"PlaybackAccepted","description":"202 acknowledgement; the real outcome is observed over the SSE stream."},"ScriptDeletedResponse":{"properties":{"status":{"type":"string","title":"Status"},"script_id":{"type":"string","title":"Script Id"}},"type":"object","required":["status","script_id"],"title":"ScriptDeletedResponse","description":"Response body for a successful delete."},"ScriptFormat":{"type":"string","enum":["funscript","autoblow_csv"],"title":"ScriptFormat","description":"Supported motion-script source formats."},"ScriptFromUrlRequest":{"properties":{"url":{"type":"string","title":"Url"},"source_format":{"anyOf":[{"$ref":"#/components/schemas/ScriptFormat"},{"type":"null"}]},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"tags":{"items":{"type":"string"},"type":"array","title":"Tags"},"author":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Author"},"max_duration_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Duration Ms"}},"type":"object","required":["url"],"title":"ScriptFromUrlRequest","description":"URL upload body for ``POST /api/scripts/from-url``."},"ScriptMetadataResponse":{"properties":{"script_id":{"type":"string","title":"Script Id"},"source_format":{"$ref":"#/components/schemas/ScriptFormat"},"point_count":{"type":"integer","title":"Point Count"},"duration_ms":{"type":"integer","title":"Duration Ms"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"tags":{"items":{"type":"string"},"type":"array","title":"Tags"},"author":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Author"},"source_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Url"}},"type":"object","required":["script_id","source_format","point_count","duration_ms","created_at","updated_at"],"title":"ScriptMetadataResponse","description":"Public script metadata. Never carries the owner DCK.\n\n``source_url`` is present only when the caller owns the script (enforced by\nthe view via ``response_model_exclude_unset``)."},"ScriptUploadRequest":{"properties":{"content":{"type":"string","title":"Content"},"source_format":{"anyOf":[{"$ref":"#/components/schemas/ScriptFormat"},{"type":"null"}]},"content_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content Type"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"tags":{"items":{"type":"string"},"type":"array","title":"Tags"},"author":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Author"},"max_duration_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Duration Ms"}},"type":"object","required":["content"],"title":"ScriptUploadRequest","description":"Inline upload body for ``POST /api/scripts``."},"SeekRequest":{"properties":{"position_ms":{"type":"integer","minimum":0.0,"title":"Position Ms"}},"type":"object","required":["position_ms"],"title":"SeekRequest","description":"Scrub to an absolute media position."},"SpeedRequest":{"properties":{"speed":{"type":"number","title":"Speed"}},"type":"object","required":["speed"],"title":"SpeedRequest","description":"Change the playback speed scale (clamped server-side)."},"SyncRequest":{"properties":{"position_ms":{"type":"integer","minimum":0.0,"title":"Position Ms"},"client_ts":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Client Ts"}},"type":"object","required":["position_ms"],"title":"SyncRequest","description":"Drift correction from an external media player."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}},"securitySchemes":{"DeviceConnectionKey":{"type":"apiKey","description":"Format: `DCK <device_connection_key>`. Include the `DCK ` prefix in the value field.","in":"header","name":"Authorization"}}}}