Files
rustdesk/docs/CONSOLE_API.md
T
mike 47f0d0fff2
build-linux / build-linux-x64 (push) Successful in 5m23s
build-macos / build-macos-x64 (push) Successful in 9m4s
build-windows / build-windows-x64 (push) Successful in 10m13s
Implement CI workflow for Gitea. Include provision scripts for Gitea runners.
2026-05-07 09:39:23 +02:00

36 KiB
Raw Blame History

RustDesk Console API — Backend Specification

This document specifies every HTTP/HTTPS endpoint that the RustDesk client in this workspace calls against its server-side Console / API server (the API surface exposed by RustDesk Server Pro / hbbs). The intent is that a backend implemented to this spec will be able to serve a stock RustDesk client with no client-side changes.

The client base URL is whatever the user / installer has configured as api-server (or, falling back, derived from custom-rendezvous-server). All paths below are relative to that base URL.

If the configured API host string contains "rustdesk.com/" or ends with "rustdesk.com", the client treats it as the public/managed server and suppresses the heartbeat, sysinfo, and audit endpoints (is_public() in src/common.rs:1088). A self-hosted backend MUST NOT use a hostname matching that pattern.


1. Conventions

1.1 Transport

1.2 Headers

For every authenticated request the client adds:

Authorization: Bearer <access_token>
Content-Type:  application/json

Authorization is built by getHttpHeaders() in flutter/lib/common.dart:2691-2695 from the locally stored access_token. Unauthenticated calls (login, OIDC, heartbeat, sysinfo, audit, record, plugin-sign) omit Authorization.

1.3 Error envelope

Successful action endpoints (peer add/update/delete, tag CRUD, etc.) typically return either an empty body / null / {} on success, or { "error": "<message>" } on failure with HTTP 200. The status code alone is not a sufficient success indicator — the client always inspects the JSON error field.

1.4 Pagination

List endpoints use 1-indexed pagination, query parameters:

?current=<page>&pageSize=<size>

The client always uses pageSize=100 and iterates pages until current * pageSize >= total. Response:

{
  "total": <int>,
  "data":  [ ... ]
}

1.5 IDs and identifiers

  • id — RustDesk peer/device ID (the 9-digit number shown in the UI).
  • uuid — A per-install machine identifier, base64-encoded by the client before transmission (crate::encode64(hbb_common::get_uuid())).
  • guid — Server-assigned GUID for an address book.
  • conn_id, session_id — Internal ints assigned by the client per remote session, echoed back in audit logs.

1.6 deviceInfo object

Sent inside login bodies. Shape (from src/hbbs_http/account.rs:31-44):

{
  "os":   "Linux | Windows | macOS | Android | iOS",
  "type": "client | browser",
  "name": "<device name from client, or browser name+version>"
}

2. Base URL & API discovery

The client resolves its base URL via get_api_server() in src/common.rs; a custom-rendezvous server with no explicit api-server is mapped to https://<rendezvous-host> (port 21114 if specified inline).

The client makes a HEAD request against <base>/api/login-options on startup as a connectivity / TLS-detection probe (src/hbbs_http/account.rs:155, src/hbbs_http/record_upload.rs:34). A backend SHOULD respond 200 to a HEAD on /api/login-options.


3. Authentication

3.1 POST /api/login

User credential / 2FA / SSO completion.

Request body (from LoginRequest.toJson() at flutter/lib/common/hbbs/hbbs.dart:133-178):

{
  "username":         "string",       // optional
  "password":         "string",       // optional
  "id":               "string",       // RustDesk peer ID
  "uuid":             "string",       // base64
  "autoLogin":        true,           // optional bool
  "type":             "account | mobile | sms_code | email_code | tfa_code | oidc/<provider>",
  "verificationCode": "string",       // optional, for email/SMS challenge
  "tfaCode":          "string",       // optional
  "secret":           "string",       // optional, echoed back from a tfa_check response
  "deviceInfo":       { "os": "...", "type": "...", "name": "..." }
}

