Skip to main content

Python Lovense API client

Project description

LovensePy

License: Apache 2.0

Python client for the Lovense API. Supports Standard API (LAN & Server), Standard Socket API, and Toy Events API.

Table of Contents


Features

  • Standard API LAN (Game Mode): GetToys, GetToyName, Function, Stop, Pattern, Preset, Position, PatternV2
  • Standard API Server: Function, Pattern, Preset via Lovense cloud; get_qr_code for QR pairing
  • Standard Socket API: getToken, getSocketUrl, WebSocket client for QR flow and remote control
  • Toy Events API: Real-time events (toy-list, button-down, function-strength-changed, etc.)

Prerequisites

Before using LovensePy, ensure you have:

  • Lovense Remote or Lovense Connect app installed on your phone or PC
  • Lovense toy paired with the app
  • Same Wi-Fi network as the device (required for LAN/Game Mode)
  • Developer token (for Server/Socket API) — obtain from Lovense Developer Dashboard
  • Callback URL (for Server API QR pairing) — e.g. ngrok tunnel or similar to receive pairing callbacks

Installation

pip install lovensepy

Note: the GitHub repository is named pylove, but the published package and import name is lovensepy.

Dependencies: httpx, pydantic, websockets


Quick Start

Get your first command running in 4 steps:

Step 1: Install the package (see Installation).

Step 2: Enable Game Mode in Lovense Remote:

  • Open Lovense Remote > Discover > Game Mode > Enable LAN
  • Note the IP address (e.g. 192.168.1.100) and port (20011 for Remote, 34567 for Connect)

Step 3: Create a Python script:

from lovensepy import LANClient, Actions

client = LANClient("MyApp", "192.168.1.100", port=20011)
client.function_request({Actions.VIBRATE: 10}, time=3)

Step 4: Run the script. Your toy should vibrate at level 10 for 3 seconds.

Note: The time parameter is in seconds. The device holds the level until the next command or until you call client.stop().


API Variants

API Client Auth Notes
Standard / local LANClient Game Mode (IP + port) Lovense Remote: 20011/30011. Connect: 34567
Standard / server ServerClient token + uid uid from QR callback. Use get_qr_code for pairing
Socket / server SocketAPIClient getToken, getSocketUrl QR scan, commands via WebSocket
Socket / local SocketAPIClient(use_local_commands=True) same + LAN Commands via HTTPS to device
Socket / local only LANClient IP + port only No token, no WebSocket
Events API ToyEventsClient access (appName) Port 20010. Lovense Remote only

Flow: Standard local → HTTP/HTTPS to device. Standard server → HTTPS to Lovense cloud. Socket → WebSocket to cloud (or HTTPS to device when use_local_commands=True). Events → WebSocket to device.

API Architecture

flowchart TB
    subgraph YourApp [Your App - lovensepy]
        LANClient
        ServerClient
        SocketAPIClient
        ToyEventsClient
    end

    subgraph Local [Local Network]
        RemoteApp[Lovense Remote App]
        Toy[Lovense Toy]
    end

    subgraph Cloud [Lovense Cloud]
        LovenseServer[Lovense Server]
    end

    LANClient -->|"HTTP/HTTPS"| RemoteApp
    RemoteApp --> Toy

    ServerClient -->|"HTTPS"| LovenseServer
    LovenseServer --> RemoteApp

    SocketAPIClient -->|"WebSocket"| LovenseServer
    SocketAPIClient -->|"HTTPS when use_local_commands"| RemoteApp
    LovenseServer --> RemoteApp

    ToyEventsClient -->|"WebSocket"| RemoteApp

Step-by-Step Tutorials

LAN Game Mode Tutorial

Step 1: Enable Game Mode in Lovense Remote

  • Open Lovense Remote > Discover > Game Mode > Enable LAN
  • Or Lovense Connect > Game Mode > Enable LAN

Step 2: Note the IP and port

  • Lovense Remote: typically port 20011 (HTTP) or 30011 (HTTPS)
  • Lovense Connect: typically port 34567

Step 3: Create the client

from lovensepy import LANClient

client = LANClient("MyApp", "192.168.1.100", port=20011)

