eMuleBB REST API Contract (frozen 0.7.3)¶
Frozen
core.*subset of the one capability-gated/api/v1contract. This document describes the0.7.3contract spoken by the frozen eMuleBB MFC client: the stablecore.*subset it advertises throughGET /api/v1/capabilities. There is a single/api/v1contract; implementations differ by the capabilities they advertise, not by a separate spec. emulebb-rust owns the canonical superset (emulebb-rust/docs/rest/REST-API-OPENAPI.yaml) and leads the contract version; this MFC subset is frozen and does not evolve. Forward controller: TrackMuleBB (aMuTorrent is the frozen0.7.3bundle consumer). Capability and versioning policy: API-V1-COMPATIBILITY.
Status: 0.7.3 contract — FROZEN (MFC core.* subset of the one
capability-gated /api/v1)
Source of truth: REST-API-OPENAPI.yaml
Adapter subsets: REST-API-ADAPTERS.md
Migrated action inventory: REST-API-PARITY-INVENTORY.md
Primary implementation: EMULEBB_WORKSPACE_ROOT\workspaces\workspace\app\emulebb-main\srchybrid\WebServerJson.cpp
Route seam: EMULEBB_WORKSPACE_ROOT\workspaces\workspace\app\emulebb-main\srchybrid\WebServerJsonSeams.h
Overview¶
main exposes an authenticated in-process JSON API from the existing eMule
WebServer listener. The broadband release contract is the resource-oriented
/api/v1 surface described by the OpenAPI document above.
The API is designed for trusted local controllers — TrackMuleBB (forward) and
the aMuTorrent consumer bundled with 0.7.3. The 0.7.3 contract intentionally
prioritizes consistency and controller completeness over preserving old
command-style route names.
After 0.7.3 RC1, the same OpenAPI document is also the canonical product-family contract for p2p-overlord implementations that choose to expose an eMuleBB-compatible REST surface. p2p-overlord may implement a documented subset, but that subset must be proven against this OpenAPI source rather than creating a parallel contract.
Controller Boundary¶
aMuTorrent is the primary UI consumer and 0.7.3 RC2 proof target, but it is not
the authority for native route shape. The aMuTorrent eMuleBB adapter must
translate UI expectations to the clean /api/v1 contract instead of requiring
native aliases, qBittorrent-compatible response shapes, or legacy command-style
routes.
Arr integrations are separate compatibility adapters. Prowlarr Torznab,
Radarr/Sonarr controller flows, and qBittorrent-compatible download-client
routes may keep adapter-specific quirks where those clients require them, but
shared parsing, validation, safety, and serialization should come from the same
native helpers wherever practical. Adapter compatibility must not broaden or
weaken the native /api/v1 OpenAPI contract. Torznab movie and TV searches run
connected global and kad probes and combine results; non-media Torznab
families keep the native default automatic search policy.
qBittorrent-compatible transfer delete requests are adapted to native cancel
semantics and therefore forward destructive transfer-file deletion to the shared
transfer delete command even when a qBit caller omits or clears its optional
flag.
The /api/v2 surface is a qBittorrent-compatible adapter for Arr download
client integration only. It is complete when Prowlarr, Radarr, Sonarr, and other
Arr clients can authenticate, test the client, add transfers, inspect transfer
state, mutate Arr-relevant transfer state, assign categories, and remove
transfers through the qBittorrent-shaped routes they call. It is not a promise
to implement the full qBittorrent Web API surface, including unrelated RSS,
tracker, peer-management, sync, logging, ban-list, global-speed, or complete
content-layout operations.
The legacy template-based WebServer UI is a frozen surface behind the same listener. The release goal for that engine is compile preservation only until it is removed. HTML templates, legacy page routes, and template interaction state are not part of the REST v1 contract, not part of the adapter contract, not supported, and not a release-gated behavior surface beyond avoiding shared listener regressions.
Contract Rules¶
- root every endpoint at
/api/v1/... - authenticate only with
X-API-Key - serve JSON only
- inherit the normal WebServer bind, HTTPS, and allowed-IP behavior
- use
camelCasefield names - use lowercase compact exact tokens for enum-like string values, with no camelCase, snake_case, case folding, or aliases
- keep native priority families separate: transfer download priority uses
auto,verylow,low,normal,high, andveryhigh; shared-file upload priority usesauto,verylow,low,normal,high, andrelease - return success envelopes as
{ "data": ..., "meta": ... } - return unpaged collections as
{ "data": { "items": [...] }, "meta": ... } - return transfer add and transfer pause/resume/stop/delete operations as stable per-item operation envelopes, including the single-link and single-hash routes
- expose pagination through one common shape: every paged response carries
itemsplus the sharedtotal/offset/limitfields, with the canonical query parametersoffset(0..2147483647, default 0) andlimit(1..1000, default 100).totalis the full count across all pages. - page the collections
GET /transfers,GET /shared-files, andGET /upload-queuewith the envelope{ "data": { "items": [...], "total": n, "offset": n, "limit": n }, "meta": ... } - page
GET /searches/{searchId}with the same fields: the search resource embeds its result page asitemsalongsidetotal/offset/limit(the resource also carriesid/query/method/type/status). It additionally accepts the booleanincludeEvidence(default true) to omit the heavy per-resultevidenceblock andexactTotal(default true) to allow an estimatedtotal. - emit paged lists in a stable order while the underlying collection is
unchanged, so sequential
offset/limitpages of a static list do not skip or duplicate rows. Paging is plain offset/limit over the live list, not a snapshot/cursor: if the collection is mutated (a file unshared, a transfer added/removed) between page reads, later offsets shift and a single walk may skip or repeat rows. Controllers that need a coherent full enumeration should detect a changedtotalbetween pages and restart the walk fromoffset0. - expose
limitwithoutoffsetonly for bounded snapshots/tails such asGET /snapshotandGET /logs - expose shared-files startup readiness through
status.stats.sharedFilesReadyandstatus.sharedStartupCache; clients must not infer readiness from an emptysharedFilesarray during startup warmup - expose app lifecycle through
app.lifecycleandstatus.lifecycleusing lowercase compact state tokens:starting,running,shuttingdown, anddone; the exit-confirmation dialog remains publicrunningstate - expose network binding and VPN Guard diagnostics through
status.networkandsnapshot.network; this includes configured and active P2P bind interface/address fields, stablenetwork.binding.resolveResultoutcome tokens, andnetwork.vpnGuard.enabled, lowercasemodetokens (off,block),allowedPublicIpCidrs,startupBlocked, andstartupBlockReason - report guarded eD2K server connect and Kad connect/bootstrap refusals as
normal network DTOs extended with
operationQueued: false,blockedByVpnGuard: true, andnetworkdiagnostics instead of silently queuing nothing - reject mutating REST requests, diagnostic unsafe requests, and
POST /app/shutdownwhile lifecycle isstarting, and reject all REST requests once lifecycle isshuttingdownordone - return errors as
{ "error": { "code": "...", "message": "...", "details": {} } } - document the stable native error envelope on each OpenAPI operation for the
concrete runtime statuses
400,401,404,405,409,500, and503, while keepingdefaultas a catch-all forward-compatibility response - return the updated resource from mutations when practical; asynchronous or native operation routes return explicit operation-result DTOs instead
- keep public response DTOs closed in OpenAPI; additive fields require an explicit OpenAPI update and matching contract tests in the same change
- expose one canonical public route for each operation; upload removal uses
POST /uploads/{clientId}/operations/remove, not a duplicateDELETEalias - validate method/path/body/query through the native route schema table before dispatching commands
- reject unknown JSON body fields and unknown or malformed query parameters with
400 INVALID_ARGUMENT - validate public rename text with the same UTF-8/control-character and 255-character public filename limit used by REST-adjacent link conversion
- accept server.met and nodes.dat URL imports only as trimmed
http://orhttps://URLs with a host, no whitespace, and a 2048-character limit - keep native v1
DELETEroutes body-free; destructive disk deletion uses explicit resource targets plusconfirm=true, such asDELETE /transfers/{hash}/files?confirm=true - use HTTP 200 for valid bulk requests with per-item results
- marshal native commands through the main UI thread before touching eMule state owned by dialogs, sockets, queues, and list controls
- keep the route execution model explicit in the native route seam; only static/direct-safe routes may bypass the UI-thread dispatch path
- reject pre-release alias spellings; public request fields are the final
OpenAPI names such as
categoryId,searchId,uploadLimitKiBps, anddownloadLimitKiBps
Scope¶
The release API must cover controller-relevant runtime capabilities that were
historically reachable through WebServer command handlers: transfers, shared
files, shared directories, uploads, upload queue, servers, Kad, searches,
friends, logs, categories, statistics, preferences, and application shutdown.
That inventory tracks migrated behavior, not a promise that the deprecated
template UI keeps functioning. Operator diagnostic dump capture and crash-test
endpoints live under /diagnostics, require explicit diagnostic REST opt-in,
and remain outside broad automated mutation/stress loops.
The release API intentionally excludes:
- HTML sessions, login/logout, templates, sort state, column hiding, and other frozen legacy WebServer presentation state
- WebServer page-only preferences such as HTML gzip and refresh interval
- host operating-system shutdown and reboot
- binary shared-file streaming
- granular REST permissions or low-rights REST mode
- dynamic capability negotiation between eMuleBB and aMuTorrent
PATCH /api/v1/shared-directories replaces the configured shared-directory
roots and requires confirmReplaceRoots: true. Each root may be either a
non-empty string path or an object with a non-empty path and optional
recursive flag. Response-only directory state such as existence, monitoring,
or shareability is not accepted in the replacement request.
Search Semantics¶
POST /api/v1/searches starts a native eMule search using the requested method:
automatic, server, global, or kad. The request may also select a native
search file type with the exact REST file-type token: empty string for any
type, arc, audio, iso, image, pro, video, doc, or
emulecollection. No aliases, alternate casing, or request-time type remapping
are accepted. The route maps those public tokens directly to the existing
eD2K/Kad search modes and file-type filters and must not change stock search
semantics for 0.7.3 RC2. automatic resolves from live network state:
connected eD2K uses global, Kad-only uses kad, and no connected network is
rejected as not connected. Search resources echo the resolved method and
selected REST type so
controllers can distinguish eD2K server/global searches from Kad searches and
audit the selected file filter without inferring from result timing or counts.
GET /api/v1/searches lists active native search sessions without expanding
their result rows and accepts no query parameters. GET /api/v1/searches/{searchId}
returns the current native visible result snapshot for one search as a paged
resource: the result rows are in items (renamed from the pre-RC2 results),
alongside the shared total/offset/limit fields. It accepts offset/limit
to bound the returned rows, plus includeEvidence (default true) and
exactTotal (default true) to shape the payload; the strict route table rejects
any other query parameter. Controllers should poll the search resource and treat
items as a bounded native snapshot governed by eMule's existing search-result
retention and visibility behavior.
Each native result also carries the resolved search method and selected search
type when the result is returned through a search resource. SearchResult.fileType
is the raw native file-type tag reported for that row; SearchResult.fileType
remains row metadata and is not constrained to or remapped through the
search-filter token enum.
Search results expose grouped SearchResult.evidence instead of a single
risk score. Controllers must treat this as explanatory evidence, not a
safety guarantee:
confidenceis a single non-authoritative summary axis that folds local fake-file heuristics, spam/rating signals, and Kad publisher consistency into one symmetricband(spam,likely_fake,suspect,caution,looks_good,genuine) plus a 0-100score(higher = more confident). It also carries the underlyingfakeScore,severity,spam,userRating, contributingkadBand, and the local fake-filereasons.availabilityEvidencecovers source counts, complete-source counts, observed clients/servers, and Kad publisher counts.nameEvidencecovers observed names, canonical names, ignored tokens, and meaningful name divergence groups.integrityEvidencecovers AICH presence, multiple-AICH conflicts, pending or cached header checks, and claimed/detected file type evidence.
The retired riskEvidence and kadPublisherEvidence objects are removed; their
signals are now folded into confidence (band/score) and availabilityEvidence
(kadPublishers). A looks_good/genuine band is not a positive safety
guarantee; clients must not display it as trusted or safe. Search creation does not clear existing searches as a side
effect; controllers must call DELETE /api/v1/searches?confirm=true when they
need a clean search set.
POST /api/v1/searches/{searchId}/results/{hash}/operations/download starts a
download from one visible search result by lowercase 32-character eD2K hash and
returns an operation result with the accepted search id and hash. It does not
pretend to return a Transfer resource before the native download queue has
materialized one.
Native v1 DELETE routes do not accept JSON request bodies.
DELETE /api/v1/transfers/{hash} removes a completed transfer row without
deleting the completed file or changing shared-file registration; incomplete
transfers cannot be detached from their .part state and return a per-item
failure. Controllers that intend to cancel a transfer and delete local completed
or partial data must call
DELETE /api/v1/transfers/{hash}/files?confirm=true.
DELETE /api/v1/shared-files/{hash} unshares/excludes the file without
deleting it from disk; actual disk deletion is
DELETE /api/v1/shared-files/{hash}/file?confirm=true.
Selected peer reads are available for controller drill-down through
GET /transfers/{hash}/sources/{clientId}, GET /uploads/{clientId}, and
GET /upload-queue/{clientId}. These routes use the same clientId selector
as peer operations and expose read models only; chat/message APIs and peer
shared-file browse result retrieval remain outside the 0.7.3 RC2 native v1
contract.
Transfer source rows expose downloadState with the same lowercase compact
token policy as the rest of v1; native debug labels are not part of the REST
contract.
Implementation Status¶
The OpenAPI contract is the implemented target contract for the current 0.7.3 RC2 pass. Native route-seam tests cover the route schema table, strict validation behavior, and the internal route execution model. The Python smoke harness includes OpenAPI route consistency checks, validates success/error envelopes, and reports whether each native route is direct or UI-thread dispatched. aMuTorrent's eMuleBB adapter consumes the same final field names while keeping aMuTorrent's own public routes stable.
The live smoke harness validates route coverage and response envelopes against
OpenAPI. The strict DTO policy is enforced by tests that reject open-ended
public response schemas except for explicitly documented extension maps such as
error.details and app.capabilities.
Search Refactoring Notes¶
Future cleanup should keep the current wire contract unchanged while reducing
implementation duplication: the native REST command seam can collapse toward the
exact type string contract instead of maintaining a parallel enum, and the
Torznab family-to-search-type mapping still resolves to REST tokens accepted by
/api/v1/searches.
Controller Compatibility Matrix¶
- Native REST clients
- Surface:
/api/v1 - Contract boundary: OpenAPI is authoritative; adapters do not define route names or envelopes.
-
Proof lane: REST smoke, OpenAPI drift, live completeness
-
aMuTorrent
- Surface:
/api/v1through its own adapter - Contract boundary: Translates UI expectations to final native fields and unwraps native envelopes.
-
Proof lane: aMuTorrent browser smoke
-
Prowlarr Torznab
- Surface: Torznab XML adapter
- Contract boundary: Keeps Torznab XML/error shape adapter-local while reusing native parsing and search commands.
-
Proof lane: Prowlarr live
-
Radarr/Sonarr
- Surface: Torznab plus qBit-compatible download client
- Contract boundary: Uses Arr-facing compatibility routes without broadening
/api/v1. -
Proof lane: Radarr/Sonarr live
-
qBittorrent-compatible clients
- Surface:
/api/v2 - Contract boundary: Implements the Arr-needed qBit subset only; qBit text/session errors stay adapter-shaped.
-
Proof lane: qBit route completeness and Arr live
-
p2p-overlord agents
- Surface: Claimed subset of
/api/v1 - Contract boundary: OpenAPI remains authoritative; missing endpoints are p2p-overlord implementation gaps, not contract drift.
- Proof lane: Future p2p-overlord REST conformance
Adapter subset details are documented in REST-API-ADAPTERS.md.
Execution Model¶
The native route table records whether a route is direct or UI-thread
dispatched. GET /api/v1/app is currently the only direct route because it
serves application identity, lifecycle, build metadata, and static
capabilities. Lifecycle gating is evaluated before direct execution, so direct
routes are still rejected after shutdown begins. Runtime reads, mutations,
destructive operations, and adapter bridges remain UI-thread dispatched until
ownership is proven safe at the implementation layer.
The same lifecycle command policy is reused by qBit-compatible and Torznab
bridge calls that invoke native REST commands in-process, so adapter bridges do
not bypass startup/shutdown safety gates.
The embedded WebServer listener is single-flight: it serves one HTTP connection
at a time across every surface behind it (/api/v1, the qBit-compatible
/api/v2 adapter, the Torznab adapter, and the frozen legacy templates). This
is a deliberate resource and concurrency-safety budget because accepted web
workers still share request/session state and reach broad main-thread eMule
objects. While one request is in flight, additional connections are refused at
accept time with 503 Service Unavailable and Retry-After: 10; a long
operation such as a native or Torznab search holds the slot until it returns.
The Torznab bridge adds its own single-flight guard (one native search at a
time, a brief wait for the slot, then a cached page or 503, with successful
result sets cached for about ten minutes). Each request uses its own connection
(Connection: close); the listener does not offer HTTP keep-alive. Transport
limits are shared by all surfaces: request headers are capped at 64 KB, request
bodies at 16 MB (an over-limit request is dropped without a normal status), and
idle accepted connections close after about 30 seconds. Controllers must honor
Retry-After, use bounded backoff on 503, and stagger independent pollers
rather than issuing concurrent bursts at the same listener. See
Controllers And REST for the
operator-facing concurrency and request-limit contract.
Use REST-API-PARITY-INVENTORY.md for the completed WebServer-to-REST action parity ledger. Runtime route completeness is expected to match REST-API-OPENAPI.yaml.
Retired Before Public Release¶
The following earlier command-style routes are not part of the final broadband release contract and should not be used by aMuTorrent:
/api/v1/app/version/api/v1/stats/global/api/v1/transfers/add/api/v1/transfers/pause/api/v1/transfers/resume/api/v1/transfers/stop/api/v1/transfers/delete/api/v1/uploads/list/api/v1/uploads/queue/api/v1/servers/list/api/v1/servers/status/api/v1/search/start/api/v1/search/results/api/v1/search/stop/api/v1/log