type constants (flutter/lib/common/hbbs/hbbs.dart:11-15): account, mobile, sms_code, email_code, tfa_code. oidc/<name> is also accepted (the oidc/ prefix triggers the device-flow described in §3.5).

Successful responseLoginResponse shape (flutter/lib/common/hbbs/hbbs.dart:180-197):

{
  "access_token": "opaque-bearer-token",
  "type":         "access_token",
  "tfa_type":     "totp | ...",   // present when type == "tfa_check"
  "secret":       "string",       // present when type == "tfa_check"
  "user": {
    "name":         "string",
    "display_name": "string",
    "avatar":       "string",
    "email":        "string",
    "note":         "string",
    "status":       1,            // -1=unverified, 0=disabled, 1=normal
    "is_admin":     false,
    "verifier":     "string"      // optional, used by web build only
  }
}

Challenge responses. When 2FA / verification is required, type is one of (flutter/lib/common/hbbs/hbbs.dart:17-19):

type Meaning
access_token Login complete, access_token populated.
email_check Server emailed a code; client must POST again with type:"email_code" and verificationCode.
tfa_check TOTP required; server returns tfa_type and secret to echo back with type:"tfa_code" + tfaCode.

Status / error semantics:

  • Non-200 with body {"error": "..."} → client surfaces the error.
  • 401 from this endpoint is treated as bad credentials (no auto-logout).
  • 401 from any other authenticated endpoint clears the local token (see §3.6).

3.2 GET /api/login-options

Returns the list of login methods the server is configured to expose.

Response — JSON array of strings:

[
  "account",
  "oidc/google",
  "oidc/github",
  "common-oidc/[ {\"name\":\"google\",\"icon\":\"...\"} ]"
]

Two recognised conventions:

  • oidc/<provider> — exposed in the UI as an SSO button labelled <provider>.
  • common-oidc/<json-array> — the suffix is a JSON-encoded array of provider descriptors { "name": "...", ... }. If present anywhere in the list, it takes precedence over individual oidc/... entries.

3.3 POST /api/currentUser

Refreshes the cached profile of the currently logged-in user.

Request body:

{ "id": "<peer id>", "uuid": "<base64 uuid>" }

Response: UserPayload (see §3.1). On HTTP 401 or 400 the client clears the access token (401 also wipes the address-book and group caches).

3.4 POST /api/logout

Request body:

{ "id": "<peer id>", "uuid": "<base64 uuid>" }

Response: body is ignored.

3.5 OIDC / SSO device flow

Two endpoints implement a polled device-code flow.

POST /api/oidc/auth

Request body:

{
  "op":         "<provider name>",      // matches an entry from /api/login-options
  "id":         "<peer id>",
  "uuid":       "<base64 uuid>",
  "deviceInfo": { "os": "...", "type": "...", "name": "..." }
}

ResponseOidcAuthUrl (src/hbbs_http/account.rs:24-28):

{
  "code": "opaque-poll-handle",
  "url":  "https://server/oidc/redirect?code=...&..."
}

The client opens url in the user's browser and polls (§ next).

GET /api/oidc/auth-query

Query parameters: code (from auth response), id, uuid.

Response wrapper — the response is an outer envelope whose body field is itself JSON:

{
  "body": "<stringified JSON response>"
}

The inner body, once parsed, is either:

  • A normal AuthBody (same shape as §3.1 success), or
  • { "error": "No authed oidc is found" } while waiting (the client keeps polling until success or timeout), or
  • Any other { "error": "..." } (terminates polling with an error).

3.6 401 handling

A 401 from any authenticated endpoint causes the Flutter client to:

  1. Clear access_token and cached user_info from local config.
  2. Reset address-book and group state.

A 400 from /api/currentUser clears the access token but does not reset other state (flutter/lib/models/user_model.dart:81-83).


4. Address book