Step 4: Get connected toys

response = client.get_toys()
toys = {toy.id: toy.model_dump() for toy in response.data.toys} if response.data else {}
# toys is {toy_id: toy_info}

Step 5: Send a Function command (auto-stop)

import time
from lovensepy import Actions

# Vibrate at level 10 for 5 seconds; toy is auto-stopped on context exit.
with client.play({Actions.VIBRATE: 10}, time=5):
    time.sleep(5)

Step 8: Optional — use Presets or Patterns

from lovensepy import Presets

client.preset_request(Presets.PULSE, time=5)
time.sleep(5)

# Custom pattern: list of strength levels (0-20)
client.pattern_request([5, 10, 15, 20], time=4)
time.sleep(4)

client.stop()

Full example:

import time
from lovensepy import LANClient, Actions, Presets

client = LANClient("MyApp", "192.168.1.100", port=20011, logging=True)

# Get toys
toys_response = client.get_toys()
toys = {toy.id: toy.model_dump() for toy in toys_response.data.toys} if toys_response.data else {}
print("Toys:", toys)

# Preset
client.preset_request(Presets.PULSE, time=5)
time.sleep(5)

# Function
client.function_request({Actions.ALL: 5}, time=3)
time.sleep(3)

# Pattern
client.pattern_request([5, 10, 15, 20], time=4)
time.sleep(4)

# Stop
client.stop()

Server API + QR Pairing Tutorial

Step 1: Get your developer token from the Lovense Developer Dashboard.

Step 2: Set up a callback URL (e.g. ngrok) and configure it in the Dashboard. Lovense will POST to this URL when a user scans the QR code.

Step 3: Call get_qr_code to get the QR image URL and 6-character code

from lovensepy import get_qr_code

qr_data = get_qr_code(developer_token="YOUR_TOKEN", uid="user_123")
qr_url = qr_data["qr"]   # Image URL for user to scan
code = qr_data["code"]   # 6-char code for PC Remote
print(f"Scan QR: {qr_url}")

Step 4: User scans the QR code in Lovense Remote.

Step 5: Lovense POSTs to your callback URL with uid and toys. Your server receives this and stores the uid.

Step 6: Create the ServerClient with the uid from the callback

from lovensepy import ServerClient, Actions

client = ServerClient(developer_token="YOUR_TOKEN", uid="user_123")

Step 7: Send commands (same as LAN)

import time

client.function_request({Actions.VIBRATE: 10}, time=5)
time.sleep(5)
client.stop()

Socket API Tutorial

The Socket API is async only. Use asyncio.run() to run your async code.

Step 1: Get an auth token

from lovensepy import get_token

auth_token = get_token(
    developer_token="YOUR_TOKEN",
    uid="user_123",
    uname="User"
)

Step 2: Get socket URL info. The platform must match the Website Name from your Lovense Developer Dashboard exactly.

from lovensepy import get_socket_url

socket_info = get_socket_url(auth_token, platform="Your App")

Step 3: Build the WebSocket URL

from lovensepy import build_websocket_url

ws_url = build_websocket_url(socket_info, auth_token)

Step 4: Create the client and connect

import asyncio
from lovensepy import SocketAPIClient

async def main():
    client = SocketAPIClient(ws_url, on_event=lambda e, p: print(e, p))
    connect_task = asyncio.create_task(client.connect())

Step 5: Request QR code (when on_socket_io_connected fires)

    client_ref = []

    def on_connected():
        client_ref[0].send_event("basicapi_get_qrcode_ts", {"ackId": "1"})

    client = SocketAPIClient(ws_url, on_socket_io_connected=on_connected, on_event=...)
    client_ref.append(client)

Step 6: User scans QR. You receive basicapi_update_device_info_tc with the toy list in the payload.

Step 7: Send commands when client.is_socket_io_connected is True

    if client.is_socket_io_connected:
        client.send_command("Function", "Vibrate:10", time_sec=5, toy="toy_id")

Step 8: Use send_command_await for critical stops (awaits delivery)

    await client.send_command_await("Function", "Stop", time_sec=0, toy="toy_id")

By local (same LAN): Pass use_local_commands=True — after QR scan, commands go via HTTPS to the device instead of WebSocket.


