Remnawave API compatibility
This catalog is generated from the same typed registry used by the runtime client.
Do not edit it manually: update panel_api_contracts.py or
remnawave_support.json, regenerate, and run the contract tests.
Support policy
Заголовок раздела «Support policy»Core certifies 2 API generations at a time: the current generation and one maintenance generation. Exact versions are certified; a new patch/minor in a known generation is reported as unverified until live CI is green. Removal gets at least 90 days and 2 Core releases of notice, and requires a breaking Core release.
Unknown future Remnawave major versions run in best-effort mode: existing reads and writes remain available because a major bump does not necessarily change the API. They stay marked unverified until certified, and endpoint/capability fallbacks limit assumptions. If the metadata endpoint is temporarily unavailable, Core preserves best-effort compatibility and uses identifier/endpoint observations instead of caching a false version result.
Certified versions
Заголовок раздела «Certified versions»| Status | API generation | Exact versions | Preset | Capabilities | Coverage | Upstream |
|---|---|---|---|---|---|---|
| current | rw3-numeric-user-id | 3.2.3, 3.2.1, 3.2.0, 3.1.0, 3.0.0 | 3.2.3 | numeric-user-ids, user-stream, user-stream-filters, targeted-squad-bulk, connections-drop, hwid-user-id-selector, empty-success-body, multi-node-usage, multi-node-top-users, bulk-squad-update | fixture, live-read, live-write, upgrade | release notes |
| maintenance | rw2-uuid-user-id | 2.8.1 | 2.8.1 | multi-node-top-users, bulk-squad-update | fixture, live-read, live-write, upgrade | release notes |
Historical presets remain useful for manual investigations but are not supported or run in the certification matrix:
2.8.0— Manual historical preset; not part of the certified CI matrix.2.7.4— Manual historical preset; not part of the certified CI matrix.
Outbound API operations
Заголовок раздела «Outbound API operations»{userRef} is a user UUID in 2.8.1 and the decimal form of a numeric user id in
3.0.0. Other UUIDs (nodes, squads, hosts, subscriptions) remain UUIDs.
| Operation | Method | Path template | Generations | Success | Response | Compatibility | Coverage |
|---|---|---|---|---|---|---|---|
system.metadata |
GET | /system/metadata |
rw2-uuid-user-id, rw3-numeric-user-id | 200 | JSON envelope with response | Stable version probe; failures are not treated as a capability result. | unit, live-read |
users.stream |
GET | /users/stream |
rw2-uuid-user-id, rw3-numeric-user-id | 200 | JSON envelope with response | 3.x is the canonical cursor stream and supports lookup filters; a 2.8 stream may expose UUID users or ignore the new filters, so Core validates results. | unit, live-read, upgrade |
users.list |
GET | /users |
rw2-uuid-user-id | 200 | JSON envelope with response | Legacy offset pagination used when the stream is absent or UUID-based. | unit, live-read |
users.get |
GET | /users/{userRef} |
rw2-uuid-user-id, rw3-numeric-user-id | 200 | JSON envelope with response | {userRef} is a UUID in 2.8.1 and a numeric id in 3.x. | unit, live-read, upgrade |
users.lookup.telegram |
GET | /users/by-telegram-id/{telegramId} |
rw2-uuid-user-id | 200 | JSON envelope with response | 3.x uses /users/stream?telegramId=… instead. | unit, live-read |
users.lookup.username |
GET | /users/by-username/{username} |
rw2-uuid-user-id, rw3-numeric-user-id | 200 | JSON envelope with response | The username lookup route remains stable through 3.2.3; 3.1+ reports an absent user as 404/A063. | unit, live-read |
users.lookup.email |
GET | /users/by-email/{email} |
rw2-uuid-user-id | 200 | JSON envelope with response | 3.x uses /users/stream?email=… instead. | unit, live-read |
users.create |
POST | /users |
rw2-uuid-user-id, rw3-numeric-user-id | 200, 201 | JSON envelope with response | Core never sends a caller-provided user UUID; 3.x returns numeric id. | unit, live-write |
users.update |
PATCH | /users |
rw2-uuid-user-id, rw3-numeric-user-id | 200, 202, 204 | JSON envelope with response; an empty 2xx body is success | Selector field is uuid in 2.8.1 and integer id in 3.x. | unit, live-write |
users.bulk-update-squads |
POST | /users/bulk/update-squads |
rw2-uuid-user-id, rw3-numeric-user-id | 200, 202, 204 | JSON envelope with response; an empty 2xx body is success | Exact-state squad update, chunked at 500 users. 2.8.1 selects UUIDs with uuids and returns affectedRows; 3.x selects numeric userIds and returns 204. An empty target state uses per-user PATCH because 3.0.0 returns A088/500. | unit, live-write |
users.status |
POST | /users/{userRef}/actions/{enable|disable} |
rw2-uuid-user-id, rw3-numeric-user-id | 200, 202, 204 | JSON envelope with response; an empty 2xx body is success | Path identity follows the UUID/numeric-id generation. | unit, live-write |
users.connections.drop-v2 |
POST | /ip-control/drop-connections |
rw2-uuid-user-id | 200, 202, 204 | JSON envelope with response; an empty 2xx body is success | 2.8 payload selects userUuids. | unit, live-write |
users.connections.drop-v3 |
POST | /connections/drop |
rw3-numeric-user-id | 200, 202, 204 | JSON envelope with response; an empty 2xx body is success | 3.x payload selects numeric userIds. | unit, live-write |
users.delete |
DELETE | /users/{userRef} |
rw2-uuid-user-id, rw3-numeric-user-id | 200, 202, 204 | JSON envelope with response; an empty 2xx body is success | 404 for a missing entity is idempotent success; route absence is distinct. | unit, live-write |
users.revoke |
POST | /users/{userRef}/actions/revoke |
rw2-uuid-user-id, rw3-numeric-user-id | 200, 202, 204 | JSON envelope with response; an empty 2xx body is success | Path identity follows the UUID/numeric-id generation. | unit, live-write |
users.reset-traffic |
POST | /users/{userRef}/actions/reset-traffic |
rw2-uuid-user-id, rw3-numeric-user-id | 200, 202, 204 | JSON envelope with response; an empty 2xx body is success | Path identity follows the UUID/numeric-id generation. | unit, live-write |
subscription.config.resolved |
GET | /subscriptions/subpage-config/{shortUuid} |
rw2-uuid-user-id, rw3-numeric-user-id | 200 | JSON envelope with response | GET intentionally carries requestHeaders in a JSON body as required upstream. | unit, live-read |
subscription-page-config.list |
GET | /subscription-page-configs |
rw2-uuid-user-id, rw3-numeric-user-id | 200 | JSON envelope with response | Stable across the certified API generations. | unit, live-read |
subscription-page-config.get |
GET | /subscription-page-configs/{uuid} |
rw2-uuid-user-id, rw3-numeric-user-id | 200 | JSON envelope with response | Stable across the certified API generations. | unit, live-read |
external-squads.get |
GET | /external-squads/{uuid} |
rw2-uuid-user-id, rw3-numeric-user-id | 200 | JSON envelope with response | Stable across the certified API generations. | unit, live-read |
hwid.devices.get |
GET | /hwid/devices/{userRef} |
rw2-uuid-user-id, rw3-numeric-user-id | 200 | JSON envelope with response | Path userRef is UUID in 2.8.1 and numeric id in 3.x. | unit, live-read |
hwid.devices.delete |
POST | /hwid/devices/delete |
rw2-uuid-user-id, rw3-numeric-user-id | 200, 202, 204 | JSON envelope with response; an empty 2xx body is success | Payload selector is userUuid in 2.8.1 and userId in 3.x. | unit, live-write |
hwid.devices.stats |
GET | /hwid/devices/stats |
rw2-uuid-user-id, rw3-numeric-user-id | 200 | JSON envelope with response | Stable across the certified API generations. | unit, live-read |
hwid.devices.top-users |
GET | /hwid/devices/top-users |
rw2-uuid-user-id, rw3-numeric-user-id | 200 | JSON envelope with response | Stable across the certified API generations. | unit, live-read |
nodes.restart |
POST | /nodes/{uuid}/actions/restart |
rw2-uuid-user-id, rw3-numeric-user-id | 200, 202, 204 | JSON envelope with response; an empty 2xx body is success | Node identifiers remain UUIDs in 3.x. | unit |
nodes.restart-all |
POST | /nodes/actions/restart-all |
rw2-uuid-user-id, rw3-numeric-user-id | 200, 202, 204 | JSON envelope with response; an empty 2xx body is success | Not run in live CI because it disrupts every registered node. | unit |
nodes.list |
GET | /nodes |
rw2-uuid-user-id, rw3-numeric-user-id | 200 | JSON envelope with response | Stable across the certified API generations. | unit, live-read |
nodes.stats |
GET | /system/stats/nodes |
rw2-uuid-user-id, rw3-numeric-user-id | 200 | JSON envelope with response | Stable across the certified API generations. | unit, live-read |
system.stats |
GET | /system/stats |
rw2-uuid-user-id, rw3-numeric-user-id | 200 | JSON envelope with response | Stable across the certified API generations. | unit, live-read |
system.stats.bandwidth |
GET | /system/stats/bandwidth |
rw2-uuid-user-id, rw3-numeric-user-id | 200 | JSON envelope with response | Stable across the certified API generations. | unit, live-read |
bandwidth.nodes |
GET | /bandwidth-stats/nodes |
rw2-uuid-user-id, rw3-numeric-user-id | 200 | JSON envelope with response | Stable across the certified API generations. | unit, live-read |
bandwidth.users |
GET | /bandwidth-stats/users/{userRef} |
rw2-uuid-user-id, rw3-numeric-user-id | 200 | JSON envelope with response | Path userRef is UUID in 2.8.1 and numeric id in 3.x. | unit, live-read |
bandwidth.node-users |
GET | /bandwidth-stats/nodes/{nodeUuid}/users |
rw2-uuid-user-id, rw3-numeric-user-id | 200 | JSON envelope with response | Stable across the certified API generations. | unit, live-read |
bandwidth.nodes-users |
POST | /bandwidth-stats/nodes/users |
rw2-uuid-user-id, rw3-numeric-user-id | 200 | JSON envelope with response | 2.8.1-compatible aggregate top-users request for a set of nodes; Core detects topUsersLimit saturation before treating missing users as zero. | unit, live-read |
bandwidth.nodes-usage |
POST | /bandwidth-stats/nodes/usage |
rw3-numeric-user-id | 200 | JSON envelope with response | 3.x returns numeric user ids and per-node totals for all requested nodes. Usage snapshots are refreshed no faster than the upstream aggregation cadence. | unit, live-read |
internal-squads.list |
GET | /internal-squads |
rw2-uuid-user-id, rw3-numeric-user-id | 200 | JSON envelope with response | Stable across the certified API generations. | unit, live-read |
internal-squads.get |
GET | /internal-squads/{uuid} |
rw2-uuid-user-id, rw3-numeric-user-id | 200 | JSON envelope with response | Stable across the certified API generations. | unit, live-read |
internal-squads.nodes |
GET | /internal-squads/{uuid}/{accessible-nodes|nodes} |
rw2-uuid-user-id, rw3-numeric-user-id | 200 | JSON envelope with response | Core tries both upstream route spellings and validates response shape. | unit, live-read |
internal-squads.add-users |
POST | /internal-squads/{uuid}/bulk-actions/add-many-users |
rw3-numeric-user-id | 200, 202, 204 | JSON envelope with response; an empty 2xx body is success | 3.x targeted numeric-id bulk, chunked at 1000. In 2.8.1 the superficially similar add-users route targets every user, so Core PATCHes requested users. | unit, live-write |
internal-squads.remove-users |
DELETE | /internal-squads/{uuid}/bulk-actions/remove-many-users |
rw3-numeric-user-id | 200, 202, 204 | JSON envelope with response; an empty 2xx body is success | 3.x targeted numeric-id bulk, chunked at 1000; 2.8.1 uses per-user PATCH. | unit, live-write |
hosts.list |
GET | /hosts |
rw2-uuid-user-id, rw3-numeric-user-id | 200 | JSON envelope with response | Stable across the certified API generations. | unit, live-read |
Inbound webhooks
Заголовок раздела «Inbound webhooks»| Event | Generations | Identity handling | Core behavior | Coverage |
|---|---|---|---|---|
| user.expires_in_72_hours / 48_hours / 24_hours | rw2-uuid-user-id, rw3-numeric-user-id | user.uuid in 2.8.1; user.id normalized to the internal uuid alias in 3.x | Subscription expiry notification and optional auto-renew processing. | unit |
| user.expiration | rw2-uuid-user-id, rw3-numeric-user-id | user object plus meta.expirationHours | Compatibility event mapped to before/after-expiration notification stages. | unit |
| user.expired / user.expired_24_hours_ago | rw2-uuid-user-id, rw3-numeric-user-id | normalized user identity | Expired subscription notification with stale-subscription suppression. | unit |
| torrent_blocker.report | rw2-uuid-user-id, rw3-numeric-user-id | typed torrent-blocker payload; user identity is resolved independently | Typed security notification; invalid or oversized payloads are rejected. | unit |
| other events (for example user.modified) | rw2-uuid-user-id, rw3-numeric-user-id | normalized user identity when a user object is present | Emitted to the Core/plugin event bus but ignored for lifecycle notifications. | unit |
Certified upgrades
Заголовок раздела «Certified upgrades»| From | To | Strategy | Verification |
|---|---|---|---|
| 2.8.1 | 3.2.3 | same-panel-database | tests/qa/test_remnawave_upgrade.py |
Compatibility rules and corner cases
Заголовок раздела «Compatibility rules and corner cases»- Panel version detection has a five-minute positive cache. Failures are not cached, so startup races and temporary authorization failures self-heal.
- 3.x users are normalized at the boundary: numeric
idis also exposed internally as a decimal-stringuuidalias. This intentionally preserves historical DB columns and service method names during an in-place panel upgrade. - Once the generation is known, user-scoped requests reject an identifier from the other generation locally. This prevents 3.x validation-error storms and prevents a stale UUID from being mistaken for a deleted user.
- Background tariff synchronization lazily relinks stale 2.8 UUIDs through Telegram/email/deterministic username identity before it can deactivate a subscription. Failed or ambiguous identity lookups never create a duplicate user.
- 2.8.1 uses offset
/userspagination and UUID lookup routes. 3.x uses the cursor stream and stream filters. Responses are shape-checked before a capability is learned. - User-miss responses A025, A062, A063, and plain 404 are normalized as an empty lookup result. Removed-route 404 responses are handled first so a live 2.8-to-3.x upgrade switches to stream filters without restarting Mini Shop.
- Empty 202/204 responses are accepted for mutation operations documented that way; malformed non-empty 2xx JSON is still a protocol error.
- The 2.8.1 squad
add-usersroute means all users. Core never calls it for a targeted request; it patches each requested UUID user. 3.x uses chunked numeric-id bulk calls. - Logs use registry labels instead of raw paths, preventing identifiers in route segments and query strings from becoming log/metric labels.
New Remnawave release checklist
Заголовок раздела «New Remnawave release checklist»- Read the upstream release/API diff and classify the API generation. Future majors remain best-effort compatible, but are never presented as certified without live QA.
- Add or update an exact entry in
remnawave_support.jsonand a dev-stand preset. Keep historical presets clearly separate from the certified matrix. - Update every affected operation contract, compatibility adapter, webhook normalizer, version fixture, and all certified-version live jobs.
- Run read/write smoke tests for current and maintenance versions. For generation changes, also run the same-database upgrade job and verify persisted user identities after sync.
- Regenerate this catalog and public API artifacts, then run all backend/frontend gates.
- To retire a generation, announce deprecation for the policy window, remove it only in a breaking Core release, and move its presets to historical status.
Manifest review date: 2026-08-11.