The client supports two modes:

  • Legacy mode — the whole address book is GET/PUT as a single JSON blob. The server returns 404 on /api/ab/personal to signal legacy mode (flutter/lib/models/ab_model.dart:271-274).
  • Shared mode — the personal AB and zero or more shared address books are represented as separate guid-keyed objects with paginated peer/tag lists.

A backend SHOULD implement shared mode (legacy mode is supported only as fall-back).

4.1 POST /api/ab/settings

Capability/limits probe. Called once per pull cycle.

  • Source: flutter/lib/models/ab_model.dart:230-258
  • Auth: Bearer
  • Request body: empty (Content-Type still application/json)
  • Special status: 404 ⇒ "this server does not support shared AB; abort init". Any other non-200 surfaces pull_ab_failed_tip.

Response:

{ "max_peer_one_ab": 100 }

4.2 POST /api/ab/personal

Returns the GUID of the caller's personal address book.

Response:

{ "guid": "personal-ab-guid" }

4.3 POST /api/ab/shared/profiles

Paginated list of shared address books visible to the user.

  • Source: flutter/lib/models/ab_model.dart:295-360
  • Auth: Bearer
  • Method: POST (the resource is fetched with POST in the Flutter client, query params are still used for pagination)
  • Query: current, pageSize
  • Request body: empty
  • Special status: 404 ⇒ no shared-AB support.

Response — page of AbProfile:

{
  "total": 12,
  "data": [
    {
      "guid":  "ab-guid",
      "name":  "Engineering",
      "owner": "alice",
      "note":  "optional string",
      "rule":  2,                  // 1=read, 2=read/write, 3=full control
      "info":  { ... }             // opaque, surfaced in the UI as-is
    }
  ]
}

rule enum from flutter/lib/common/hbbs/hbbs.dart:210-256.

4.4 POST /api/ab/peers?ab=<guid>

Paginated list of peers in a given address book.

Response page entry — Peer:

{
  "id":       "123456789",
  "alias":    "string",
  "tags":     ["tag1", "tag2"],
  "note":     "string",
  "hash":     "string",      // present in personal AB only (password hash)
  "password": "string",      // present in shared AB only (encrypted password)
  "username": "string",      // OS username on the remote device
  "hostname": "string",
  "platform": "Windows | Linux | Mac OS | Android | iOS"
}

hash/password are mutually exclusive: the client strips password for personal ABs and hash for shared ABs before pushing (flutter/lib/models/ab_model.dart:1561-1565).

4.5 POST /api/ab/tags/{guid}

List the tags of an address book. Returns a JSON array (no pagination).

Response:

[
  { "name": "Production", "color": -16776961 },
  { "name": "QA",         "color": -65536 }
]

color is a Flutter Color.value (signed 32-bit ARGB packed integer).

4.6 Peer mutations on a shared/personal AB

All four take Authorization + Content-Type: application/json. Success is either an empty body or {}; failure is HTTP 200 with {"error":"..."} (the client reads error regardless of status).

POST /api/ab/peer/add/{guid} — add a peer

Source: flutter/lib/models/ab_model.dart:1548-1578.

{
  "id":       "123456789",
  "alias":    "string",
  "tags":     ["..."],
  "note":     "string",
  "password": "string",   // shared AB
  "hash":     "string",   // personal AB
  "username": "string",
  "hostname": "string",
  "platform": "string"
}

The client adds peers one-by-one (one HTTP call per peer).

PUT /api/ab/peer/update/{guid} — partial peer update

Source: flutter/lib/models/ab_model.dart:1580-1729.

The body always contains id, plus any subset of mutable fields. The client uses this single endpoint for: alias change, note change, tag change, password (password) / hash (hash) change, and username/hostname/platform sync from recent connections.

{
  "id":       "123456789",
  "alias":    "string",
  "tags":     ["..."],
  "note":     "string",
  "password": "string",
  "hash":     "string",
  "username": "string",
  "hostname": "string",
  "platform": "string"
}

DELETE /api/ab/peer/{guid} — bulk delete