Toy Events Tutorial

Toy Events is Lovense Remote only (port 20010). Lovense Connect does not support Toy Events.

Step 1: Ensure you use Lovense Remote with Game Mode enabled. Port is typically 20010.

Step 2: Create the client with an event callback

import asyncio
from lovensepy import ToyEventsClient

def on_event(event_type, payload):
    print(event_type, payload)

client = ToyEventsClient(
    "192.168.1.100",
    port=20010,
    app_name="My App",
    on_event=on_event
)

Step 3: Connect (async)

async def main():
    await client.connect()

asyncio.run(main())

Step 4: User grants access when Lovense Remote prompts "Allow [My App] to access?"

Step 5: Receive events: toy-list, button-down, function-strength-changed, shake, etc.


API Reference

LANClient

Standard API LAN (Game Mode) client. Sends commands via HTTP/HTTPS to the Lovense app on the same network.

Constructor

LANClient(
    app_name: str,
    local_ip: str | None = None,
    *,
    domain: str | None = None,
    port: int = 20010,
    ssl_port: int = 30010,
    use_https: bool = False,
    verify_ssl: bool = True,
    timeout: float = 10.0,
    logging: bool = False,
)
Parameter Type Default Description
app_name str Application name (e.g. "MyApp")
local_ip str None Device IP (e.g. "192.168.1.100"). Use with domain=None.
domain str None Pre-built domain (e.g. "192-168-1-100.lovense.club"). Use when you have domain from Socket API.
port int 20010 HTTP port (Lovense Remote: 20011, Connect: 34567)
ssl_port int 30010 HTTPS port
use_https bool False Use HTTPS instead of HTTP
verify_ssl bool True Verify SSL cert. If False, uses fingerprint pinning.
timeout float 10.0 Request timeout in seconds
logging bool False Print requests/responses

Example:

client = LANClient("MyApp", "192.168.1.100", port=20011)

Class method: LANClient.from_device_info(app_name, domain, https_port=30010, **kwargs) — Create from Socket API device info (e.g. basicapi_update_device_info_tc payload).

Methods

Method Parameters Returns Description
get_toys() GetToysResponse Get connected toys. Uses a typed data.toys[] list.
get_toys_name() GetToyNameResponse Get connected toy names.
function_request(actions, time=0, loop_on_time=None, loop_off_time=None, toy_id=None, stop_previous=None) actions: dict like {Actions.VIBRATE: 10}; time: seconds CommandResponse Send Function command. time in seconds.
stop(toy_id=None) toy_id: str or list CommandResponse Stop all motors.
preset_request(name, time=0, toy_id=None) name: Presets enum or str CommandResponse Send Preset (pulse, wave, etc.).
pattern_request(pattern, actions=None, interval=100, time=0, toy_id=None) pattern: list of 0–20; interval: ms CommandResponse Custom pattern.
pattern_request_raw(strength, rule="V:1;F:;S:100#", time=0, toy_id=None) Raw rule/strength strings CommandResponse Advanced pattern.
position_request(value, toy_id=None) value: 0–100 CommandResponse Position for Solace Pro.
pattern_v2_setup(actions) actions: list of {ts, pos} CommandResponse PatternV2 Setup.
pattern_v2_play(toy_id=None, start_time=None, offset_time=None, time_ms=None) CommandResponse PatternV2 Play.
pattern_v2_init_play(actions, toy_id=None, ...) CommandResponse PatternV2 Setup + Play.
pattern_v2_stop(toy_id=None) CommandResponse PatternV2 Stop.
pattern_v2_sync_time() CommandResponse PatternV2 SyncTime.
send_command(command_data, timeout=None) Raw command dict dict Low-level; returns raw dict. Raises LovenseError on failures.
decode_response(response) Response dict str Human-readable response string.

Example:

import time

with client.play({Actions.VIBRATE: 10}, time=5, toy_id="T123"):
    time.sleep(5)

ServerClient

Standard API Server client. Sends commands via Lovense cloud. Requires developer token and uid from QR pairing.

Constructor

