Files
samiandClaude Sonnet 5 5e3e21543a proto: give the generated C++ Dispatcher a real error channel (P1, 1.4.0)
DAEMON's daemon/docs/proto-requests-m1.md P1: velox::proto::Dispatcher's
on_* methods returned Result<T> = expected<T, ParseError>, and dispatch()
mapped every handler error to -32603 InternalError. A handler had no way to
return -32010 (download.get not-found), -32011 (download.add invalid-path)
or -32013 (probe-failed) with their data payloads -- three error fixtures a
conformant server must satisfy were unreachable, blocking DAEMON's
"conformance as a server" M1 DoD.

Two error channels now, kept separate on purpose:
  - parse: Result<T> / ParseError -- dispatch() failing to turn the wire into
    typed params. Always -32602, always structural.
  - handler: HandlerResult<T> / HandlerError -- a handler deciding the request
    can't be fulfilled. Carries any ErrorCode + message + free-form data.

    struct HandlerError {
        ErrorCode code{ErrorCode::InternalError};  // bare {} is a valid -32603
        std::string message;
        nlohmann::json data = nullptr;             // straight into the error's data
    };
    template <class T> using HandlerResult = std::expected<T, HandlerError>;

dispatch()'s handler branch is now
  make_error(id, r.error().code, r.error().message, r.error().data)
instead of a hard-coded InternalError. -32001/-32002/-32003 stay the server
layer's to raise around dispatch(), as DAEMON already does.

Verified end to end against the real dispatch() path: a handler returning
TaskNotFound/InvalidPath/ProbeFailed produces -32010/-32011/-32013 with the
data object intact, and a bare HandlerError{} still yields a clean -32603
with no data field. The `= nullptr` on the member (not `{nullptr}`) matters:
brace-init of nlohmann::json from nullptr is the array [null], not JSON null.

FixtureDispatcher regenerated to HandlerResult; conformance_main.cpp only
inspects dispatch()'s JSON and needed no change. TS side is untouched beyond
the version string -- no server Dispatcher is generated there.

P2 also handled: session.hello.version-mismatch's data.expected was a stale
"1.0.0"; now $any, with a note that the error-fixture compare is on `code`
only so a server echoing kProtocolVersion there is fine.

Version: minor, 1.3.0 -> 1.4.0. Wire is byte-identical (no schema, fixture,
or OpenRPC change) but every Dispatcher implementer must swap Result ->
HandlerResult on regen, and the bump is how lanes are told to. Not an ADR:
one lane consumes this binding, it's the one that asked, and the shape is
the one they proposed. Answered in contracts/proto-answers-daemon-m1.md.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_012fgjnqFCS5h5L7gZTZo3rV
2026-09-10 15:10:13 +04:00

5079 lines
147 KiB
JSON