Source: flutter/lib/models/ab_model.dart:1751-1771.

Body is a JSON array of peer IDs:

["123456789", "987654321"]

4.7 Tag mutations on a shared/personal AB

POST /api/ab/tag/add/{guid}

Source: flutter/lib/models/ab_model.dart:1775-1802. The client iterates one POST per tag.

{ "name": "tag", "color": -16776961 }

PUT /api/ab/tag/rename/{guid}

Source: flutter/lib/models/ab_model.dart:1804-1831.

{ "old": "old-name", "new": "new-name" }

PUT /api/ab/tag/update/{guid} — set color

Source: flutter/lib/models/ab_model.dart:1833-1855.

{ "name": "tag", "color": -16776961 }

DELETE /api/ab/tag/{guid} — bulk delete

Source: flutter/lib/models/ab_model.dart:1857-1876.

Body is a JSON array of tag names: ["tagA", "tagB"].

4.8 Legacy address book (single-blob mode)

Used only when /api/ab/personal returns 404.

GET /api/ab

Source: flutter/lib/models/ab_model.dart:1007-1053.

  • Auth: Bearer
  • Headers: Accept-Encoding: gzip
  • Response:
{
  "data":              "<stringified JSON of {tags,peers,tag_colors}>",
  "licensed_devices":  100
}

data decoded:

{
  "tags":       ["tag1", ...],
  "peers":      [ { "id": "...", "alias": "...", ... }, ... ],
  "tag_colors": "<stringified JSON map: { \"tag1\": -16776961, ... }>"
}

A response body of the literal string null (or empty) means "empty AB, no error".

POST /api/ab

Source: flutter/lib/models/ab_model.dart:1055-1096.

  • Auth: Bearer
  • Body: the entire address book replaces what the server stores.
{ "data": "<stringified JSON of {tags,peers,tag_colors}>" }

Success: HTTP 200 with body empty / null / {}. Failure: {"error":"..."}.


5. Device groups, users, peers (group view)

These three endpoints together populate the "Group / Device" tab. All three are GET, paginated, Bearer-authenticated.

5.1 GET /api/device-group/accessible

Source: flutter/lib/models/group_model.dart:103-158.

  • Query: current, pageSize
  • Behaviour: the client treats any error from this endpoint as "old hbbs without device-group support" and silently continues.

Response page entry — DeviceGroupPayload:

{ "name": "Engineering" }

5.2 GET /api/users

Source: flutter/lib/models/group_model.dart:160-222.

  • Query: current, pageSize, accessible= (empty string), status=1
  • Auth: Bearer

Response page entry — UserPayload (same shape as §3.1).

The client recognises the legacy errors "Admin required!" and "ambiguous column name: status" and translates them to a "please upgrade RustDesk Server Pro" toast. Backends should not produce them.

5.3 GET /api/peers

Source: flutter/lib/models/group_model.dart:224-282.

  • Query: current, pageSize, accessible= (empty), status=1
  • Auth: Bearer

Response page entry — PeerPayload (from flutter/lib/common/hbbs/hbbs.dart:77-131):

{
  "id":                "123456789",
  "user":              "alice",
  "user_name":         "Alice Doe",
  "device_group_name": "Engineering",
  "note":              "string",
  "status":            1,
  "info": {
    "username":    "alice",
    "device_name": "ALICE-PC",
    "os":          "Windows 10 / x64"   // first " / "-separated token used
                                         // to determine platform
  }
}

6. Heartbeat & system info

These three endpoints are the "agent loop". They are never sent if the configured API server matches the public rustdesk.com pattern (src/hbbs_http/sync.rs:276-285).

The loop wakes every 3 s (TIME_CONN in src/hbbs_http/sync.rs:19) but only sends a heartbeat every 15 s unless connections changed (TIME_HEARTBEAT in src/hbbs_http/sync.rs:17). Sysinfo is re-uploaded at most every 120 s (UPLOAD_SYSINFO_TIMEOUT in src/hbbs_http/sync.rs:18) and only if hash/version differ.