ServerClient(
    developer_token: str,
    uid: str,
    timeout: float = 10.0,
    logging: bool = False,
)
Parameter Type Description
developer_token str From Lovense Developer Dashboard
uid str User ID from QR pairing callback
timeout float Request timeout
logging bool Print requests/responses

Methods

Same command methods as LANClient: function_request, stop, pattern_request, preset_request, send_command, decode_response. Note: Server pattern_request uses (rule, strength, time, toy_id) — different signature from LAN.


SocketAPIClient

Async WebSocket client for Socket API. Commands via WebSocket (or LAN HTTPS when use_local_commands=True).

Constructor

SocketAPIClient(
    ws_url: str,
    *,
    use_local_commands: bool = False,
    app_name: str = "lovensepy",
    raise_on_disconnect: bool = False,
    on_socket_open: Callable | None = None,
    on_socket_close: Callable | None = None,
    on_socket_error: Callable[[Exception], ...] | None = None,
    on_socket_io_connected: Callable | None = None,
    on_event: Callable[[str, Any], ...] | None = None,
)
Parameter Type Description
ws_url str WebSocket URL from build_websocket_url
use_local_commands bool Send commands via LAN HTTPS when device on same network
app_name str App name for local commands
raise_on_disconnect bool Raise ConnectionError when sending while disconnected
on_socket_open, on_socket_close, on_socket_error Callable Connection lifecycle callbacks
on_socket_io_connected Callable Fired when Socket.IO handshake complete
on_event Callable Fired for each Socket.IO event (event_name, payload)

Methods

Method Description
connect() Async. Connect and run until disconnected.
disconnect() Close connection.
send_command(command, action, time_sec=0, toy=None, ...) Send command (non-blocking).
send_command_await(command, action, ...) Send command and await delivery. Use for stops.
send_event(event, payload=None) Send raw Socket.IO event.

Properties

Property Type Description
is_socket_io_connected bool True when Socket.IO handshake done and ready for commands
is_using_local_commands bool True when commands go via LAN HTTPS

ToyEventsClient

Async WebSocket client for Toy Events API. Receives real-time events from toys. Lovense Remote only, port 20010.

Constructor

ToyEventsClient(
    ip: str,
    port: int = 20010,
    use_https: bool = False,
    https_port: int = 30010,
    app_name: str = "lovensepy",
    *,
    on_open: Callable | None = None,
    on_close: Callable | None = None,
    on_error: Callable[[Exception], ...] | None = None,
    on_event: Callable[[str, Any], ...] | None = None,
)

Methods and Properties

Method/Property Description
connect() Async. Connect, request access, receive events until disconnected.
disconnect() Close connection.
is_connected True if WebSocket connected.
is_access_granted True when user granted access in Lovense Remote.

Pattern Players

High-level API for sine waves and combo patterns.

SyncPatternPlayer

For use with LANClient. Synchronous.

SyncPatternPlayer(client: LANClient, toys: dict[str, dict] | GetToysResponse)
Method Parameters Description
play_sine_wave(toy_id, feature, duration_sec=5, num_steps=100, stop_prev_first=True) feature: e.g. "Vibrate1" Play sine wave on one feature.
play_combo(targets, duration_sec=4, num_steps=100) targets: [(toy_id, feature), ...] Play combo with random phases.
stop(toy_id) Stop toy.
features(toy_id) Get features for toy.

Example:

player = SyncPatternPlayer(client, toys)
player.play_sine_wave("T123", "Vibrate1", duration_sec=5)
player.play_combo([("T1", "Vibrate1"), ("T2", "Vibrate")], duration_sec=4)
player.stop("T123")

AsyncPatternPlayer

For use with SocketAPIClient. Same methods, async (use await).

player = AsyncPatternPlayer(client, toys)
await player.play_sine_wave("T123", "Vibrate1", duration_sec=5)
await player.stop("T123")

Utilities