{
"openrpc": "1.2.6",
"info": {
"title": "Velox Download Manager",
"version": "1.4.0",
"description": "The wire contract between veloxd and every client: the Qt GUI, the CLI, the native-messaging host and the Firefox extension. One JSON-RPC 2.0 payload set over four framings; only the framing differs.\n\nGENERATED from contracts/schema/ by contracts/codegen/gen_openrpc.py. Do not edit by hand.",
"license": {
"name": "See repository LICENSE"
}
},
"servers": [
{
"name": "unix-socket",
"url": "unix:$XDG_RUNTIME_DIR/velox/velox.sock",
"description": "NDJSON. GUI, CLI and nmhost. Peer credentials checked via SO_PEERCRED; same UID only, no token."
},
{
"name": "loopback-ws",
"url": "ws://127.0.0.1:52000",
"description": "One JSON message per text frame. Extension fallback. Bound to 127.0.0.1 only, Origin-checked, token-authenticated, rate-limited. Port is the first free one in 52000-52016."
}
],
"methods": [
{
"name": "capture.getRules",
"summary": "The daemon's capture policy, so the extension's shouldCapture decision cannot drift from the daemon's.",
"description": "The daemon's capture policy, so the extension's shouldCapture decision cannot drift from the daemon's. Fetched on connect and whenever event.settings.changed names a capture.* key. If this call fails the extension keeps its last known rules and stays fail-open.",
"paramStructure": "by-name",
"params": [],
"result": {
"name": "capture.getRulesResult",
"schema": {
"$ref": "#/components/schemas/CaptureRules"
}
},
"x-privileged": false,
"x-transports": [
"uds",
"ws"
],
"x-deadlineMs": 2000
},
{
"name": "capture.offer",
"summary": "Firefox offers an intercepted response to the daemon.",
"description": "Firefox offers an intercepted response to the daemon. The daemon MUST reply within 750 ms; the extension abandons the offer and lets Firefox download normally on timeout. This deadline is the whole reason capture fails open, and it is conformance-tested: a daemon that is slow, down, or erroring must never cost the user a download.",
"paramStructure": "by-name",
"params": [
{
"name": "url",
"schema": {
"type": "string",
"format": "uri"
},
"required": true
},
{
"name": "method",
"schema": {
"type": "string",
"enum": [
"GET",
"POST"
]
},
"required": true
},
{
"name": "tabUrl",
"schema": {
"type": "string",
"format": "uri"
},
"required": true
},
{
"name": "headers",
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/Headers"
},
{
"type": "null"
}
]
}
},
{
"name": "cookies",
"schema": {
"type": [
"array",
"null"
],
"description": "Cookies for the URL, so authenticated downloads work outside the browser.",
"items": {
"$ref": "#/components/schemas/Cookie"
}
},
"description": "Cookies for the URL, so authenticated downloads work outside the browser."
},
{
"name": "contentType",
"schema": {
"type": [
"string",
"null"
]
}
},
{
"name": "contentLength",
"schema": {
"type": [
"integer",
"null"
],
"minimum": 0
}
},
{
"name": "contentDisposition",
"schema": {
"type": [
"string",
"null"
]
}
},
{
"name": "filename",
"schema": {
"type": [
"string",
"null"
],
"description": "The extension's best guess; the daemon may override it."
},
"description": "The extension's best guess; the daemon may override it."
},
{
"name": "userAgent",
"schema": {
"type": [
"string",
"null"
]
}
},
{
"name": "referrer",
"schema": {
"type": [
"string",
"null"
]
}
},
{
"name": "origin",
"schema": {
"type": [
"string",
"null"
],
"description": "moz-extension://... The daemon verifies this on the WS transport and refuses anything else."
},
"description": "moz-extension://... The daemon verifies this on the WS transport and refuses anything else."
},
{
"name": "requestId",
"schema": {
"type": [
"string",
"null"
],
"description": "The extension's webRequest id, echoed in logs so a capture decision can be traced back to one browser request."
},
"description": "The extension's webRequest id, echoed in logs so a capture decision can be traced back to one browser request."
}
],
"result": {
"name": "capture.offerResult",
"schema": {
"type": "object",
"additionalProperties": false,
"required": [
"action"
],
"properties": {
"action": {
"type": "string",
"enum": [
"take",
"ignore"
]
},
"taskId": {
"type": [
"string",
"null"
],
"format": "uuid",
"description": "Set when action is 'take'."
},
"reason": {
"type": [
"string",
"null"
],
"description": "Why the offer was declined. Set when action is 'ignore'; the extension logs it in the popup's diagnostics.",
"enum": [
"excluded_host",
"type_not_monitored",
"below_min_size",
"duplicate",
"capture_disabled",
"user_declined",
"rule_ignore",
null
]
}
}
}
},
"x-privileged": false,
"x-transports": [
"uds",
"ws"
],
"x-deadlineMs": 750,
"errors": [
{
"code": -32011,
"message": "Destination is outside the allowed roots, or is not writable. data.path is set."
}
]
},
{
"name": "category.list",
"summary": "Every category with its folder and extension list.",
"description": "Every category with its folder and extension list. The extension calls this to populate its default-category picker, which is why it is not privileged; it is read-only and exposes only paths the user already configured.",
"paramStructure": "by-name",
"params": [],
"result": {
"name": "category.listResult",
"schema": {
"type": "object",
"additionalProperties": false,
"required": [
"items"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Category"
}
}
}
}
},
"x-privileged": false,
"x-transports": [
"uds",
"ws"
],
"x-deadlineMs": 2000
},
{
"name": "category.remove",
"summary": "Delete a user-created category.",
"description": "Delete a user-created category. Built-in categories are refused with -32602. Tasks filed under it are reassigned to reassignTo, or to the default category when that is null; no task is ever orphaned.",
"paramStructure": "by-name",
"params": [
{
"name": "categoryId",
"schema": {
"type": "string"
},
"required": true
},
{
"name": "reassignTo",
"schema": {
"type": [
"string",
"null"
]
}
}
],
"result": {
"name": "category.removeResult",
"schema": {
"type": "object",
"additionalProperties": false,
"required": [
"removed",
"reassignedTaskIds"
],
"properties": {
"removed": {
"type": "boolean"
},
"reassignedTaskIds": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
}
}
}
}
},
"x-privileged": true,
"x-transports": [
"uds"
],
"x-deadlineMs": 5000,
"errors": [
{
"code": -32003,
"message": "Method is privileged and was called over a transport that may not use it."
},
{
"code": -32602,
"message": "Params failed schema validation."
}
]
},
{
"name": "category.upsert",
"summary": "Create or replace a category.",
"description": "Create or replace a category. Omit categoryId to create; supply it to replace. Changing saveDir does not move existing files \u2014 the GUI asks separately and issues download.update per task, so a re-point is never a surprise mass file move.",
"paramStructure": "by-name",
"params": [
{
"name": "category",
"schema": {
"$ref": "#/components/schemas/Category"
},
"required": true
}
],
"result": {
"name": "category.upsertResult",
"schema": {
"type": "object",
"additionalProperties": false,
"description": "The stored category, with categoryId filled in on create.",
"required": [
"category"
],
"properties": {
"category": {
"$ref": "#/components/schemas/Category"
}
}
}
},
"x-privileged": true,
"x-transports": [
"uds"
],
"x-deadlineMs": 5000,
"errors": [
{
"code": -32003,
"message": "Method is privileged and was called over a transport that may not use it."
},
{
"code": -32011,
"message": "Destination is outside the allowed roots, or is not writable. data.path is set."
}
]
},
{
"name": "download.add",
"summary": "Create one task.",
"description": "Create one task. saveDir is canonicalized and checked against saveTo.allowedRoots before anything is written; a path that escapes them is refused with -32011 and no file is created.",
"paramStructure": "by-name",
"params": [
{
"name": "url",
"schema": {
"type": "string",
"format": "uri"
},
"required": true
},
{
"name": "headers",
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/Headers"
},
{
"type": "null"
}
]
}
},
{
"name": "cookies",
"schema": {
"type": [
"array",
"null"
],
"items": {
"$ref": "#/components/schemas/Cookie"
}
}
},
{
"name": "referrer",
"schema": {
"type": [
"string",
"null"
]
}
},
{
"name": "userAgent",
"schema": {
"type": [
"string",
"null"
]
}
},
{
"name": "filename",
"schema": {
"type": [
"string",
"null"
],
"maxLength": 255,
"description": "Overrides the name derived from Content-Disposition or the URL."
},
"description": "Overrides the name derived from Content-Disposition or the URL."
},
{
"name": "saveDir",
"schema": {
"type": [
"string",
"null"
],
"description": "Canonicalized and checked against the allowed roots before any write. -32011 if it fails."
},
"description": "Canonicalized and checked against the allowed roots before any write. -32011 if it fails."
},
{
"name": "categoryId",
"schema": {
"type": [
"string",
"null"
],
"description": "null means the rules engine picks one."
},
"description": "null means the rules engine picks one."
},
{
"name": "queueId",
"schema": {
"type": [
"string",
"null"
],
"description": "Required when startMode is 'queue'."
},
"description": "Required when startMode is 'queue'."
},
{
"name": "segments",
"schema": {
"type": [
"integer",
"null"
],
"minimum": 1,
"maximum": 32,
"description": "The REQUESTED connection count. An upper bound, not a promise: the engine lowers it to the per-host cap, and to 1 when the source turns out not to be resumable. What is actually in use comes back as TaskSummary.segments. null means use connection.maxSegmentsPerDownload."
},
"description": "The REQUESTED connection count. An upper bound, not a promise: the engine lowers it to the per-host cap, and to 1 when the source turns out not to be resumable. What is actually in use comes back as TaskSummary.segments. null means use connection.maxSegmentsPerDownload."
},
{
"name": "bufferBytes",
"schema": {
"type": [
"integer",
"null"
],
"minimum": 65536,
"maximum": 16777216,
"description": "Requested write buffer per segment, in bytes. null means use connection.bufferBytes. Default 1 MiB; range 64 KiB - 16 MiB. Silently reduced to fit connection.maxTotalBufferBytes across all live segments; the effective value is reported back as TaskDetail.effectiveBufferBytes."
},
"description": "Requested write buffer per segment, in bytes. null means use connection.bufferBytes. Default 1 MiB; range 64 KiB - 16 MiB. Silently reduced to fit connection.maxTotalBufferBytes across all live segments; the effective value is reported back as TaskDetail.effectiveBufferBytes."
},
{
"name": "startMode",
"schema": {
"$ref": "#/components/schemas/StartMode"
}
},
{
"name": "description",
"schema": {
"type": [
"string",
"null"
],
"maxLength": 1024
}
},
{
"name": "checksum",
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/Checksum"
},
{
"type": "null"
}
]
}
}
],
"result": {
"name": "download.addResult",
"schema": {
"type": "object",
"additionalProperties": false,
"required": [
"taskId",
"state"
],
"properties": {
"taskId": {
"type": "string",
"format": "uuid"
},
"state": {
"$ref": "#/components/schemas/TaskState"
},
"duplicate": {
"type": [
"string",
"null"
],
"format": "uuid",
"description": "The existing task this URL matched, when downloads.duplicatePolicy resolved to 'skip'. taskId then names that existing task."
}
}
}
},
"x-privileged": false,
"x-transports": [
"uds",
"ws"
],
"x-deadlineMs": 5000,
"errors": [
{
"code": -32011,
"message": "Destination is outside the allowed roots, or is not writable. data.path is set."
},
{
"code": -32012,
"message": "Not enough free space to preallocate."
},
{
"code": -32013,
"message": "Could not probe the URL. data.httpStatus is set when there was an HTTP response."
}
],
"x-wsRestrictions": [
"saveDir must be absent or resolve inside an existing category folder; anything else is -32011. The extension may request a download, it may not choose an arbitrary destination."
]
},
{
"name": "download.addBatch",
"summary": "Create many tasks in one call: the clipboard blob, the wildcard expander, and the extension's 'Download all links'.",
"description": "Create many tasks in one call: the clipboard blob, the wildcard expander, and the extension's 'Download all links'. Partial success is normal and is reported per item rather than failing the whole batch.",
"paramStructure": "by-name",
"params": [
{
"name": "items",
"schema": {
"type": "array",
"minItems": 1,
"maxItems": 5000,
"items": {
"$ref": "#/components/schemas/DownloadSpec"
}
},
"required": true
},
{
"name": "defaults",
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/DownloadSpec"
},
{
"type": "null"
}
],
"description": "Applied to any field an item left unset. Its url is ignored."
},
"description": "Applied to any field an item left unset. Its url is ignored."
}
],
"result": {
"name": "download.addBatchResult",
"schema": {
"type": "object",
"additionalProperties": false,
"required": [
"taskIds",
"failed"
],
"properties": {
"taskIds": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
},
"description": "In the same order as the accepted items."
},
"failed": {
"type": "array",
"description": "One entry per item that could not be added. index refers to params.items.",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"index",
"code",
"message"
],
"properties": {
"index": {
"type": "integer",
"minimum": 0
},
"code": {
"$ref": "#/components/schemas/ErrorCode"
},
"message": {
"type": "string"
}
}
}
}
}
}
},
"x-privileged": false,
"x-transports": [
"uds",
"ws"
],
"x-deadlineMs": 30000,
"errors": [
{
"code": -32011,
"message": "Destination is outside the allowed roots, or is not writable. data.path is set."
},
{
"code": -32012,
"message": "Not enough free space to preallocate."
}
],
"x-wsRestrictions": [
"Same saveDir restriction as download.add, applied to defaults and to every item."
]
},
{
"name": "download.cancel",
"summary": "Stop the given tasks and mark them cancelled.",
"description": "Stop the given tasks and mark them cancelled. The .veloxpart file is kept so the user can still resume from the list; download.remove is what deletes bytes.",
"paramStructure": "by-name",
"params": [
{
"name": "taskIds",
"schema": {
"type": "array",
"minItems": 1,
"maxItems": 5000,
"items": {
"type": "string",
"format": "uuid"
}
},
"required": true
}
],
"result": {
"name": "download.cancelResult",
"schema": {
"$ref": "#/components/schemas/BulkTaskResult"
}
},
"x-privileged": false,
"x-transports": [
"uds",
"ws"
],
"x-deadlineMs": 5000,
"errors": [
{
"code": -32010,
"message": "No task with that id."
}
]
},
{
"name": "download.get",
"summary": "Full detail for one task, including per-segment state.",
"description": "Full detail for one task, including per-segment state. Backs the progress dialog. Poll it no faster than the progress dialog repaints; the table must use events instead.",
"paramStructure": "by-name",
"params": [
{
"name": "taskId",
"schema": {
"type": "string",
"format": "uuid"
},
"required": true
}
],
"result": {
"name": "download.getResult",
"schema": {
"$ref": "#/components/schemas/TaskDetail"
}
},
"x-privileged": false,
"x-transports": [
"uds",
"ws"
],
"x-deadlineMs": 5000,
"errors": [
{
"code": -32010,
"message": "No task with that id."
}
]
},
{
"name": "download.list",
"summary": "The main table.",
"description": "The main table. Filtering, sorting and paging all happen in the daemon so the GUI never materializes 100k rows to show 40. Called once on connect; after that the table is maintained from events, never re-fetched on a progress tick.",
"paramStructure": "by-name",
"params": [
{
"name": "filter",
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/TaskFilter"
},
{
"type": "null"
}
]
}
},
{
"name": "sort",
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/TaskSort"
},
{
"type": "null"
}
]
}
},
{
"name": "offset",
"schema": {
"type": [
"integer",
"null"
],
"minimum": 0
}
},
{
"name": "limit",
"schema": {
"type": [
"integer",
"null"
],
"minimum": 1,
"maximum": 5000,
"description": "Defaults to 500. The GUI pages; the extension popup asks for far fewer."
},
"description": "Defaults to 500. The GUI pages; the extension popup asks for far fewer."
}
],
"result": {
"name": "download.listResult",
"schema": {
"type": "object",
"additionalProperties": false,
"required": [
"total",
"items"
],
"properties": {
"total": {
"type": "integer",
"minimum": 0,
"description": "Rows matching the filter, ignoring offset and limit."
},
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/TaskSummary"
}
}
}
}
},
"x-privileged": false,
"x-transports": [
"uds",
"ws"
],
"x-deadlineMs": 5000
},
{
"name": "download.pause",
"summary": "Suspend transfers and flush every segment's progress to the .",
"description": "Suspend transfers and flush every segment's progress to the .veloxpart.meta file, so a pause is indistinguishable from a crash as far as resume is concerned. Never loses bytes already written.",
"paramStructure": "by-name",
"params": [
{
"name": "taskIds",
"schema": {
"type": "array",
"minItems": 1,
"maxItems": 5000,
"items": {
"type": "string",
"format": "uuid"
}
},
"required": true
}
],
"result": {
"name": "download.pauseResult",
"schema": {
"$ref": "#/components/schemas/BulkTaskResult"
}
},
"x-privileged": false,
"x-transports": [
"uds",
"ws"
],
"x-deadlineMs": 5000,
"errors": [
{
"code": -32010,
"message": "No task with that id."
}
]
},
{
"name": "download.probe",
"summary": "Ask what is at a URL without creating a task.",
"description": "Ask what is at a URL without creating a task. Populates the File Info dialog. Runs a HEAD, falling back to a ranged GET when HEAD is refused, which is also how resumability is established. Never blocks the RPC loop; the dialog opens immediately and fills in when this lands.",
"paramStructure": "by-name",
"params": [
{
"name": "url",
"schema": {
"type": "string",
"format": "uri"
},
"required": true
},
{
"name": "headers",
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/Headers"
},
{
"type": "null"
}
]
}
},
{
"name": "cookies",
"schema": {
"type": [
"array",
"null"
],
"items": {
"$ref": "#/components/schemas/Cookie"
}
}
},
{
"name": "referrer",
"schema": {
"type": [
"string",
"null"
]
}
},
{
"name": "userAgent",
"schema": {
"type": [
"string",
"null"
]
}
}
],
"result": {
"name": "download.probeResult",
"schema": {
"type": "object",
"additionalProperties": false,
"required": [
"filename",
"mime",
"resumable",
"effectiveUrl",
"suggestedCategoryId"
],
"properties": {
"filename": {
"type": "string",
"description": "From Content-Disposition when present, else the URL path, sanitized."
},
"sizeBytes": {
"type": [
"integer",
"null"
],
"minimum": 0
},
"mime": {
"type": "string"
},
"resumable": {
"type": "boolean",
"description": "Accept-Ranges: bytes and a validator (ETag or Last-Modified) are both present."
},
"effectiveUrl": {
"type": "string",
"format": "uri"
},
"suggestedCategoryId": {
"type": "string",
"description": "What the rules engine would pick. The dialog preselects it; the user may override."
},
"suggestedSaveDir": {
"type": [
"string",
"null"
]
},
"etag": {
"type": [
"string",
"null"
]
},
"lastModified": {
"type": [
"string",
"null"
]
},
"acceptRanges": {
"type": "boolean"
},
"redirectChain": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"description": "Every hop, so the user can see where a shortener actually led."
},
"requiresAuth": {
"type": "boolean",
"description": "The probe got a 401/407. The GUI should collect credentials before adding."
}
}
}
},
"x-privileged": false,
"x-transports": [
"uds",
"ws"
],
"x-deadlineMs": 30000,
"errors": [
{
"code": -32013,
"message": "Could not probe the URL. data.httpStatus is set when there was an HTTP response."
}
]
},
{
"name": "download.provideAuth",
"summary": "Answer an event.",
"description": "Answer an event.auth.required challenge. The task sits in retry_wait until this arrives; on success the daemon retries with the credentials attached and the task resumes on its own \u2014 this method does not itself start the transfer. Privileged and Unix-socket-only: a credential-bearing method must never be reachable from the browser, which is exactly the boundary event.auth.required's own description draws ('never back through this event, never into a log') \u2014 this is the other half of that promise. Credentials are handed to the Secret Service, never to SQLite and never logged; save only tells the daemon whether to persist them there for next time, or use them for this attempt alone.",
"paramStructure": "by-name",
"params": [
{
"name": "taskId",
"schema": {
"type": "string",
"format": "uuid"
},
"required": true
},
{
"name": "username",
"schema": {
"type": "string",
"maxLength": 256
},
"required": true
},
{
"name": "password",
"schema": {
"type": "string",
"maxLength": 1024
},
"required": true
},
{
"name": "save",
"schema": {
"type": [
"boolean",
"null"
],
"description": "true persists the credential in the Secret Service, keyed by host and realm, for future downloads from the same site. false or null uses it for this task's retry only. Never affects SQLite or the daemon's logs either way."
},
"description": "true persists the credential in the Secret Service, keyed by host and realm, for future downloads from the same site. false or null uses it for this task's retry only. Never affects SQLite or the daemon's logs either way."
}
],
"result": {
"name": "download.provideAuthResult",
"schema": {
"type": "object",
"additionalProperties": false,
"required": [
"ok"
],
"properties": {
"ok": {
"type": "boolean"
}
}
}
},
"x-privileged": true,
"x-transports": [
"uds"
],
"x-deadlineMs": 5000,
"errors": [
{
"code": -32003,
"message": "Method is privileged and was called over a transport that may not use it."
},
{
"code": -32010,
"message": "No task with that id."
}
]
},
{
"name": "download.refreshUrl",
"summary": "IDM's 'Refresh Download Address'.",
"description": "IDM's 'Refresh Download Address'. Point an existing task at a freshly-issued URL when a signed link has expired, keeping every byte already on disk. The daemon re-probes and compares size and validator: if they still match, the transfer resumes from where it stopped; if they do not, it says so rather than silently restarting.",
"paramStructure": "by-name",
"params": [
{
"name": "taskId",
"schema": {
"type": "string",
"format": "uuid"
},
"required": true
},
{
"name": "url",
"schema": {
"type": "string",
"format": "uri"
},
"required": true
},
{
"name": "headers",
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/Headers"
},
{
"type": "null"
}
]
}
},
{
"name": "cookies",
"schema": {
"type": [
"array",
"null"
],
"items": {
"$ref": "#/components/schemas/Cookie"
}
}
}
],
"result": {
"name": "download.refreshUrlResult",
"schema": {
"type": "object",
"additionalProperties": false,
"required": [
"ok",
"resumable",
"contentChanged"
],
"properties": {
"ok": {
"type": "boolean"
},
"resumable": {
"type": "boolean"
},
"contentChanged": {
"type": "boolean",
"description": "true when size or validator differ from what was recorded. The GUI must ask before restarting from zero \u2014 never discard bytes without consent."
},
"sizeBytes": {
"type": [
"integer",
"null"
],
"minimum": 0
},
"effectiveUrl": {
"type": [
"string",
"null"
],
"format": "uri"
}
}
}
},
"x-privileged": false,
"x-transports": [
"uds",
"ws"
],
"x-deadlineMs": 30000,
"errors": [
{
"code": -32010,
"message": "No task with that id."
},
{
"code": -32013,
"message": "Could not probe the URL. data.httpStatus is set when there was an HTTP response."
}
]
},
{
"name": "download.remove",
"summary": "Drop tasks from the list, optionally deleting the bytes on disk.",
"description": "Drop tasks from the list, optionally deleting the bytes on disk. Privileged: this is the only method that destroys user data, and the extension is never allowed to reach it. The daemon deletes the .veloxpart and .veloxpart.meta pair, and the finished file only when deleteFile is true.",
"paramStructure": "by-name",
"params": [
{
"name": "taskIds",
"schema": {
"type": "array",
"minItems": 1,
"maxItems": 5000,
"items": {
"type": "string",
"format": "uuid"
}
},
"required": true
},
{
"name": "deleteFile",
"schema": {
"type": "boolean",
"description": "Explicit and required \u2014 there is no default for deleting a user's file."
},
"required": true,
"description": "Explicit and required \u2014 there is no default for deleting a user's file."
}
],
"result": {
"name": "download.removeResult",
"schema": {
"type": "object",
"additionalProperties": false,
"required": [
"removed",
"failed"
],
"properties": {
"removed": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
}
},
"failed": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"taskId",
"code",
"message"
],
"properties": {
"taskId": {
"type": "string",
"format": "uuid"
},
"code": {
"$ref": "#/components/schemas/ErrorCode"
},
"message": {
"type": "string"
}
}
}
}
}
}
},
"x-privileged": true,
"x-transports": [
"uds"
],
"x-deadlineMs": 10000,
"errors": [
{
"code": -32003,
"message": "Method is privileged and was called over a transport that may not use it."
},
{
"code": -32010,
"message": "No task with that id."
}
]
},
{
"name": "download.resume",
"summary": "Continue paused tasks.",
"description": "Continue paused tasks. Resumption is revalidated with If-Range against the stored ETag or Last-Modified; a 200 where 206 was expected means the file changed on the server, and the task moves to failed with a clear error rather than corrupting the part file.",
"paramStructure": "by-name",
"params": [
{
"name": "taskIds",
"schema": {
"type": "array",
"minItems": 1,
"maxItems": 5000,
"items": {
"type": "string",
"format": "uuid"
}
},
"required": true
}
],
"result": {
"name": "download.resumeResult",
"schema": {
"$ref": "#/components/schemas/BulkTaskResult"
}
},
"x-privileged": false,
"x-transports": [
"uds",
"ws"
],
"x-deadlineMs": 5000,
"errors": [
{
"code": -32010,
"message": "No task with that id."
}
]
},
{
"name": "download.start",
"summary": "Begin or restart the given tasks.",
"description": "Begin or restart the given tasks. A task in 'queued' jumps its queue; a task already downloading is a no-op reported as changed false.",
"paramStructure": "by-name",
"params": [
{
"name": "taskIds",
"schema": {
"type": "array",
"minItems": 1,
"maxItems": 5000,
"items": {
"type": "string",
"format": "uuid"
}
},
"required": true
}
],
"result": {
"name": "download.startResult",
"schema": {
"$ref": "#/components/schemas/BulkTaskResult"
}
},
"x-privileged": false,
"x-transports": [
"uds",
"ws"
],
"x-deadlineMs": 5000,
"errors": [
{
"code": -32010,
"message": "No task with that id."
}
]
},
{
"name": "download.update",
"summary": "Change a task's mutable fields.",
"description": "Change a task's mutable fields. Moving saveDir or filename moves the file on disk in the same operation, which is what makes dragging a row onto a category work as one RPC. Privileged: it can name a destination path.",
"paramStructure": "by-name",
"params": [
{
"name": "taskId",
"schema": {
"type": "string",
"format": "uuid"
},
"required": true
},
{
"name": "patch",
"schema": {
"type": "object",
"additionalProperties": false,
"description": "Only the present fields change. An explicit null clears a nullable field.",
"properties": {
"filename": {
"type": [
"string",
"null"
],
"maxLength": 255
},
"saveDir": {
"type": [
"string",
"null"
]
},
"categoryId": {
"type": [
"string",
"null"
]
},
"queueId": {
"type": [
"string",
"null"
]
},
"description": {
"type": [
"string",
"null"
],
"maxLength": 1024
},
"segments": {
"type": [
"integer",
"null"
],
"minimum": 1,
"maximum": 32,
"description": "The REQUESTED connection count, subject to the same per-host cap and non-resumable demotion as DownloadSpec.segments. Takes effect on the next start; a running task is not re-segmented underneath the user."
},
"bufferBytes": {
"type": [
"integer",
"null"
],
"minimum": 65536,
"maximum": 16777216,
"description": "The REQUESTED write buffer per segment. Subject to the same maxTotalBufferBytes reduction as DownloadSpec.bufferBytes; the effective value comes back on the next download.get."
},
"checksum": {
"oneOf": [
{
"$ref": "#/components/schemas/Checksum"
},
{
"type": "null"
}
]
}
}
},
"required": true,
"description": "Only the present fields change. An explicit null clears a nullable field."
}
],
"result": {
"name": "download.updateResult",
"schema": {
"$ref": "#/components/schemas/TaskSummary"
}
},
"x-privileged": true,
"x-transports": [
"uds"
],
"x-deadlineMs": 30000,
"errors": [
{
"code": -32003,
"message": "Method is privileged and was called over a transport that may not use it."
},
{
"code": -32010,
"message": "No task with that id."
},
{
"code": -32011,
"message": "Destination is outside the allowed roots, or is not writable. data.path is set."
}
]
},
{
"name": "grabber.harvest",
"summary": "Turn selected crawl results into tasks.",
"description": "Turn selected crawl results into tasks. This is the only grabber call that creates downloads, and it names exactly the files the user ticked \u2014 a crawl never starts a download on its own.",
"paramStructure": "by-name",
"params": [
{
"name": "jobId",
"schema": {
"type": "string"
},
"required": true
},
{
"name": "select",
"schema": {
"type": "array",
"minItems": 1,
"items": {
"type": "string"
},
"description": "fileIds from grabber.status."
},
"required": true,
"description": "fileIds from grabber.status."
},
{
"name": "defaults",
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/DownloadSpec"
},
{
"type": "null"
}
]
}
}
],
"result": {
"name": "grabber.harvestResult",
"schema": {
"type": "object",
"additionalProperties": false,
"required": [
"taskIds",
"failed"
],
"properties": {
"taskIds": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
}
},
"failed": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"fileId",
"code",
"message"
],
"properties": {
"fileId": {
"type": "string"
},
"code": {
"$ref": "#/components/schemas/ErrorCode"
},
"message": {
"type": "string"
}
}
}
}
}
}
},
"x-privileged": true,
"x-transports": [
"uds"
],
"x-deadlineMs": 30000,
"errors": [
{
"code": -32003,
"message": "Method is privileged and was called over a transport that may not use it."
},
{
"code": -32011,
"message": "Destination is outside the allowed roots, or is not writable. data.path is set."
}
]
},
{
"name": "grabber.start",
"summary": "Start a depth-limited crawl.",
"description": "Start a depth-limited crawl. Nothing is downloaded by this call: it only walks pages and collects candidate links, which the wizard then shows for selection. Privileged because an unbounded crawl is a resource commitment the browser must not be able to make on the user's behalf.",
"paramStructure": "by-name",
"params": [
{
"name": "startUrl",
"schema": {
"type": "string",
"format": "uri"
},
"required": true
},
{
"name": "depth",
"schema": {
"type": "integer",
"minimum": 0,
"maximum": 10
},
"required": true
},
{
"name": "includePatterns",
"schema": {
"type": [
"array",
"null"
],
"items": {
"type": "string"
}
}
},
{
"name": "excludePatterns",
"schema": {
"type": [
"array",
"null"
],
"items": {
"type": "string"
}
}
},
{
"name": "fileTypes",
"schema": {
"type": [
"array",
"null"
],
"items": {
"type": "string"
},
"description": "Extensions, without the dot. null means every type."
},
"description": "Extensions, without the dot. null means every type."
},
{
"name": "sameHostOnly",
"schema": {
"type": "boolean"
}
},
{
"name": "maxFiles",
"schema": {
"type": [
"integer",
"null"
],
"minimum": 1,
"maximum": 10000
}
},
{
"name": "headers",
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/Headers"
},
{
"type": "null"
}
]
}
},
{
"name": "cookies",
"schema": {
"type": [
"array",
"null"
],
"items": {
"$ref": "#/components/schemas/Cookie"
}
}
}
],
"result": {
"name": "grabber.startResult",
"schema": {
"type": "object",
"additionalProperties": false,
"required": [
"jobId"
],
"properties": {
"jobId": {
"type": "string"
}
}
}
},
"x-privileged": true,
"x-transports": [
"uds"
],
"x-deadlineMs": 5000,
"errors": [
{
"code": -32003,
"message": "Method is privileged and was called over a transport that may not use it."
}
]
},
{
"name": "grabber.status",
"summary": "Poll one crawl.",
"description": "Poll one crawl. Also delivered as event.grabber.progress; the poll exists so the wizard can be reopened on a job it did not start and still catch up.",
"paramStructure": "by-name",
"params": [
{
"name": "jobId",
"schema": {
"type": "string"
},
"required": true
}
],
"result": {
"name": "grabber.statusResult",
"schema": {
"type": "object",
"additionalProperties": false,
"required": [
"jobId",
"state",
"crawled",
"found",
"files"
],
"properties": {
"jobId": {
"type": "string"
},
"state": {
"type": "string",
"enum": [
"crawling",
"done",
"failed",
"cancelled"
]
},
"crawled": {
"type": "integer",
"minimum": 0
},
"found": {
"type": "integer",
"minimum": 0
},
"files": {
"type": "array",
"items": {
"$ref": "#/components/schemas/GrabberFile"
}
},
"error": {
"type": [
"string",
"null"
]
}
}
}
},
"x-privileged": true,
"x-transports": [
"uds"
],
"x-deadlineMs": 5000,
"errors": [
{
"code": -32003,
"message": "Method is privileged and was called over a transport that may not use it."
},
{
"code": -32602,
"message": "Params failed schema validation."
}
]
},
{
"name": "limiter.get",
"summary": "Current global speed limit.",
"description": "Current global speed limit. Privileged: changing or reading the limiter belongs to the GUI and CLI; the extension shows throughput from event.speed.global instead.",
"paramStructure": "by-name",
"params": [],
"result": {
"name": "limiter.getResult",
"schema": {
"$ref": "#/components/schemas/Limiter"
}
},
"x-privileged": true,
"x-transports": [
"uds"
],
"x-deadlineMs": 2000,
"errors": [
{
"code": -32003,
"message": "Method is privileged and was called over a transport that may not use it."
}
]
},
{
"name": "limiter.set",
"summary": "Set the global token-bucket limit.",
"description": "Set the global token-bucket limit. With applyToRunning true the change re-tunes transfers already in flight instead of taking effect only on the next task \u2014 the Speed Limiter window's 'apply now' button.",
"paramStructure": "by-name",
"params": [
{
"name": "enabled",
"schema": {
"type": "boolean"
},
"required": true
},
{
"name": "globalBps",
"schema": {
"type": "integer",
"minimum": 0,
"description": "Bytes per second. 0 with enabled true means 'stop everything', which the GUI must not offer."
},
"required": true,
"description": "Bytes per second. 0 with enabled true means 'stop everything', which the GUI must not offer."
},
{
"name": "applyToRunning",
"schema": {
"type": "boolean",
"description": "Re-tune already-running transfers instead of waiting for the next task."
},
"description": "Re-tune already-running transfers instead of waiting for the next task."
}
],
"result": {
"name": "limiter.setResult",
"schema": {
"$ref": "#/components/schemas/Limiter"
}
},
"x-privileged": true,
"x-transports": [
"uds"
],
"x-deadlineMs": 5000,
"errors": [
{
"code": -32003,
"message": "Method is privileged and was called over a transport that may not use it."
},
{
"code": -32602,
"message": "Params failed schema validation."
}
]
},
{
"name": "media.addVariant",
"summary": "Turn one enumerated variant into a task.",
"description": "Turn one enumerated variant into a task. The daemon fetches the segments in parallel and muxes them with ffmpeg; the result is an ordinary task that appears in the list like any other download. Refused with -32602 when the variant is DRM-protected.",
"paramStructure": "by-name",
"params": [
{
"name": "manifestUrl",
"schema": {
"type": "string",
"format": "uri"
},
"required": true
},
{
"name": "variantId",
"schema": {
"type": "string"
},
"required": true
},
{
"name": "audioVariantId",
"schema": {
"type": [
"string",
"null"
],
"description": "For DASH and HLS renditions where audio is a separate track to be muxed in."
},
"description": "For DASH and HLS renditions where audio is a separate track to be muxed in."
},
{
"name": "spec",
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/DownloadSpec"
},
{
"type": "null"
}
]
}
}
],
"result": {
"name": "media.addVariantResult",
"schema": {
"type": "object",
"additionalProperties": false,
"required": [
"taskId",
"state"
],
"properties": {
"taskId": {
"type": "string",
"format": "uuid"
},
"state": {
"$ref": "#/components/schemas/TaskState"
},
"estimatedBytes": {
"type": [
"integer",
"null"
],
"minimum": 0
}
}
}
},
"x-privileged": false,
"x-transports": [
"uds",
"ws"
],
"x-deadlineMs": 30000,
"errors": [
{
"code": -32011,
"message": "Destination is outside the allowed roots, or is not writable. data.path is set."
},
{
"code": -32602,
"message": "Params failed schema validation."
},
{
"code": -32013,
"message": "Could not probe the URL. data.httpStatus is set when there was an HTTP response."
}
],
"x-wsRestrictions": [
"Same saveDir restriction as download.add."
]
},
{
"name": "media.listVariants",
"summary": "Parse an HLS or DASH manifest in the daemon and enumerate its renditions.",
"description": "Parse an HLS or DASH manifest in the daemon and enumerate its renditions. The extension never parses a manifest \u2014 that logic lives in one language, in one place. Variants with drm true are reported so the UI can grey them out; DRM-protected streams are refused, not attempted.",
"paramStructure": "by-name",
"params": [
{
"name": "manifestUrl",
"schema": {
"type": "string",
"format": "uri"
},
"required": true
},
{
"name": "headers",
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/Headers"
},
{
"type": "null"
}
]
}
},
{
"name": "cookies",
"schema": {
"type": [
"array",
"null"
],
"items": {
"$ref": "#/components/schemas/Cookie"
}
}
},
{
"name": "referrer",
"schema": {
"type": [
"string",
"null"
]
}
}
],
"result": {
"name": "media.listVariantsResult",
"schema": {
"type": "object",
"additionalProperties": false,
"required": [
"variants",
"manifestType",
"drmProtected"
],
"properties": {
"variants": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MediaVariant"
}
},
"manifestType": {
"type": "string",
"enum": [
"hls",
"dash"
]
},
"durationSec": {
"type": [
"number",
"null"
],
"minimum": 0
},
"drmProtected": {
"type": "boolean",
"description": "The manifest as a whole is DRM-protected. Refuse with a clear message rather than downloading undecryptable segments."
}
}
}
},
"x-privileged": false,
"x-transports": [
"uds",
"ws"
],
"x-deadlineMs": 30000,
"errors": [
{
"code": -32013,
"message": "Could not probe the URL. data.httpStatus is set when there was an HTTP response."
}
]
},
{
"name": "queue.list",
"summary": "Every queue with its run state and ordering.",
"description": "Every queue with its run state and ordering. Not privileged: the extension's 'Add to Queue' picker needs it.",
"paramStructure": "by-name",
"params": [],
"result": {
"name": "queue.listResult",
"schema": {
"type": "object",
"additionalProperties": false,
"required": [
"items"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Queue"
}
}
}
}
},
"x-privileged": false,
"x-transports": [
"uds",
"ws"
],
"x-deadlineMs": 2000
},
{
"name": "queue.reorder",
"summary": "Rewrite a queue's run order.",
"description": "Rewrite a queue's run order. taskIds must be a permutation of the queue's current membership; anything else is -32602 rather than a partial reorder, so a stale drag from an out-of-date view cannot quietly reshuffle the queue.",
"paramStructure": "by-name",
"params": [
{
"name": "queueId",
"schema": {
"type": "string"
},
"required": true
},
{
"name": "taskIds",
"schema": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
}
},
"required": true
}
],
"result": {
"name": "queue.reorderResult",
"schema": {
"type": "object",
"additionalProperties": false,
"required": [
"queue"
],
"properties": {
"queue": {
"$ref": "#/components/schemas/Queue"
}
}
}
},
"x-privileged": true,
"x-transports": [
"uds"
],
"x-deadlineMs": 5000,
"errors": [
{
"code": -32003,
"message": "Method is privileged and was called over a transport that may not use it."
},
{
"code": -32602,
"message": "Params failed schema validation."
}
]
},
{
"name": "queue.start",
"summary": "Start a queue running.",
"description": "Start a queue running. The scheduler then admits up to maxConcurrent tasks from it, in order, and keeps that many running until the queue drains or is stopped.",
"paramStructure": "by-name",
"params": [
{
"name": "queueId",
"schema": {
"type": "string"
},
"required": true
}
],
"result": {
"name": "queue.startResult",
"schema": {
"type": "object",
"additionalProperties": false,
"required": [
"queue",
"startedTaskIds"
],
"properties": {
"queue": {
"$ref": "#/components/schemas/Queue"
},
"startedTaskIds": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
}
}
}
}
},
"x-privileged": true,
"x-transports": [
"uds"
],
"x-deadlineMs": 5000,
"errors": [
{
"code": -32003,
"message": "Method is privileged and was called over a transport that may not use it."
}
]
},
{
"name": "queue.stop",
"summary": "Stop admitting new tasks from a queue.",
"description": "Stop admitting new tasks from a queue. Tasks already running are paused when pauseRunning is true, and otherwise allowed to finish \u2014 the difference between 'stop the queue' and 'stop everything', which IDM conflates and users trip over.",
"paramStructure": "by-name",
"params": [
{
"name": "queueId",
"schema": {
"type": "string"
},
"required": true
},
{
"name": "pauseRunning",
"schema": {
"type": "boolean"
}
}
],
"result": {
"name": "queue.stopResult",
"schema": {
"type": "object",
"additionalProperties": false,
"required": [
"queue",
"pausedTaskIds"
],
"properties": {
"queue": {
"$ref": "#/components/schemas/Queue"
},
"pausedTaskIds": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
}
}
}
}
},
"x-privileged": true,
"x-transports": [
"uds"
],
"x-deadlineMs": 5000,
"errors": [
{
"code": -32003,
"message": "Method is privileged and was called over a transport that may not use it."
}
]
},
{
"name": "queue.upsert",
"summary": "Create or replace a queue, including its schedule and concurrency cap.",
"description": "Create or replace a queue, including its schedule and concurrency cap. Omit queueId to create. taskIds in the payload is ignored \u2014 membership changes through download.update and queue.reorder so that two clients editing at once cannot silently drop a task.",
"paramStructure": "by-name",
"params": [
{
"name": "queue",
"schema": {
"$ref": "#/components/schemas/Queue"
},
"required": true
}
],
"result": {
"name": "queue.upsertResult",
"schema": {
"type": "object",
"additionalProperties": false,
"required": [
"queue"
],
"properties": {
"queue": {
"$ref": "#/components/schemas/Queue"
}
}
}
},
"x-privileged": true,
"x-transports": [
"uds"
],
"x-deadlineMs": 5000,
"errors": [
{
"code": -32003,
"message": "Method is privileged and was called over a transport that may not use it."
}
]
},
{
"name": "rules.list",
"summary": "The rules engine's table, in priority order.",
"description": "The rules engine's table, in priority order. Privileged: these are the daemon's routing policy. The extension gets its own narrowed view through capture.getRules instead.",
"paramStructure": "by-name",
"params": [],
"result": {
"name": "rules.listResult",
"schema": {
"type": "object",
"additionalProperties": false,
"required": [
"items"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Rule"
}
}
}
}
},
"x-privileged": true,
"x-transports": [
"uds"
],
"x-deadlineMs": 2000,
"errors": [
{
"code": -32003,
"message": "Method is privileged and was called over a transport that may not use it."
}
]
},
{
"name": "rules.upsert",
"summary": "Create, replace, or delete rules in one atomic write.",
"description": "Create, replace, or delete rules in one atomic write. 'upsert' carries the rules to store and 'remove' the ruleIds to drop; applying both at once means a reprioritisation never leaves the table in a half-valid state.",
"paramStructure": "by-name",
"params": [
{
"name": "upsert",
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Rule"
}
},
"required": true
},
{
"name": "remove",
"schema": {
"type": [
"array",
"null"
],
"items": {
"type": "string"
}
}
}
],
"result": {
"name": "rules.upsertResult",
"schema": {
"type": "object",
"additionalProperties": false,
"description": "The full table after the write, in priority order.",
"required": [
"items"
],
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Rule"
}
}
}
}
},
"x-privileged": true,
"x-transports": [
"uds"
],
"x-deadlineMs": 5000,
"errors": [
{
"code": -32003,
"message": "Method is privileged and was called over a transport that may not use it."
}
]
},
{
"name": "schedule.get",
"summary": "The schedule for one queue, or every schedule when queueId is null.",
"description": "The schedule for one queue, or every schedule when queueId is null. Backs the Scheduler window.",
"paramStructure": "by-name",
"params": [
{
"name": "queueId",
"schema": {
"type": [
"string",
"null"
]
}
}
],
"result": {
"name": "schedule.getResult",
"schema": {
"type": "object",
"additionalProperties": false,
"required": [
"items"
],
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"queueId",
"schedule"
],
"properties": {
"queueId": {
"type": "string"
},
"schedule": {
"oneOf": [
{
"$ref": "#/components/schemas/Schedule"
},
{
"type": "null"
}
]
}
}
}
}
}
}
},
"x-privileged": true,
"x-transports": [
"uds"
],
"x-deadlineMs": 2000,
"errors": [
{
"code": -32003,
"message": "Method is privileged and was called over a transport that may not use it."
}
]
},
{
"name": "schedule.set",
"summary": "Set or clear a queue's schedule.",
"description": "Set or clear a queue's schedule. A null schedule clears it and leaves the queue under manual control. Times are local wall-clock and are re-evaluated on a DST change rather than being resolved to absolute instants at set time.",
"paramStructure": "by-name",
"params": [
{
"name": "queueId",
"schema": {
"type": "string"
},
"required": true
},
{
"name": "schedule",
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/Schedule"
},
{
"type": "null"
}
]
},
"required": true
}
],
"result": {
"name": "schedule.setResult",
"schema": {
"type": "object",
"additionalProperties": false,
"required": [
"queueId",
"schedule"
],
"properties": {
"queueId": {
"type": "string"
},
"schedule": {
"oneOf": [
{
"$ref": "#/components/schemas/Schedule"
},
{
"type": "null"
}
]
},
"nextRunAt": {
"type": [
"string",
"null"
],
"format": "date-time"
}
}
}
},
"x-privileged": true,
"x-transports": [
"uds"
],
"x-deadlineMs": 5000,
"errors": [
{
"code": -32003,
"message": "Method is privileged and was called over a transport that may not use it."
},
{
"code": -32602,
"message": "Params failed schema validation."
}
]
},
{
"name": "session.hello",
"summary": "First call on every connection, on every transport.",
"description": "First call on every connection, on every transport. The daemon compares protocolVersion majors and refuses a mismatch with -32001 so a stale GUI or extension fails loudly on connect instead of subtly at the tenth field. On the WebSocket transport a valid token is required unless the client is about to call session.pair.",
"paramStructure": "by-name",
"params": [
{
"name": "clientType",
"schema": {
"type": "string",
"enum": [
"gui",
"cli",
"extension",
"nmhost",
"test"
]
},
"required": true
},
{
"name": "clientName",
"schema": {
"type": "string",
"maxLength": 64,
"description": "Human-readable, shown in the pairing prompt and the logs."
},
"required": true,
"description": "Human-readable, shown in the pairing prompt and the logs."
},
{
"name": "protocolVersion",
"schema": {
"type": "string",
"pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+(-[0-9A-Za-z.-]+)?$"
},
"required": true
},
{
"name": "token",
"schema": {
"type": [
"string",
"null"
],
"description": "Required on the WebSocket transport once paired. Ignored on the Unix socket, where SO_PEERCRED is the authorization."
},
"description": "Required on the WebSocket transport once paired. Ignored on the Unix socket, where SO_PEERCRED is the authorization."
}
],
"result": {
"name": "session.helloResult",
"schema": {
"type": "object",
"additionalProperties": false,
"required": [
"daemonVersion",
"protocolVersion",
"capabilities",
"sessionId"
],
"properties": {
"daemonVersion": {
"type": "string"
},
"protocolVersion": {
"type": "string"
},
"capabilities": {
"type": "array",
"items": {
"type": "string"
},
"description": "Optional features this build has, e.g. 'media', 'grabber', 'secretservice'. A client must degrade gracefully when one is absent rather than assuming it."
},
"sessionId": {
"type": "string",
"format": "uuid"
},
"transport": {
"type": "string",
"enum": [
"uds",
"ws"
],
"description": "How the daemon sees this connection. Lets a client know up front which privileged methods will be refused."
}
}
}
},
"x-privileged": false,
"x-transports": [
"uds",
"ws"
],
"x-deadlineMs": 2000,
"errors": [
{
"code": -32001,
"message": "Protocol major version mismatch. GUI renders this as 'Velox needs updating'."
},
{
"code": -32002,
"message": "Missing or invalid token on the WebSocket transport."
}
]
},
{
"name": "session.pair",
"summary": "WebSocket transport only.",
"description": "WebSocket transport only. Triggers a GUI or desktop-notification prompt showing a four-digit code; the user must approve before a token is issued. Failed attempts are rate-limited to 5/min followed by a 60 s lockout (-32014) so a token cannot be brute-forced by another local process. The daemon stores only a hash of the token.",
"paramStructure": "by-name",
"params": [
{
"name": "clientName",
"schema": {
"type": "string",
"maxLength": 64
},
"required": true
},
{
"name": "extensionId",
"schema": {
"type": "string",
"description": "The moz-extension origin UUID. Must match the Origin header verified on the WS upgrade."
},
"required": true,
"description": "The moz-extension origin UUID. Must match the Origin header verified on the WS upgrade."
},
{
"name": "code",
"schema": {
"type": [
"string",
"null"
],
"pattern": "^[0-9]{4}$",
"description": "Set when the user typed the code into the extension's Options page instead of clicking Allow in the GUI."
},
"description": "Set when the user typed the code into the extension's Options page instead of clicking Allow in the GUI."
}
],
"result": {
"name": "session.pairResult",
"schema": {
"type": "object",
"additionalProperties": false,
"required": [
"token",
"expiresAt"
],
"properties": {
"token": {
"type": "string",
"minLength": 43,
"description": "256 bits, base64url. Stored by the extension in browser.storage.local and sent on every later connect."
},
"expiresAt": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "null means the token does not expire; it is revoked from Options -> Unpair."
}
}
}
},
"x-privileged": false,
"x-transports": [
"ws"
],
"x-deadlineMs": 120000,
"errors": [
{
"code": -32003,
"message": "Method is privileged and was called over a transport that may not use it."
},
{
"code": -32014,
"message": "Pairing brute-force lockout. data.retryAfterSec is set."
}
]
},
{
"name": "session.subscribe",
"summary": "Choose which notifications this connection receives.",
"description": "Choose which notifications this connection receives. Subscribing replaces the previous selection rather than adding to it, so a client can narrow its firehose without reconnecting. Nothing is delivered until this is called.",
"paramStructure": "by-name",
"params": [
{
"name": "events",
"schema": {
"type": "array",
"items": {
"type": "string",
"enum": [
"event.task.added",
"event.task.removed",
"event.task.state",
"event.task.progress",
"event.speed.global",
"event.auth.required",
"event.notify",
"event.settings.changed",
"event.grabber.progress"
]
}
},
"required": true
},
{
"name": "taskIds",
"schema": {
"type": [
"array",
"null"
],
"items": {
"type": "string",
"format": "uuid"
},
"description": "Narrow task events to these ids. The extension popup uses it to avoid receiving progress for downloads it is not showing. null means all tasks."
},
"description": "Narrow task events to these ids. The extension popup uses it to avoid receiving progress for downloads it is not showing. null means all tasks."
}
],
"result": {
"name": "session.subscribeResult",
"schema": {
"type": "object",
"additionalProperties": false,
"required": [
"ok",
"events"
],
"properties": {
"ok": {
"type": "boolean"
},
"events": {
"type": "array",
"items": {
"type": "string"
},
"description": "Echoed back so a client can detect that it asked for an event this daemon does not emit."
}
}
}
},
"x-privileged": false,
"x-transports": [
"uds",
"ws"
],
"x-deadlineMs": 2000
},
{
"name": "settings.get",
"summary": "Read settings.",
"description": "Read settings. keys null means everything. Privileged: the settings bag names local filesystem paths and the allowed write roots, which the extension has no business enumerating \u2014 it gets capture.getRules instead.",
"paramStructure": "by-name",
"params": [
{
"name": "keys",
"schema": {
"type": [
"array",
"null"
],
"items": {
"$ref": "#/components/schemas/SettingKey"
}
}
}
],
"result": {
"name": "settings.getResult",
"schema": {
"type": "object",
"additionalProperties": false,
"required": [
"values"
],
"properties": {
"values": {
"$ref": "#/components/schemas/Settings"
}
}
}
},
"x-privileged": true,
"x-transports": [
"uds"
],
"x-deadlineMs": 2000,
"errors": [
{
"code": -32003,
"message": "Method is privileged and was called over a transport that may not use it."
}
]
},
{
"name": "settings.set",
"summary": "Write settings.",
"description": "Write settings. Only the keys present in values change. Rejected with -32602 if a key is unknown or a value fails the Settings schema, and with -32011 if a directory key names a path that cannot be written. Emits event.settings.changed with exactly the keys that took effect.",
"paramStructure": "by-name",
"params": [
{
"name": "values",
"schema": {
"$ref": "#/components/schemas/Settings"
},
"required": true
}
],
"result": {
"name": "settings.setResult",
"schema": {
"type": "object",
"additionalProperties": false,
"description": "The stored values for the keys that were set, and the list of keys that actually changed.",
"required": [
"values",
"changed"
],
"properties": {
"values": {
"$ref": "#/components/schemas/Settings"
},
"changed": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SettingKey"
}
}
}
}
},
"x-privileged": true,
"x-transports": [
"uds"
],
"x-deadlineMs": 5000,
"errors": [
{
"code": -32003,
"message": "Method is privileged and was called over a transport that may not use it."
},
{
"code": -32602,
"message": "Params failed schema validation."
},
{
"code": -32011,
"message": "Destination is outside the allowed roots, or is not writable. data.path is set."
}
]
}
],
"components": {
"schemas": {
"BulkTaskResult": {
"description": "Result of a state transition applied to many tasks. A bulk call never fails as a whole because one id was bad: the ids that moved come back in 'updated' and the rest are explained in 'failed'. This is what lets the GUI's toolbar act on a multi-selection without pre-validating it.",
"type": "object",
"additionalProperties": false,
"required": [
"updated",
"failed"
],
"properties": {
"updated": {
"type": "array",
"description": "One entry per task that actually changed. A task already in the target state is reported here with changed false rather than as a failure.",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"taskId",
"state",
"changed"
],
"properties": {
"taskId": {
"type": "string",
"format": "uuid"
},
"state": {
"$ref": "#/components/schemas/TaskState"
},
"changed": {
"type": "boolean"
}
}
}
},
"failed": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"taskId",
"code",
"message"
],
"properties": {
"taskId": {
"type": "string",
"format": "uuid"
},
"code": {
"$ref": "#/components/schemas/ErrorCode"
},
"message": {
"type": "string"
}
}
}
}
},
"title": "BulkTaskResult"
},
"BypassModifier": {
"description": "The modifier key a user holds to make one click bypass capture and let Firefox download normally. Shared by Settings and CaptureRules so the daemon's setting and the extension's mirror of it are literally the same type.",
"type": "string",
"enum": [
"alt",
"ctrl",
"shift",
"none"
],
"title": "BypassModifier"
},
"CaptureRules": {
"description": "The daemon's capture policy, mirrored into the extension so the two can never disagree about what should be intercepted. The extension refreshes this on connect and on event.settings.changed.",
"type": "object",
"additionalProperties": false,
"required": [
"enabled",
"monitoredExtensions",
"monitoredMimeTypes",
"minSizeBytes",
"excludedHosts",
"rulesVersion"
],
"properties": {
"enabled": {
"type": "boolean"
},
"monitoredExtensions": {
"type": "array",
"items": {
"type": "string"
}
},
"monitoredMimeTypes": {
"type": "array",
"items": {
"type": "string"
}
},
"minSizeBytes": {
"type": "integer",
"minimum": 0
},
"excludedHosts": {
"type": "array",
"items": {
"type": "string"
}
},
"bypassModifier": {
"$ref": "#/components/schemas/BypassModifier"
},
"rulesVersion": {
"type": "integer",
"minimum": 0,
"description": "Bumped on every change. The extension re-fetches when it sees a higher value."
}
},
"title": "CaptureRules"
},
"Category": {
"description": "A destination folder plus the extensions that route to it. The extension mirrors the extension lists so its capture decision agrees with the daemon's.",
"type": "object",
"additionalProperties": false,
"required": [
"categoryId",
"name",
"saveDir",
"extensions",
"builtin"
],
"properties": {
"categoryId": {
"type": "string"
},
"name": {
"type": "string",
"maxLength": 64
},
"saveDir": {
"type": "string"
},
"extensions": {
"type": "array",
"items": {
"type": "string",
"pattern": "^[A-Za-z0-9][A-Za-z0-9+._-]*$"
},
"description": "Without the leading dot, lowercase."
},
"mimeTypes": {
"type": "array",
"items": {
"type": "string"
}
},
"builtin": {
"type": "boolean",
"description": "Compressed, Documents, Music, Programs, Video. Cannot be removed; can be renamed and re-pointed."
},
"sortOrder": {
"type": "integer",
"minimum": 0
}
},
"title": "Category"
},
"Checksum": {
"description": "Optional integrity check, verified during the verifying state. A mismatch moves the task to failed and never overwrites a good file.",
"type": "object",
"additionalProperties": false,
"required": [
"algorithm",
"value"
],
"properties": {
"algorithm": {
"type": "string",
"enum": [
"md5",
"sha1",
"sha256",
"sha512"
]
},
"value": {
"type": "string",
"pattern": "^[0-9a-fA-F]{32,128}$"
}
},
"title": "Checksum"
},
"Cookie": {
"description": "One cookie the daemon replays so an authenticated download works outside the browser.",
"type": "object",
"additionalProperties": false,
"required": [
"name",
"value"
],
"properties": {
"name": {
"type": "string"
},
"value": {
"type": "string"
},
"domain": {
"type": "string"
},
"path": {
"type": "string"
},
"secure": {
"type": "boolean"
},
"httpOnly": {
"type": "boolean"
}
},
"title": "Cookie"
},
"DownloadSpec": {
"description": "Everything needed to create one task. Shared by download.add and each item of download.addBatch, so the two can never drift apart.",
"type": "object",
"additionalProperties": false,
"required": [
"url"
],
"properties": {
"url": {
"type": "string",
"format": "uri"
},
"headers": {
"oneOf": [
{
"$ref": "#/components/schemas/Headers"
},
{
"type": "null"
}
]
},
"cookies": {
"type": [
"array",
"null"
],
"items": {
"$ref": "#/components/schemas/Cookie"
}
},
"referrer": {
"type": [
"string",
"null"
]
},
"userAgent": {
"type": [
"string",
"null"
]
},
"filename": {
"type": [
"string",
"null"
],
"maxLength": 255,
"description": "Overrides the name derived from Content-Disposition or the URL."
},
"saveDir": {
"type": [
"string",
"null"
],
"description": "Canonicalized and checked against the allowed roots before any write. -32011 if it fails."
},
"categoryId": {
"type": [
"string",
"null"
],
"description": "null means the rules engine picks one."
},
"queueId": {
"type": [
"string",
"null"
],
"description": "Required when startMode is 'queue'."
},
"segments": {
"type": [
"integer",
"null"
],
"minimum": 1,
"maximum": 32,
"description": "The REQUESTED connection count. An upper bound, not a promise: the engine lowers it to the per-host cap, and to 1 when the source turns out not to be resumable. What is actually in use comes back as TaskSummary.segments. null means use connection.maxSegmentsPerDownload."
},
"bufferBytes": {
"type": [
"integer",
"null"
],
"minimum": 65536,
"maximum": 16777216,
"description": "Requested write buffer per segment, in bytes. null means use connection.bufferBytes. Default 1 MiB; range 64 KiB - 16 MiB. Silently reduced to fit connection.maxTotalBufferBytes across all live segments; the effective value is reported back as TaskDetail.effectiveBufferBytes."
},
"startMode": {
"$ref": "#/components/schemas/StartMode"
},
"description": {
"type": [
"string",
"null"
],
"maxLength": 1024
},
"checksum": {
"oneOf": [
{
"$ref": "#/components/schemas/Checksum"
},
{
"type": "null"
}
]
}
},
"title": "DownloadSpec"
},
"ErrorCode": {
"description": "Every error code the daemon may return. Adding one is a minor bump; changing the meaning of one is a major bump.",
"type": "integer",
"x-enum": [
{
"name": "ParseError",
"value": -32700,
"doc": "Malformed JSON on the wire."
},
{
"name": "InvalidRequest",
"value": -32600,
"doc": "Not a valid JSON-RPC 2.0 request object."
},
{
"name": "MethodNotFound",
"value": -32601,
"doc": "Unknown method name."
},
{
"name": "InvalidParams",
"value": -32602,
"doc": "Params failed schema validation."
},
{
"name": "InternalError",
"value": -32603,
"doc": "Unhandled daemon-side failure."
},
{
"name": "VersionMismatch",
"value": -32001,
"doc": "Protocol major version mismatch. GUI renders this as 'Velox needs updating'."
},
{
"name": "NotPaired",
"value": -32002,
"doc": "Missing or invalid token on the WebSocket transport."
},
{
"name": "TransportForbidden",
"value": -32003,
"doc": "Method is privileged and was called over a transport that may not use it."
},
{
"name": "TaskNotFound",
"value": -32010,
"doc": "No task with that id."
},
{
"name": "InvalidPath",
"value": -32011,
"doc": "Destination is outside the allowed roots, or is not writable. data.path is set."
},
{
"name": "DiskFull",
"value": -32012,
"doc": "Not enough free space to preallocate."
},
{
"name": "ProbeFailed",
"value": -32013,
"doc": "Could not probe the URL. data.httpStatus is set when there was an HTTP response."
},
{
"name": "RateLimited",
"value": -32014,
"doc": "Pairing brute-force lockout. data.retryAfterSec is set."
}
],
"enum": [
-32700,
-32600,
-32601,
-32602,
-32603,
-32001,
-32002,
-32003,
-32010,
-32011,
-32012,
-32013,
-32014
],
"title": "ErrorCode"
},
"GrabberFile": {
"description": "One candidate found by the Site Grabber crawl. Nothing is downloaded until grabber.harvest selects it.",
"type": "object",
"additionalProperties": false,
"required": [
"fileId",
"url",
"depth"
],
"properties": {
"fileId": {
"type": "string"
},
"url": {
"type": "string",
"format": "uri"
},
"filename": {
"type": [
"string",
"null"
]
},
"sizeBytes": {
"type": [
"integer",
"null"
],
"minimum": 0,
"description": "From a HEAD, when the server answered one."
},
"contentType": {
"type": [
"string",
"null"
]
},
"depth": {
"type": "integer",
"minimum": 0
},
"foundOn": {
"type": [
"string",
"null"
],
"format": "uri",
"description": "The page this link was found on."
}
},
"title": "GrabberFile"
},
"Headers": {
"description": "HTTP request headers, verbatim as the browser would have sent them. Needed for signed-URL and referrer-gated CDNs.",
"type": "object",
"additionalProperties": {
"type": "string"
},
"title": "Headers"
},
"Limiter": {
"description": "Global token-bucket speed limit. Applies across every active task, not per task.",
"type": "object",
"additionalProperties": false,
"required": [
"enabled",
"globalBps"
],
"properties": {
"enabled": {
"type": "boolean"
},
"globalBps": {
"type": "integer",
"minimum": 0,
"description": "Bytes per second. 0 with enabled true means 'stop everything', which the GUI must not offer."
},
"applyToRunning": {
"type": "boolean",
"description": "Re-tune already-running transfers instead of waiting for the next task."
}
},
"title": "Limiter"
},
"MediaVariant": {
"description": "One quality rendition from an HLS or DASH manifest. The daemon parses the manifest; the extension only renders this list. DRM-protected variants are reported with drm true and must be shown greyed out rather than failing later.",
"type": "object",
"additionalProperties": false,
"required": [
"variantId",
"kind",
"drm"
],
"properties": {
"variantId": {
"type": "string"
},
"kind": {
"type": "string",
"enum": [
"video",
"audio",
"muxed",
"subtitle"
]
},
"resolution": {
"type": [
"string",
"null"
],
"pattern": "^[0-9]{2,5}x[0-9]{2,5}$"
},
"bitrateBps": {
"type": [
"integer",
"null"
],
"minimum": 0
},
"codec": {
"type": [
"string",
"null"
]
},
"container": {
"type": [
"string",
"null"
],
"enum": [
"ts",
"mp4",
"webm",
"mkv",
null
]
},
"frameRate": {
"type": [
"number",
"null"
],
"minimum": 0
},
"language": {
"type": [
"string",
"null"
]
},
"sizeEstimate": {
"type": [
"integer",
"null"
],
"minimum": 0,
"description": "bitrate x duration. Never exact \u2014 the GUI must label it as approximate."
},
"drm": {
"type": "boolean",
"description": "Widevine/EME detected. Explicitly out of scope; refuse rather than fail mysteriously."
}
},
"title": "MediaVariant"
},
"Queue": {
"description": "An ordered run of tasks with its own concurrency cap and optional schedule.",
"type": "object",
"additionalProperties": false,
"required": [
"queueId",
"name",
"state",
"maxConcurrent"
],
"properties": {
"queueId": {
"type": "string"
},
"name": {
"type": "string",
"maxLength": 64
},
"state": {
"type": "string",
"enum": [
"running",
"stopped"
]
},
"maxConcurrent": {
"type": "integer",
"minimum": 1,
"maximum": 32
},
"taskIds": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
},
"description": "In run order. queue.reorder rewrites this."
},
"schedule": {
"oneOf": [
{
"$ref": "#/components/schemas/Schedule"
},
{
"type": "null"
}
]
},
"onComplete": {
"type": "string",
"enum": [
"nothing",
"exit",
"shutdown",
"hangup"
],
"description": "shutdown goes through org.freedesktop.login1 and must be confirmed by the user."
}
},
"title": "Queue"
},
"Rule": {
"description": "One row of the rules engine: match on extension, MIME, host or size, then route. First match by priority wins; no rule matching means the default category.",
"type": "object",
"additionalProperties": false,
"required": [
"ruleId",
"enabled",
"priority",
"match",
"action"
],
"properties": {
"ruleId": {
"type": "string"
},
"name": {
"type": [
"string",
"null"
],
"maxLength": 64
},
"enabled": {
"type": "boolean"
},
"priority": {
"type": "integer",
"minimum": 0,
"description": "Lower runs first."
},
"match": {
"type": "object",
"additionalProperties": false,
"description": "All present clauses must match. An absent clause is not a constraint.",
"properties": {
"extensions": {
"type": [
"array",
"null"
],
"items": {
"type": "string"
}
},
"mimeTypes": {
"type": [
"array",
"null"
],
"items": {
"type": "string"
}
},
"hostPattern": {
"type": [
"string",
"null"
],
"description": "Glob against the effective URL's host, e.g. *.example.com"
},
"urlPattern": {
"type": [
"string",
"null"
],
"description": "Glob against the whole effective URL."
},
"minSizeBytes": {
"type": [
"integer",
"null"
],
"minimum": 0
},
"maxSizeBytes": {
"type": [
"integer",
"null"
],
"minimum": 0
}
}
},
"action": {
"type": "object",
"additionalProperties": false,
"description": "What to do with a matching download.",
"properties": {
"categoryId": {
"type": [
"string",
"null"
]
},
"saveDir": {
"type": [
"string",
"null"
]
},
"queueId": {
"type": [
"string",
"null"
]
},
"segments": {
"type": [
"integer",
"null"
],
"minimum": 1,
"maximum": 32
},
"startMode": {
"oneOf": [
{
"$ref": "#/components/schemas/StartMode"
},
{
"type": "null"
}
]
},
"capture": {
"type": [
"string",
"null"
],
"enum": [
"take",
"ignore",
null
],
"description": "Lets a rule veto capture for a host without touching the exclusion list."
}
}
}
},
"title": "Rule"
},
"Schedule": {
"description": "When a queue may run. Times are local wall-clock in HH:MM; the daemon re-evaluates them on a DST change rather than caching absolute instants.",
"type": "object",
"additionalProperties": false,
"required": [
"enabled",
"mode"
],
"properties": {
"enabled": {
"type": "boolean"
},
"mode": {
"type": "string",
"enum": [
"once",
"periodic"
]
},
"startTime": {
"type": [
"string",
"null"
],
"pattern": "^([01][0-9]|2[0-3]):[0-5][0-9]$"
},
"stopTime": {
"type": [
"string",
"null"
],
"pattern": "^([01][0-9]|2[0-3]):[0-5][0-9]$",
"description": "null means run until the queue drains."
},
"daysOfWeek": {
"type": "array",
"items": {
"type": "integer",
"minimum": 0,
"maximum": 6
},
"description": "0 = Sunday. Ignored when mode is 'once'."
},
"onceDate": {
"type": [
"string",
"null"
],
"format": "date",
"description": "Set only when mode is 'once'."
}
},
"title": "Schedule"
},
"Segment": {
"description": "One byte range being fetched by one connection. This is the deepest the contract ever exposes the engine: the GUI draws a bar per segment and is never told what a segment steal is.\n\nRANGE CONVENTION \u2014 READ THIS BEFORE IMPLEMENTING. The range is CLOSED and INCLUSIVE on both ends: [startByte, endByte]. The segment covers endByte - startByte + 1 bytes, and endByte is the index of the LAST byte in the range, not one past it. This deliberately matches the HTTP Range header the engine actually sends ('Range: bytes=<startByte>-<endByte>' is a byte-for-byte copy of these two fields, and RFC 9110 ranges are inclusive), so no arithmetic happens between the wire and the socket and there is nowhere for an off-by-one to hide. CORE asked for half-open [start, end); PROTO chose inclusive for that reason and this note exists so nobody discovers the difference at integration. A segment always covers at least one byte: endByte >= startByte always holds. An empty range is not representable and is not needed \u2014 a zero-length download carries an empty segmentDetail array, and a segment that has donated its remainder to a steal keeps the bytes it already wrote.",
"type": "object",
"additionalProperties": false,
"required": [
"index",
"startByte",
"endByte",
"downloadedBytes",
"state"
],
"properties": {
"index": {
"type": "integer",
"minimum": 0,
"maximum": 31,
"description": "Position in TaskDetail.segmentDetail. Spelled 'index' here and in event.task.progress; there is no 'i' spelling anywhere in the contract."
},
"startByte": {
"type": "integer",
"minimum": 0,
"description": "Absolute offset of the first byte of the range. Inclusive."
},
"endByte": {
"type": "integer",
"minimum": 0,
"description": "Absolute offset of the LAST byte of the range. Inclusive \u2014 this is not one-past-the-end. Always >= startByte."
},
"downloadedBytes": {
"type": "integer",
"minimum": 0,
"description": "Bytes written for this range so far, out of endByte - startByte + 1."
},
"speedBps": {
"type": "integer",
"minimum": 0
},
"state": {
"type": "string",
"enum": [
"pending",
"connecting",
"downloading",
"stalled",
"complete",
"failed"
],
"description": "'downloading' is spelled as in TaskState, not 'receiving'. 'pending' is a range that has been planned but not yet dialled."
},
"httpStatus": {
"type": [
"integer",
"null"
],
"minimum": 100,
"maximum": 599,
"description": "The status this segment's request got. 206 on a healthy ranged fetch."
}
},
"title": "Segment"
},
"SettingKey": {
"description": "Every settings key that exists. The Options dialog maps 1:1 onto this list and the GUI must not invent a key that is not here. Kept in lockstep with Settings.schema.json by a conformance check.",
"type": "string",
"enum": [
"general.launchOnLogin",
"general.minimizeToTray",
"general.showDropTarget",
"general.confirmOnExit",
"general.language",
"general.checkForUpdates",
"capture.enabled",
"capture.monitoredExtensions",
"capture.monitoredMimeTypes",
"capture.minSizeBytes",
"capture.excludedHosts",
"capture.bypassModifier",
"capture.autoStartTypes",
"saveTo.defaultDir",
"saveTo.tempDir",
"saveTo.allowedRoots",
"saveTo.fileExistsPolicy",
"saveTo.createSubfolderPerSite",
"connection.preset",
"connection.maxSegmentsPerDownload",
"connection.bufferBytes",
"connection.maxTotalBufferBytes",
"connection.maxActiveSegments",
"connection.maxConcurrentDownloads",
"connection.timeoutSec",
"connection.maxRetries",
"connection.retryBackoffSec",
"downloads.speedLimitBps",
"downloads.speedLimitEnabled",
"downloads.virusScanCommand",
"downloads.postDownloadCommand",
"downloads.duplicatePolicy",
"downloads.verifyChecksums",
"proxy.mode",
"proxy.host",
"proxy.port",
"proxy.username",
"proxy.bypassHosts",
"proxy.pacUrl",
"sounds.enabled",
"sounds.onComplete",
"sounds.onQueueComplete",
"sounds.onError"
],
"title": "SettingKey"
},
"Settings": {
"description": "A sparse bag of settings. Every property is optional because settings.get returns only the keys that were asked for and settings.set carries only the keys that changed. Property names must match SettingKey exactly. NOTE: no password lives here \u2014 proxy and site-login credentials go to the Secret Service, never to SQLite and never over the wire.",
"type": "object",
"additionalProperties": false,
"properties": {
"general.launchOnLogin": {
"type": "boolean"
},
"general.minimizeToTray": {
"type": "boolean"
},
"general.showDropTarget": {
"type": "boolean"
},
"general.confirmOnExit": {
"type": "boolean"
},
"general.language": {
"type": "string",
"description": "BCP 47, or 'system'."
},
"general.checkForUpdates": {
"type": "boolean"
},
"capture.enabled": {
"type": "boolean"
},
"capture.monitoredExtensions": {
"type": "array",
"items": {
"type": "string"
}
},
"capture.monitoredMimeTypes": {
"type": "array",
"items": {
"type": "string"
}
},
"capture.minSizeBytes": {
"type": "integer",
"minimum": 0
},
"capture.excludedHosts": {
"type": "array",
"items": {
"type": "string"
}
},
"capture.bypassModifier": {
"$ref": "#/components/schemas/BypassModifier"
},
"capture.autoStartTypes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Extensions that skip the File Info dialog and start immediately."
},
"saveTo.defaultDir": {
"type": "string"
},
"saveTo.tempDir": {
"type": "string"
},
"saveTo.allowedRoots": {
"type": "array",
"items": {
"type": "string"
},
"description": "Every write target is canonicalized and must resolve inside one of these. Read-only over the WebSocket transport."
},
"saveTo.fileExistsPolicy": {
"type": "string",
"enum": [
"ask",
"rename",
"overwrite",
"resume"
]
},
"saveTo.createSubfolderPerSite": {
"type": "boolean"
},
"connection.preset": {
"type": "string",
"enum": [
"auto",
"lan",
"broadband",
"slow"
]
},
"connection.maxSegmentsPerDownload": {
"type": "integer",
"minimum": 1,
"maximum": 32
},
"connection.bufferBytes": {
"type": "integer",
"minimum": 65536,
"maximum": 16777216,
"description": "Default per-segment write buffer, in bytes, when a task does not request its own. Default 1 MiB (1048576); range 64 KiB - 16 MiB. This is the single biggest throughput knob and is exposed in Options -> Downloads -> 'Write buffer per connection'."
},
"connection.maxConcurrentDownloads": {
"type": "integer",
"minimum": 1,
"maximum": 64
},
"connection.timeoutSec": {
"type": "integer",
"minimum": 1,
"maximum": 3600
},
"connection.maxRetries": {
"type": "integer",
"minimum": 0,
"maximum": 100
},
"connection.retryBackoffSec": {
"type": "integer",
"minimum": 0,
"maximum": 3600
},
"downloads.speedLimitBps": {
"type": "integer",
"minimum": 0
},
"downloads.speedLimitEnabled": {
"type": "boolean"
},
"downloads.virusScanCommand": {
"type": "string"
},
"downloads.postDownloadCommand": {
"type": "string"
},
"downloads.duplicatePolicy": {
"type": "string",
"enum": [
"ask",
"skip",
"rename",
"redownload"
]
},
"downloads.verifyChecksums": {
"type": "boolean"
},
"proxy.mode": {
"type": "string",
"enum": [
"system",
"none",
"http",
"https",
"socks5",
"pac"
]
},
"proxy.host": {
"type": "string"
},
"proxy.port": {
"type": "integer",
"minimum": 1,
"maximum": 65535
},
"proxy.username": {
"type": "string"
},
"proxy.bypassHosts": {
"type": "array",
"items": {
"type": "string"
}
},
"proxy.pacUrl": {
"type": "string"
},
"sounds.enabled": {
"type": "boolean"
},
"sounds.onComplete": {
"type": "string"
},
"sounds.onQueueComplete": {
"type": "string"
},
"sounds.onError": {
"type": "string"
},
"connection.maxTotalBufferBytes": {
"type": "integer",
"minimum": 16777216,
"maximum": 2147483648,
"description": "Global cap on write-buffer memory across every live segment, in bytes. Default 128 MiB (134217728). Every live segment's buffer is reduced to fit maxTotalBufferBytes / (live segment count, capped at maxActiveSegments); the reduced value is reported per task as TaskDetail.effectiveBufferBytes. Exists so a burst of large downloads with a large per-segment buffer cannot exhaust memory."
},
"connection.maxActiveSegments": {
"type": "integer",
"minimum": 1,
"maximum": 256,
"description": "Global ceiling on segments actually transferring at once, across every task. Default 32. This is the real bound behind '20 active downloads': the rest of each download's segments queue rather than all dialling out simultaneously. DAEMON's scheduler needs this value to decide what to admit; CORE enforces it."
}
},
"title": "Settings"
},
"StartMode": {
"description": "What the daemon does with a task the moment it is added. 'later' is the File Info dialog's Download Later button and lands the task in paused.",
"type": "string",
"enum": [
"now",
"later",
"queue"
],
"title": "StartMode"
},
"TaskDetail": {
"description": "Everything TaskSummary carries, plus what only the progress dialog and the File Info dialog need. Returned by download.get; never sent in a list or an event, because it is expensive to build.",
"type": "object",
"additionalProperties": false,
"required": [
"summary",
"segmentDetail"
],
"properties": {
"summary": {
"$ref": "#/components/schemas/TaskSummary"
},
"segmentDetail": {
"type": "array",
"maxItems": 32,
"items": {
"$ref": "#/components/schemas/Segment"
},
"description": "Exactly TaskSummary.segments entries, in index order, covering [0, sizeBytes) with no gaps and no overlaps. Empty for a zero-length download, and empty before the task has been segmented."
},
"headers": {
"oneOf": [
{
"$ref": "#/components/schemas/Headers"
},
{
"type": "null"
}
]
},
"referrer": {
"type": [
"string",
"null"
]
},
"userAgent": {
"type": [
"string",
"null"
]
},
"mime": {
"type": [
"string",
"null"
]
},
"bufferBytes": {
"type": [
"integer",
"null"
],
"minimum": 65536,
"maximum": 16777216,
"description": "The REQUESTED write buffer per segment. See effectiveBufferBytes for what is actually in use."
},
"effectiveBufferBytes": {
"type": [
"integer",
"null"
],
"minimum": 65536,
"maximum": 16777216,
"description": "The write buffer actually in use per live segment, right now. May be well below bufferBytes: the engine reduces every live segment's buffer to fit connection.maxTotalBufferBytes across connection.maxActiveSegments concurrently-transferring segments, and reports the reduced value here so the GUI can show '16 MiB (using 4 MiB)'. null before the task has started its first segment."
},
"partPath": {
"type": [
"string",
"null"
],
"description": "Absolute path of the .veloxpart file while the task is unfinished."
},
"checksum": {
"oneOf": [
{
"$ref": "#/components/schemas/Checksum"
},
{
"type": "null"
}
]
},
"checksumVerified": {
"type": [
"boolean",
"null"
],
"description": "null until the verifying state has run."
},
"averageSpeedBps": {
"type": [
"integer",
"null"
],
"minimum": 0
},
"retryCount": {
"type": "integer",
"minimum": 0
}
},
"title": "TaskDetail"
},
"TaskError": {
"description": "Why a task is in the failed, retry_wait, or (when the daemon paused it on its own initiative rather than the user) paused state. Distinct from the JSON-RPC Error, which describes a failed call rather than a failed download \u2014 the two live in different code spaces on purpose, and `code` here is a TaskErrorCode string, never a JSON-RPC integer. A pause the user or the scheduler requested carries no error: this field only explains a paused state the daemon entered unilaterally (auth_required, server_file_changed, disk_full and the like), never a deliberate one.",
"type": "object",
"additionalProperties": false,
"required": [
"code",
"message",
"retryable"
],
"properties": {
"code": {
"$ref": "#/components/schemas/TaskErrorCode"
},
"message": {
"type": "string",
"description": "Human-readable, safe to show a user. Never carries a credential, a token or a full local path outside the download roots."
},
"httpStatus": {
"type": [
"integer",
"null"
],
"minimum": 100,
"maximum": 599,
"description": "Set for the codes listed in TaskErrorCode's x-carriesHttpStatus, and null otherwise."
},
"retryable": {
"type": "boolean",
"description": "Whether the scheduler will pick this task up again on its own. Carried per-occurrence rather than derived from the code, because 'probe_failed' is retryable or not depending on what the probe hit."
},
"cause": {
"oneOf": [
{
"$ref": "#/components/schemas/TaskErrorCode"
},
{
"type": "null"
}
],
"description": "The underlying failure, for codes that wrap one. max_retries_exhausted sets it to whatever the last attempt actually failed with, so a user learns the reason rather than just that Velox gave up."
},
"attempt": {
"type": [
"integer",
"null"
],
"minimum": 0,
"description": "How many attempts have been made so far."
},
"nextRetryAt": {
"type": [
"string",
"null"
],
"format": "date-time"
}
},
"title": "TaskError"
},
"TaskErrorCode": {
"description": "Why a download failed. This is the WIRE failure taxonomy and it is deliberately NOT the JSON-RPC ErrorCode space: ErrorCode says why a *call* failed, TaskErrorCode says why a *download* failed. A task can fail while every RPC involved succeeded. The values mirror vdm::Error in core/include/vdm/util/error.hpp one-for-one, by name, so DAEMON's projection from the engine taxonomy onto the wire is lossless and the GUI can tell 'the file on the server changed' from 'the checksum did not match'. CORE's 'ok' has no wire spelling: a TaskError only exists when there is a failure. Adding a value here is a minor bump; renaming or removing one is major, and would desynchronise the engine.",
"type": "string",
"enum": [
"canceled",
"resolve_failed",
"connect_failed",
"tls_failed",
"connection_reset",
"timeout",
"too_many_redirects",
"http_client_error",
"http_server_error",
"auth_required",
"forbidden",
"not_found",
"range_not_satisfiable",
"gone",
"server_file_changed",
"content_length_mismatch",
"checksum_mismatch",
"disk_full",
"io_error",
"path_rejected",
"permission_denied",
"meta_corrupt",
"meta_version_unsupported",
"probe_failed",
"unsupported_url_scheme",
"max_retries_exhausted",
"internal"
],
"x-groups": {
"cancellation": [
"canceled"
],
"network": [
"resolve_failed",
"connect_failed",
"tls_failed",
"connection_reset",
"timeout",
"too_many_redirects"
],
"http": [
"http_client_error",
"http_server_error",
"auth_required",
"forbidden",
"not_found",
"range_not_satisfiable",
"gone"
],
"content": [
"server_file_changed",
"content_length_mismatch",
"checksum_mismatch"
],
"localIo": [
"disk_full",
"io_error",
"path_rejected",
"permission_denied"
],
"resumeMetadata": [
"meta_corrupt",
"meta_version_unsupported"
],
"probe": [
"probe_failed",
"unsupported_url_scheme"
],
"retry": [
"max_retries_exhausted"
],
"internal": [
"internal"
]
},
"x-carriesHttpStatus": [
"http_client_error",
"http_server_error",
"auth_required",
"forbidden",
"not_found",
"range_not_satisfiable",
"gone",
"server_file_changed",
"probe_failed",
"max_retries_exhausted"
],
"title": "TaskErrorCode"
},
"TaskFilter": {
"description": "Which rows download.list returns. This is the category tree and the All/Unfinished/Finished nodes, expressed on the wire. Absent clauses are not constraints.",
"type": "object",
"additionalProperties": false,
"properties": {
"states": {
"type": [
"array",
"null"
],
"items": {
"$ref": "#/components/schemas/TaskState"
}
},
"categoryId": {
"type": [
"string",
"null"
]
},
"queueId": {
"type": [
"string",
"null"
]
},
"query": {
"type": [
"string",
"null"
],
"maxLength": 256,
"description": "Case-insensitive substring of filename or url."
},
"addedAfter": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"addedBefore": {
"type": [
"string",
"null"
],
"format": "date-time"
}
},
"title": "TaskFilter"
},
"TaskSort": {
"description": "Sort order for download.list. The GUI persists the user's choice and sends it on every list call; the daemon does the sorting so a 100k-row list never has to be materialized client-side.",
"type": "object",
"additionalProperties": false,
"required": [
"field",
"direction"
],
"properties": {
"field": {
"type": "string",
"enum": [
"filename",
"sizeBytes",
"state",
"etaSeconds",
"speedBps",
"lastTryAt",
"createdAt",
"queuePosition",
"description"
]
},
"direction": {
"type": "string",
"enum": [
"asc",
"desc"
]
}
},
"title": "TaskSort"
},
"TaskState": {
"description": "Lifecycle of one download. The daemon is the only writer; clients render it and nothing more. Terminal states are complete, failed and cancelled.",
"type": "string",
"enum": [
"new",
"probing",
"queued",
"connecting",
"downloading",
"paused",
"retry_wait",
"assembling",
"verifying",
"complete",
"failed",
"cancelled"
],
"title": "TaskState"
},
"TaskSummary": {
"description": "One row of the main download list. Everything the GUI table needs, and nothing more. TaskDetail is the same shape plus the fields only the progress dialog and File Info dialog need.",
"type": "object",
"additionalProperties": false,
"required": [
"taskId",
"filename",
"saveDir",
"url",
"state",
"downloadedBytes",
"speedBps",
"resumable",
"segments",
"createdAt"
],
"properties": {
"taskId": {
"type": "string",
"format": "uuid"
},
"filename": {
"type": "string",
"maxLength": 255
},
"saveDir": {
"type": "string",
"description": "Absolute, canonicalized, inside an allowed root."
},
"url": {
"type": "string",
"format": "uri",
"description": "The URL as the user or the extension supplied it."
},
"effectiveUrl": {
"type": [
"string",
"null"
],
"format": "uri",
"description": "After redirects. null until the first probe succeeds."
},
"sizeBytes": {
"type": [
"integer",
"null"
],
"minimum": 0,
"description": "null when the server did not report a length."
},
"downloadedBytes": {
"type": "integer",
"minimum": 0
},
"state": {
"$ref": "#/components/schemas/TaskState"
},
"speedBps": {
"type": "integer",
"minimum": 0
},
"etaSeconds": {
"type": [
"integer",
"null"
],
"minimum": 0,
"description": "null when the size or the speed is unknown."
},
"resumable": {
"type": "boolean"
},
"segments": {
"type": "integer",
"minimum": 1,
"maximum": 32,
"description": "The EFFECTIVE connection count in use right now \u2014 not the number that was requested. It is what remains after the per-host connection cap has been applied and after the demotion to 1 for a non-resumable source, so a task the user asked for 16 connections on legitimately reports 4, or 1. The GUI displays this value and must not assume it equals what download.add asked for. The requested value lives in DownloadSpec.segments and is not echoed back on this type. TaskDetail.segmentDetail always has exactly this many entries."
},
"categoryId": {
"type": [
"string",
"null"
]
},
"queueId": {
"type": [
"string",
"null"
]
},
"queuePosition": {
"type": [
"integer",
"null"
],
"minimum": 0,
"description": "The Q column."
},
"description": {
"type": [
"string",
"null"
],
"maxLength": 1024
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"lastTryAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"completedAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"error": {
"oneOf": [
{
"$ref": "#/components/schemas/TaskError"
},
{
"type": "null"
}
],
"description": "Set when state is failed or retry_wait, and also when state is paused and the daemon entered that state on its own initiative rather than at a user's or scheduler's request. null on every other state, including a deliberate pause."
}
},
"title": "TaskSummary"
}
}
},
"x-events": [
{
"name": "event.auth.required",
"description": "A server asked for credentials. The task sits in retry_wait until the client supplies them. Credentials travel to the Secret Service, never back through this event and never into a log.",
"params": {
"type": "object",
"additionalProperties": false,
"required": [
"taskId",
"host",
"scheme"
],
"properties": {
"taskId": {
"type": "string",
"format": "uuid"
},
"host": {
"type": "string"
},
"realm": {
"type": [
"string",
"null"
]
},
"scheme": {
"type": "string",
"enum": [
"basic",
"digest",
"ntlm",
"negotiate",
"proxy"
]
}
}
},
"x-maxRateHz": null
},
{
"name": "event.grabber.progress",
"description": "Crawl progress for the Site Grabber wizard. done true means the file list in grabber.status is final.",
"params": {
"type": "object",
"additionalProperties": false,
"required": [
"jobId",
"found",
"crawled",
"done"
],
"properties": {
"jobId": {
"type": "string"
},
"found": {
"type": "integer",
"minimum": 0
},
"crawled": {
"type": "integer",
"minimum": 0
},
"done": {
"type": "boolean"
},
"currentUrl": {
"type": [
"string",
"null"
],
"format": "uri"
}
}
},
"x-maxRateHz": 4
},
{
"name": "event.notify",
"description": "Something the user should see: a completion, a failure, a queue finishing. The client decides between a toast, a tray balloon and a sound; the daemon does not assume a GUI is running.",
"params": {
"type": "object",
"additionalProperties": false,
"required": [
"level",
"title",
"body"
],
"properties": {
"level": {
"type": "string",
"enum": [
"info",
"success",
"warning",
"error"
]
},
"body": {
"type": "string",
"maxLength": 1024
},
"taskId": {
"type": [
"string",
"null"
],
"format": "uuid"
},
"sound": {
"type": [
"string",
"null"
],
"enum": [
"complete",
"queueComplete",
"error",
null
]
}
}
},
"x-maxRateHz": null
},
{
"name": "event.settings.changed",
"description": "Settings were written by some client. Carries only the key names; a client re-reads what it cares about. The extension watches for capture.* here and re-fetches capture.getRules so its rules never lag the daemon's.",
"params": {
"type": "object",
"additionalProperties": false,
"required": [
"keys"
],
"properties": {
"keys": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SettingKey"
}
}
}
},
"x-maxRateHz": null
},
{
"name": "event.speed.global",
"description": "Aggregate throughput for the status bar, the tray tooltip and the extension popup. Emitted at 1 Hz even when nothing is active, so a client can tell 'idle' from 'disconnected'.",
"params": {
"type": "object",
"additionalProperties": false,
"required": [
"downBps",
"activeCount"
],
"properties": {
"downBps": {
"type": "integer",
"minimum": 0
},
"activeCount": {
"type": "integer",
"minimum": 0
},
"queuedCount": {
"type": "integer",
"minimum": 0
},
"limitBps": {
"type": [
"integer",
"null"
],
"minimum": 0,
"description": "null when the limiter is off."
}
}
},
"x-maxRateHz": 1
},
{
"name": "event.task.added",
"description": "A task entered the list. summary is always present so a client can insert the row without a follow-up download.get.",
"params": {
"type": "object",
"additionalProperties": false,
"required": [
"taskId",
"summary"
],
"properties": {
"taskId": {
"type": "string",
"format": "uuid"
},
"summary": {
"$ref": "#/components/schemas/TaskSummary"
}
}
},
"x-maxRateHz": null
},
{
"name": "event.task.progress",
"description": "Batched byte counters for every active task. Emitted at no more than 4 Hz as one array, never one notification per task: at twenty active downloads that is four messages a second instead of eighty. Clients apply a row patch and repaint the touched columns; rebuilding a model on this event is a bug.",
"params": {
"type": "object",
"additionalProperties": false,
"required": [
"tasks",
"at"
],
"properties": {
"tasks": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"taskId",
"downloadedBytes",
"speedBps"
],
"properties": {
"taskId": {
"type": "string",
"format": "uuid"
},
"downloadedBytes": {
"type": "integer",
"minimum": 0
},
"speedBps": {
"type": "integer",
"minimum": 0
},
"etaSeconds": {
"type": [
"integer",
"null"
],
"minimum": 0
},
"segments": {
"type": "array",
"maxItems": 32,
"items": {
"type": "object",
"additionalProperties": false,
"description": "Only what a segment bar needs. Full segment state comes from download.get.",
"required": [
"index",
"downloadedBytes",
"speedBps"
],
"properties": {
"index": {
"type": "integer",
"minimum": 0,
"maximum": 31
},
"downloadedBytes": {
"type": "integer",
"minimum": 0
},
"speedBps": {
"type": "integer",
"minimum": 0
}
}
}
}
}
}
},
"at": {
"type": "string",
"format": "date-time"
}
}
},
"x-maxRateHz": 4
},
{
"name": "event.task.removed",
"description": "A task left the list. The client deletes the row; there is nothing further to fetch.",
"params": {
"type": "object",
"additionalProperties": false,
"required": [
"taskId",
"deletedFile"
],
"properties": {
"taskId": {
"type": "string",
"format": "uuid"
},
"deletedFile": {
"type": "boolean"
}
}
},
"x-maxRateHz": null
},
{
"name": "event.task.state",
"description": "A task changed lifecycle state. Carries the summary so the row can be repainted in full without a round trip, and error whenever the daemon has something to say about why: on every failed or retry_wait transition, and on a paused transition the daemon entered unilaterally rather than at a user's or scheduler's request.",
"params": {
"type": "object",
"additionalProperties": false,
"required": [
"taskId",
"state"
],
"properties": {
"taskId": {
"type": "string",
"format": "uuid"
},
"state": {
"$ref": "#/components/schemas/TaskState"
},
"previousState": {
"oneOf": [
{
"$ref": "#/components/schemas/TaskState"
},
{
"type": "null"
}
]
},
"summary": {
"oneOf": [
{
"$ref": "#/components/schemas/TaskSummary"
},
{
"type": "null"
}
]
},
"error": {
"oneOf": [
{
"$ref": "#/components/schemas/TaskError"
},
{
"type": "null"
}
],
"description": "Set when the new state is failed or retry_wait, and also when it is paused and the daemon entered that state on its own initiative \u2014 auth_required, server_file_changed, disk_full and the like \u2014 rather than because of a user action, a schedule window closing, or an admission-control decision. null on every other transition, including every deliberately-requested pause. A client must not assume a paused task has no error just because it usually doesn't; check this field rather than the state name alone."
}
}
},
"x-maxRateHz": null
}
],
"x-envelope": {
"Id": {
"description": "Request correlation id. Velox clients always send an integer; string ids are accepted for spec compliance. A notification has no id.",
"type": [
"integer",
"string"
]
},
"Request": {
"type": "object",
"additionalProperties": false,
"required": [
"jsonrpc",
"method"
],
"properties": {
"jsonrpc": {
"const": "2.0"
},
"id": {
"$ref": "#/x-envelope/Id"
},
"method": {
"type": "string"
},
"params": {
"type": "object"
}
}
},
"Response": {
"description": "Exactly one of result or error is present.",
"type": "object",
"additionalProperties": false,
"required": [
"jsonrpc",
"id"
],
"properties": {
"jsonrpc": {
"const": "2.0"
},
"id": {
"$ref": "#/x-envelope/Id"
},
"result": {},
"error": {
"$ref": "#/x-envelope/Error"
}
}
},
"Notification": {
"description": "Server to client. No id, never answered.",
"type": "object",
"additionalProperties": false,
"required": [
"jsonrpc",
"method",
"params"
],
"properties": {
"jsonrpc": {
"const": "2.0"
},
"method": {
"type": "string"
},
"params": {
"type": "object"
}
}
},
"Error": {
"type": "object",
"additionalProperties": false,
"required": [
"code",
"message"
],
"properties": {
"code": {
"$ref": "#/components/schemas/ErrorCode"
},
"message": {
"type": "string"
},
"data": {
"type": [
"object",
"null"
],
"description": "Code-specific detail. -32013 carries data.httpStatus; -32011 carries data.path; -32014 carries data.retryAfterSec.",
"properties": {
"httpStatus": {
"type": [
"integer",
"null"
]
},
"path": {
"type": [
"string",
"null"
]
},
"retryAfterSec": {
"type": [
"integer",
"null"
]
},
"taskId": {
"type": [
"string",
"null"
]
},
"expected": {
"type": [
"string",
"null"
]
},
"actual": {
"type": [
"string",
"null"
]
}
}
}
}
}
},
"x-transports": {
"uds": "Unix domain socket, newline-delimited JSON.",
"ws": "Loopback WebSocket, one JSON message per text frame. Privileged methods are refused here with -32003."
}
}