6.1 POST /api/heartbeat

Source: src/hbbs_http/sync.rs:235-271.

  • Auth: none
  • Headers: Content-Type: application/json

Request body:

{
  "id":          "123456789",
  "uuid":        "<base64 uuid>",
  "ver":         123456,             // numeric version (hbb_common::get_version_number)
  "conns":       [101, 102],         // omitted if no active connections
  "modified_at": 0                   // last strategy timestamp the client knows
}

Response — any subset of:

{
  "sysinfo":     "any-truthy-value",   // presence forces sysinfo re-upload
  "disconnect": [101],                 // conn IDs the client should drop
  "modified_at": 1700000000,           // newer timestamp ⇒ persist locally
  "strategy": {
    "config_options": { "<key>": "<value>", ... },
    "extra":          { ... }
  }
}

Any value the client does not recognise is ignored. strategy.config_options is merged into the client's options; an empty value with no built-in default removes the option, otherwise it overwrites.

6.2 POST /api/sysinfo

The client first probes versions with /api/sysinfo_ver (§6.3); if the version matches what the server already has, this POST is skipped.

Source: src/hbbs_http/sync.rs:131-229.

  • Auth: none
  • Headers: Content-Type: application/json

Request body (top-level fields are merged from get_sysinfo() plus preset options):

{
  "version":  "1.4.x",
  "id":       "123456789",
  "uuid":     "<base64 uuid>",
  "username": "alice",                           // OS username
  "hostname": "ALICE-PC",
  "os":       "Windows 10 / x64",
  "cpu":      "...", "memory": "...",            // and other fields from get_sysinfo
  "preset_address_book_name":     "...",         // optional, only if configured
  "preset_address_book_tag":      "...",
  "preset_address_book_alias":    "...",
  "preset_address_book_password": "...",
  "preset_address_book_note":     "...",
  "preset_username":              "...",
  "preset_strategy_name":         "...",
  "preset_device_group_name":     "..."
}

Response — body is treated as a bare string (not JSON):

Body Meaning
SYSINFO_UPDATED Success. Client caches a SHA-256 of (URL+body) and the version returned by /api/sysinfo_ver.
ID_NOT_FOUND Re-upload at next heartbeat tick (no cache).
anything else / err Treated as success-with-deferral (cache still skipped).

6.3 POST /api/sysinfo_ver

Source: src/hbbs_http/sync.rs:192-208.

  • Auth: none
  • Body: empty
  • Response: an opaque version string. The client compares against its cached sysinfo_ver; if equal and the request hash is unchanged, the full sysinfo upload is skipped this cycle.

Backends without versioning may always return an empty string; the client will then upload sysinfo each cycle.


7. Audit logging

Audit endpoints are only called when the API server is non-public (get_audit_server() in src/common.rs:1119-1125).

All three are POST with Content-Type: application/json, no Authorization header, and fire-and-forget (response is ignored beyond logging). They share a common envelope:

{
  "id":         "<peer id of this client>",
  "uuid":       "<base64 uuid of this client>",
  ...endpoint-specific fields
}

7.1 POST /api/audit/conn

Source: src/server/connection.rs:1248-1279.

{
  "id":         "...",
  "uuid":       "...",
  "conn_id":    101,
  "session_id": 7,
  "ip":         "192.0.2.10",
  "action":     "new"
}

Currently the only action emitted by the client is "new" (sent immediately after the remote IP is verified against the IP whitelist). The server response is ignored, but the dialog flow in §7.4 implies the server returns or stores a guid per audit row.

7.2 POST /api/audit/file

Source: src/server/connection.rs:1297-1330.

{
  "id":      "...",
  "uuid":    "...",
  "peer_id": "<remote peer id>",
  "type":    0,                         // 0 = RemoteSend, 1 = RemoteReceive
  "path":    "C:\\path\\to\\dir",
  "is_file": false,
  "info":    "<stringified JSON>"
}