Function Parameters Returns Description
get_token(developer_token, uid, uname=None, utoken=None, timeout=10) str Get auth token for Socket API. Raises on error.
get_socket_url(auth_token, platform, timeout=10) platform: Website Name from Dashboard dict Get socket info dict.
build_websocket_url(socket_info, auth_token) str Build full wss:// URL.
get_qr_code(developer_token, uid, uname=None, utoken=None, timeout=10) dict Get QR for Server API. Returns {qr, code}. See security note in docstring.
features_for_toy(toy) toy: dict from GetToys list[str] Get features (e.g. ["Vibrate1", "Rotate"]).
stop_actions(toy) toy: dict dict Build {Vibrate1: 0, ...} to stop.

Appendix

Actions and Presets

Actions (function types)

Action Range Toys
Actions.VIBRATE 0–20 Most
Actions.VIBRATE1, VIBRATE2, VIBRATE3 0–20 Edge, Diamo, multi-motor
Actions.ROTATE 0–20 Nora, Max, etc.
Actions.PUMP 0–3 Max 2
Actions.THRUSTING 0–20 Solace, Mission
Actions.FINGERING 0–20 Solace
Actions.SUCTION 0–20 Max 2
Actions.DEPTH 0–3 Solace Pro
Actions.STROKE 0–100 Solace Pro
Actions.OSCILLATE 0–20 Some toys
Actions.ALL 0–20 All motors at once
Actions.STOP Stop

Usage:

client.function_request({Actions.VIBRATE: 10}, time=5)
client.function_request({Actions.VIBRATE1: 5, Actions.VIBRATE2: 10}, time=3)

Presets (built-in patterns)

Preset Description
Presets.PULSE Pulse pattern
Presets.WAVE Wave pattern
Presets.FIREWORKS Fireworks pattern
Presets.EARTHQUAKE Earthquake pattern

Usage:

client.preset_request(Presets.PULSE, time=5)

Toy Events Event Types

Event When
toy-list Toys added/removed/enabled
toy-status Toy connected/disconnected
button-down, button-up, button-pressed User pressed toy button
function-strength-changed User changed level in app
shake, shake-frequency-changed Shake sensor
battery-changed, depth-changed, motion-changed Sensor updates
event-closed Game mode disabled
access-granted User granted access (internal)
pong Ping response (internal)

Lovense Flow Diagrams

The following sequence diagrams illustrate the flows described in the Lovense developer documentation.

Server API — QR pairing flow:

sequenceDiagram
    participant User as Your User
    participant App as Your App
    participant Server as Your Server
    participant Lovense as Lovense Server
    participant Remote as Lovense Remote App
    participant Toy as Lovense Toy

    User->>Remote: Open Lovense Remote
    User->>Toy: Turn on the toy
    User->>App: User logs in to your app
    App->>Server: Request to bind with Lovense Toy
    Server->>Lovense: Request QR code from Lovense
    Lovense-->>Server: Return a QR code URL
    Server->>App: Display the QR code
    User->>Remote: User scans the QR code with Lovense Remote App
    Remote->>Server: Lovense Remote app POSTs to your server
    App->>Remote: Control the toy by instructing the App
    Remote->>Toy: Trigger vibration

Socket API — authorization and connection flow:

sequenceDiagram
    participant User as User (Lovense App)
    participant Interface as Developer Interface
    participant DevServer as Developer Server
    participant Lovense as Lovense Server

    Interface->>Lovense: 1) Application for authorization token
    Lovense-->>Interface: Response authorization token
    Interface->>DevServer: Forward authorization token
    DevServer->>Lovense: 2) Validate authorization token
    Lovense-->>DevServer: Verification success, response socket connection info
    DevServer->>Lovense: Establishing socket connection
    DevServer->>Lovense: Get QR code information by socket
    User->>Interface: Start Lovense App, connect toys and scan the QR code
    Lovense->>User: 3) Report device information periodically
    Note over Lovense,User: Device info contains toy list, domain and port of local HTTP service
    Lovense->>DevServer: Synchronizing device information
    Interface->>User: Show toys and send command

Architecture

  • Clients: LANClient, ServerClient, SocketAPIClient, ToyEventsClient — command building, protocols
  • Transport: HttpTransport (POST JSON), WsTransport (WebSocket)
  • Security: Certificate fingerprint verification for HTTPS (port 30010/30011) when verify_ssl=False

HTTPS Certificate