info, decoded:

{
  "ip":    "192.0.2.10",
  "name":  "alice (display name)",
  "num":   42,                                // total files in the operation
  "files": [ { "name": "big.iso", "size": 4400000000 }, ... ]   // top-10 by size
}

type enum from src/server/connection.rs:5063-5066: 0 = RemoteSend, 1 = RemoteReceive. is_file is true only when the operation is a single file (files.len() == 1 && files[0].name == "").

7.3 POST /api/audit/alarm

Source: src/server/connection.rs:1332-1349.

{
  "id":   "...",
  "uuid": "...",
  "typ":  0,                  // see enum below
  "info": "<stringified JSON, free-form>"
}

typ enum from src/server/connection.rs:5053-5061:

Value Constant Meaning
0 IpWhitelist Connection rejected by IP whitelist.
1 ExceedThirtyAttempts >30 password attempts.
2 SixAttemptsWithinOneMinute 6 password attempts in 60 s.
6 ExceedIPv6PrefixAttempts Per-/64 IPv6 attempt ceiling exceeded.

(Values 35 are reserved / commented out.)

7.4 PUT /api/audit

Update an existing connection-audit row (used by the "leave a note at end of session" dialog).

{
  "guid": "<audit-row guid as returned by /api/audit/conn>",
  "note": "free-form text"
}

A 200 status is treated as success; non-200 is logged and discarded.


8. Session recording upload

Used when session-recording-on-the-server is enabled. All requests share the endpoint POST /api/record and disambiguate via the type query parameter.

Per-call query parameters:

type file offset length Body When sent
new yes empty New recording starts.
part yes byte offset (decimal) bytes to follow raw chunk Periodic upload.
tail yes 0 header length (≤1024) first ≤1024 bytes of file Recording finished; uploaded after final part.
remove yes empty Recording aborted.

file is the basename, e.g. 2025-04-12_14-22-01.mp4.

Response: any JSON object. If it contains {"error": "..."}, the client aborts the recording session and logs the error.


9. Generic file download

Used by the auto-update / installer-fetch path (src/hbbs_http/downloader.rs).

  • The URL is arbitrary; not necessarily on the API server. Path-format agnostic.
  • The client first sends HEAD to read Content-Length, then GET to stream the body. Both calls go through the same TLS-fallback machinery as the API client.
  • The server MUST return Content-Length on the HEAD response; without it the download is aborted with "Failed to get content length".
  • Streamed responses (chunked transfer-encoding) are fine for the GET.

10. Plugin signature service

For installations that ship signed plugins.

POST /lic/web/api/plugin-sign

Request body (PluginSignReq at src/plugin/callback_msg.rs:82-87):

{
  "plugin_id": "string",
  "version":   "string",
  "msg":       [/* byte array; serde will encode Vec<u8> as JSON array of u8 */]
}

Response (PluginSignResp):

{ "signed_msg": [/* byte array */] }

The bytes are passed verbatim into the plugin's handle_signature_verification entry point.


11. CLI device assignment (--assign)

Triggered from a terminal: rustdesk --assign --token <bearer> [...]. Used by mass-deploy scripts to register a freshly-installed agent into a tenant.

POST /api/devices/cli

  • Source: src/core_main.rs:519-616
  • Auth: Authorization: Bearer <token> (passed via --token)
  • Headers: Content-Type: application/json

Request body (only id/uuid are mandatory; at least one of the optional fields must be present, see CLI help):

{
  "id":                    "<this device's peer id>",
  "uuid":                  "<base64 uuid>",
  "user_name":             "...",     // optional
  "strategy_name":         "...",     // optional
  "address_book_name":     "...",     // optional
  "address_book_tag":      "...",     // optional
  "address_book_alias":    "...",     // optional
  "address_book_password": "...",     // optional
  "address_book_note":     "...",     // optional
  "device_group_name":     "...",     // optional
  "note":                  "...",     // optional
  "device_username":       "...",     // optional
  "device_name":           "..."      // optional
}

Response: plain text. Empty body ⇒ Done! is printed; otherwise the body is printed verbatim to stdout. No structured error contract.


12. Endpoint index

# Method Path Auth Notes
1 GET /api/login-options none Probe + SSO list
2 POST /api/login none Username/password, 2FA, SSO completion
3 POST /api/currentUser Bearer Refresh profile
4 POST /api/logout Bearer Best-effort
5 POST /api/oidc/auth none Begin device-flow
6 GET /api/oidc/auth-query none Poll device-flow
7 POST /api/ab/settings Bearer max_peer_one_ab
8 POST /api/ab/personal Bearer Personal AB GUID; 404 ⇒ legacy mode
9 POST /api/ab/shared/profiles Bearer Paginated AB list
10 POST /api/ab/peers?ab=<guid> Bearer Paginated peer list
11 POST /api/ab/tags/{guid} Bearer All tags
12 POST /api/ab/peer/add/{guid} Bearer Add one peer
13 PUT /api/ab/peer/update/{guid} Bearer Partial update of one peer
14 DELETE /api/ab/peer/{guid} Bearer Bulk delete by ID list
15 POST /api/ab/tag/add/{guid} Bearer Add one tag
16 PUT /api/ab/tag/rename/{guid} Bearer Rename
17 PUT /api/ab/tag/update/{guid} Bearer Set color
18 DELETE /api/ab/tag/{guid} Bearer Bulk delete by name list
19 GET /api/ab Bearer Legacy AB blob (gzip)
20 POST /api/ab Bearer Legacy AB blob save
21 GET /api/device-group/accessible Bearer Paginated; errors silently tolerated
22 GET /api/users Bearer Paginated, accessible=&status=1
23 GET /api/peers Bearer Paginated, accessible=&status=1
24 POST /api/heartbeat none Every 15 s (3 s when active)
25 POST /api/sysinfo_ver none Cache probe
26 POST /api/sysinfo none Bare-string response
27 POST /api/audit/conn none Connection start
28 POST /api/audit/file none File transfer summary
29 POST /api/audit/alarm none Security alarms
30 PUT /api/audit Bearer Update note on a conn audit row
31 POST /api/record none ?type=new|part|tail|remove&file=&offset=&length=
32 POST /api/devices/cli Bearer Used by rustdesk --assign
33 POST /lic/web/api/plugin-sign none Plugin signature
34 HEAD (configured api-server) probe none Performed once on startup against /api/login-options
35 HEAD+GET (arbitrary URL) none Generic downloader; HEAD must return Content-Length

13. Minimum viable backend

To stand up a backend that a stock RustDesk client can use end-to-end, in priority order:

  1. Connectivity probe — answer HEAD /api/login-options with 200 OK.
  2. Auth core — implement GET /api/login-options, POST /api/login, POST /api/logout, POST /api/currentUser. Return access_token on successful login.
  3. Heartbeat & sysinfoPOST /api/heartbeat, POST /api/sysinfo, POST /api/sysinfo_ver. Without these the agent loop logs errors but continues; with them, the server has live device tracking.
  4. Address book — pick one mode:
    • Modern: POST /api/ab/settings, POST /api/ab/personal, POST /api/ab/shared/profiles, POST /api/ab/peers, POST /api/ab/tags/{guid}, plus the per-peer / per-tag mutation set (§4.6, §4.7).
    • Legacy: respond 404 to /api/ab/personal, then implement GET /api/ab + POST /api/ab (§4.8).
  5. Group viewGET /api/users, GET /api/peers, GET /api/device-group/accessible. Without these the device-tab is empty.
  6. Audit — implement the four audit routes (§7) only if you want server-side logging; the client tolerates 4xx/5xx silently.
  7. Optional: /api/record, /api/devices/cli, /api/oidc/*, /lic/web/api/plugin-sign.

A backend that returns { "error": "..." } with a clear message on any unsupported endpoint will produce reasonable UX in the client.