For local HTTPS (ports 30010/30011), lovensepy verifies the Lovense certificate fingerprint instead of disabling SSL. Fingerprint in lovensepy.security.LOVENSE_HTTPS_FINGERPRINT.


Examples

File Description
examples/lan_game_mode.py LAN Game Mode — get toys, presets, functions, patterns
examples/patterns_demo.py Sine waves and combos with SyncPatternPlayer
examples/server_api.py Server API with token and uid
examples/socket_api_full.py Socket API with QR flow and command sending
examples/toy_events_full.py Toy Events — receive real-time events

Run with env vars, e.g. LOVENSE_LAN_IP=192.168.1.100 python examples/lan_game_mode.py


Tests

Install

pip install -e ".[dev]"

Unit tests (no devices)

pytest tests/test_unit.py -v

Integration tests

Integration tests require Lovense hardware and/or a developer token. Set environment variables for the test mode you use, then run the corresponding test file.

Test modes and required env vars:

Test file Mode Required env vars
test_standard_local.py Standard / local LOVENSE_LAN_IP, LOVENSE_LAN_PORT (20011 Remote, 34567 Connect)
test_standard_server.py Standard / server LOVENSE_DEV_TOKEN, LOVENSE_UID — or LOVENSE_QR_PAIRING=1 + ngrok
test_socket_server.py Socket / server LOVENSE_DEV_TOKEN, LOVENSE_UID, LOVENSE_PLATFORM
test_socket_server.py::test_by_local Socket / local Same as server + device on same LAN
test_socket_local.py Socket / local only LOVENSE_LAN_IP, LOVENSE_LAN_PORT
test_toy_events.py Toy Events LOVENSE_LAN_IP, LOVENSE_TOY_EVENTS_PORT (20010)

Example env setup:

export LOVENSE_LAN_IP=192.168.1.100
export LOVENSE_LAN_PORT=34567          # Lovense Connect
export LOVENSE_DEV_TOKEN=your_token
export LOVENSE_UID=your_uid
export LOVENSE_PLATFORM="Your App"
export LOVENSE_TOY_EVENTS_PORT=20010   # Toy Events (Lovense Remote only)
export LOVENSE_QR_PAIRING=1
export LOVENSE_CALLBACK_PORT=8765      # ngrok or cloudflared

Run integration tests:

pytest tests/test_standard_local.py -v -s
pytest tests/test_standard_server.py -v -s
pytest tests/test_socket_server.py -v -s
pytest tests/test_socket_local.py -v -s
pytest tests/test_toy_events.py -v -s

Links


License

Apache License 2.0 — see LICENSE for full text.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

lovensepy-1.0.1.tar.gz (56.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

lovensepy-1.0.1-py3-none-any.whl (68.7 kB view details)

Uploaded Python 3

File details

Details for the file lovensepy-1.0.1.tar.gz.

File metadata

  • Download URL: lovensepy-1.0.1.tar.gz
  • Upload date:
  • Size: 56.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for lovensepy-1.0.1.tar.gz
Algorithm Hash digest
SHA256 c526a2b55ba51ed7b8f5ff1b953f3ac5b881ae065559d624b546456087d76cdc
MD5 a5969d136571ed46ce6a6b08b91e9c08
BLAKE2b-256 6f083a7b41112b42d349e1501613c6e888c205a4dab7a4273a377f7bacc23410

See more details on using hashes here.

Provenance

The following attestation bundles were made for lovensepy-1.0.1.tar.gz:

Publisher: publish-pypi.yml on koval01/pylove

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file lovensepy-1.0.1-py3-none-any.whl.

File metadata

  • Download URL: lovensepy-1.0.1-py3-none-any.whl
  • Upload date:
  • Size: 68.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for lovensepy-1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 da4517cf198aba3f07f812fae601de897bd920c4aeb6498c9e116c419d72bc62
MD5 f5bb9edb4cb2cb92476fc6ed87ec38d5
BLAKE2b-256 ccd0018c793427f04e1d117026134c462435b7a34f4742e663d585530d77d8e7

See more details on using hashes here.

Provenance

The following attestation bundles were made for lovensepy-1.0.1-py3-none-any.whl:

Publisher: publish-pypi.yml on koval01/pylove

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page