diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000000..ec2b9b8298f --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +# The committed generated artifacts. They belong in the tree — a fresh clone +# needs no build step to run the CLI or read its reference — but they are +# output, so a review should not have to scroll them and the language +# statistics should not count them. +docs/api/dist/** linguist-generated=true +src/cli/generated/** linguist-generated=true diff --git a/.gitignore b/.gitignore index ed3fe53b116..1ae84f2d55c 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,9 @@ coverage/ /ios/EdgeApiSecret.h /android/app/src/main/cpp/edge_api_secret.c /android/app/src/main/cpp/edge_api_secret.h +/native/edge-api-signer/node/edge_api_secret.c +/native/edge-api-signer/node/edge_api_secret.h +/native/edge-api-signer/node/build/ /vendor/*.tgz /vendor/edge-core-js-*.tgz /*.tgz @@ -143,3 +146,9 @@ yarn-error.log # Maestro run output /maestro/screenshots + +# Edge CLI runtime +.edge-cli/ + +# Built CLI +/lib/ diff --git a/.travis.yml b/.travis.yml index f8edca4305e..d8b8c30bdbc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,4 +15,39 @@ install: script: - npm run lint - npx tsc + # The five documentation gates. Read-only and seconds between them, but + # inert on their own here: `npm run prepare` above has already regenerated + # the committed artifacts, so the first gate compares fresh output against + # fresh output and passes whatever was committed. Demonstrated by editing + # one JSDoc summary — the gates fail locally and exit 0 after a prepare. + - npm run docs:api:gates + # So this is the gate that actually catches a stale artifact in CI: prepare + # skips a write when nothing changed, so a dirty generated path *is* the + # staleness, and git is the oracle. The husky hook is the only other live + # check and `git commit --no-verify` skips it. + - npm run docs:api:committed + # The published CLI's dependency list, derived from the module graph: a new + # import that did not reach the manifest is an `npm install` that succeeds + # and a CLI that cannot resolve a module. `npm run prepare` regenerates + # this too — a routine version bump used to stale it and turn `develop` red + # — so here it is as inert as the gates above, and `docs:api:committed` + # catches a stale one: `src/cli/generated/npmPackage.json` is under the + # paths it compares against git. + - npm run cli:manifest:check + - npm run test:cli:node-safe - npm test + - npm run test:cli:offline + # And again against the rollup bundle. Three transforms produce a working + # CLI — `sucrase` above, `@react-native/babel-preset` for jest, + # `@babel/preset-env` for the bundle — so a defect can exist in only one of + # them, and two severity-0 defects have existed in the bundle alone. + # `build:cli` is a two-second rollup. + - npm run test:cli:offline:built + # Last, because it is the only check that reads the built bundles' own + # `require` calls, and `--require-bundles` makes an absent `lib/` a failure + # rather than a skip. Run where `cli:manifest:check` is, nine lines above + # the build, `built` was false and the one direction that matters — a + # package the bundles require and the manifest does not declare, which a + # published CLI could not resolve — printed "bundles absent, so the require + # cross-check did not run" and passed. + - npm run cli:manifest -- --check --require-bundles diff --git a/AGENTS.md b/AGENTS.md index 24184a3af69..789bae5bec2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,10 +14,33 @@ - `npm test` - Run Jest tests (single run) - `npm run watch` - Run Jest tests in watch mode - `npm test -- --testNamePattern="test name"` - Run specific test by name -- `npm run verify` - Run lint, typechain, tsc, and test (full verification) -- `npm run precommit` - Full pre-commit check (localize, lint-staged, tsc, test) +- `npm run verify` - Full verification: lint, typechain, tsc, the five + documentation gates, the published CLI manifest check, the Node-safety smoke + test, Jest, and the CLI's offline suites against both the sources and the + built bundle +- `npm run precommit` - Pre-commit check: localize, update-eslint-warnings, + lint-staged, tsc and Jest on every commit. The documentation gates, the + manifest check, the Node-safety smoke test and the CLI's offline suites run + only when the commit stages a path in `GATE_PATHS` + (`scripts/util/cliGatePaths.js`, which is the list) — about two and a half + minutes that a commit elsewhere has no reason to pay, and a hook people skip + with `--no-verify` also skips the `tsc` and `jest` that predate the CLI. CI + runs all of them regardless. - `tsc` - TypeScript type checking (via package.json script) +### Edge CLI + +The repository also builds `edge-cli` and `edge-engine` from `src/cli/`. +[`docs/EDGE_CLI.md`](docs/EDGE_CLI.md) is the guide; `docs/api/README.md` +covers the route declarations the reference is generated from. + +- `npm run cli -- ` / `npm run engine` - run either half from source +- `npm run build:cli` - the single-file bundles in `lib/` +- `npm run docs:api` - regenerate the committed reference and command table +- `npm run docs:api:gates` - the five read-only checks that it is in step +- `npm run test:cli:offline` - the fake-world suites, no network +- `npm run test:cli:network` - the suites that need the tester servers + ## Swap Provider Integration The plugin itself lives in `edge-exchange-plugins`; this repo only wires it up, and every wiring point below fails SILENTLY when missed (no error, just a blank icon or a provider that never initializes). Registering a new swap `pluginId` means all of: @@ -39,6 +62,8 @@ New swap icons live at `https://content.edge.app/exchangeIcons//icon.p - **Naming**: camelCase for variables/functions, PascalCase for components/types - **Files**: `.tsx` for React components, `.ts` for utilities/hooks - **Error Handling**: Use proper error boundaries, avoid throwing in render +- **Node-safe trees**: `src/util`, `src/locales` and `src/cli` must stay importable under plain Node, with no `react-native*` module anywhere on their require graph — the CLI engine loads them without the app's bundler. Reach platform code through an injected dependency or a `src/cli`-side module instead. `npm run test:cli:node-safe` is the check, and `scripts/util/cliGatePaths.js` says which modules it covers +- **lstrings at module scope**: in those same three trees, read `lstrings` inside a function, never at module scope. `applyLocale` mutates `lstrings` in place and the CLI boots the locale from somewhere these modules do not import, so a module-scope capture is right only if the boot happened to run first. `src/util/txDisplay/txActionLabels.ts` is the pattern; `edge/no-module-scope-lstrings` enforces it - **Text Components**: Use `EdgeText`, `Paragraph`, `SmallText`, `WarningText` instead of raw text - **Component Reuse**: Strongly prefer reusing existing shared components over building new ones or dropping to raw library primitives. Before adding UI, look for a component that already covers the need (e.g. text via `EdgeText`), and keep color, sizing, and styling driven by `useTheme()` rather than hard-coded per call site. When nothing suitable exists, add a reusable, themed definition instead of a one-off - **Spacing**: Keep a minimum of 1rem TOTAL space between an element and its neighbors, including screen edges. "Total" is the sum contributed across nearby and parent elements, so examine them rather than each element in isolation: scene-edge padding from `SceneWrapper`/`SceneContainer` (`DEFAULT_MARGIN_REM`, 0.5rem) plus an element's own 0.5rem margins via `Space`/`useSpaceStyle` compose to the 1rem total. An explicit override always takes precedence: the "unless otherwise specified" escape hatch applies to every case, screen edges included. The only built-in exception is flex layouts, which rely on flex gap and alignment for sibling spacing; even there, the 1rem screen-edge minimum still applies. Express spacing in rem through the layout primitives instead of hard-coded pixel margins diff --git a/CHANGELOG.md b/CHANGELOG.md index ef1c149839d..2993540be5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ ## Unreleased (develop) +- changed: `npm run precommit`, `npm run verify` and CI gained the CLI's gates. The husky hook runs the five documentation gates, `test:cli:node-safe` and `test:cli:offline` only when a commit stages something under `src/cli`, `scripts/`, `docs/api` or `docs/EDGE_CLI.md`, so a commit elsewhere pays nothing. `verify` adds those plus `cli:manifest:check` and `test:all`, which builds the rollup bundle and runs the offline suites against it. Travis runs all of them unconditionally, plus `docs:api:committed`, which fails on a stale committed artifact. The offline suites spawn a real engine daemon on a Unix socket under a temporary data directory; they need no network and no API key. +- changed: Moved transaction display metadata, denominations, spam-threshold resolution, local settings, the transaction export pipeline, transaction tagging, locale selection, exchange rates and the network helpers out of GUI-only modules so they load under plain Node, with no behaviour change to the app. +- fixed: Historical rates no longer re-query a pair the server has answered but cannot price, which looped without delay and never settled the caller's promise. +- fixed: Historical rate requests are capped below the rates server's 100-asset limit, where the previous check let a batch reach 101 and the rejection priced the whole page at zero. +- fixed: An unusable OS number format no longer also falls the language back to English when resolving info-server localized strings. +- fixed: `splitCategory` keeps an unrecognised category prefix as part of the subcategory rather than discarding it, so opening and saving such a transaction no longer writes the prefix away. +- fixed: Transaction fiat amounts are fetched in one batch rather than in groups of ten, each of which paid a fresh one-second debounce. +- fixed: QBO exports escape a non-ASCII payee or memo, so the `ENCODING:USASCII` header the file declares is true. +- added: Edge CLI (`edge-cli`) and its engine daemon: a long-lived process owning an `EdgeContext`, a JSON REST API over a Unix socket with an optional loopback TCP listener, and a thin client that spawns the engine on demand. 117 routes, generated command table, help text and OpenAPI reference. - fixed: Recognize promotions that only set a preferred exchange in the exchange settings, which now say the promotion is choosing it and offer to remove it, instead of showing a list whose taps were saved but silently overridden for the promotion's whole window ## 4.52.0 (staging) diff --git a/babel.config.js b/babel.config.js index 9adf2e4f163..78781a0afbd 100644 --- a/babel.config.js +++ b/babel.config.js @@ -4,6 +4,10 @@ module.exports = function (api) { return { presets: ['module:@react-native/babel-preset'], plugins: [ + // `typechain` emits `export * as factories from './factories'` in + // src/plugins/contracts, which the React Native preset does not + // transform on its own. + '@babel/plugin-transform-export-namespace-from', isAndroid ? './node_modules/r3-hack/node_modules/react-native-reanimated/plugin' : 'react-native-worklets/plugin' diff --git a/docs/EDGE_CLI.md b/docs/EDGE_CLI.md new file mode 100644 index 00000000000..4b9d4b607a7 --- /dev/null +++ b/docs/EDGE_CLI.md @@ -0,0 +1,634 @@ +# Edge CLI + +A command-line interface for the Edge platform. Useful for account management, +wallet operations, debugging, and scripting against edge-core-js. + +The CLI is a **thin one-shot client**. A long-lived **engine daemon** owns the +`EdgeContext`, keeps logged-in accounts alive across invocations, and exposes a +JSON REST API over a Unix domain socket (TCP is optional). + +For the full surface — every command, its REST call, and the `edge-core-js` +call behind it — see the generated reference at +[docs/api/dist/index.html](./api/dist/index.html), built from `docs/api/`. + +## Overview + +| Piece | Role | +|-------|------| +| `edge-engine` | Long-lived daemon. Owns one `EdgeContext` and N `EdgeAccount`s keyed by `sessionId`. Serves HTTP. | +| `edge-cli` | One-shot client. Parses argv, auto-spawns the engine if needed, talks over the Unix socket, prints results. | + +By default the client uses only the Unix socket at +`~/.edge-cli/run//engine.sock`. Enable loopback TCP with +`--tcp=9008` on the engine (useful for `curl` / scripts). That port is +authenticated with a token from the run file — see +[Sessions](#sessions). + +The CLI keeps state under two roots: + +| Path | Holds | +|------|-------| +| `~/.config/edge-cli/` | `edge-cli.conf`, and the account data directory `--directory` defaults to. The data directory is created `0700`: it holds the login stashes | +| `~/.edge-cli/run//` | `engine.sock`, `engine.json`, `session.json` — per profile; directory `0700`, files `0600` | +| `~/.edge-cli/logs/` | `engine-.log` | +| `~/.edge-cli/keys.json` | API keys, after `./keys.json` | + +Account repos therefore live under `~/.config/edge-cli`, not `~/.edge-cli`. +Pass `--directory` to put them somewhere else; it is part of the profile hash, +so a different directory is a different engine. + +## Running + +**Development (from source):** + +```bash +npm run cli -- help # One-shot via client (auto-spawns engine) +npm run cli -- login-with-password --username=u --password=p # sessionId is persisted +npm run cli -- balance-map --wallet-id= # Reuses the engine + session + +npm run engine # Start the engine alone +npm run engine -- -t # Engine against tester servers +npm run engine -- --tcp=9008 # Also listen on 127.0.0.1:9008 +``` + +**Built artifact:** + +```bash +npm run build:cli # → lib/edgeCli.js + lib/edgeEngine.js +node lib/edgeCli.js help +node lib/edgeEngine.js -t --tcp=9008 +``` + +`build:cli` produces the two bundles and nothing else, so a CLI built that +way has no native HMAC signer and falls back to `keys.json`. For the signed +build — the one a publish uses — run: + +```bash +npm run build:cli:all # native signer, then both bundles +``` + +That needs `edgeKey.json` in the repository root, because +`scripts/makeApiSigner.ts` XOR-shards the secret out of it into +`native/edge-api-signer/node/edge_api_secret.c`; without the file there is +nothing to compile. It runs `build:cli:native` (node-gyp over +`native/edge-api-signer/node/`), then `build:cli`, then +`build:cli:copy-native`, which puts `edge_api_signer.node` beside the bundles +where `loadNodeApiSignerNative` looks for it. `npm run test:cli:node-hmac` +checks the result signs. See [HMAC_SIGNING.md](./HMAC_SIGNING.md). + +**From npm:** the supported route for anyone who is not working on this +repository. `@edgeapp/cli` is the package; `edge-cli` is the command it puts +on a `PATH`. + +```sh +npm install -g @edgeapp/cli --omit=peer # or: npx @edgeapp/cli --help +edge-cli --help +``` + +`--omit=peer` because `edge-currency-accountbased` declares four React Native +modules as non-optional peers — `react-native-monero`, +`react-native-pirate-wallet`, `react-native-zano` and `react-native-zcash` — +which npm 7+ installs by default and the CLI never loads. Without it a global +install pulls React Native itself. + +Inside this checkout, `node lib/edgeCli.js` is the same program under a +different name; `edge-cli` throughout this document means either. The app's +own `package.json` stays `private: true` and is never what is published — +`scripts/publishCli.ts` assembles the package separately, described under +[Publishing to npm](#publishing-to-npm). + +**Interactive prompt:** run `edge-cli` with no command and it reads commands +from stdin instead of exiting, with tab completion over the command list. The +engine, session and flags are the same as one-shot mode — `edge-cli -t` with no +command opens a prompt already pointed at the tester servers. `help` lists the +commands, and EOF (Ctrl-D) leaves. + +### Engine / client flags + +| Flag | Who | Description | +|------|-----|-------------| +| `-t, --test` | both | Use the six `-tester` servers — see below | +| `--fake` | both | Emulate the login, info and sync servers in-process — no network, no API key. Its own engine profile, so it never shares a socket with a real one | +| `-d, --directory` | both | Working directory for local Edge data | +| `-a, --app-id` | both | Application ID | +| `-k, --api-key` | both | Override API key from `keys.json` — also turns off the keys.json secret and the native HMAC signer. The client forwards it to the engine in the environment, not on the command line. An `apiKey` in the config file supplies the key *without* that override | +| `--locale ` | both | Language tag (BCP 47 or POSIX). Also `EDGE_CLI_LOCALE` or `locale` in the config file | +| `--tcp=` | both | Bind TCP on `127.0.0.1`, token-authenticated — off by default; bare `--tcp` and `--tcp=` are both errors, and `--tcp=0` picks an ephemeral port. On the client it is forwarded to the engine it spawns | +| `--idle-timeout=` | engine | Self-shutdown once nothing holds the engine open — default `300`, `0` means never, and a blank value is an error | +| `--no-spawn` | client | Do not auto-start the engine; fail if none is running | +| `--timeout=` | client | Per-request deadline (default `120`) — on expiry the client gives up while the engine runs the request to completion, so raise it for a whole-wallet `get-transactions`, `wait-for-all-wallets` or `resync-blockchain` | +| `--session ` | client | Override the persisted `sessionId` | +| `--solve-captcha` | client | On `CHALLENGE_REQUIRED`, auto-solve ALTCHA PoW and retry | +| `-c, --config ` | both | Configuration file. Forwarded to the engine the client spawns, so one file decides both halves | +| `--tcp-host=` | engine | TCP bind host, loopback only (default `127.0.0.1`) — a non-loopback address is a usage error, because the port would expose `spend` and `get-raw-private-key` to the network | +| `-u, --username` | client | Legacy one-shot login helper | +| `-p, --password` | client | Legacy one-shot login helper | +| `-h, --help` | both | Show options | + +API keys load from `./keys.json`, then `~/.edge-cli/keys.json` +(`edgeApiKey`, `edgeApiSecret`, `pluginApiKeys`). + +When the native Edge API HMAC signer is available, the engine prefers it over +`keys.json` secrets for **both** `edge-core-js` and `GET /v1/getKeys` on the +info server. Plugin secrets (including Monero LWS `edgeApiKey`) come from that +fetch and overlay local `pluginApiKeys`. Set `EDGE_CLI_FORCE_KEYS_JSON=1` +(or pass `-k`) to force the JSON key/secret pair instead — useful for tester +embeds and debugging. `-t` signs getKeys against `info-tester.edge.app`. + +Locale: `--locale`, then `locale` in `edge-cli.conf`, then `EDGE_CLI_LOCALE`, +then `LC_ALL` / `LC_MESSAGES` / `LANG`, then `Intl`, then `en-US`. The tag +selects the language tables the engine's responses are built from. It also +sets `decimalSeparator` and `groupingSeparator`, which `GET /engine/status` +reports — but nothing on either CLI entry's import graph formats a number +through them, so today they are reported for a caller's benefit rather than +applied to anything the CLI prints. An already-running engine keeps its +locale; the client warns on mismatch and continues. + +## Tester servers + +**Always use `-t` / `--test` for testing. Never hit production in tests.** + +`-t` points the engine at these six hosts (the only `*-tester.edge.app` +names that resolve): + +| Host | `EdgeContextOptions` field | +|------|----------------------------| +| `https://login-tester.edge.app` | `loginServer` | +| `https://info-tester.edge.app` | `infoServer` | +| `https://sync-tester-us1.edge.app` | `syncServer` (array) | +| `https://sync-tester-us2.edge.app` | `syncServer` | +| `https://sync-tester-us3.edge.app` | `syncServer` | +| `https://change-tester.edge.app` | `changeServer` | + +```bash +npm run cli -- -t --solve-captcha create-account --username=alice --password='pass' --pin=1234 +npm run cli -- -t login-with-password --username=alice --password='pass' +``` + +Confirm with `edge-cli engine-config` — every server URL should be a +`*-tester.edge.app` host. `testMode` being true is necessary but not +sufficient: it means "not production", and `--fake` reports true while pointed +at `fake://login`, so read the server list to tell the two apart. + +## Architecture + +```mermaid +flowchart LR + cli["edge-cli (one-shot)"] -->|"HTTP / unix socket"| engine + script["scripts / curl"] -->|"HTTP / TCP (opt-in --tcp=9008)"| engine + subgraph engine [edge-engine daemon] + router[Router] --> sessions[SessionStore] + sessions --> account1["EdgeAccount (sess_A)"] + sessions --> account2["EdgeAccount (sess_B)"] + router --> context["EdgeContext (single)"] + end + context --> core[edge-core-js + currency plugins] +``` + +ASCII equivalent: + +``` +edge-cli ──HTTP──► engine.sock ──► edge-engine + │ + ├─ EdgeContext (one) + └─ accounts by sessionId + (sess_… → EdgeAccount) +``` + +A *profile* is a hash of `{ appId, directory, testMode, loginServer }`. +Distinct profiles get distinct run directories, so a tester engine and a +production engine can coexist. `directory` is canonicalised first — resolved +to an absolute path and through any symlink — so one data directory is one +profile however the path is spelled, and a trailing slash or a relative `-d` +cannot give it a second engine. + +## Discovery + +Under `~/.edge-cli/run//` (files mode `0600`): + +| File | Purpose | +|------|---------| +| `engine.json` | Discovery / lock: pid, apiVersion, socketPath, tcpPort, appId, testMode, startedAt | +| `engine.sock` | Unix domain socket (always on) | +| `session.json` | Last `sessionId` written by the client. Removed when the engine stops, since a session cannot outlive it | +| `engine-startup.log` | The spawned engine's stdout and stderr, so a startup that dies before the socket exists (bad `keys.json`, a plugin that will not load) leaves a record rather than a spawn timeout. Kept when a stale lock is cleared, because the replacement engine is already writing to it | + +A clean shutdown removes all four and the profile directory with them. + +Example `engine.json`: + +```json +{ + "pid": 40123, + "apiVersion": "1.0.0", + "socketPath": "/Users/you/.edge-cli/run/8f3a.../engine.sock", + "tcpPort": null, + "appId": "", + "testMode": true, + "startedAt": "2026-08-06T04:55:00.000Z" +} +``` + +Client flow: the profile is a pure hash of four argv-derived values, so the +client needs nothing on disk to know which socket to use. It sends the request +straight at that socket; only on ENOENT or ECONNREFUSED does it spawn the +engine (unless `--no-spawn`), and `ensureEngine` pings `/engine/status` first +in case one is already up, then polls readiness for up to 30 s and retries the +request once. + +```bash +# Manual status check over the socket +curl --unix-socket ~/.edge-cli/run//engine.sock \ + http://localhost/engine/status +``` + +## Sessions + +Successful login returns an opaque `sessionId` (`sess_` + base58 of 16 random +bytes). Account-scoped REST paths look like: + +``` +/account/{sessionId}/wallet/balance-map?walletId= +``` + +A `sessionId` **is** the credential: every account-scoped route checks it and +nothing else, so holding one means `get-pin`, `get-raw-private-key`, +`get-login-key` and `spend`. Core authenticates the login itself via password +/ PIN / key / recovery; `sessionId` scopes everything after that. + +The Unix socket therefore needs no transport auth of its own — it is `0600` +inside a `0700` directory, so the operating system is the check. **The +loopback TCP listener does**, because any process on the host can reach +`127.0.0.1` whatever user it runs as, and so can a web page the user happens +to be looking at. With `--tcp` the engine: + +- mints a bearer token at startup and writes it to the `0600` run file as + `tcpToken`, and requires it in an `X-Edge-Token` header; +- refuses any request carrying an `Origin` header, and any request whose + `Host` is not the address it bound — which is what stops a page reaching it + by rebinding a name onto the port; +- refuses to bind anything but a loopback address. + +```bash +edge-cli engine-status # starts an engine +TOKEN=$(jq -r .tcpToken ~/.edge-cli/run//engine.json) +curl -sH "X-Edge-Token: $TOKEN" http://127.0.0.1:9008/engine/status +``` + +A missing or wrong token is `401 UNAUTHORIZED`; a bad `Origin` or `Host` is +`403 FORBIDDEN`. `engine-sessions` truncates every `sessionId` it reports, so +the listing is a diagnostic rather than a way to collect credentials. + +The client persists the latest id in `session.json` so commands chain without +re-typing. Override with `--session ` or `EDGE_CLI_SESSION`. + +**Auto-logout** mirrors the GUI: the engine reads `autoLogoutTimeInSeconds` +from the account’s synced `Settings.json` (default `3600`, `0` = disabled) and +logs the account out after that much idle time since the last REST call that +touched the session. `edge-cli touch` is an explicit keepalive. + +The setting is re-read as the engine sweeps, so changing it on another device +reaches a session the engine is already holding: within 15 seconds normally, +and within a minute for a session whose auto-logout is currently off, which is +re-read less often because the read is a decrypt and a parse with no cache. + +**Engine idle shutdown:** after ~5 minutes with nothing holding it open, the +engine closes the context, unlinks the socket / run file, and exits. Four +things hold it: a logged-in session, a live subscription, a request being +served, and a handle that belongs to no session — a pending edge login or a +parked admin lobby, each with a TTL of the same 5 minutes. Configure with +`--idle-timeout` (`0` = never). A live `subscribe` holds it open — see +[Subscribing to events](#subscribing-to-events). + +`engine-status`'s `idleShutdownAt` reports the first, second and fourth of +those but not the third, because the request asking for it is itself in +flight. + +```bash +edge-cli -t login-with-password --username=alice --password='pass' # stores sessionId +edge-cli currency-wallets # uses persisted session +edge-cli engine-sessions +edge-cli touch +edge-cli logout +``` + +## CAPTCHA + +`usernameAvailable`, `createAccount`, and `loginWithPassword` can raise a +login-server CAPTCHA. The engine does **not** solve it. It returns: + +```json +{ + "error": { + "code": "CHALLENGE_REQUIRED", + "status": 403, + "message": "Login requires a CAPTCHA challenge challengeId=YUXCPENDRSDHMMA7 challengeUri=https://login-tester.edge.app/captcha/YUXCPENDRSDHMMA7 Retry the same request with body/query challengeId after solving, or use CLI --solve-captcha.", + "details": { + "challengeId": "YUXCPENDRSDHMMA7", + "challengeUri": "https://login-tester.edge.app/captcha/YUXCPENDRSDHMMA7" + } + } +} +``` + +Options: + +1. **CLI helper** — `--solve-captcha` on any login command headlessly + solves ALTCHA PoW at `challengeUri` and retries with `challengeId`. +2. **Manual** — open the URI in a browser, then re-run the command with + `--challenge-id ` (or pass `challengeId` in the REST body). +3. **Prefetch** — `edge-cli fetch-challenge` → `POST /fetch-challenge`. + +Automated tests use the same ALTCHA solver (see `src/cli/client/solveCaptcha.ts`). + +## Edge login (QR / barcode) + +`edge-cli request-edge-login` requests a pending Edge login and prints JSON the +approving device can use. By default it then **blocks**, polling every 2 seconds +for up to 5 minutes, and stores the session as soon as the login is approved. +Pass `--no-wait` to print the JSON and exit immediately, which is what a script +that drives its own polling wants: + +```json +{ + "pendingId": "pending_7Qk3mVJ2xR4t", + "lobbyId": "HbC9mVJ2xR4tN8pL", + "uri": "edge://edge/HbC9mVJ2xR4tN8pL", + "state": "pending" +} +``` + +Approve from another logged-in Edge device (Scan QR), or paste `uri` / +`lobbyId` via **Scan QR → Enter** (useful with Maestro on the iOS simulator). + +After `--no-wait`, poll it yourself with `poll-edge-login ` (or +`GET /pending-edge-login/{pendingId}`) until `state` is `done` — it then carries +the session — or `error`. `fetch-lobby ` reads the lobby without +waiting, and `cancel-request ` abandons it. + +## Command shape + +Commands are not listed here. The full reference — every command paired with +the REST call it makes and the `edge-core-js` call behind it, with request and +response types and an example — is generated from the route declarations: + +**[docs/api/dist/index.html](./api/dist/index.html)** + +```bash +npm run docs:api # rebuild it +npm run docs:api:gates # check it still matches src/cli +``` + +Every command follows one shape: + +``` +edge-cli [global flags] [--flag=value ...] +``` + +| Form | Example | +|------|---------| +| Preferred | `--wallet-id=7o7i6` | +| Also accepted | `--wallet-id 7o7i6` | +| Boolean | `--paused` means true; `--paused=false` turns it off. A required one must be written out: `--paused=true` | +| Repeatable | `--answer=rex --answer=oak` | +| Lists | comma-separated, no spaces: `--export-format=csv,qbo` | +| JSON | single-quoted: `--spend-info='{"tokenId":null}'` | + +Arguments are named. A command takes a bare positional only where the value is +a base58 identifier the engine issued — an object handle, a pending login — +because only those are safe as a URL path segment. A wallet id is base64 and a +username is free text, so both are flags. `edge-cli help ` prints the +exact usage for any of them, and that text is generated from the same source +as the reference. + +For the native asset, omit `--token-id` rather than passing the literal +`null`. An empty `--name=` is a usage error, as are unknown flags and extra +positionals. + +### Subscribing to events + +`edge-cli subscribe` holds a Server-Sent Events stream open and prints one JSON +object per line until you interrupt it. It runs concurrently with ordinary +one-shot commands, so a subscriber in one terminal watches what another +terminal does: + +```bash +# terminal 1 +edge-cli subscribe --type=session.created --type=session.expired + +# terminal 2 +edge-cli -t login-with-password --username=alice --password='pass' +edge-cli logout +``` + +A live subscription keeps the **engine** alive past its idle timeout — the +stream would otherwise die under the subscriber. It does **not** keep an +**account** logged in: the auto-logout timer still fires on schedule. + +Scope comes from the query string. `subscribe` opens an **unscoped** stream, +which carries context-level events and survives for as long as the engine +does — so a subscriber that logged in, was auto-logged-out and logged in again +keeps the same stream, and `subscription.closed` arrives only when the engine +stops. A stream opened with `?sessionId=` is account-scoped and that session's +logout closes it. `?walletId=` narrows it within that account, for whenever a +wallet-scoped event exists — none does today, so it changes nothing you +receive; `?type=` repeated +filters it in the engine, so an unwanted event never crosses the socket. + +`subscribe` exits `0` on Ctrl-C and `7` when the engine ends the stream, or +for any close reason it does not recognise. A Ctrl-C during a cold start has +to wait for the engine to finish spawning before the stream can be closed +cleanly; a second Ctrl-C in that window exits `130` immediately instead. + +### Exit codes + +| Code | Meaning | +|------|---------| +| `0` | Success | +| `1` | Generic failure | +| `2` | Usage / bad argv | +| `3` | Auth / session | +| `4` | Not found | +| `5` | Validation / funds | +| `6` | Network | +| `7` | Engine unavailable | + +Codes `3` to `6` are assigned from an explicit list of error codes, published +per code in the generated reference. Three rules sit outside that list, and +the reference states them with the codes they belong to: + +- An unlisted error code arriving with HTTP `503` exits `6` (network), not + `1`. +- A failure to connect to or spawn the engine exits `7`, and is not an API + error at all. +- A bad command line exits `2` before any request is made. + +Only after those does `1` apply, so `1` means "failed, with no more specific +mapping" rather than "unknown error". + +## Source layout + +``` +src/cli/ + engine/ + index.ts # Daemon entry, signals, shutdown + engineArgs.ts # The daemon's argv, from the shared flag table + makeCoreContext.ts # Plugin registration + makeEdgeContext + server.ts # HTTP handler; unix (+ optional TCP) listeners + router.ts # Method + path dispatch + route.ts # route() — the declaration every doc and command is built from + doc.ts # doc() — prose attached to a cleaner + fieldDocs.ts # Prose for fields that recur across schemas + schemas.ts # Query coercions and shared response shapes + objectHandles.ts # Handles for core values that cannot cross JSON + sessions.ts # SessionStore + auto-logout ticker + idleShutdown.ts # Idle self-shutdown + shutdownTiming.ts # Shutdown budgets, read by the client too + discovery.ts # Profile hash, run-file, socket paths + runFile.ts # The run file's cleaner, so the log sweep can read it + errors.ts # EngineError + core → HTTP mapping + errorGroups.ts # Error codes that recur across routes + apiVersion.ts # The protocol version, for every surface that shows it + transportAuth.ts # Token, Host and Origin checks for the TCP listener + tcpPort.ts # --tcp parsing, shared by both entries + cliHome.ts # ~/.edge-cli and the paths under it + readJsonConfig.ts # One read-parse-clean for the config files + sweepTicker.ts # The periodic sweep both stores run + json.ts # Body parse / Uint8Array·Map codec + internal.ts # `$internalStuff` access for the admin routes + resolve.ts # walletId prefix, tokenId parsing + events.ts # SSE hub + logger.ts # Engine log file + cliConfig.ts # edge-cli.conf + default directory + keysConfig.ts # keys.json search path + appConfig.ts # appId / app config + fetchPluginKeys.ts # Remote plugin keys over the signed infoRollup + nodeApiSigner.ts # Node HMAC signer for the Edge API + testerServers.ts # The six -tester hosts + routes/ # status, login, account, wallets, … + client/ + apiClient.ts # HTTP over the engine’s unix socket + spawnEngine.ts # Auto-spawn + readiness poll + sessionFile.ts # Persisted sessionId + solveCaptcha.ts # Headless ALTCHA solver for --solve-captcha + output.ts # JSON / NDJSON output + exit codes + exitCodes.ts # Error code → exit code, shared with the reference + commands/ # Argv → apiClient → output (no core imports) + command.ts # command() registry + commandArgs.ts # Per-command flag parsing + parseArgs.ts # Client/engine argv before the command name + flagTable.ts # Every global flag, once; both help texts render it + bootNodeLocale.ts # Locale detection, before anything reads a string + bootEngineLocale.ts # Applies it; engine only, so the client ships no tables + generatedSchemas.ts # Cleaners for the files scripts/build* generate + index.ts # One-shot and interactive front-end +``` + +Shared, outside `src/cli/`: `src/util/predicates.ts` holds the small +predicates both halves use, because `src/util/exportTxInfo.ts` needs one and +that module is reached from the app — the React Native bundle must not import +out of the daemon's directory. + +Every module in `src/cli/engine/` is listed above, and +`npm run docs:api:verify` fails on one that is not — the map is the only +hand-maintained inventory of the engine left, so it is gated rather than +trusted. + +## Tests + +| Script | What it runs | +|--------|--------------| +| `npm run test:cli:offline` | `testCliFake` + `testCliSubscribe` against the sources, through `sucrase/register`. No network, no Edge API key. | +| `npm run test:cli:offline:built` | The same suites against `lib/edgeCli.js`, the bundle `build:cli` produces — which is how the CLI is run until a package is published. Part of `verify` and of Travis's `script`. | +| `npm run test:cli:node-safe` | Loads the GUI modules the CLI shares, plus the client entry, under plain Node — so a `react-native` import at module scope in the code that crosses the boundary fails here. The engine's own graph is covered by `test:cli:offline`, which spawns the real engine from source. | +| `npm run test:cli:network` | One-shot, CAPTCHA and Edge-login suites. Needs the network and an Edge API key. | +| `npm run docs:api:gates` | The five documentation gates: `check`, `verify`, `contracts`, `core` and `coverage`. `docs:api:committed` and `cli:manifest:check` run beside them in CI and in `precommit:cli`. | +| `npm run test:cli:node-hmac` | The Node HMAC addon against a JS reference, and `makeCoreContext` signing a real `infoRollup` fetch. Needs `npm run build:cli:all` first, so it is in neither hook nor CI. | + +The husky `precommit` hook runs the gates, `cli:manifest:check`, +`test:cli:node-safe` and `test:cli:offline` only when the commit stages one +of the paths `scripts/util/cliGatePaths.js` names — `src/cli`, `scripts`, +`docs/api`, `docs/EDGE_CLI.md`, `src/util`, `src/locales` and +`package.json`, the last two trees because the CLI shares them and the last +file because the manifest mirrors it — about two and a half minutes +that the great majority of commits in this repository have no reason to pay, +and a hook people skip with `--no-verify` also skips the `tsc` and `jest` that +were there before the CLI existed. Travis runs all of them unconditionally. + +Both offline suites run with `EDGE_CLI_CHECK_RESPONSES=strict`, so every +response they provoke is checked against its route's own `returns` cleaner and +a shape that drifts from the published reference fails the suite. The engine's +default is `warn`: a mismatch is a documentation bug rather than the caller's +fault, so production logs it and sends the body through untouched. + +Three different transforms produce a working CLI — `sucrase` for the suites, +`@react-native/babel-preset` for jest, `@babel/preset-env` for the bundle — so +a defect can exist in only one of them. `EDGE_CLI_BIN=` points either +offline suite at any built CLI. + +## REST API + +Full method/path/body/error documentation is generated: +**[docs/api/dist/index.html](./api/dist/index.html)**, with an OpenAPI 3.1 +document beside it at `docs/api/dist/openapi.json`. The source of truth is +`docs/api/`; see [docs/api/README.md](./api/README.md). + +## Publishing to npm + +The CLI ships as its own scoped package, built from this repository but not +containing it: rollup inlines every module the CLI reaches from `src/`, so the +published package is the two bundles, the native addon, this document as its +README, and `LICENSE`. The app's `package.json` stays `private: true` and is +never the thing published — `scripts/publishCli.ts` assembles a separate +manifest in a temporary directory. + +| File | Role | +| --- | --- | +| `src/cli/npmMeta.ts` | The decisions: package name, bin name, licence, the per-platform native packages. | +| `src/cli/generated/npmPackage.json` | The manifest, generated. `npm run cli:manifest` writes it; `cli:manifest:check` is the gate. | +| `scripts/buildCliManifest.ts` | Derives the dependency list from the module graph. | +| `scripts/publishCli.ts` | Builds, stages and publishes. | + +The version is not a decision: the CLI ships in lockstep with the app, so the +manifest takes it from the app's own `package.json`. There is no second number +to bump, and a published CLI says which app release it corresponds to. The +cost is that each version can be published once, since npm will not replace an +existing one — so a CLI-only fix goes out on the next app version bump rather +than on its own. + +The dependency values are the versions `package-lock.json` resolves, not the +app's caret ranges. Lockstep is the point of pinning the CLI's version to the +app's, and npm does not publish a lock — so a published `^2.22.1` would +resolve again, elsewhere, later. `npm run cli:manifest` prints any dependency +the lock does not resolve, and the non-optional peers an install would pull in +beside them: `edge-currency-accountbased` declares four React Native modules +that way, which is why the install line says `--omit=peer`. + +The dependency list is derived rather than written down. `rollup.config.cli.mjs` +externalises every key of the app's `dependencies`, so the bundles leave all of +them as bare `require`s while needing about a dozen; anything the app does not +declare is inlined instead. The generator walks the module graph from both +entry points, keeps the bare specifiers the app declares as dependencies, +skips builtins and type-only imports, and treats the rest as bundled. The +result is checked against the built bundles' own `require` calls. + +A build server needs `edgeKey.json` and nothing else: + +```sh +npm run publish:cli -- --dry-run # build, stage, pack, publish nothing +npm run publish:cli -- --out /tmp/pkg # stage for inspection, then stop +npm run publish:cli # publish +``` + +With `edgeKey.json` present the script runs `build:cli:all`, which generates +the XOR-split secret shards, compiles the Node HMAC addon and copies it beside +the bundles. Without the key the addon cannot be built, so publishing requires +`--allow-unsigned` and the staged README says the build cannot sign. A publish +from a dirty tree is refused, because the registry copy could not then be +re-derived from any commit. + +The addon is compiled for the build machine's platform and Node ABI. +`loadNodeApiSignerNative` answers `null` when it cannot load one, so a CLI on +another platform still runs with unsigned info-server requests. +`CliPackageMeta.nativePackages` is where per-platform packages are declared +once they are published; it is empty until then. diff --git a/docs/HMAC_SIGNING.md b/docs/HMAC_SIGNING.md index 8abca3881f8..805af0e9d0b 100644 --- a/docs/HMAC_SIGNING.md +++ b/docs/HMAC_SIGNING.md @@ -19,17 +19,28 @@ appKeys layer matching lives in `prepare.sh`) when that file exists. It XOR-shards the secret: 1. Five random pads plus a stored remainder (`SHARD_COUNT = 6`). -2. A runtime pad of `sha256(bundleId)` (Android `applicationId` and iOS - `PRODUCT_BUNDLE_IDENTIFIER` must match). +2. A runtime pad of `sha256(bundleId)`. The mobile pair share one id (Android + `applicationId` and iOS `PRODUCT_BUNDLE_IDENTIFIER` must match); the Node + build has its own, `NODE_API_SIGNER_BUNDLE_ID` from + `src/cli/engine/nodeApiSigner.ts`, so the CLI's shards are useless to a + mobile binary and vice versa. Both ids are hashed into the stamp that + decides whether a regeneration is needed. 3. The C sources reconstruct `secret = s0 ⊕ … ⊕ s5 ⊕ runtimePad`. -Generated (gitignored) outputs: +Generated (gitignored) outputs — three pairs, one per target: - `ios/EdgeApiSecret.c` + `ios/EdgeApiSecret.h` - `android/app/src/main/cpp/edge_api_secret.c` + `edge_api_secret.h` +- `native/edge-api-signer/node/edge_api_secret.c` + `edge_api_secret.h` Native modules (`ios/edge/EdgeApiSigner.m`, -`android/.../EdgeApiSignerModule.kt`) expose `signMessage` and `getApiKey`. +`android/.../EdgeApiSignerModule.kt`, +`native/edge-api-signer/node/edge_api_signer_napi.c`) expose `signMessage` +and `getApiKey`. The first two are React Native modules; the third is an +N-API addon built by `npm run build:cli:native` and loaded by +`src/cli/engine/nodeApiSigner.ts`, which is how the CLI signs without a +React Native runtime. `npm run build:cli:all` does the generate, the compile +and the two rollup bundles in one step. `src/util/edgeApiSigner.ts` wraps that module as an `EdgeApiSigner` whose `signMessage(message)` returns `{ apiKey, signature }` (base64 HMAC-SHA256). Every native build (debug, beta, or release) needs a real `edgeKey.json`: diff --git a/docs/api/README.md b/docs/api/README.md new file mode 100644 index 00000000000..9e97ab0eb79 --- /dev/null +++ b/docs/api/README.md @@ -0,0 +1,189 @@ +# Edge CLI API docs + +The `edge-cli` command line and the `edge-engine` REST API, declared once and +rendered together. Each call is a single `route({…})` in +`src/cli/engine/routes/`, holding both forms, so the CLI usage and the HTTP +request cannot drift apart — from each other or from the code. + +```bash +npm run docs:api # rebuild the command table, help text and dist/ +npm run docs:api:gates # the five checks: do the artifacts match the code? +npm run cli:manifest:check # does the published CLI's npm manifest? +npm run docs:api:committed # are the committed artifacts the current ones? +``` + +The two answer different questions, and CI needs both. The five gates +regenerate and compare, which catches a stale artifact locally — but Travis +runs `npm run prepare` first, and that regenerates in write mode, so by the +time the gates run they are comparing fresh output against fresh output. +`docs:api:committed` asks git instead: prepare skips a write when nothing +changed, so a dirty generated path is exactly the staleness. + +Open `docs/api/dist/index.html` in a browser. The command line comes first in +every entry, the REST call second, and each states the `edge-core-js` call it +fronts. + +**`dist/` is committed on purpose** so the reference can be read on GitHub and +linked to without a build step. `npm run docs:api` is idempotent: it rewrites +the generated files only when they change, and `docs:api:check` fails if a +route edit landed without them being rebuilt. + +## Naming + +Routes are named after the core call they front, kebab-cased, and the command +matches: `context.forgetAccount` becomes `POST /forget-account` and +`forget-account`. Parameters keep core's names. + +A path parameter is a base58 identifier, and nothing else — `sessionId`, +`objectId`, `pendingId`, `lobbyId`, `syncKey`. Base58 has no `/`, `?` or `#`, +so it survives a URL as written. A base64 wallet id or a free-text username +does not, so those are named arguments: the query for `GET`, the body for +`POST`. That is why `balance-map` is +`GET /account/{sessionId}/wallet/balance-map?walletId=…` rather than putting +the wallet id in the path. Where a path parameter is allowed it comes last, in +the order the command reads. Collection segments are singular, since each call +acts on one. Only `GET` and `POST` are used, since core has no HTTP verbs, and +a core method returning `void` answers `204`. + +A call with no core equivalent sets `core: null` and explains itself in a +`@coreNote`; the verifier enforces that. + +## Why generated, not hand-written + +A hand-maintained reference drifted badly: response shapes no route returned, +status codes off by a category, body fields under the wrong name, and a +documented `confirm=true` guard on account deletion the engine never +implemented. None of that is visible by reading either the doc or the code +alone — only by diffing them. + +So the declaration is the documentation. `scripts/extractRoutes.ts` reads every +`route(…)` with the TypeScript checker: the JSDoc above it is the prose, and +its `query`, `body` and `returns` cleaners are the shapes, resolved to the +validator's own types. Everything downstream — the CLI's command table, its +`help` text, the HTML reference and the OpenAPI document — is generated from +that one source. + +## The gates + +`npm run docs:api:gates`, run by CI on every build and by `precommit` +only when the commit stages a CLI path (see `scripts/util/cliGatePaths.js` +for which paths those are): + +| Gate | Checks | +| --- | --- | +| `docs:api:check` | the generated files are current — rebuild them and nothing changes. The local staleness check: `npm run prepare` regenerates them, so in CI this compares fresh output against fresh output | +| `docs:api:verify` | the surface matches: no route without the command it claims, no command nobody declares, no flag on one side missing from the other, no `core` naming a member `edge-core-js` does not have | +| `docs:api:contracts` | the contract holds: every field a caller can send is described, nothing described has gone away, and no handler reads a field its cleaner would strip | +| `docs:api:core` | each route's request matches the real signature of the core call it fronts, or records why it differs in `coreExtra` | +| `docs:api:coverage` | every command's *handler* is reached by an automated test, or the command is listed with the suite that drives it or the reason it cannot run offline. A refusal — a request the route rejects before the handler body — is counted apart, because it proves the rejection and not the command. 90 of the 118 commands reach a handler offline; the other 28 are listed in `scripts/checkCliCoverage.ts` with the suite that drives each one | + +Two more run beside them in CI and in `precommit:cli`, on the same generated +artifacts: + +| Gate | Checks | +| --- | --- | +| `docs:api:committed` | git is the oracle: `prepare` skips a write when nothing changed, so a dirty path under `src/cli/generated` or `docs/api/dist` *is* the staleness. This is the one that holds in CI | +| `cli:manifest:check` | the published CLI's npm manifest matches the module graph and this package's version and plugin ranges | + +`docs:api:core` exists because checking the core member by name is not enough: +that is how `currency-wallets` came to carry a `waitForAll` parameter +`account.currencyWallets` does not have — it is a property, and waiting is a +separate method. + +## Layout + +``` +docs/api/ + README.md this file + groups.ts section titles, order and prose, keyed by route-file basename + shared.ts the error catalogue; re-exports the shared error + groups and the exit-code table from runtime code + dist/ generated — do not edit +scripts/ + extractRoutes.ts reads the route declarations + cliUsage.ts renders a usage line, and decides which fields + need a JSON argument + writeIfChanged.ts writes an output only when its bytes differ + buildCliCommands.ts -> src/cli/generated/commands.json + buildCliHelp.ts -> src/cli/generated/helpDocs.json + buildApiDocs.ts -> dist/index.html and dist/openapi.json + verifyApiDocs.ts surface drift + checkRouteContracts.ts contract drift + checkCoreAlignment.ts core signature drift + checkCliCoverage.ts untested commands +``` + +There is no separate doc file per route: the route file *is* the doc file. +`groups.ts` decides section titles, render order, and the prose each +section and group is introduced with. + +## Adding an endpoint + +Declare the route, and it documents itself: + +```ts +/** + * Balances for every asset in the wallet. + * + * @note On the CLI, omit `--token-id` for the native asset rather than passing + * the literal `null`. + * @coreNote Rendered as an array, with currencyCode and displayAmount added + * from the wallet's denominations. + */ +export const balanceMap = route({ + core: 'wallet.balanceMap', // or null, with a @coreNote saying why + method: 'GET', + path: '/account/{sessionId}/wallet/balance-map', + cli: { command: 'balance-map' }, + query: asObject({ walletId: asWalletId }).withRest, + returns: asObject({ + balances: doc( + asArray(asBalance), + 'One entry per asset the wallet holds, native coin first.' + ) + }), + errors: WALLET_ERRORS, + + handler(ctx) { + /* … */ + } +}) +``` + +Then `npm run docs:api && npm run docs:api:gates`. + +Conventions worth keeping: + +- Wrap a cleaner in `doc(…)` to describe a field. `checkRouteContracts` fails a + response field with no prose, so the reference cannot ship a bare type. +- Reuse the shared error lists from `src/cli/engine/errorGroups.ts` + (`SESSION_ERRORS`, `WALLET_ERRORS`, `HANDLE_ERRORS`) rather than restating + them. They live in runtime code so routes can import them and `shared.ts` + can re-export them for the reference; a route importing from `docs/` would + have the dependency backwards. Take error codes from the catalogue in + `shared.ts` — a code not in it fails `verify`. +- Put anything a caller would get wrong from the type alone in a `@note`: + surprising defaults, fields that look symmetric but are not, calls that write + when they look like reads. +- Two commands may share a route (`spend` / `spend-max`, via `preset`), and a + route may declare `cli: { custom: true }` when its command needs code of its + own. Both are fine; declare every binding on the route it calls. + +## Runtime validation + +The engine validates its own responses. `checkResponse` in +`src/cli/engine/route.ts` runs each response through the route's `returns` +cleaner on every request and **discards the cleaned value** — response cleaners +strip unknown keys, so returning it would quietly delete fields the engine +means to send. The check reports drift; it never reshapes anything. + +`EDGE_CLI_CHECK_RESPONSES` picks what a mismatch costs: + +| Value | Behaviour | +| --- | --- | +| unset, or `warn` | log `Response type mismatch` and answer normally (the default) | +| `strict`, or `1` | fail the request with `500 INTERNAL_ERROR` | +| `off`, or `0` | skip the check | + +So the documented shape is the validated shape, and a drifting response shows +up in the engine log rather than silently reaching a caller. diff --git a/docs/api/dist/index.html b/docs/api/dist/index.html new file mode 100644 index 00000000000..82a8d226a2d --- /dev/null +++ b/docs/api/dist/index.html @@ -0,0 +1,8549 @@ + + + + +Edge CLI API + + +
+ +
+

Overview

+

Every entry is one API call shown twice: as an edge-cli command, then as the JSON REST request that command sends. Both are generated from a single declaration in src/cli/engine/routes/, so the two forms cannot drift apart.

+

Routes are named after the edge-core-js call they front, kebab-cased: context.forgetAccount becomes POST /forget-account, and the command is forget-account. Parameters carry core's own names. Every entry states its core call, or says why there is none. Only GET and POST appear — core has no HTTP verbs, so reads are GET and everything else is POST.

+

The edge-cli client is a thin one-shot process. A long-lived edge-engine daemon owns the EdgeContext and every logged-in account, serving this API over a Unix socket at ~/.edge-cli/run/<profile>/engine.sock, plus loopback TCP when started with --tcp=9008.

+

Transport authentication differs by transport. The Unix socket needs none: it is 0600 inside a 0700 directory, so the operating system is the check and only this user's processes can connect. The loopback TCP listener needs more, because every process on the host can reach 127.0.0.1 whatever user it runs as, and a web page the user happens to visit can reach it too — so it requires the bearer token from the engine's 0600 run file in an X-Edge-Token header, refuses any request carrying an Origin, requires Host to be the address it bound, and answers no preflight. A sessionId is itself full account authority, so treat one as a credential wherever it travels.

+

+Ephemeral object handles. In edge-core-js a method-bearing value is identified by object reference — you call wallet.signTx(tx) on the very tx that makeSpend returned. That does not survive HTTP, so the engine parks such values under an objectId with a 5 minute TTL and later steps name the id. Reads do not extend the TTL; only a step that updates the value does. Finishing a workflow, or POST /account/{sessionId}/object/delete/{objectId}, releases the handle early. A handle read after its TTL answers 410 OBJECT_EXPIRED if the 15-second sweeper has not reached it yet and 404 OBJECT_NOT_FOUND once it has, which is the usual case — so a caller retrying on an expired handle has to branch on both.

+

Serialization. Uint8Array becomes base64, Date becomes an ISO-8601 string, Map becomes an object, amounts are always decimal strings, and EdgeTokenId is JSON null for a native asset.

+

Testing. Always pass -t / --test to point at the *-tester.edge.app servers.

+
+

Engine

+

The edge-engine daemon itself. None of these have an edge-core-js equivalent — they describe the process — and none need a session.

+
+

Lifecycle

+

Lifecycle and configuration of the edge-engine daemon. None of these have an edge-core-js equivalent — they describe the daemon itself — and none need a session.

+
+
+
+

Engine liveness and summary.

+
engine-statussrc/cli/engine/routes/status.ts
+
+

coreEngine lifecycle; the daemon is not part of the core API.

+

The readiness probe the client polls after auto-spawning the engine.

+
+
+

Command line

+
engine-status
+ + + +
+

REST

+

GET/engine/status

+ + + + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/engine/status'
+
+
+

Response 200

+

Every field is documented on asEngineStatus; the two nullable ones, idleShutdownAt and tcpPort, say there what null means.

+
+
Response body
+
{
+  pid: number
+  apiVersion: string
+  uptimeSeconds: number
+  sessionCount: number
+  testMode: boolean
+  idleShutdownAt: string | null
+  tcpPort: number | null
+  socketPath: string
+  rateCachedCount: number
+  rateUnpricedCount: number
+  locale: string
+  localeMatched: boolean
+  decimalSeparator: string
+  groupingSeparator: string
+}
+
Example
{
+  "pid": 0,
+  "apiVersion": "string",
+  "uptimeSeconds": 1,
+  "sessionCount": 1,
+  "testMode": true,
+  "idleShutdownAt": "2026-09-02T16:35:00.000Z",
+  "tcpPort": 0,
+  "socketPath": "string",
+  "rateCachedCount": 1,
+  "rateUnpricedCount": 1,
+  "locale": "string",
+  "localeMatched": true,
+  "decimalSeparator": "string",
+  "groupingSeparator": "string"
+}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
pidnumberThe daemon process, for kill when it will not stop.
apiVersionstringThe API this engine speaks. A client refusing to talk to an older engine checks this.
uptimeSecondsnumberHow long the daemon has been running.
sessionCountnumberLogged-in accounts held open right now.
testModebooleanTrue when the engine is not pointed at production: the tester fleet, or the in-process fake world under --fake.
idleShutdownAtstring | nullWhen the engine will exit for want of work. Null while a client is deliberately holding it open — a logged-in session, a live subscription, or a handle that belongs to no session (a pending edge login, or a parked admin lobby) — and null when the timeout is disabled. A request being served also disarms the timer, but is not reported here: the request asking for this field is itself in flight.
tcpPortnumber | nullThe loopback port, null unless started with --tcp.
socketPathstringUnix socket the CLI connects to.
rateCachedCountnumberExchange rates held in the engine’s process cache. It is bounded and cleared when the last session goes away, and this is how an operator sees it.
rateUnpricedCountnumberRate keys the server answered without a price, remembered for a few minutes so a repeated listing does not re-ask for every date. A number that stays high means an asset with no feed on the rates server — a hand-added custom token, a long-tail token, or dates predating its market.
localestringLanguage tag the engine resolved at boot.
localeMatchedbooleanWhether a translation table for that tag was actually found. False means the tag was accepted but the engine is answering in English, which is otherwise indistinguishable from a build that has the language.
decimalSeparatorstringDecimal mark for that locale.
groupingSeparatorstringThousands mark for that locale.
+
+
Errors

503ENGINE_SHUTTING_DOWN

+
+ +
+
+

Configured context options.

+
engine-configsrc/cli/engine/routes/status.ts
+
+

coreReflects the EdgeContextOptions the engine supplied at startup.

+

What the engine passed to makeEdgeContext. Contains no secrets. Use it to assert tester hosts before a test run.

+
+
+

Command line

+
engine-config
+ + + +
+

REST

+

GET/engine/config

+ + + + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/engine/config'
+
+
+

Response 200

+
+
Response body
+
{
+  appId: string
+  testMode: boolean
+  directory: string
+  servers: {
+    [keys: string]: string | string[]
+  }
+  plugins: string[]
+}
+
Example
{
+  "appId": "FS8xJ2kQ…",
+  "testMode": true,
+  "directory": "string",
+  "servers": {},
+  "plugins": [
+    "string"
+  ]
+}
+ + + + + + + + + + + + + + + + + + + + + + + + +
appIdstringApplication ID the engine was started with.
testModebooleanTrue when the engine is not pointed at production: the tester fleet, or the in-process fake world under --fake. Read servers to tell those apart.
directorystringWorking directory holding the core data.
servers{ [keys: string]: string | string[]; }The URLs this engine talks to, keyed by role. syncServer is a list, since core rotates across the sync fleet.
pluginsstring[]Plugin IDs the engine loaded, sorted.
+
+ +
+

Notes

  • Outside -t / --test, servers is an empty object — core is using its built-in production defaults, so there is nothing to echo back.
+
+
+

Stop the engine.

+
engine-stopsrc/cli/engine/routes/status.ts
+
+

coreEngine lifecycle. Internally calls context.close().

+

Logs out every session, closes the context, unlinks the socket and run-file, then exits. The engine answers before it starts tearing down, so a response is not proof the process is gone.

+
+
+

Command line

+
engine-stop
+ + + +
+

REST

+

POST/engine/stop

+ + + + +
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  'http://localhost/engine/stop'
+
+
+

Response 200

+
+
Response body
+
{
+  ok: boolean
+}
+
Example
{
+  "ok": true
+}
+ + + + +
okbooleanAlways true; a failure arrives as an error envelope.
+
+ +
+

Notes

  • 503 ENGINE_SHUTTING_DOWN reaches requests that arrive after teardown starts, not ones already in flight: handleRequest tests the flag at the top only. A request already past that point is waited for — shutdown drains in-flight work before logging out — so it gets its real response. This route is exempt: a second stop is answered ok, because stopping an engine that is already stopping has succeeded.
+

Event stream

+

A Server-Sent Events feed of engine activity, served outside the router because the response never ends.

+
+
+
+

Subscribe to engine events.

+
subscribesrc/cli/engine/routes/events.ts
+
+

coreEngine-side fan-out; core.log frames carry core's onLog output.

+

Holds a Server-Sent Events stream open until the caller disconnects or the engine closes it. Runs concurrently with one-shot calls, so a subscriber in one terminal watches what another terminal does.

+

A live subscription holds the engine open past its idle timeout. It does not hold an account logged in: the auto-logout timer still fires, and closes any subscription scoped to that account or one of its wallets. Context-scoped subscriptions survive, because the context outlives every account.

+

Scope comes from the query string. With no sessionId the stream is context-scoped and nothing but the engine stopping ends it, which is what the subscribe command asks for. With sessionId it is account-scoped: a logout closes it, and the session events of other accounts are filtered out. walletId narrows it further within that account, for whenever a wallet-scoped event exists: no event carries a wallet scope today, so it changes nothing a caller receives, and a wallet-scoped stream gets exactly what the same stream without walletId would have got. It is accepted now so a caller's URL does not have to change when the first such event arrives.

+
+
+

Command line

+
subscribe [--session-id=<sessionId>] [--wallet-id=<walletId>] [--type=<value>]
+ +
Client-only flags
--typeoptionalOnly these event types. Applied by the engine, so an unwanted type never crosses the socket; the client filters again for the types the engine sends regardless, like subscription.closed.
+

Prints newline-delimited JSON and runs until interrupted. Exits 0 on SIGINT and 7 when the engine ends the stream. subscribe opens an unscoped stream, which only the engine stopping ends; a stream opened with sessionId is closed by that session logging out.

+
+
+

REST

+

GET/engine/events

+ +
+
Query
+
{
+  sessionId?: string
+  walletId?: string
+}
+
Example
{
+  "sessionId": "FS8xJ2kQ…",
+  "walletId": "FS8xJ2kQ…"
+}
+ + + + + + + + + +
sessionIdstring optionalScope the stream to one account, so a logout closes it. Omitted, the stream is context-scoped and only the engine stopping ends it.
walletIdstring optionalWith sessionId, narrow the stream to one wallet. No event carries a wallet scope yet, so today this changes nothing you receive — the stream still carries every account event the same sessionId alone would carry. Ignored on its own.
+
+ + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/engine/events'
+
+
+

Response 200

+

One frame per event, as event: then data: lines.

+
+
Response body
+
{
+  type: string
+  data: unknown
+}
+
Example
{
+  "type": "string",
+  "data": {}
+}
+ + + + + + + + + +
typestringThe event name.
dataunknownPayload, shaped by the event type.
+
+ +
+

Notes

  • Frame types: core.log, session.created, session.expired, engine.shutdown, and subscription.closed when the engine ends it.
  • The subscribe command prints one further frame of its own once the stream is over, subscription.ended, carrying the close reason. It comes from the client, so --type does not filter it.
  • sessionId in event payloads is truncated to its first 10 characters.
  • A client more than 1 MiB behind is disconnected rather than buffered.
  • Served directly by the HTTP handler rather than through the router, because the response never ends.
+

Context

+

Calls on the shared EdgeContext: device state, username queries, and every way of logging in. None of them need a session, because a session is what they produce.

+
+

Device and usernames

+

Calls on the shared EdgeContext: local device state and login-server queries that do not need a session.

+
+
+
+

List local users on this device.

+
local-userssrc/cli/engine/routes/context.ts
+
+

corecontext.localUsers

+ +
+

Command line

+
local-users
+ + + +
+

REST

+

GET/local-users

+ + + + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/local-users'
+
+
+

Response 200

+

Everything context.localUsers reports, including which login methods each user has enabled on this device.

+
+
Response body
+
{
+  localUsers: unknown[]
+}
+
Example
{
+  "localUsers": [
+    {}
+  ]
+}
+ + + + +
localUsersunknown[]EdgeUserInfo[]: one entry per account cached on this device.
+
+ +
+ +
+
+

Forget an account on this device.

+
forget-accountsrc/cli/engine/routes/context.ts
+
+

corecontext.forgetAccount

+

Removes locally cached credentials. The remote account is untouched.

+
+
+

Command line

+
forget-account --root-login-id=<rootLoginId>
+ + + +
+

REST

+

POST/forget-account

+ + + +
+
Request body
+
{
+  rootLoginId: string
+}
+
Example
{
+  "rootLoginId": "FS8xJ2kQ…"
+}
+ + + + +
rootLoginIdstringCore takes a rootLoginId. A username is also accepted and resolved against localUsers first, so callers need not hash it.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"rootLoginId":"FS8xJ2kQ…"}' \
+  'http://localhost/forget-account'
+
+
+

Response 204

+

No body.

+
Errors

404USER_NOT_FOUND 400BAD_REQUEST

+
+ +
+
+

Check whether a username is free.

+
username-availablesrc/cli/engine/routes/context.ts
+
+

corecontext.usernameAvailable

+ +
+

Command line

+
username-available --username=<username> [--challenge-id=<challengeId>]
+ + + +
+

REST

+

GET/username-available

+ +
+
Query
+
{
+  username: string
+  challengeId?: string
+}
+
Example
{
+  "username": "string",
+  "challengeId": "FS8xJ2kQ…"
+}
+ + + + + + + + + +
usernamestringThe name to check.
challengeIdstring optionalSupply after solving a CAPTCHA to retry the same check.
+
+ + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/username-available?username=…'
+
+
+

Response 200

+
+
Response body
+
{
+  username: string
+  available: boolean
+}
+
Example
{
+  "username": "string",
+  "available": true
+}
+ + + + + + + + + +
usernamestringThe name that was checked, echoed back.
availablebooleanTrue when nobody holds this name. It is not reserved by asking.
+
+
Errors

400USERNAME_ERROR 403CHALLENGE_REQUIRED 503NETWORK_ERROR

+
+ +
+
+

Normalize a username.

+
fix-usernamesrc/cli/engine/routes/context.ts
+
+

corecontext.fixUsername

+

Applies the same rules the login server does, so a caller can show the user what their name will actually be before creating an account.

+
+
+

Command line

+
fix-username --username=<username>
+ + + +
+

REST

+

GET/fix-username

+ +
+
Query
+
{
+  username: string
+}
+
Example
{
+  "username": "string"
+}
+ + + + +
usernamestringThe name to normalize.
+
+ + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/fix-username?username=…'
+
+
+

Response 200

+
+
Response body
+
{
+  username: string
+}
+
Example
{
+  "username": "string"
+}
+ + + + +
usernamestringThe normalized value. The input is not echoed.
+
+ +
+ +
+
+

Score a candidate password.

+
check-password-rulessrc/cli/engine/routes/context.ts
+
+

corecontext.checkPasswordRules

+ +
+

Command line

+
check-password-rules --password=<password>
+ + + +
+

REST

+

GET/check-password-rules

+ +
+
Query
+
{
+  password: string
+}
+
Example
{
+  "password": "string"
+}
+ + + + +
passwordstringThe candidate password to score.
+
+ + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/check-password-rules?password=…'
+
+
+

Response 200

+

EdgePasswordRules from core: passed, tooShort, noNumber, noLowerCase, noUpperCase, secondsToCrack.

+
unknown
+ +
+

Notes

  • Send it with curl --get --data-urlencode rather than putting it in a shell-visible URL.
+
+
+

Fetch login-server messages for every local user.

+
fetch-login-messagessrc/cli/engine/routes/context.ts
+
+

corecontext.fetchLoginMessages

+ +
+

Command line

+
fetch-login-messages
+ + + +
+

REST

+

GET/fetch-login-messages

+ + + + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/fetch-login-messages'
+
+
+

Response 200

+

An EdgeLoginMessage[] from core: one entry per local login, each with its own loginId, otpResetPending, pendingVouchers, recovery2Corrupt and, where the stash has one, username.

+
unknown
+
Errors

503NETWORK_ERROR

+
+ +
+
+

Request a 2FA reset.

+
request-otp-resetsrc/cli/engine/routes/context.ts
+
+

corecontext.requestOtpReset

+

Starts the timed reset a user falls back on after losing their authenticator.

+
+
+

Command line

+
request-otp-reset --username=<username> --otp-reset-token=<otpResetToken>
+ + + +
+

REST

+

POST/request-otp-reset

+ + + +
+
Request body
+
{
+  username: string
+  otpResetToken: string
+}
+
Example
{
+  "username": "string",
+  "otpResetToken": "string"
+}
+ + + + + + + + + +
usernamestringWhose 2FA to reset.
otpResetTokenstringFrom details.resetToken on an OTP_REQUIRED error.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"username":"string","otpResetToken":"string"}' \
+  'http://localhost/request-otp-reset'
+
+
+

Response 200

+

When the reset completes if nobody cancels it.

+
+
Response body
+
{
+  resetDate: string
+}
+
Example
{
+  "resetDate": "2026-09-02T16:35:00.000Z"
+}
+ + + + +
resetDatestringWhen 2FA will actually come off. The login server enforces a waiting period so the real owner has time to cancel.
+
+
Errors

400USERNAME_ERROR 400BAD_REQUEST 503NETWORK_ERROR

+
+ +
+
+

Fetch a user’s recovery questions.

+
fetch-recovery-questionssrc/cli/engine/routes/context.ts
+
+

corecontext.fetchRecovery2Questions Our surface drops the 2 from the path, command and recoveryKey parameter; a future Recovery1 would be suffixed V1.

Differs from core:

  • recoveryKey — Core calls it recovery2Key. The 2 is dropped throughout.
+ +
+

Command line

+
fetch-recovery-questions --recovery-key=<recoveryKey> --username=<username>
+ + + +
+

REST

+

GET/fetch-recovery-questions

+ +
+
Query
+
{
+  recoveryKey: string
+  username: string
+}
+
Example
{
+  "recoveryKey": "string",
+  "username": "string"
+}
+ + + + + + + + + +
recoveryKeystringFrom change-recovery, stored by the user out of band.
usernamestringWhose questions to fetch.
+
+ + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/fetch-recovery-questions?recoveryKey=…&username=…'
+
+
+

Response 200

+
+
Response body
+
{
+  questions: string[]
+}
+
Example
{
+  "questions": [
+    "string"
+  ]
+}
+ + + + +
questionsstring[]The questions in the order login-with-recovery expects the answers.
+
+
Errors

400USERNAME_ERROR 503NETWORK_ERROR

+
+ +
+
+

Pre-fetch a CAPTCHA challenge.

+
fetch-challengesrc/cli/engine/routes/context.ts
+
+

corecontext.fetchChallenge

+

Lets a client solve a challenge before it hits 403 CHALLENGE_REQUIRED mid-flow.

+
+
+

Command line

+
fetch-challenge
+ + + +
+

REST

+

POST/fetch-challenge

+ + + + +
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  'http://localhost/fetch-challenge'
+
+
+

Response 200

+

challengeUri is absent when the server considers the challenge already satisfied.

+
+
Response body
+
{
+  challengeId: string
+  challengeUri?: string
+}
+
Example
{
+  "challengeId": "FS8xJ2kQ…",
+  "challengeUri": "string"
+}
+ + + + + + + + + +
challengeIdstringPass to the call that demanded a challenge once the user has solved it.
challengeUristring optionalWhere to send the user to solve the CAPTCHA. Absent when the server issued a challenge that needs no interaction.
+
+
Errors

503NETWORK_ERROR

+
+ +
+
+

List plugin ids usable for wallet creation.

+
currency-configssrc/cli/engine/routes/context.ts
+
+

coreEngine view of the enabled plugin set; core exposes account.currencyConfig per plugin instead.

+

Currency and accountbased plugins only — swap plugins are excluded.

+
+
+

Command line

+
currency-configs
+ + + +
+

REST

+

GET/currency-configs

+ + + + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/currency-configs'
+
+
+

Response 200

+
+
Response body
+
{
+  pluginIds: string[]
+}
+
Example
{
+  "pluginIds": [
+    "string"
+  ]
+}
+ + + + +
pluginIdsstring[]Currency plugins this engine loaded.
+
+ +
+ +

Login methods

+

Every successful login returns a Session and registers it in the engine, so later calls need only the sessionId. The CLI writes that id to session.json automatically.

+
+
+
+

Log in with a password.

+
login-with-passwordsrc/cli/engine/routes/login.ts
+
+

corecontext.loginWithPassword

+ +
+

Command line

+
login-with-password [--otp=<otp>] [--otp-key=<otpKey>] [--challenge-id=<challengeId>] --username=<username> --password=<password>
+ + + +
+

REST

+

POST/login-with-password

+ + + +
+
Request body
+
{
+  otp?: string
+  otpKey?: string
+  challengeId?: string
+  username: string
+  password: string
+}
+
Example
{
+  "otp": "string",
+  "otpKey": "string",
+  "challengeId": "FS8xJ2kQ…",
+  "username": "string",
+  "password": "string"
+}
+ + + + + + + + + + + + + + + + + + + + + + + + +
otpstring optionalA current 2FA code.
otpKeystring optionalThe 2FA secret itself, instead of a code.
challengeIdstring optionalSupply after solving a CAPTCHA to retry the same request.
usernamestringThe account name.
passwordstringThe account password.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"otp":"string","otpKey":"string","challengeId":"FS8xJ2kQ…","username":"string","password":"string"}' \
+  'http://localhost/login-with-password'
+
+
+

Response 200

+

A session with loginMethod: "password".

+
+
Response body
+
{
+  sessionId: string
+  username?: string
+  rootLoginId: string
+  loginMethod: "create" | "edge" | "key" | "password" | "pin" | "recovery"
+  autoLogoutSeconds: number
+  expiresAt: string | null
+  lastActivityAt: string
+  createdAt: string
+}
+
Example
{
+  "sessionId": "FS8xJ2kQ…",
+  "username": "string",
+  "rootLoginId": "FS8xJ2kQ…",
+  "loginMethod": "create",
+  "autoLogoutSeconds": 1,
+  "expiresAt": "2026-09-02T16:35:00.000Z",
+  "lastActivityAt": "2026-09-02T16:35:00.000Z",
+  "createdAt": "2026-09-02T16:35:00.000Z"
+}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
sessionIdstringIdentifies this login. Every account-scoped call carries it, and the CLI stores the most recent one so commands can omit it.
usernamestring optionalAbsent for a light account, which has no username.
rootLoginIdstringThe account root, stable across appIds. Two sessions sharing it are the same account.
loginMethod"create" | "edge" | "key" | "password" | "pin" | "recovery"How this session was established.
autoLogoutSecondsnumberIdle time before the engine logs the account out. 0 disables it.
expiresAtstring | nullWhen auto-logout will fire, or null when it is disabled.
lastActivityAtstringLast call on this session, which is what auto-logout measures from.
createdAtstringWhen the login completed.
+
+
Errors

401PASSWORD_ERROR 400USERNAME_ERROR 401OTP_REQUIRED 403CHALLENGE_REQUIRED 503NETWORK_ERROR

+
+

Notes

  • With --solve-captcha the client solves a CHALLENGE_REQUIRED response headlessly (ALTCHA proof-of-work) and retries once.
+
+
+

Log in with a device PIN.

+
login-with-pinsrc/cli/engine/routes/login.ts
+
+

corecontext.loginWithPIN

+

Only works on a device that has already saved a PIN for the account.

+
+
+

Command line

+
login-with-pin [--otp=<otp>] [--otp-key=<otpKey>] [--challenge-id=<challengeId>] --username-or-login-id=<usernameOrLoginId> --pin=<pin> [--use-login-id[=false]]
+ + + +
+

REST

+

POST/login-with-pin

+ + + +
+
Request body
+
{
+  otp?: string
+  otpKey?: string
+  challengeId?: string
+  usernameOrLoginId: string
+  pin: string
+  useLoginId?: boolean
+}
+
Example
{
+  "otp": "string",
+  "otpKey": "string",
+  "challengeId": "FS8xJ2kQ…",
+  "usernameOrLoginId": "FS8xJ2kQ…",
+  "pin": "string",
+  "useLoginId": true
+}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
otpstring optionalA current 2FA code.
otpKeystring optionalThe 2FA secret itself, instead of a code.
challengeIdstring optionalSupply after solving a CAPTCHA to retry the same request.
usernameOrLoginIdstringA username, or a login id.
pinstringThe device PIN.
useLoginIdboolean optionalTreat the value as a login id.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"otp":"string","otpKey":"string","challengeId":"FS8xJ2kQ…","usernameOrLoginId":"FS8xJ2kQ…","pin":"string","useLoginId":true}' \
+  'http://localhost/login-with-pin'
+
+
+

Response 200

+

A session with loginMethod: "pin".

+
+
Response body
+
{
+  sessionId: string
+  username?: string
+  rootLoginId: string
+  loginMethod: "create" | "edge" | "key" | "password" | "pin" | "recovery"
+  autoLogoutSeconds: number
+  expiresAt: string | null
+  lastActivityAt: string
+  createdAt: string
+}
+
Example
{
+  "sessionId": "FS8xJ2kQ…",
+  "username": "string",
+  "rootLoginId": "FS8xJ2kQ…",
+  "loginMethod": "create",
+  "autoLogoutSeconds": 1,
+  "expiresAt": "2026-09-02T16:35:00.000Z",
+  "lastActivityAt": "2026-09-02T16:35:00.000Z",
+  "createdAt": "2026-09-02T16:35:00.000Z"
+}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
sessionIdstringIdentifies this login. Every account-scoped call carries it, and the CLI stores the most recent one so commands can omit it.
usernamestring optionalAbsent for a light account, which has no username.
rootLoginIdstringThe account root, stable across appIds. Two sessions sharing it are the same account.
loginMethod"create" | "edge" | "key" | "password" | "pin" | "recovery"How this session was established.
autoLogoutSecondsnumberIdle time before the engine logs the account out. 0 disables it.
expiresAtstring | nullWhen auto-logout will fire, or null when it is disabled.
lastActivityAtstringLast call on this session, which is what auto-logout measures from.
createdAtstringWhen the login completed.
+
+
Errors

401PASSWORD_ERROR 403PIN_DISABLED 400USERNAME_ERROR 400BAD_REQUEST 503NETWORK_ERROR

+
+ +
+
+

Log in with an account login key.

+
login-with-keysrc/cli/engine/routes/login.ts
+
+

corecontext.loginWithKey

+

The key comes from get-login-key on an already-authenticated session.

+
+
+

Command line

+
login-with-key [--otp=<otp>] [--otp-key=<otpKey>] [--challenge-id=<challengeId>] --username-or-login-id=<usernameOrLoginId> --login-key=<loginKey> [--use-login-id[=false]]
+ + + +
+

REST

+

POST/login-with-key

+ + + +
+
Request body
+
{
+  otp?: string
+  otpKey?: string
+  challengeId?: string
+  usernameOrLoginId: string
+  loginKey: string
+  useLoginId?: boolean
+}
+
Example
{
+  "otp": "string",
+  "otpKey": "string",
+  "challengeId": "FS8xJ2kQ…",
+  "usernameOrLoginId": "FS8xJ2kQ…",
+  "loginKey": "string",
+  "useLoginId": true
+}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
otpstring optionalA current 2FA code.
otpKeystring optionalThe 2FA secret itself, instead of a code.
challengeIdstring optionalSupply after solving a CAPTCHA to retry the same request.
usernameOrLoginIdstringA username, or a login id.
loginKeystringFrom get-login-key.
useLoginIdboolean optionalTreat the value as a login id.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"otp":"string","otpKey":"string","challengeId":"FS8xJ2kQ…","usernameOrLoginId":"FS8xJ2kQ…","loginKey":"string","useLoginId":true}' \
+  'http://localhost/login-with-key'
+
+
+

Response 200

+

A session with loginMethod: "key".

+
+
Response body
+
{
+  sessionId: string
+  username?: string
+  rootLoginId: string
+  loginMethod: "create" | "edge" | "key" | "password" | "pin" | "recovery"
+  autoLogoutSeconds: number
+  expiresAt: string | null
+  lastActivityAt: string
+  createdAt: string
+}
+
Example
{
+  "sessionId": "FS8xJ2kQ…",
+  "username": "string",
+  "rootLoginId": "FS8xJ2kQ…",
+  "loginMethod": "create",
+  "autoLogoutSeconds": 1,
+  "expiresAt": "2026-09-02T16:35:00.000Z",
+  "lastActivityAt": "2026-09-02T16:35:00.000Z",
+  "createdAt": "2026-09-02T16:35:00.000Z"
+}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
sessionIdstringIdentifies this login. Every account-scoped call carries it, and the CLI stores the most recent one so commands can omit it.
usernamestring optionalAbsent for a light account, which has no username.
rootLoginIdstringThe account root, stable across appIds. Two sessions sharing it are the same account.
loginMethod"create" | "edge" | "key" | "password" | "pin" | "recovery"How this session was established.
autoLogoutSecondsnumberIdle time before the engine logs the account out. 0 disables it.
expiresAtstring | nullWhen auto-logout will fire, or null when it is disabled.
lastActivityAtstringLast call on this session, which is what auto-logout measures from.
createdAtstringWhen the login completed.
+
+
Errors

401PASSWORD_ERROR 400USERNAME_ERROR 503NETWORK_ERROR

+
+ +
+
+

Log in with recovery answers.

+
login-with-recoverysrc/cli/engine/routes/login.ts
+
+

corecontext.loginWithRecovery2 Our surface drops the 2 from core's recovery2 naming, and calls the key recoveryKey to match what change-recovery returns.

Differs from core:

  • recoveryKey — Core calls it recovery2Key. The 2 is dropped throughout.
+

Needs both the recovery key and the answers; neither works alone.

+
+
+

Command line

+
login-with-recovery [--otp=<otp>] [--otp-key=<otpKey>] [--challenge-id=<challengeId>] --recovery-key=<recoveryKey> --username=<username> --answer=<answers> …
+ + + +
+

REST

+

POST/login-with-recovery

+ + + +
+
Request body
+
{
+  otp?: string
+  otpKey?: string
+  challengeId?: string
+  recoveryKey: string
+  username: string
+  answers: string[]
+}
+
Example
{
+  "otp": "string",
+  "otpKey": "string",
+  "challengeId": "FS8xJ2kQ…",
+  "recoveryKey": "string",
+  "username": "string",
+  "answers": [
+    "string"
+  ]
+}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
otpstring optionalA current 2FA code.
otpKeystring optionalThe 2FA secret itself, instead of a code.
challengeIdstring optionalSupply after solving a CAPTCHA to retry the same request.
recoveryKeystringFrom change-recovery.
usernamestringThe account name.
answersstring[]In the same order as the questions.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"otp":"string","otpKey":"string","challengeId":"FS8xJ2kQ…","recoveryKey":"string","username":"string","answers":["string"]}' \
+  'http://localhost/login-with-recovery'
+
+
+

Response 200

+

A session with loginMethod: "recovery".

+
+
Response body
+
{
+  sessionId: string
+  username?: string
+  rootLoginId: string
+  loginMethod: "create" | "edge" | "key" | "password" | "pin" | "recovery"
+  autoLogoutSeconds: number
+  expiresAt: string | null
+  lastActivityAt: string
+  createdAt: string
+}
+
Example
{
+  "sessionId": "FS8xJ2kQ…",
+  "username": "string",
+  "rootLoginId": "FS8xJ2kQ…",
+  "loginMethod": "create",
+  "autoLogoutSeconds": 1,
+  "expiresAt": "2026-09-02T16:35:00.000Z",
+  "lastActivityAt": "2026-09-02T16:35:00.000Z",
+  "createdAt": "2026-09-02T16:35:00.000Z"
+}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
sessionIdstringIdentifies this login. Every account-scoped call carries it, and the CLI stores the most recent one so commands can omit it.
usernamestring optionalAbsent for a light account, which has no username.
rootLoginIdstringThe account root, stable across appIds. Two sessions sharing it are the same account.
loginMethod"create" | "edge" | "key" | "password" | "pin" | "recovery"How this session was established.
autoLogoutSecondsnumberIdle time before the engine logs the account out. 0 disables it.
expiresAtstring | nullWhen auto-logout will fire, or null when it is disabled.
lastActivityAtstringLast call on this session, which is what auto-logout measures from.
createdAtstringWhen the login completed.
+
+
Errors

401PASSWORD_ERROR 400USERNAME_ERROR 503NETWORK_ERROR

+
+ +
+
+

Create an account.

+
create-accountsrc/cli/engine/routes/login.ts
+
+

corecontext.createAccount

+

Every credential is optional over REST: omitting all three creates a light account with no username.

+
+
+

Command line

+
create-account [--otp=<otp>] [--otp-key=<otpKey>] [--challenge-id=<challengeId>] [--username=<username>] [--password=<password>] [--pin=<pin>]
+ + + +
+

REST

+

POST/create-account

+ + + +
+
Request body
+
{
+  otp?: string
+  otpKey?: string
+  challengeId?: string
+  username?: string
+  password?: string
+  pin?: string
+}
+
Example
{
+  "otp": "string",
+  "otpKey": "string",
+  "challengeId": "FS8xJ2kQ…",
+  "username": "string",
+  "password": "string",
+  "pin": "string"
+}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
otpstring optionalA current 2FA code.
otpKeystring optionalThe 2FA secret itself, instead of a code.
challengeIdstring optionalSupply after solving a CAPTCHA to retry the same request.
usernamestring optionalThe name to claim.
passwordstring optionalThe account password.
pinstring optionalA device PIN to save.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"otp":"string","otpKey":"string","challengeId":"FS8xJ2kQ…","username":"string","password":"string","pin":"string"}' \
+  'http://localhost/create-account'
+
+
+

Response 200

+

A session with loginMethod: "create".

+
+
Response body
+
{
+  sessionId: string
+  username?: string
+  rootLoginId: string
+  loginMethod: "create" | "edge" | "key" | "password" | "pin" | "recovery"
+  autoLogoutSeconds: number
+  expiresAt: string | null
+  lastActivityAt: string
+  createdAt: string
+}
+
Example
{
+  "sessionId": "FS8xJ2kQ…",
+  "username": "string",
+  "rootLoginId": "FS8xJ2kQ…",
+  "loginMethod": "create",
+  "autoLogoutSeconds": 1,
+  "expiresAt": "2026-09-02T16:35:00.000Z",
+  "lastActivityAt": "2026-09-02T16:35:00.000Z",
+  "createdAt": "2026-09-02T16:35:00.000Z"
+}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
sessionIdstringIdentifies this login. Every account-scoped call carries it, and the CLI stores the most recent one so commands can omit it.
usernamestring optionalAbsent for a light account, which has no username.
rootLoginIdstringThe account root, stable across appIds. Two sessions sharing it are the same account.
loginMethod"create" | "edge" | "key" | "password" | "pin" | "recovery"How this session was established.
autoLogoutSecondsnumberIdle time before the engine logs the account out. 0 disables it.
expiresAtstring | nullWhen auto-logout will fire, or null when it is disabled.
lastActivityAtstringLast call on this session, which is what auto-logout measures from.
createdAtstringWhen the login completed.
+
+
Errors

400USERNAME_ERROR 403CHALLENGE_REQUIRED 400BAD_REQUEST 503NETWORK_ERROR

+
+

Notes

  • The command requires a username, password and PIN. Creating a light account is REST-only.
+
+
+

Start a QR login.

+
request-edge-loginsrc/cli/engine/routes/login.ts
+
+

corecontext.requestEdgeLogin

+

Asks the login server for a lobby another logged-in Edge device can approve. The returned lobbyId is what goes in the QR code.

+
+
+

Command line

+
request-edge-login [--no-wait]
+ +
Client-only flags
--no-waitoptionalPrint the lobby and exit instead of polling, so the QR can be displayed while poll-edge-login watches the same handle from another process.
+

Prints the pending login, then polls every 2s for up to 5 minutes. On done it stores the session. With --no-wait it returns immediately and poll-edge-login takes over.

+
+
+

REST

+

POST/request-edge-login

+ + + + +
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  'http://localhost/request-edge-login'
+
+
+

Response 200

+
+
Response body
+
{
+  objectId: string
+  pendingId: string
+  kind: string
+  expiresAt: string | null
+  lobbyId: string
+  uri: string
+  state: string
+  username: string | null
+  session: {
+    sessionId: string;
+    username: string | undefined;
+    rootLoginId: string;
+    loginMethod: "password" | "pin" | "edge" | "key" | "create" | "recovery";
+    autoLogoutSeconds: number;
+    expiresAt: string | null;
+    lastActivityAt: string;
+    createdAt: string
+  } | null
+  error: string | null
+}
+
Example
{
+  "objectId": "FS8xJ2kQ…",
+  "pendingId": "FS8xJ2kQ…",
+  "kind": "string",
+  "expiresAt": "2026-09-02T16:35:00.000Z",
+  "lobbyId": "FS8xJ2kQ…",
+  "uri": "string",
+  "state": "string",
+  "username": "string",
+  "session": {},
+  "error": "string"
+}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
objectIdstringHandle for the value the engine is holding. Pass it to the calls that consume it.
pendingIdstringSame value as objectId, under the name the poll command takes.
kindstringWhat the handle refers to, which decides the calls that accept it.
expiresAtstring | nullWhen the lobby closes and the QR code stops working.
lobbyIdstringLobby the phone connects to.
uristringThe edge:// URI to render as a QR code for the phone to scan.
statestringHow far the login has got: pending before the phone scans, started once it has, and done when session is filled in.
usernamestring | nullAccount that approved the login, known once the phone has scanned.
session{ sessionId: string; username: string | undefined; rootLoginId: string; loginMethod: "password" | "pin" | "edge" | "key" | "create" | "recovery"; autoLogoutSeconds: number; expiresAt: string | null; lastActivityAt: string; createdAt: string; } | nullThe session, null until state is done.
errorstring | nullWhy the login failed, set only when state is error.
+
+
Errors

503NETWORK_ERROR

+
+

Notes

  • The pending login is an object handle with a 5 minute TTL. On expiry the engine cancels the request on the login server for you.
+
+
+

Poll a pending QR login.

+
poll-edge-loginsrc/cli/engine/routes/login.ts
+
+

coreEngine state for an in-flight requestEdgeLogin; core exposes it as EdgePendingEdgeLogin properties.

+

Once state reaches done the engine has already created the session, so the response carries one ready to use.

+
+
+

Command line

+
poll-edge-login <pendingId>
+ + + +
+

REST

+

GET/pending-edge-login/{pendingId}

+
Path
pendingIdstringThe pendingId returned when the QR login was requested.
+ + + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/pending-edge-login/$PENDINGID'
+
+
+

Response 200

+
+
Response body
+
{
+  objectId: string
+  pendingId: string
+  kind: string
+  expiresAt: string | null
+  lobbyId: string
+  uri: string
+  state: string
+  username: string | null
+  session: {
+    sessionId: string;
+    username: string | undefined;
+    rootLoginId: string;
+    loginMethod: "password" | "pin" | "edge" | "key" | "create" | "recovery";
+    autoLogoutSeconds: number;
+    expiresAt: string | null;
+    lastActivityAt: string;
+    createdAt: string
+  } | null
+  error: string | null
+}
+
Example
{
+  "objectId": "FS8xJ2kQ…",
+  "pendingId": "FS8xJ2kQ…",
+  "kind": "string",
+  "expiresAt": "2026-09-02T16:35:00.000Z",
+  "lobbyId": "FS8xJ2kQ…",
+  "uri": "string",
+  "state": "string",
+  "username": "string",
+  "session": {},
+  "error": "string"
+}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
objectIdstringHandle for the value the engine is holding. Pass it to the calls that consume it.
pendingIdstringSame value as objectId, under the name the poll command takes.
kindstringWhat the handle refers to, which decides the calls that accept it.
expiresAtstring | nullWhen the lobby closes and the QR code stops working.
lobbyIdstringLobby the phone connects to.
uristringThe edge:// URI to render as a QR code for the phone to scan.
statestringHow far the login has got: pending before the phone scans, started once it has, and done when session is filled in.
usernamestring | nullAccount that approved the login, known once the phone has scanned.
session{ sessionId: string; username: string | undefined; rootLoginId: string; loginMethod: "password" | "pin" | "edge" | "key" | "create" | "recovery"; autoLogoutSeconds: number; expiresAt: string | null; lastActivityAt: string; createdAt: string; } | nullThe session, null until state is done.
errorstring | nullWhy the login failed, set only when state is error.
+
+
Errors

404PENDING_LOGIN_NOT_FOUND 410OBJECT_EXPIRED 400OBJECT_KIND_MISMATCH

+
+

Notes

  • Session creation is attempted once. A failure is sticky, so later polls report the same error rather than retrying.
  • Polling does not extend the handle TTL; only the original 5 minute window applies.
+
+
+

Cancel a pending QR login.

+
cancel-requestsrc/cli/engine/routes/login.ts
+
+

coreEdgePendingEdgeLogin.cancelRequest

+ +
+

Command line

+
cancel-request <pendingId>
+ + + +
+

REST

+

POST/pending-edge-login/cancel-request/{pendingId}

+
Path
pendingIdstringThe pendingId returned when the QR login was requested.
+ + + +
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  'http://localhost/pending-edge-login/cancel-request/$PENDINGID'
+
+
+

Response 204

+

No body.

+
Errors

404PENDING_LOGIN_NOT_FOUND

+
+

Notes

  • If the login already completed and a session exists, that session is force-logged-out too, so cancelling cannot leave an orphan visible in engine-sessions.
+
+
+

List active sessions.

+
engine-sessionssrc/cli/engine/routes/login.ts
+
+

coreThe session registry is an engine construct; core has no multi-account session concept.

+ +
+

Command line

+
engine-sessions
+ + + +
+

REST

+

GET/engine/sessions

+ + + + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/engine/sessions'
+
+
+

Response 200

+

A bare array, not wrapped in a key. Each sessionId is truncated: this route needs no session, so a usable id here would be a credential anyone who can reach the engine could collect.

+
{
+  sessionId: string;
+  username: string | undefined;
+  rootLoginId: string;
+  loginMethod: "password" | "pin" | "edge" | "key" | "create" | "recovery";
+  autoLogoutSeconds: number;
+  expiresAt: string | null;
+  lastActivityAt: string;
+  createdAt: string
+}[]
+ +
+ +

Account

+

Calls on a logged-in EdgeAccount, addressed by sessionId. All of these can also return 401 INVALID_SESSION or 401 SESSION_EXPIRED.

+
+

Session

+

Calls on a logged-in EdgeAccount, addressed by sessionId. All of these can also return 401 INVALID_SESSION or 401 SESSION_EXPIRED.

+
+
+
+

Account and session summary.

+
account-infosrc/cli/engine/routes/account.ts
+
+

coreEngine composite of the session record plus EdgeAccount properties.

+

Session fields are spread at the top level alongside the account's own properties — there is no nested session object.

+
+
+

Command line

+
account-info
+ + + +
+

REST

+

GET/account/{sessionId}

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/account/$SESS'
+
+
+

Response 200

+
+
Response body
+
{
+  appId: string
+  created: string | null
+  lastLogin: string
+  loggedIn: boolean
+  recoveryKey: string | null
+  otpEnabled: boolean
+  otpResetPending: boolean
+  canDuressLogin: boolean
+  isDuressAccount: boolean
+  edgeLogin: boolean
+  keyLogin: boolean
+  newAccount: boolean
+  passwordLogin: boolean
+  pinLogin: boolean
+  recoveryLogin: boolean
+}
+
Example
{
+  "appId": "FS8xJ2kQ…",
+  "created": "string",
+  "lastLogin": "string",
+  "loggedIn": true,
+  "recoveryKey": "string",
+  "otpEnabled": true,
+  "otpResetPending": true,
+  "canDuressLogin": true,
+  "isDuressAccount": true,
+  "edgeLogin": true,
+  "keyLogin": true,
+  "newAccount": true,
+  "passwordLogin": true,
+  "pinLogin": true,
+  "recoveryLogin": true
+}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
appIdstringApplication this session logged into.
createdstring | nullWhen the account was created, null for accounts predating the field.
lastLoginstringThe previous login, not this one.
loggedInbooleanFalse once the account has been logged out; the session object outlives it briefly.
recoveryKeystring | nullPresent only while recovery is configured.
otpEnabledboolean2FA is on for this account.
otpResetPendingbooleanTrue while somebody has a reset pending against this account.
canDuressLoginbooleanA duress PIN is configured, so this account can be opened in duress mode.
isDuressAccountbooleanTrue when this very session is the duress account rather than the real one.
edgeLoginbooleanThis account was reached by QR login.
keyLoginbooleanThis session was reached with a login key.
newAccountbooleanThis session created the account rather than logging into an existing one.
passwordLoginbooleanThis session was reached with a password.
pinLoginbooleanThis session was reached with a PIN.
recoveryLoginbooleanThis session was reached by answering recovery questions.
+
+
Errors

401INVALID_SESSION 401SESSION_EXPIRED

+
+

Notes

  • The otpEnabled and otpResetPending flags here are derived. For the secret itself use otp-key.
+
+
+

Log out.

+
logoutsrc/cli/engine/routes/account.ts
+
+

coreaccount.logout

+

Ends the session and drops it from the engine. Any subscription scoped to this account or its wallets is closed with it.

+
+
+

Command line

+
logout
+ + +

Also clears the stored id from session.json.

+
+
+

REST

+

POST/account/{sessionId}/logout

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + + +
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  'http://localhost/account/$SESS/logout'
+
+
+

Response 204

+

No body.

+
Errors

401INVALID_SESSION 401SESSION_EXPIRED

+
+ +
+
+

Keepalive.

+
touchsrc/cli/engine/routes/account.ts
+
+

coreEngine auto-logout timer; core has no idle concept.

+

Resets the idle auto-logout timer without doing any other work.

+
+
+

Command line

+
touch
+ + + +
+

REST

+

POST/account/{sessionId}/touch

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + + +
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  'http://localhost/account/$SESS/touch'
+
+
+

Response 200

+

The session, with a refreshed expiresAt.

+
+
Response body
+
{
+  sessionId: string
+  username?: string
+  rootLoginId: string
+  loginMethod: "create" | "edge" | "key" | "password" | "pin" | "recovery"
+  autoLogoutSeconds: number
+  expiresAt: string | null
+  lastActivityAt: string
+  createdAt: string
+}
+
Example
{
+  "sessionId": "FS8xJ2kQ…",
+  "username": "string",
+  "rootLoginId": "FS8xJ2kQ…",
+  "loginMethod": "create",
+  "autoLogoutSeconds": 1,
+  "expiresAt": "2026-09-02T16:35:00.000Z",
+  "lastActivityAt": "2026-09-02T16:35:00.000Z",
+  "createdAt": "2026-09-02T16:35:00.000Z"
+}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
sessionIdstringIdentifies this login. Every account-scoped call carries it, and the CLI stores the most recent one so commands can omit it.
usernamestring optionalAbsent for a light account, which has no username.
rootLoginIdstringThe account root, stable across appIds. Two sessions sharing it are the same account.
loginMethod"create" | "edge" | "key" | "password" | "pin" | "recovery"How this session was established.
autoLogoutSecondsnumberIdle time before the engine logs the account out. 0 disables it.
expiresAtstring | nullWhen auto-logout will fire, or null when it is disabled.
lastActivityAtstringLast call on this session, which is what auto-logout measures from.
createdAtstringWhen the login completed.
+
+
Errors

401INVALID_SESSION 401SESSION_EXPIRED

+
+ +
+
+

Read the account login key.

+
get-login-keysrc/cli/engine/routes/account.ts
+
+

coreaccount.getLoginKey

+

The key login-with-key takes. It grants full account access, so treat the output as secret.

+
+
+

Command line

+
get-login-key
+ + + +
+

REST

+

GET/account/{sessionId}/get-login-key

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/account/$SESS/get-login-key'
+
+
+

Response 200

+
+
Response body
+
{
+  loginKey: string
+}
+
Example
{
+  "loginKey": "string"
+}
+ + + + +
loginKeystringbase58. Full account access — keep it safe.
+
+
Errors

401INVALID_SESSION 401SESSION_EXPIRED

+
+ +
+
+

Force an account data sync.

+
syncsrc/cli/engine/routes/account.ts
+
+

coreaccount.sync

+

Pushes and pulls the account repos immediately rather than waiting for the next scheduled sync.

+
+
+

Command line

+
sync
+ + +

Named sync for the account; the wallet one is wallet-sync.

+
+
+

REST

+

POST/account/{sessionId}/sync

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + + +
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  'http://localhost/account/$SESS/sync'
+
+
+

Response 204

+

No body.

+
Errors

503NETWORK_ERROR 401INVALID_SESSION 401SESSION_EXPIRED

+
+ +
+
+

Permanently delete the remote account.

+
delete-remote-accountsrc/cli/engine/routes/account.ts
+
+

coreaccount.deleteRemoteAccount

+

Irreversible. The account is removed from the login server, and funds in its wallets are unrecoverable without the keys. The session is logged out afterwards.

+
+
+

Command line

+
delete-remote-account --yes
+ +
Client-only flags
--yesrequiredConfirms intent. Without it the command refuses to run.
+ +
+

REST

+

POST/account/{sessionId}/delete-remote-account

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + + +
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  'http://localhost/account/$SESS/delete-remote-account'
+
+
+

Response 204

+

No body.

+
Errors

503NETWORK_ERROR 401INVALID_SESSION 401SESSION_EXPIRED

+
+

Notes

  • The engine performs no confirmation check — the call runs as soon as it arrives, so any guard has to live in the caller. The command requires --yes for exactly this reason.
+
+
+

Wait for every wallet to finish loading.

+
wait-for-all-walletssrc/cli/engine/routes/account.ts
+
+

coreaccount.waitForAllWallets

+

Wallets load in the background after login, so a list taken straight afterwards can be short. This resolves once each active wallet has either loaded or failed — balances may still be syncing afterwards.

+
+
+

Command line

+
wait-for-all-wallets
+ + + +
+

REST

+

POST/account/{sessionId}/wait-for-all-wallets

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + + +
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  'http://localhost/account/$SESS/wait-for-all-wallets'
+
+
+

Response 204

+

No body.

+
Errors

401INVALID_SESSION 401SESSION_EXPIRED

+
+

Notes

  • There is no timeout: a wallet that never resolves holds this open. The engine's own idle shutdown does not fire while a request is in flight, so give the client one.
  • Nothing is returned. Call currency-wallets afterwards to see the result, including any wallet that failed to load.
+
+
+

List the account's loaded wallets.

+
currency-walletssrc/cli/engine/routes/account.ts
+
+

coreaccount.currencyWallets account.currencyWallets is keyed by activeWalletIds.

+

Every wallet core has built an API for, which is the account's active set: activeWalletIds is ids.filter(id => !archived), so an archived or deleted wallet is not here. Paused wallets are.

+

This took a filter of active, archived, hidden or all, and three of those four could not work. The handler resolved each id through account.currencyWallets, which core builds from activeWalletIds alone, so the archived and hidden lists were disjoint from the resolvable set by construction: every id mapped to undefined, the filter(wallet != null) dropped it, and ?filter=archived answered {"currencyWallets":[]} on every account, always. all concatenated the three lists, so it was active with each hidden wallet — which is active and hidden at once, core deriving the two flags independently — listed twice. Nor could the missing wallets be served in this shape: an archived wallet has no loaded API, so name, currencyCode, blockHeight, syncStatus and paused do not exist for it, and seven required response fields would have had to become nullable for every caller to carry three values that never worked.

+

all-keys is the route for that, and already was: it returns EdgeWalletInfoFull[] — id, type, archived, deleted, hidden, sortIndex — which is everything knowable about a wallet core has not loaded.

+
+
+

Command line

+
currency-wallets
+ + + +
+

REST

+

GET/account/{sessionId}/currency-wallets

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/account/$SESS/currency-wallets'
+
+
+

Response 200

+
+
Response body
+
{
+  currencyWallets: {
+    walletId: string;
+    id: string;
+    type: string;
+    name: string | null;
+    pluginId: string;
+    currencyCode: string;
+    fiatCurrencyCode: string;
+    blockHeight: number;
+    syncStatus: unknown;
+    syncRatio: string | undefined;
+    paused: boolean;
+    imported: boolean | undefined;
+    created: string | null;
+    enabledTokenIds: string[];
+    detectedTokenIds: string[];
+    unactivatedTokenIds: string[]
+  }[]
+}
+
Example
{
+  "currencyWallets": [
+    {}
+  ]
+}
+ + + + +
currencyWallets{ walletId: string; id: string; type: string; name: string | null; pluginId: string; currencyCode: string; fiatCurrencyCode: string; blockHeight: number; syncStatus: unknown; syncRatio: string | undefined; paused: boolean; imported: boolean | undefined; created: string | null; enabledTokenIds: string[]; detectedTokenIds: string[]; unactivatedTokenIds: string[]; }[]Every wallet core has loaded for this account, including paused ones. Archived, deleted and hidden wallets are not loaded; all-keys lists those.
+
+
Errors

401INVALID_SESSION 401SESSION_EXPIRED

+
+

Notes

  • Wallets load in the background after login, so a list taken straight afterwards can be short. Call wait-for-all-wallets first to be sure the account has finished loading.
  • Archived, deleted and hidden wallets are not here, because core builds no API for them. Use all-keys, which carries their ids and their flags.
+
+
+

Create a currency wallet.

+
create-currency-walletsrc/cli/engine/routes/account.ts
+
+

coreaccount.createCurrencyWallet

+ +
+

Command line

+
create-currency-wallet --wallet-type=<walletType> [--name=<name>] [--import-text=<importText>]
+ + + +
+

REST

+

POST/account/{sessionId}/create-currency-wallet

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + +
+
Request body
+
{
+  walletType: string
+  name?: string
+  importText?: string
+}
+
Example
{
+  "walletType": "string",
+  "name": "string",
+  "importText": "string"
+}
+ + + + + + + + + + + + + + +
walletTypestringFrom currency-configs, e.g. wallet:bitcoin.
namestring optionalDisplay name.
importTextstring optionalSeed or key text to import instead of generating.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"walletType":"string","name":"string","importText":"string"}' \
+  'http://localhost/account/$SESS/create-currency-wallet'
+
+
+

Response 200

+
+
Response body
+
{
+  walletId: string
+  id: string
+  type: string
+  name: string | null
+  pluginId: string
+  currencyCode: string
+  fiatCurrencyCode: string
+  blockHeight: number
+  syncStatus: unknown
+  syncRatio?: string
+  paused: boolean
+  imported?: boolean
+  created: string | null
+  enabledTokenIds: string[]
+  detectedTokenIds: string[]
+  unactivatedTokenIds: string[]
+}
+
Example
{
+  "walletId": "FS8xJ2kQ…",
+  "id": "FS8xJ2kQ…",
+  "type": "string",
+  "name": "string",
+  "pluginId": "FS8xJ2kQ…",
+  "currencyCode": "string",
+  "fiatCurrencyCode": "string",
+  "blockHeight": 1,
+  "syncStatus": {},
+  "syncRatio": "string",
+  "paused": true,
+  "imported": true,
+  "created": "string",
+  "enabledTokenIds": [
+    "string"
+  ],
+  "detectedTokenIds": [
+    "string"
+  ],
+  "unactivatedTokenIds": [
+    "string"
+  ]
+}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
walletIdstringThe full wallet id. Commands taking a wallet accept any unique prefix.
idstringSame value as walletId, under the name edge-core-js uses. Core’s EdgeCurrencyWallet has only id; walletId is the name every route’s parameter takes, so both are published and a caller can use whichever half of the API it is reading.
typestringKey type, such as wallet:bitcoin.
namestring | nullUser-assigned name, null until one is set.
pluginIdstringCurrency plugin backing this wallet.
currencyCodestringTicker for the native asset.
fiatCurrencyCodestringFiat the wallet reports value in, as iso:USD.
blockHeightnumberChain height this wallet has seen.
syncStatusunknownEdgeWalletSyncStatus from core.
syncRatiostring optionalSync progress as a percentage, for display.
pausedbooleanTrue while the engine is not syncing this wallet.
importedboolean optionalTrue when the keys came from an import rather than being generated here.
createdstring | nullWhen the wallet was created, null for wallets predating the field.
enabledTokenIdsstring[]Tokens the user turned on.
detectedTokenIdsstring[]Tokens found on-chain that are not enabled yet.
unactivatedTokenIdsstring[]Enabled tokens still awaiting on-chain activation.
+
+
Errors

400BAD_REQUEST 401INVALID_SESSION 401SESSION_EXPIRED

+
+

Notes

  • The fiat currency is not set here. Core still accepts it on create, but that path is deprecated — use set-fiat-currency-code afterwards, so there is one way to do it.
+
+
+

Create several wallets at once.

+
create-currency-walletssrc/cli/engine/routes/account.ts
+
+

coreaccount.createCurrencyWallets

+

Partial success is normal: each entry reports its own outcome, and one failure does not roll back the others.

+
+
+

Command line

+
create-currency-wallets --create-wallets='<json>'
+ + + +
+

REST

+

POST/account/{sessionId}/create-currency-wallets

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + +
+
Request body
+
{
+  createWallets: {
+    walletType: string;
+    name: string | undefined;
+    fiatCurrencyCode: string | undefined
+  }[]
+}
+
Example
{
+  "createWallets": [
+    {}
+  ]
+}
+ + + + +
createWallets{ walletType: string; name: string | undefined; fiatCurrencyCode: string | undefined; }[]EdgeCreateCurrencyWallet[]: walletType, plus optional name and fiatCurrencyCode.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"createWallets":[{}]}' \
+  'http://localhost/account/$SESS/create-currency-wallets'
+
+
+

Response 200

+
+
Response body
+
{
+  results: unknown[]
+}
+
Example
{
+  "results": [
+    {}
+  ]
+}
+ + + + +
resultsunknown[]Mirrors core’s EdgeResult[]: { ok, wallet } or { ok: false, error }.
+
+
Errors

400BAD_REQUEST 401INVALID_SESSION 401SESSION_EXPIRED

+
+ +

Credentials

+

Password, PIN, username and recovery changes on a logged-in account.

+
+
+
+

Set or change the password.

+
change-passwordsrc/cli/engine/routes/credentials.ts
+
+

coreaccount.changePassword

+

The login server enforces its own rules; check-password-rules scores a candidate first.

+
+
+

Command line

+
change-password --password=<password>
+ + + +
+

REST

+

POST/account/{sessionId}/change-password

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + +
+
Request body
+
{
+  password: string
+}
+
Example
{
+  "password": "string"
+}
+ + + + +
passwordstringThe new password.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"password":"string"}' \
+  'http://localhost/account/$SESS/change-password'
+
+ + +
+
+

Remove password login.

+
delete-passwordsrc/cli/engine/routes/credentials.ts
+
+

coreaccount.deletePassword

+

The account keeps its other login methods; only the password stops working.

+
+
+

Command line

+
delete-password
+ + + +
+

REST

+

POST/account/{sessionId}/delete-password

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + + +
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  'http://localhost/account/$SESS/delete-password'
+
+ + +
+
+

Verify a password.

+
check-passwordsrc/cli/engine/routes/credentials.ts
+
+

coreaccount.checkPassword

+

Checks without changing anything, which is how a caller gates a destructive action behind a re-entry prompt.

+
+
+

Command line

+
check-password --password=<password>
+ + + +
+

REST

+

POST/account/{sessionId}/check-password

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + +
+
Request body
+
{
+  password: string
+}
+
Example
{
+  "password": "string"
+}
+ + + + +
passwordstringThe account password.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"password":"string"}' \
+  'http://localhost/account/$SESS/check-password'
+
+
+

Response 200

+
+
Response body
+
{
+  ok: boolean
+}
+
Example
{
+  "ok": true
+}
+ + + + +
okbooleanFalse for a wrong password — not an error response.
+
+
Errors

401INVALID_SESSION 401SESSION_EXPIRED

+
+ +
+
+

Read the account PIN.

+
get-pinsrc/cli/engine/routes/credentials.ts
+
+

coreaccount.getPin

+

Returns the PIN itself, not a status flag, so treat the output as secret.

+
+
+

Command line

+
get-pin
+ + + +
+

REST

+

GET/account/{sessionId}/get-pin

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/account/$SESS/get-pin'
+
+
+

Response 200

+
+
Response body
+
{
+  pin: string | null
+}
+
Example
{
+  "pin": "string"
+}
+ + + + +
pinstring | nullNull when no PIN is set.
+
+
Errors

401INVALID_SESSION 401SESSION_EXPIRED

+
+ +
+
+

Set or change the PIN.

+
change-pinsrc/cli/engine/routes/credentials.ts
+
+

coreaccount.changePin

+ +
+

Command line

+
change-pin --pin=<pin> [--enable-login[=false]] [--for-duress-account[=false]]
+ + + +
+

REST

+

POST/account/{sessionId}/change-pin

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + +
+
Request body
+
{
+  pin: string
+  enableLogin?: boolean
+  forDuressAccount?: boolean
+}
+
Example
{
+  "pin": "string",
+  "enableLogin": true,
+  "forDuressAccount": true
+}
+ + + + + + + + + + + + + + +
pinstringThe new PIN.
enableLoginboolean optionalAllow logging in with this PIN on this device.
forDuressAccountboolean optionalAct on the duress account rather than the real one.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"pin":"string","enableLogin":true,"forDuressAccount":true}' \
+  'http://localhost/account/$SESS/change-pin'
+
+
+

Response 200

+
+
Response body
+
{
+  pin2Key: string
+}
+
Example
{
+  "pin2Key": "string"
+}
+ + + + +
pin2KeystringThe new PIN login key core returns.
+
+
Errors

400BAD_REQUEST 401INVALID_SESSION 401SESSION_EXPIRED

+
+ +
+
+

Remove the PIN.

+
delete-pinsrc/cli/engine/routes/credentials.ts
+
+

coreaccount.deletePin

+

PIN login stops working on this device; other methods are untouched.

+
+
+

Command line

+
delete-pin
+ + + +
+

REST

+

POST/account/{sessionId}/delete-pin

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + + +
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  'http://localhost/account/$SESS/delete-pin'
+
+
+

Response 204

+

No body.

+
Errors

401INVALID_SESSION 401SESSION_EXPIRED

+
+ +
+
+

Verify a PIN.

+
check-pinsrc/cli/engine/routes/credentials.ts
+
+

coreaccount.checkPin

+ +
+

Command line

+
check-pin --pin=<pin> [--for-duress-account[=false]]
+ + + +
+

REST

+

POST/account/{sessionId}/check-pin

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + +
+
Request body
+
{
+  pin: string
+  forDuressAccount?: boolean
+}
+
Example
{
+  "pin": "string",
+  "forDuressAccount": true
+}
+ + + + + + + + + +
pinstringThe device PIN, usually four digits.
forDuressAccountboolean optionalAct on the duress account rather than the real one.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"pin":"string","forDuressAccount":true}' \
+  'http://localhost/account/$SESS/check-pin'
+
+
+

Response 200

+
+
Response body
+
{
+  ok: boolean
+}
+
Example
{
+  "ok": true
+}
+ + + + +
okbooleanFalse for a wrong PIN — not an error response.
+
+
Errors

401INVALID_SESSION 401SESSION_EXPIRED

+
+ +
+
+

Change the username.

+
change-usernamesrc/cli/engine/routes/credentials.ts
+
+

coreaccount.changeUsername

+

The old name is released, so it becomes available to anyone else.

+
+
+

Command line

+
change-username --username=<username> [--password=<password>]
+ + + +
+

REST

+

POST/account/{sessionId}/change-username

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + +
+
Request body
+
{
+  username: string
+  password?: string
+}
+
Example
{
+  "username": "string",
+  "password": "string"
+}
+ + + + + + + + + +
usernamestringThe new username.
passwordstring optionalRequired by core when the account has a password.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"username":"string","password":"string"}' \
+  'http://localhost/account/$SESS/change-username'
+
+ + +
+
+

Set recovery questions and answers.

+
change-recoverysrc/cli/engine/routes/credentials.ts
+
+

coreaccount.changeRecovery Our surface drops the 2 from core's recovery2 naming; a future Recovery1 would be suffixed V1.

+

The returned key is half of the credential: without it the answers alone cannot recover the account, so it has to be stored somewhere else.

+
+
+

Command line

+
change-recovery --question=<questions> … --answer=<answers> …
+ + + +
+

REST

+

POST/account/{sessionId}/change-recovery

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + +
+
Request body
+
{
+  questions: string[]
+  answers: string[]
+}
+
Example
{
+  "questions": [
+    "string"
+  ],
+  "answers": [
+    "string"
+  ]
+}
+ + + + + + + + + +
questionsstring[]The questions to ask.
answersstring[]Same length and order as questions.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"questions":["string"],"answers":["string"]}' \
+  'http://localhost/account/$SESS/change-recovery'
+
+
+

Response 200

+
+
Response body
+
{
+  recoveryKey: string
+}
+
Example
{
+  "recoveryKey": "string"
+}
+ + + + +
recoveryKeystringStore this out of band. login-with-recovery needs it alongside the answers.
+
+
Errors

400BAD_REQUEST 401INVALID_SESSION 401SESSION_EXPIRED

+
+ +
+
+

Disable recovery login.

+
delete-recoverysrc/cli/engine/routes/credentials.ts
+
+

coreaccount.deleteRecovery

+

The existing recovery key stops working.

+
+
+

Command line

+
delete-recovery
+ + + +
+

REST

+

POST/account/{sessionId}/delete-recovery

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + + +
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  'http://localhost/account/$SESS/delete-recovery'
+
+
+

Response 204

+

No body.

+
Errors

401INVALID_SESSION 401SESSION_EXPIRED

+
+ +

Two-factor authentication

+

OTP state and the reset flow a user falls back on after losing their authenticator.

+
+
+
+

Read the 2FA secret and reset state.

+
otp-keysrc/cli/engine/routes/otp.ts
+
+

coreaccount.otpKey Also carries account.otpResetDate.

+ +
+

Command line

+
otp-key
+ + + +
+

REST

+

GET/account/{sessionId}/otp-key

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/account/$SESS/otp-key'
+
+
+

Response 200

+
+
Response body
+
{
+  otpKey: string | null
+  otpResetDate: string | null
+}
+
Example
{
+  "otpKey": "string",
+  "otpResetDate": "2026-09-02T16:35:00.000Z"
+}
+ + + + + + + + + +
otpKeystring | nullNull when 2FA is off. The 2FA secret itself. Secret material — record it safely.
otpResetDatestring | nullSet once somebody has requested a reset; cancel it with cancel-otp-reset.
+
+
Errors

401INVALID_SESSION 401SESSION_EXPIRED

+
+ +
+
+

Enable 2FA.

+
enable-otpsrc/cli/engine/routes/otp.ts
+
+

coreaccount.enableOtp

+

Record the returned key before leaving the terminal: it is the only copy.

+
+
+

Command line

+
enable-otp [--timeout=<timeout>]
+ + + +
+

REST

+

POST/account/{sessionId}/enable-otp

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + +
+
Request body
+
{
+  timeout?: number
+}
+
Example
{
+  "timeout": 0
+}
+ + + + +
timeoutnumber optionalHow long a reset request must wait before it completes, in seconds. Core supplies the default when omitted.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"timeout":0}' \
+  'http://localhost/account/$SESS/enable-otp'
+
+
+

Response 200

+
+
Response body
+
{
+  otpKey: string | null
+}
+
Example
{
+  "otpKey": "string"
+}
+ + + + +
otpKeystring | nullThe new secret. The 2FA secret itself. Secret material — record it safely.
+
+
Errors

401INVALID_SESSION 401SESSION_EXPIRED

+
+ +
+
+

Disable 2FA.

+
disable-otpsrc/cli/engine/routes/otp.ts
+
+

coreaccount.disableOtp

+

Logins stop requiring a code immediately.

+
+
+

Command line

+
disable-otp
+ + + +
+

REST

+

POST/account/{sessionId}/disable-otp

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + + +
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  'http://localhost/account/$SESS/disable-otp'
+
+
+

Response 204

+

No body.

+
Errors

401INVALID_SESSION 401SESSION_EXPIRED

+
+ +
+
+

Cancel a pending 2FA reset.

+
cancel-otp-resetsrc/cli/engine/routes/otp.ts
+
+

coreaccount.cancelOtpReset

+

The defence against somebody else requesting a reset on your account: as long as you cancel before the timer runs out, their reset never lands.

+
+
+

Command line

+
cancel-otp-reset
+ + + +
+

REST

+

POST/account/{sessionId}/cancel-otp-reset

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + + +
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  'http://localhost/account/$SESS/cancel-otp-reset'
+
+
+

Response 204

+

No body.

+
Errors

401INVALID_SESSION 401SESSION_EXPIRED

+
+ +
+
+

Re-point the account at a known 2FA secret.

+
repair-otpsrc/cli/engine/routes/otp.ts
+
+

coreaccount.repairOtp

+

For a device whose stored secret has drifted from the server's.

+
+
+

Command line

+
repair-otp --otp-key=<otpKey>
+ + + +
+

REST

+

POST/account/{sessionId}/repair-otp

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + +
+
Request body
+
{
+  otpKey: string
+}
+
Example
{
+  "otpKey": "string"
+}
+ + + + +
otpKeystringThe secret the account should use.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"otpKey":"string"}' \
+  'http://localhost/account/$SESS/repair-otp'
+
+ + +

Vouchers

+

When 2FA blocks a login, the login server issues a voucher an already-trusted device can approve or reject.

+
+
+
+

List pending 2FA vouchers.

+
pending-voucherssrc/cli/engine/routes/vouchers.ts
+
+

coreaccount.pendingVouchers

+

When 2FA blocks a login, the login server issues a voucher that an already-trusted device can approve or reject.

+
+
+

Command line

+
pending-vouchers
+ + + +
+

REST

+

GET/account/{sessionId}/pending-vouchers

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/account/$SESS/pending-vouchers'
+
+
+

Response 200

+
+
Response body
+
{
+  pendingVouchers: unknown[]
+}
+
Example
{
+  "pendingVouchers": [
+    {}
+  ]
+}
+ + + + +
pendingVouchersunknown[]EdgePendingVoucher[]: voucherId, activates, created, deviceDescription, ipDescription.
+
+
Errors

401INVALID_SESSION 401SESSION_EXPIRED

+
+ +
+
+

Approve a voucher.

+
approve-vouchersrc/cli/engine/routes/vouchers.ts
+
+

coreaccount.approveVoucher

+

Lets the waiting device finish logging in.

+
+
+

Command line

+
approve-voucher --voucher-id=<voucherId>
+ + + +
+

REST

+

POST/account/{sessionId}/approve-voucher

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + +
+
Request body
+
{
+  voucherId: string
+}
+
Example
{
+  "voucherId": "FS8xJ2kQ…"
+}
+ + + + +
voucherIdstringFrom pending-vouchers, or an OTP_REQUIRED error’s details.voucherId.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"voucherId":"FS8xJ2kQ…"}' \
+  'http://localhost/account/$SESS/approve-voucher'
+
+ + +
+
+

Reject a voucher.

+
reject-vouchersrc/cli/engine/routes/vouchers.ts
+
+

coreaccount.rejectVoucher

+

Denies the waiting device. The login it was issued for cannot complete.

+
+
+

Command line

+
reject-voucher --voucher-id=<voucherId>
+ + + +
+

REST

+

POST/account/{sessionId}/reject-voucher

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + +
+
Request body
+
{
+  voucherId: string
+}
+
Example
{
+  "voucherId": "FS8xJ2kQ…"
+}
+ + + + +
voucherIdstringFrom pending-vouchers, or an OTP_REQUIRED error’s details.voucherId.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"voucherId":"FS8xJ2kQ…"}' \
+  'http://localhost/account/$SESS/reject-voucher'
+
+ + +

Approving a login

+

The other side of request-edge-login: a logged-in account inspecting and approving a login somebody scanned.

+
+
+
+

Inspect a login request.

+
fetch-lobbysrc/cli/engine/routes/lobby.ts
+
+

coreaccount.fetchLobby

+

The other side of request-edge-login: shows who is asking, so a human can decide before approving.

+
+
+

Command line

+
fetch-lobby <lobbyId>
+ + + +
+

REST

+

GET/account/{sessionId}/fetch-lobby/{lobbyId}

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
lobbyIdstringFrom the QR code, or an edge://edge/<lobbyId> link.
+ + + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/account/$SESS/fetch-lobby/$LOBBYID?lobbyId=…'
+
+
+

Response 200

+
+
Response body
+
{
+  lobbyId: string
+  loginRequest: {
+    appId: string;
+    displayName: string;
+    displayImageDarkUrl: string | null;
+    displayImageLightUrl: string | null
+  } | null
+}
+
Example
{
+  "lobbyId": "FS8xJ2kQ…",
+  "loginRequest": {}
+}
+ + + + + + + + + +
lobbyIdstringThe lobby that was fetched, echoed back.
loginRequest{ appId: string; displayName: string; displayImageDarkUrl: string | null; displayImageLightUrl: string | null; } | nullNull when the lobby carries no pending login request.
+
+
Errors

400BAD_REQUEST 503NETWORK_ERROR 401INVALID_SESSION 401SESSION_EXPIRED

+
+ +
+
+

Approve a login request.

+
approve-login-requestsrc/cli/engine/routes/lobby.ts
+
+

coreEdgeLoginRequest.approve Reached through account.fetchLobby(lobbyId).loginRequest.

Differs from core:

  • lobbyId — Core calls approve() on a request object. Over HTTP there is no object to hold, so the lobby names which one to approve.
+

Grants the requesting device access to this account.

+
+
+

Command line

+
approve-login-request <lobbyId>
+ + + +
+

REST

+

POST/account/{sessionId}/approve-login-request/{lobbyId}

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
lobbyIdstringFrom the QR code, or an edge://edge/<lobbyId> link.
+ + + +
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"lobbyId":"FS8xJ2kQ…"}' \
+  'http://localhost/account/$SESS/approve-login-request/$LOBBYID'
+
+
+

Response 200

+
+
Response body
+
{
+  ok: boolean
+}
+
Example
{
+  "ok": true
+}
+ + + + +
okbooleanAlways true; a failure arrives as an error envelope.
+
+
Errors

404NO_LOGIN_REQUEST 400BAD_REQUEST 503NETWORK_ERROR 401INVALID_SESSION 401SESSION_EXPIRED

+
+

Notes

  • The lobby is re-fetched on approve, so a request that expired between inspecting and approving fails with 404 NO_LOGIN_REQUEST.
+

Keys

+

Raw key infrastructure beneath the wallet API. Several of these return private key material, so they are the routes to think hardest about who can reach the engine: a process that can read the 0600 socket, or the TCP token, holds every logged-in account. All of them need a session, and a sessionId is itself full account authority.

+
+
+
+

List every key in the account.

+
all-keyssrc/cli/engine/routes/keys.ts
+
+

coreaccount.allKeys

+

Includes archived and deleted keys, unlike currency-wallets.

+
+
+

Command line

+
all-keys
+ + + +
+

REST

+

GET/account/{sessionId}/all-keys

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/account/$SESS/all-keys'
+
+
+

Response 200

+
+
Response body
+
{
+  allKeys: unknown[]
+}
+
Example
{
+  "allKeys": [
+    {}
+  ]
+}
+ + + + +
allKeysunknown[]EdgeWalletInfoFull[]: id, type, keys, archived, deleted, hidden, sortIndex.
+
+
Errors

401INVALID_SESSION 401SESSION_EXPIRED

+
+ +
+
+

Create a wallet from raw key JSON.

+
create-walletsrc/cli/engine/routes/keys.ts
+
+

coreaccount.createWallet

+

The import path. Use create-currency-wallet to make a fresh wallet with generated keys.

+
+
+

Command line

+
create-wallet --type=<type> [--keys='<json>']
+ + + +
+

REST

+

POST/account/{sessionId}/create-wallet

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + +
+
Request body
+
{
+  type: string
+  keys?: {
+    [keys: string]: unknown
+  }
+}
+
Example
{
+  "type": "string",
+  "keys": {}
+}
+ + + + + + + + + +
typestringWallet type, e.g. wallet:bitcoin.
keys{ [keys: string]: unknown; } optionalPlugin key material. Omit to let core generate it.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"type":"string","keys":{}}' \
+  'http://localhost/account/$SESS/create-wallet'
+
+
+

Response 200

+
+
Response body
+
{
+  walletId: string
+}
+
Example
{
+  "walletId": "FS8xJ2kQ…"
+}
+ + + + +
walletIdstringThe new wallet. Its keys are already saved.
+
+
Errors

400BAD_REQUEST 401INVALID_SESSION 401SESSION_EXPIRED

+
+ +
+
+

Read one wallet's key info.

+
get-wallet-infosrc/cli/engine/routes/keys.ts
+
+

coreaccount.getWalletInfo

+ +
+

Command line

+
get-wallet-info --id=<id>
+ + + +
+

REST

+

GET/account/{sessionId}/get-wallet-info

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+
+
Query
+
{
+  id: string
+}
+
Example
{
+  "id": "FS8xJ2kQ…"
+}
+ + + + +
idstringThe key id, from all-keys. Base64, like a wallet id.
+
+ + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/account/$SESS/get-wallet-info?id=…'
+
+
+

Response 200

+

EdgeWalletInfoFull, verbatim from core — including the keys object.

+
unknown
+
Errors

404WALLET_NOT_FOUND 409AMBIGUOUS_WALLET_ID 401INVALID_SESSION 401SESSION_EXPIRED

+
+

Notes

  • An exact lookup: unlike the wallet-scoped routes this does not accept an id prefix.
+
+
+

Read raw private key material.

+
get-raw-private-keysrc/cli/engine/routes/keys.ts
+
+

coreaccount.getRawPrivateKey

+

Secret. Whatever the plugin stores — seed, mnemonic, xpriv.

+
+
+

Command line

+
get-raw-private-key --wallet-id=<walletId>
+ + + +
+

REST

+

GET/account/{sessionId}/get-raw-private-key

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+
+
Query
+
{
+  walletId: string
+}
+
Example
{
+  "walletId": "FS8xJ2kQ…"
+}
+ + + + +
walletIdstringThe wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns 409 AMBIGUOUS_WALLET_ID with details.candidates.
+
+ + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/account/$SESS/get-raw-private-key?walletId=…'
+
+
+

Response 200

+

The plugin’s key object, at the top level.

+
unknown
+
Errors

404WALLET_NOT_FOUND 409AMBIGUOUS_WALLET_ID 401INVALID_SESSION 401SESSION_EXPIRED

+
+ +
+
+

Read raw public key material.

+
get-raw-public-keysrc/cli/engine/routes/keys.ts
+
+

coreaccount.getRawPublicKey

+ +
+

Command line

+
get-raw-public-key --wallet-id=<walletId>
+ + + +
+

REST

+

GET/account/{sessionId}/get-raw-public-key

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+
+
Query
+
{
+  walletId: string
+}
+
Example
{
+  "walletId": "FS8xJ2kQ…"
+}
+ + + + +
walletIdstringThe wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns 409 AMBIGUOUS_WALLET_ID with details.candidates.
+
+ + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/account/$SESS/get-raw-public-key?walletId=…'
+
+
+

Response 200

+

The plugin’s public key object.

+
unknown
+
Errors

404WALLET_NOT_FOUND 409AMBIGUOUS_WALLET_ID 401INVALID_SESSION 401SESSION_EXPIRED

+
+ +
+
+

Export the private key for display.

+
get-display-private-keysrc/cli/engine/routes/keys.ts
+
+

coreaccount.getDisplayPrivateKey

+

Secret. The human-facing form — WIF, seed phrase, whatever the plugin shows on its export screen.

+
+
+

Command line

+
get-display-private-key --wallet-id=<walletId>
+ + + +
+

REST

+

GET/account/{sessionId}/get-display-private-key

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+
+
Query
+
{
+  walletId: string
+}
+
Example
{
+  "walletId": "FS8xJ2kQ…"
+}
+ + + + +
walletIdstringThe wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns 409 AMBIGUOUS_WALLET_ID with details.candidates.
+
+ + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/account/$SESS/get-display-private-key?walletId=…'
+
+
+

Response 200

+
+
Response body
+
{
+  key: string
+}
+
Example
{
+  "key": "string"
+}
+ + + + +
keystringThe displayable private key.
+
+
Errors

404WALLET_NOT_FOUND 409AMBIGUOUS_WALLET_ID 409WALLET_NOT_RUNNING 401INVALID_SESSION 401SESSION_EXPIRED

+
+ +
+
+

Export the public key for display.

+
get-display-public-keysrc/cli/engine/routes/keys.ts
+
+

coreaccount.getDisplayPublicKey

+

The xpub or equivalent — safe to share for watch-only use.

+
+
+

Command line

+
get-display-public-key --wallet-id=<walletId>
+ + + +
+

REST

+

GET/account/{sessionId}/get-display-public-key

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+
+
Query
+
{
+  walletId: string
+}
+
Example
{
+  "walletId": "FS8xJ2kQ…"
+}
+ + + + +
walletIdstringThe wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns 409 AMBIGUOUS_WALLET_ID with details.candidates.
+
+ + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/account/$SESS/get-display-public-key?walletId=…'
+
+
+

Response 200

+
+
Response body
+
{
+  key: string
+}
+
Example
{
+  "key": "string"
+}
+ + + + +
keystringThe displayable public key.
+
+
Errors

404WALLET_NOT_FOUND 409AMBIGUOUS_WALLET_ID 409WALLET_NOT_RUNNING 401INVALID_SESSION 401SESSION_EXPIRED

+
+ +
+
+

List chains a wallet can split into.

+
list-splittable-wallet-typessrc/cli/engine/routes/keys.ts
+
+

coreaccount.listSplittableWalletTypes

+

Forked-chain support: which wallet types can be derived from these keys.

+
+
+

Command line

+
list-splittable-wallet-types --wallet-id=<walletId>
+ + + +
+

REST

+

GET/account/{sessionId}/list-splittable-wallet-types

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+
+
Query
+
{
+  walletId: string
+}
+
Example
{
+  "walletId": "FS8xJ2kQ…"
+}
+ + + + +
walletIdstringThe wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns 409 AMBIGUOUS_WALLET_ID with details.candidates.
+
+ + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/account/$SESS/list-splittable-wallet-types?walletId=…'
+
+
+

Response 200

+
+
Response body
+
{
+  walletTypes: string[]
+}
+
Example
{
+  "walletTypes": [
+    "string"
+  ]
+}
+ + + + +
walletTypesstring[]Types valid for split.
+
+
Errors

404WALLET_NOT_FOUND 409AMBIGUOUS_WALLET_ID 401INVALID_SESSION 401SESSION_EXPIRED

+
+ +
+
+

Archive, delete, hide, or reorder wallets.

+
change-wallet-statessrc/cli/engine/routes/keys.ts
+
+

coreaccount.changeWalletStates

+

The canonical backend for every wallet flag; there are no separate archive, unarchive or undelete verbs.

+
+
+

Command line

+
change-wallet-states [--wallet-states='<json>'] --wallet-id=<value> [--archived=<value>] [--deleted=<value>] [--hidden=<value>] [--sort-index=<value>]
+ +
Client-only flags
--wallet-idrequiredThe wallet to change. The command makes it the key of a single-entry walletStates map.
--archivedoptionalHide from the active list.
--deletedoptionalMark deleted.
--hiddenoptionalHide from the wallet picker.
--sort-indexoptionalPosition in the wallet list.
+

The command builds a single-wallet walletStates map from these flags, and needs at least one.

+
+
+

REST

+

POST/account/{sessionId}/change-wallet-states

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + +
+
Request body
+
{
+  walletStates?: {
+    [keys: string]: {
+      archived: boolean | undefined;
+      deleted: boolean | undefined;
+      hidden: boolean | undefined;
+      sortIndex: number | undefined
+    }
+  }
+}
+
Example
{
+  "walletStates": {}
+}
+ + + + +
walletStates{ [keys: string]: { archived: boolean | undefined; deleted: boolean | undefined; hidden: boolean | undefined; sortIndex: number | undefined; }; } optionalEdgeWalletStates: wallet ids to the flags being changed.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"walletStates":{}}' \
+  'http://localhost/account/$SESS/change-wallet-states'
+
+
+

Response 204

+

No body.

+
Errors

400BAD_REQUEST 401INVALID_SESSION 401SESSION_EXPIRED

+
+ +

Swap quotes

+

Cross-asset exchange. Quotes are live objects held server-side under a swap_ handle, so approving one means naming its objectId rather than re-uploading the quote.

+
+
+
+

Fetch swap quotes.

+
fetch-swap-quotessrc/cli/engine/routes/swap.ts
+
+

coreaccount.fetchSwapQuotes

Differs from core:

  • fromWalletId — Core takes the wallet object; over HTTP it is an id.
  • toWalletId — Core takes the wallet object; over HTTP it is an id.
+

Polls every enabled swap plugin and parks each result under its own swap_ handle with a 5 minute TTL.

+
+
+

Command line

+
fetch-swap-quotes --from-wallet-id=<fromWalletId> --to-wallet-id=<toWalletId> --native-amount=<nativeAmount> [--from-token-id=<fromTokenId>] [--to-token-id=<toTokenId>] [--quote-for=from|max|to] [--plugin-id=<preferPluginId>]
+ + + +
+

REST

+

POST/account/{sessionId}/fetch-swap-quotes

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + +
+
Request body
+
{
+  fromWalletId: string
+  toWalletId: string
+  nativeAmount: string
+  fromTokenId?: string | null
+  toTokenId?: string | null
+  quoteFor?: "from" | "max" | "to"
+  preferPluginId?: string
+}
+
Example
{
+  "fromWalletId": "FS8xJ2kQ…",
+  "toWalletId": "FS8xJ2kQ…",
+  "nativeAmount": "12345",
+  "fromTokenId": "FS8xJ2kQ…",
+  "toTokenId": "FS8xJ2kQ…",
+  "quoteFor": "from",
+  "preferPluginId": "FS8xJ2kQ…"
+}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
fromWalletIdstringSource wallet. Accepts a unique prefix.
toWalletIdstringDestination wallet.
nativeAmountstringHow much, in native units.
fromTokenIdstring | null optionalDefaults to the native asset.
toTokenIdstring | null optionalDefaults to the native asset.
quoteFor"from" | "max" | "to" optionalfrom spends this much of the source, to receives this much at the destination, max sends everything. Defaults to from.
preferPluginIdstring optionalRestrict to one exchange.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"fromWalletId":"FS8xJ2kQ…","toWalletId":"FS8xJ2kQ…","nativeAmount":"12345","fromTokenId":"FS8xJ2kQ…","toTokenId":"FS8xJ2kQ…","quoteFor":"from","preferPluginId":"FS8xJ2kQ…"}' \
+  'http://localhost/account/$SESS/fetch-swap-quotes'
+
+
+

Response 200

+
+
Response body
+
{
+  quoteCount: number
+  quotes: {
+    objectId: string;
+    kind: string;
+    expiresAt: string;
+    pluginId: string;
+    isEstimate: boolean;
+    canBePartial: boolean | null;
+    maxFulfillmentSeconds: number | null;
+    minReceiveAmount: string | null;
+    fromNativeAmount: string;
+    toNativeAmount: string;
+    networkFee: {
+      nativeAmount: string;
+      tokenId: string | null
+    };
+    quoteExpirationDate: string | null;
+    swapInfo: {
+      pluginId: string;
+      displayName: string;
+      supportEmail: string;
+      isDex: boolean | null
+    };
+    request: {
+      fromTokenId: string | null;
+      toTokenId: string | null;
+      nativeAmount: string;
+      quoteFor: "from" | "to" | "max";
+      fromWalletId: string;
+      toWalletId: string
+    }
+  }[]
+}
+
Example
{
+  "quoteCount": 1,
+  "quotes": [
+    {}
+  ]
+}
+ + + + + + + + + +
quoteCountnumberHow many plugins answered.
quotes{ objectId: string; kind: string; expiresAt: string; pluginId: string; isEstimate: boolean; canBePartial: boolean | null; maxFulfillmentSeconds: number | null; minReceiveAmount: string | null; fromNativeAmount: string; toNativeAmount: string; networkFee: { nativeAmount: string; tokenId: string | null; }; quoteExpirationDate: string | null; swapInfo: { pluginId: string; displayName: string; supportEmail: string; isDex: boolean | null; }; request: { fromTokenId: string | null; toTokenId: string | null; nativeAmount: string; quoteFor: "from" | "to" | "max"; fromWalletId: string; toWalletId: string; }; }[]One quote per plugin that answered, each already parked under its own handle. Plugins that failed or had nothing to offer are simply absent.
+
+
Errors

400BAD_REQUEST 422SWAP_BELOW_LIMIT 422SWAP_ABOVE_LIMIT 422SWAP_CURRENCY 403SWAP_PERMISSION 422SWAP_ADDRESS 400SAME_CURRENCY 422INSUFFICIENT_FUNDS 404WALLET_NOT_FOUND 404TOKEN_NOT_FOUND 503NETWORK_ERROR 401INVALID_SESSION 401SESSION_EXPIRED

+
+

Notes

  • Every returned quote holds an open plugin object. Approving one releases only that handle; close the rest, or let them expire.
  • An empty quotes array with quoteCount: 0 is a success, not an error — no plugin could serve the pair.
+
+
+

Re-read a quote.

+
swap-quote-getsrc/cli/engine/routes/swap.ts
+
+

coreEngine handle store; the quote is a live EdgeSwapQuote held server-side.

+ +
+

Command line

+
swap-quote-get <objectId>
+ + + +
+

REST

+

GET/account/{sessionId}/swap-quote/{objectId}

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
objectIdstringAn ephemeral object handle id.
+ + + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/account/$SESS/swap-quote/$OBJECTID'
+
+
+

Response 200

+
+
Response body
+
{
+  objectId: string
+  kind: string
+  expiresAt: string
+  pluginId: string
+  isEstimate: boolean
+  canBePartial: boolean | null
+  maxFulfillmentSeconds: number | null
+  minReceiveAmount: string | null
+  fromNativeAmount: string
+  toNativeAmount: string
+  networkFee: {
+    nativeAmount: string;
+    tokenId: string | null
+  }
+  quoteExpirationDate: string | null
+  swapInfo: {
+    pluginId: string;
+    displayName: string;
+    supportEmail: string;
+    isDex: boolean | null
+  }
+  request: {
+    fromTokenId: string | null;
+    toTokenId: string | null;
+    nativeAmount: string;
+    quoteFor: "from" | "to" | "max";
+    fromWalletId: string;
+    toWalletId: string
+  }
+}
+
Example
{
+  "objectId": "FS8xJ2kQ…",
+  "kind": "string",
+  "expiresAt": "2026-09-02T16:35:00.000Z",
+  "pluginId": "FS8xJ2kQ…",
+  "isEstimate": true,
+  "canBePartial": true,
+  "maxFulfillmentSeconds": 1,
+  "minReceiveAmount": "12345",
+  "fromNativeAmount": "12345",
+  "toNativeAmount": "12345",
+  "networkFee": {},
+  "quoteExpirationDate": "2026-09-02T16:35:00.000Z",
+  "swapInfo": {},
+  "request": {}
+}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
objectIdstringHandle for the value the engine is holding. Pass it to the calls that consume it.
kindstringWhat the handle refers to, which decides the calls that accept it.
expiresAtstringWhen the engine drops the handle. Handles live 5 minutes.
pluginIdstringSwap provider that produced this quote.
isEstimatebooleanTrue when the provider may settle at a different rate than quoted.
canBePartialboolean | nullTrue when the provider may fill only part of the order. Null when it does not say.
maxFulfillmentSecondsnumber | nullLongest the provider expects a partial fill to take.
minReceiveAmountstring | nullLeast the provider guarantees to deliver, in the destination’s native units.
fromNativeAmountstringAmount leaving the source wallet.
toNativeAmountstringAmount arriving in the destination wallet.
networkFee{ nativeAmount: string; tokenId: string | null; }On-chain fee for the sending transaction. It is not the provider’s own spread, which is already in the rate.
quoteExpirationDatestring | nullWhen the provider stops honouring the rate. Null when it does not expire.
swapInfo{ pluginId: string; displayName: string; supportEmail: string; isDex: boolean | null; }EdgeSwapInfo: how to name the provider and where to send complaints.
request{ fromTokenId: string | null; toTokenId: string | null; nativeAmount: string; quoteFor: "from" | "to" | "max"; fromWalletId: string; toWalletId: string; }The EdgeSwapRequest this quote answers, echoed back so quotes from different plugins can be compared without tracking what was asked.
+
+
Errors

404OBJECT_NOT_FOUND 410OBJECT_EXPIRED 400OBJECT_KIND_MISMATCH 400OBJECT_SESSION_MISMATCH 401INVALID_SESSION 401SESSION_EXPIRED

+
+

Notes

  • Check quoteExpirationDate as well as expiresAt: the plugin's price can go stale before the handle does.
+
+
+

Execute a quote.

+
approve-swap-quotesrc/cli/engine/routes/swap.ts
+
+

coreEdgeSwapQuote.approve

+

Moves funds. The handle is released afterwards whether or not the response is read, so record orderId from it.

+
+
+

Command line

+
approve-swap-quote <objectId>
+ + + +
+

REST

+

POST/account/{sessionId}/swap-quote/approve/{objectId}

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
objectIdstringAn ephemeral object handle id.
+ + + +
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  'http://localhost/account/$SESS/swap-quote/approve/$OBJECTID'
+
+
+

Response 200

+
+
Response body
+
{
+  ok: unknown
+  objectId: string
+  orderId: unknown
+  destinationAddress: unknown
+  transaction: unknown
+}
+
Example
{
+  "ok": {},
+  "objectId": "FS8xJ2kQ…",
+  "orderId": {},
+  "destinationAddress": {},
+  "transaction": {}
+}
+ + + + + + + + + + + + + + + + + + + + + + + + +
okunknownTrue once the swap is submitted and the send broadcast.
objectIdstringThe handle that was consumed.
orderIdunknownThe exchange’s order reference, when it gives one.
destinationAddressunknownAddress the funds were sent to, when the exchange reports one.
transactionunknownThe on-chain send to the exchange.
+
+
Errors

404OBJECT_NOT_FOUND 410OBJECT_EXPIRED 400OBJECT_KIND_MISMATCH 400OBJECT_SESSION_MISMATCH 409OBJECT_IN_USE 422INSUFFICIENT_FUNDS 503NETWORK_ERROR 401INVALID_SESSION 401SESSION_EXPIRED

+
+

Notes

  • A failure releases the handle too, and a retry answers OBJECT_NOT_FOUND. approve() signs, broadcasts and then does its own bookkeeping, and from outside the plugin an error after the money moved cannot be told from one before it — so a retry has to start from a fresh quote rather than risk a second broadcast.
  • The plugin attaches its own savedAction and assetAction metadata; the engine adds none.
+
+
+

Discard a quote.

+
close-swap-quotesrc/cli/engine/routes/swap.ts
+
+

coreEdgeSwapQuote.close

+

Closes the plugin object without executing, freeing whatever the exchange was holding.

+
+
+

Command line

+
close-swap-quote <objectId>
+ + + +
+

REST

+

POST/account/{sessionId}/swap-quote/close/{objectId}

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
objectIdstringAn ephemeral object handle id.
+ + + +
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  'http://localhost/account/$SESS/swap-quote/close/$OBJECTID'
+
+
+

Response 200

+
+
Response body
+
{
+  ok: boolean
+  objectId: string
+}
+
Example
{
+  "ok": true,
+  "objectId": "FS8xJ2kQ…"
+}
+ + + + + + + + + +
okbooleanAlways true; a failure arrives as an error envelope.
objectIdstringThe handle this call consumed. It is now expired.
+
+
Errors

404OBJECT_NOT_FOUND 410OBJECT_EXPIRED 400OBJECT_KIND_MISMATCH 400OBJECT_SESSION_MISMATCH 401INVALID_SESSION 401SESSION_EXPIRED

+
+ +

Data store

+

The account’s synced key-value store, where plugins keep their own state. One route per EdgeDataStore method.

+
+
+
+

List data-store ids.

+
list-store-idssrc/cli/engine/routes/dataStore.ts
+
+

coreaccount.dataStore.listStoreIds

+

The account's synced key-value store, where plugins keep their own state.

+
+
+

Command line

+
list-store-ids
+ + + +
+

REST

+

GET/account/{sessionId}/list-store-ids

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/account/$SESS/list-store-ids'
+
+
+

Response 200

+
+
Response body
+
{
+  storeIds: string[]
+}
+
Example
{
+  "storeIds": [
+    "string"
+  ]
+}
+ + + + +
storeIdsstring[]Every store holding at least one item.
+
+
Errors

401INVALID_SESSION 401SESSION_EXPIRED

+
+ +
+
+

List item ids in a store.

+
list-item-idssrc/cli/engine/routes/dataStore.ts
+
+

coreaccount.dataStore.listItemIds

+ +
+

Command line

+
list-item-ids --store-id=<storeId>
+ + + +
+

REST

+

GET/account/{sessionId}/list-item-ids

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+
+
Query
+
{
+  storeId: string
+}
+
Example
{
+  "storeId": "FS8xJ2kQ…"
+}
+ + + + +
storeIdstringPlugin or app namespace within the account data store.
+
+ + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/account/$SESS/list-item-ids?storeId=…'
+
+
+

Response 200

+
+
Response body
+
{
+  itemIds: string[]
+}
+
Example
{
+  "itemIds": [
+    "string"
+  ]
+}
+ + + + +
itemIdsstring[]Keys in this store. Empty if it has none.
+
+
Errors

401INVALID_SESSION 401SESSION_EXPIRED

+
+ +
+
+

Read an item.

+
get-itemsrc/cli/engine/routes/dataStore.ts
+
+

coreaccount.dataStore.getItem

+

Values are opaque strings; encoding is the caller's business.

+
+
+

Command line

+
get-item --store-id=<storeId> --item-id=<itemId>
+ + + +
+

REST

+

GET/account/{sessionId}/get-item

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+
+
Query
+
{
+  storeId: string
+  itemId: string
+}
+
Example
{
+  "storeId": "FS8xJ2kQ…",
+  "itemId": "FS8xJ2kQ…"
+}
+ + + + + + + + + +
storeIdstringPlugin or app namespace within the account data store.
itemIdstringKey within the store.
+
+ + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/account/$SESS/get-item?storeId=…&itemId=…'
+
+
+

Response 200

+
+
Response body
+
{
+  value: string
+}
+
Example
{
+  "value": "string"
+}
+ + + + +
valuestringThe stored string.
+
+
Errors

404NOT_FOUND 400BAD_REQUEST 401INVALID_SESSION 401SESSION_EXPIRED

+
+ +
+
+

Write an item.

+
set-itemsrc/cli/engine/routes/dataStore.ts
+
+

coreaccount.dataStore.setItem

+

Creates the store if it does not exist.

+
+
+

Command line

+
set-item --store-id=<storeId> --item-id=<itemId> --value=<value>
+ + + +
+

REST

+

POST/account/{sessionId}/set-item

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + +
+
Request body
+
{
+  storeId: string
+  itemId: string
+  value: string
+}
+
Example
{
+  "storeId": "FS8xJ2kQ…",
+  "itemId": "FS8xJ2kQ…",
+  "value": "string"
+}
+ + + + + + + + + + + + + + +
storeIdstringPlugin or app namespace within the account data store.
itemIdstringKey within the store.
valuestringThe string to store.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"storeId":"FS8xJ2kQ…","itemId":"FS8xJ2kQ…","value":"string"}' \
+  'http://localhost/account/$SESS/set-item'
+
+
+

Response 204

+

No body.

+
Errors

400BAD_REQUEST 401INVALID_SESSION 401SESSION_EXPIRED

+
+ +
+
+

Delete an item.

+
delete-itemsrc/cli/engine/routes/dataStore.ts
+
+

coreaccount.dataStore.deleteItem

+ +
+

Command line

+
delete-item --store-id=<storeId> --item-id=<itemId>
+ + + +
+

REST

+

POST/account/{sessionId}/delete-item

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + +
+
Request body
+
{
+  storeId: string
+  itemId: string
+}
+
Example
{
+  "storeId": "FS8xJ2kQ…",
+  "itemId": "FS8xJ2kQ…"
+}
+ + + + + + + + + +
storeIdstringPlugin or app namespace within the account data store.
itemIdstringKey within the store.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"storeId":"FS8xJ2kQ…","itemId":"FS8xJ2kQ…"}' \
+  'http://localhost/account/$SESS/delete-item'
+
+
+

Response 204

+

No body.

+
Errors

400BAD_REQUEST 401INVALID_SESSION 401SESSION_EXPIRED

+
+ +
+
+

Delete an entire store.

+
delete-storesrc/cli/engine/routes/dataStore.ts
+
+

coreaccount.dataStore.deleteStore

+

Removes every item in it, which cannot be undone from this API.

+
+
+

Command line

+
delete-store --store-id=<storeId>
+ + + +
+

REST

+

POST/account/{sessionId}/delete-store

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + +
+
Request body
+
{
+  storeId: string
+}
+
Example
{
+  "storeId": "FS8xJ2kQ…"
+}
+ + + + +
storeIdstringPlugin or app namespace within the account data store.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"storeId":"FS8xJ2kQ…"}' \
+  'http://localhost/account/$SESS/delete-store'
+
+
+

Response 204

+

No body.

+
Errors

400BAD_REQUEST 401INVALID_SESSION 401SESSION_EXPIRED

+
+ +

Wallet

+

Calls on a single EdgeCurrencyWallet. Each names its wallet with --wallet-id, which accepts a full id or any unique prefix.

+
+

Wallet state

+

Account-level wallet listing and creation, then per-wallet calls. A {walletId} segment accepts a unique prefix, so those routes can also return 404 WALLET_NOT_FOUND or 409 AMBIGUOUS_WALLET_ID.

+
+
+
+

Wallet detail.

+
wallet-infosrc/cli/engine/routes/wallets.ts
+
+

coreEngine composite of EdgeCurrencyWallet properties plus its EdgeCurrencyConfig token map.

+ +
+

Command line

+
wallet-info --wallet-id=<walletId>
+ + + +
+

REST

+

GET/account/{sessionId}/wallet

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+
+
Query
+
{
+  walletId: string
+}
+
Example
{
+  "walletId": "FS8xJ2kQ…"
+}
+ + + + +
walletIdstringThe wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns 409 AMBIGUOUS_WALLET_ID with details.candidates.
+
+ + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/account/$SESS/wallet?walletId=…'
+
+
+

Response 200

+

Every WalletSummary field, plus denominations and walletSettings. allTokens is not here — wallet-tokens exists to carry it, and returning it from both sent the same map twice in a session that calls both.

+
unknown
+
Errors

404WALLET_NOT_FOUND 409AMBIGUOUS_WALLET_ID 401INVALID_SESSION 401SESSION_EXPIRED

+
+ +
+
+

Rename a wallet.

+
rename-walletsrc/cli/engine/routes/wallets.ts
+
+

corewallet.renameWallet

+ +
+

Command line

+
rename-wallet --wallet-id=<walletId> --name=<name>
+ + + +
+

REST

+

POST/account/{sessionId}/wallet/rename-wallet

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + +
+
Request body
+
{
+  walletId: string
+  name: string
+}
+
Example
{
+  "walletId": "FS8xJ2kQ…",
+  "name": "string"
+}
+ + + + + + + + + +
walletIdstringThe wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns 409 AMBIGUOUS_WALLET_ID with details.candidates.
namestringThe new display name.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"walletId":"FS8xJ2kQ…","name":"string"}' \
+  'http://localhost/account/$SESS/wallet/rename-wallet'
+
+ + +
+
+

Change a wallet's fiat currency.

+
set-fiat-currency-codesrc/cli/engine/routes/wallets.ts
+
+

corewallet.setFiatCurrencyCode

+

Affects how balances and history are priced, not the asset itself.

+
+
+

Command line

+
set-fiat-currency-code --wallet-id=<walletId> --fiat-currency-code=<fiatCurrencyCode>
+ + + +
+

REST

+

POST/account/{sessionId}/wallet/set-fiat-currency-code

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + +
+
Request body
+
{
+  walletId: string
+  fiatCurrencyCode: string
+}
+
Example
{
+  "walletId": "FS8xJ2kQ…",
+  "fiatCurrencyCode": "string"
+}
+ + + + + + + + + +
walletIdstringThe wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns 409 AMBIGUOUS_WALLET_ID with details.candidates.
fiatCurrencyCodestringe.g. iso:EUR.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"walletId":"FS8xJ2kQ…","fiatCurrencyCode":"string"}' \
+  'http://localhost/account/$SESS/wallet/set-fiat-currency-code'
+
+ + +
+
+

Pause or resume a wallet engine.

+
change-pausedsrc/cli/engine/routes/wallets.ts
+
+

corewallet.changePaused

+

A paused wallet stops syncing, which is how a caller quiets a chain it does not currently care about.

+
+
+

Command line

+
change-paused --wallet-id=<walletId> --paused=true|false
+ + + +
+

REST

+

POST/account/{sessionId}/wallet/change-paused

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + +
+
Request body
+
{
+  walletId: string
+  paused: boolean
+}
+
Example
{
+  "walletId": "FS8xJ2kQ…",
+  "paused": true
+}
+ + + + + + + + + +
walletIdstringThe wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns 409 AMBIGUOUS_WALLET_ID with details.candidates.
pausedbooleanTrue to stop syncing.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"walletId":"FS8xJ2kQ…","paused":true}' \
+  'http://localhost/account/$SESS/wallet/change-paused'
+
+ + +
+
+

Nudge one wallet to sync.

+
wallet-syncsrc/cli/engine/routes/wallets.ts
+
+

corewallet.sync

+ +
+

Command line

+
wallet-sync --wallet-id=<walletId>
+ + + +
+

REST

+

POST/account/{sessionId}/wallet/sync

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + +
+
Request body
+
{
+  walletId: string
+}
+
Example
{
+  "walletId": "FS8xJ2kQ…"
+}
+ + + + +
walletIdstringThe wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns 409 AMBIGUOUS_WALLET_ID with details.candidates.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"walletId":"FS8xJ2kQ…"}' \
+  'http://localhost/account/$SESS/wallet/sync'
+
+ +

Notes

  • Named wallet-sync on the CLI because sync is account.sync.
+
+
+

Rescan the blockchain from scratch.

+
resync-blockchainsrc/cli/engine/routes/wallets.ts
+
+

corewallet.resyncBlockchain

+

Drops cached chain state and re-scans. Expensive, and the wallet reports an incomplete balance until it finishes.

+
+
+

Command line

+
resync-blockchain --wallet-id=<walletId>
+ + + +
+

REST

+

POST/account/{sessionId}/wallet/resync-blockchain

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + +
+
Request body
+
{
+  walletId: string
+}
+
Example
{
+  "walletId": "FS8xJ2kQ…"
+}
+ + + + +
walletIdstringThe wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns 409 AMBIGUOUS_WALLET_ID with details.candidates.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"walletId":"FS8xJ2kQ…"}' \
+  'http://localhost/account/$SESS/wallet/resync-blockchain'
+
+ +

Notes

  • Returns when the resync is requested, not when it completes. Watch syncRatio for progress.
+
+
+

Split a wallet into another chain.

+
splitsrc/cli/engine/routes/wallets.ts
+
+

corewallet.split

+

Forked-chain support: derive a wallet of a different type from the same keys. list-splittable-wallet-types says which are valid.

+
+
+

Command line

+
split --wallet-id=<walletId> --split-wallets='<json>'
+ + + +
+

REST

+

POST/account/{sessionId}/wallet/split

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + +
+
Request body
+
{
+  walletId: string
+  splitWallets: {
+    walletType: string;
+    name: string | undefined;
+    fiatCurrencyCode: string | undefined
+  }[]
+}
+
Example
{
+  "walletId": "FS8xJ2kQ…",
+  "splitWallets": [
+    {}
+  ]
+}
+ + + + + + + + + +
walletIdstringThe wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns 409 AMBIGUOUS_WALLET_ID with details.candidates.
splitWallets{ walletType: string; name: string | undefined; fiatCurrencyCode: string | undefined; }[]EdgeSplitCurrencyWallet[]: walletType, plus optional name and fiatCurrencyCode.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"walletId":"FS8xJ2kQ…","splitWallets":[{}]}' \
+  'http://localhost/account/$SESS/wallet/split'
+
+
+

Response 200

+
+
Response body
+
{
+  results: unknown[]
+}
+
Example
{
+  "results": [
+    {}
+  ]
+}
+ + + + +
resultsunknown[]Per-entry outcomes, like batch create.
+
+
Errors

400BAD_REQUEST 404WALLET_NOT_FOUND 409AMBIGUOUS_WALLET_ID 401INVALID_SESSION 401SESSION_EXPIRED

+
+ +
+
+

Dump wallet engine state.

+
dump-datasrc/cli/engine/routes/wallets.ts
+
+

corewallet.dumpData

+

Plugin-defined debug output. Shape varies by plugin and can be very large.

+
+
+

Command line

+
dump-data --wallet-id=<walletId>
+ + + +
+

REST

+

GET/account/{sessionId}/wallet/dump-data

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+
+
Query
+
{
+  walletId: string
+}
+
Example
{
+  "walletId": "FS8xJ2kQ…"
+}
+ + + + +
walletIdstringThe wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns 409 AMBIGUOUS_WALLET_ID with details.candidates.
+
+ + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/account/$SESS/wallet/dump-data?walletId=…'
+
+
+

Response 200

+

EdgeDataDump, straight from the plugin.

+
unknown
+
Errors

404WALLET_NOT_FOUND 409AMBIGUOUS_WALLET_ID 401INVALID_SESSION 401SESSION_EXPIRED

+
+ +
+
+

Balances for every asset in the wallet.

+
balance-mapsrc/cli/engine/routes/wallets.ts
+
+

corewallet.balanceMap Rendered as an array, with currencyCode and displayAmount added from the wallet's denominations.

+

The native currency plus every enabled token.

+
+
+

Command line

+
balance-map --wallet-id=<walletId> [--token-id=<value>]
+ +
Client-only flags
--token-idoptionalClient-side filter; core has no single-balance accessor.
+ +
+

REST

+

GET/account/{sessionId}/wallet/balance-map

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+
+
Query
+
{
+  walletId: string
+}
+
Example
{
+  "walletId": "FS8xJ2kQ…"
+}
+ + + + +
walletIdstringThe wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns 409 AMBIGUOUS_WALLET_ID with details.candidates.
+
+ + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/account/$SESS/wallet/balance-map?walletId=…'
+
+
+

Response 200

+
+
Response body
+
{
+  balances: {
+    tokenId: string | null;
+    currencyCode: string;
+    nativeAmount: string;
+    displayAmount: string | null;
+    unknownToken: boolean
+  }[]
+}
+
Example
{
+  "balances": [
+    {}
+  ]
+}
+ + + + +
balances{ tokenId: string | null; currencyCode: string; nativeAmount: string; displayAmount: string | null; unknownToken: boolean; }[]One entry per asset the wallet holds, native coin first: tokenId, currencyCode, nativeAmount, displayAmount and unknownToken. A token the plugin reports a balance for but whose config it no longer carries is listed with unknownToken: true and a null displayAmount, rather than failing the whole wallet.
+
+
Errors

404WALLET_NOT_FOUND 409AMBIGUOUS_WALLET_ID 401INVALID_SESSION 401SESSION_EXPIRED

+
+

Notes

  • On the CLI, omit --token-id for the native asset rather than passing the literal null.
+
+
+

Receive addresses.

+
get-addressessrc/cli/engine/routes/wallets.ts
+
+

corewallet.getAddresses

+ +
+

Command line

+
get-addresses --wallet-id=<walletId> [--token-id=<tokenId>] [--force-index=<forceIndex>]
+ + + +
+

REST

+

GET/account/{sessionId}/wallet/get-addresses

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+
+
Query
+
{
+  walletId: string
+  tokenId?: string | null
+  forceIndex?: number
+}
+
Example
{
+  "walletId": "FS8xJ2kQ…",
+  "tokenId": "FS8xJ2kQ…",
+  "forceIndex": 0
+}
+ + + + + + + + + + + + + + +
walletIdstringThe wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns 409 AMBIGUOUS_WALLET_ID with details.candidates.
tokenIdstring | null optionalDefaults to the native asset.
forceIndexnumber optionalDerive at a specific index.
+
+ + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/account/$SESS/wallet/get-addresses?walletId=…'
+
+
+

Response 200

+
+
Response body
+
{
+  addresses: unknown[]
+}
+
Example
{
+  "addresses": [
+    {}
+  ]
+}
+ + + + +
addressesunknown[]EdgeAddress[]: addressType, publicAddress, nativeBalance.
+
+
Errors

404TOKEN_NOT_FOUND 404WALLET_NOT_FOUND 409AMBIGUOUS_WALLET_ID 401INVALID_SESSION 401SESSION_EXPIRED

+
+ +

Tokens

+

Which tokens a wallet tracks. Enabled tokens are the ones it syncs balances for; detected ones were seen on-chain but are not yet enabled.

+
+
+
+

List a wallet's tokens.

+
wallet-tokenssrc/cli/engine/routes/tokens.ts
+
+

coreEngine composite of the EdgeCurrencyConfig token maps plus wallet.enabledTokenIds and wallet.detectedTokenIds.

+

"Enabled" tokens are the ones the wallet syncs balances for; "detected" ones were seen on-chain but are not yet enabled.

+
+
+

Command line

+
wallet-tokens --wallet-id=<walletId>
+ + + +
+

REST

+

GET/account/{sessionId}/wallet/tokens

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+
+
Query
+
{
+  walletId: string
+}
+
Example
{
+  "walletId": "FS8xJ2kQ…"
+}
+ + + + +
walletIdstringThe wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns 409 AMBIGUOUS_WALLET_ID with details.candidates.
+
+ + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/account/$SESS/wallet/tokens?walletId=…'
+
+
+

Response 200

+
+
Response body
+
{
+  allTokens: {
+    [keys: string]: unknown
+  }
+  customTokens: {
+    [keys: string]: unknown
+  }
+  enabledTokenIds: string[]
+  detectedTokenIds: string[]
+}
+
Example
{
+  "allTokens": {},
+  "customTokens": {},
+  "enabledTokenIds": [
+    "string"
+  ],
+  "detectedTokenIds": [
+    "string"
+  ]
+}
+ + + + + + + + + + + + + + + + + + + +
allTokens{ [keys: string]: unknown; }EdgeToken by tokenId: everything the plugin ships with, plus this account’s own. builtinTokens is not returned separately — it is this map minus customTokens, and sending both wrote the whole built-in list down the socket twice.
customTokens{ [keys: string]: unknown; }EdgeToken by tokenId: tokens this account added by hand.
enabledTokenIdsstring[]Which of the above the wallet is actually tracking.
detectedTokenIdsstring[]Seen on-chain but not enabled, so their balances are not synced.
+
+
Errors

404WALLET_NOT_FOUND 409AMBIGUOUS_WALLET_ID 401INVALID_SESSION 401SESSION_EXPIRED

+
+ +
+
+

Set the enabled token set.

+
change-enabled-token-idssrc/cli/engine/routes/tokens.ts
+
+

corewallet.changeEnabledTokenIds

+

Absolute: anything missing from tokenIds is disabled. Core has only this setter, so there is no add or remove call.

+
+
+

Command line

+
change-enabled-token-ids --wallet-id=<walletId> --token-ids='<json>' [--add=<value>] [--remove=<value>]
+ +
Client-only flags
--addoptionalRead the current set, add this id, write it back.
--removeoptionalRead the current set, drop this id, write it back.
+ +
+

REST

+

POST/account/{sessionId}/wallet/change-enabled-token-ids

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + +
+
Request body
+
{
+  walletId: string
+  tokenIds: string[]
+}
+
Example
{
+  "walletId": "FS8xJ2kQ…",
+  "tokenIds": [
+    "string"
+  ]
+}
+ + + + + + + + + +
walletIdstringThe wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns 409 AMBIGUOUS_WALLET_ID with details.candidates.
tokenIdsstring[]The complete desired set.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"walletId":"FS8xJ2kQ…","tokenIds":["string"]}' \
+  'http://localhost/account/$SESS/wallet/change-enabled-token-ids'
+
+
+

Response 200

+
+
Response body
+
{
+  enabledTokenIds: string[]
+}
+
Example
{
+  "enabledTokenIds": [
+    "string"
+  ]
+}
+ + + + +
enabledTokenIdsstring[]The wallet’s enabled tokens after the change, not just what changed.
+
+
Errors

400BAD_REQUEST 404WALLET_NOT_FOUND 409AMBIGUOUS_WALLET_ID 401INVALID_SESSION 401SESSION_EXPIRED

+
+

Notes

  • The command's --add and --remove are client-side sugar over this one route, and cost an extra read first.
+

Transactions

+

Reading transaction history, exporting it, and editing its metadata.

+
+
+
+

List or export a wallet's transactions.

+
get-transactionssrc/cli/engine/routes/transactions.ts
+
+

corewallet.getTransactions

Differs from core:

  • limit — Engine-side paging; core returns every match.
  • offset — Engine-side paging; core returns every match.
  • fiat — Selects the currency the engine values each transaction in.
  • exportFormat — Engine-side rendering to CSV, QBO or Bitwave.
  • bitwaveAccountId — Required by the Bitwave export format.
  • saveExportPrefs — Opt-in write of the wallet’s synced exportTxInfo.json, the record the GUI export scene reads back.
+

Reads history, overlays the display metadata the GUI shows, fills historical fiat, and optionally formats the result — all on this one call.

+
+
+

Command line

+
get-transactions --wallet-id=<walletId> [--token-id=<tokenId>] [--limit=<limit>] [--offset=<offset>] [--save-export-prefs[=false]] [--start-date=<startDate>] [--end-date=<endDate>] [--search-string=<searchString>] [--spam-threshold=<spamThreshold>] [--fiat=<fiat>] [--export-format=<exportFormat>] [--bitwave-account=<bitwaveAccountId>] [--out=<value>]
+ +
Client-only flags
--outoptionalWhere to write the returned files. One format: the path. Several: a stem, plus .csv / .qbo / .bitwave.csv.
+ +
+

REST

+

GET/account/{sessionId}/wallet/get-transactions

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+
+
Query
+
{
+  walletId: string
+  tokenId?: string | null
+  limit?: number
+  offset?: number
+  saveExportPrefs?: boolean
+  startDate?: Date
+  endDate?: Date
+  searchString?: string
+  spamThreshold?: string
+  fiat?: string
+  exportFormat?: string
+  bitwaveAccountId?: string
+}
+
Example
{
+  "walletId": "FS8xJ2kQ…",
+  "tokenId": "FS8xJ2kQ…",
+  "limit": 0,
+  "offset": 0,
+  "saveExportPrefs": true,
+  "startDate": {
+    "type": "string",
+    "format": "date-time"
+  },
+  "endDate": {
+    "type": "string",
+    "format": "date-time"
+  },
+  "searchString": "string",
+  "spamThreshold": "string",
+  "fiat": "2026-09-02T16:35:00.000Z",
+  "exportFormat": "2026-09-02T16:35:00.000Z",
+  "bitwaveAccountId": "FS8xJ2kQ…"
+}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
walletIdstringThe wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns 409 AMBIGUOUS_WALLET_ID with details.candidates.
tokenIdstring | null optionalDefaults to the native asset.
limitnumber optionalHow many to return. Defaults to 99; pass 0 for every transaction from offset on. total in the response says how many matched, so a caller can page with offset.
offsetnumber optionalWhere to start. Defaults to 0.
saveExportPrefsboolean optionalSave bitwaveAccountId and the chosen formats into the wallet’s synced exportTxInfo.json, which the GUI export scene reads back. Off by default: a read does not change saved preferences.
startDateDate optionalISO-8601, or epoch milliseconds.
endDateDate optionalISO-8601, or epoch milliseconds.
searchStringstring optionalMatches payee, category, notes and txid.
spamThresholdstring optionalNative-amount floor. Omitted, the account spam-filter setting applies to a listing and nothing is filtered from an exportFormat export; a value always overrides both, and 0 shows everything. An empty value reads as omitted, like every other query parameter.
fiatstring optionalThree-letter ISO 4217 code. Defaults to the account defaultIsoFiat.
exportFormatstring optionalComma list of csv, qbo, bitwave.
bitwaveAccountIdstring optionalA 400 unless exportFormat includes bitwave.
+
+ + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/account/$SESS/wallet/get-transactions?walletId=…'
+
+
+

Response 200

+

{ transactions, total, isoFiat }, or { ok, isoFiat, total, files } when exportFormat is set. Each transaction carries metadata.name, metadata.category and metadata.notes as localized prose in the engine’s boot locale — the engine has no per-request locale, so a second shell with a different LANG reuses the first engine and gets its language. displayInfo beside them carries the machine values the prose was derived from (direction, assetActionType, actionType, and the category split whose category member is one of transfer, exchange, expense, income), so a caller can render its own text.

+
unknown
+
Errors

400BAD_REQUEST 400MISSING_BITWAVE_ACCOUNT_ID 404TOKEN_NOT_FOUND 404WALLET_NOT_FOUND 409AMBIGUOUS_WALLET_ID 401INVALID_SESSION 401SESSION_EXPIRED

+
+

Notes

  • The metadata overlay and the fiat fill are response-only. Neither writes to disk.
  • limit and offset apply before the fiat fill, so a large page asks the rates server about more dates. Unpriced dates are batched into one request per 100, and every rate is cached for the life of the engine, so a second listing of the same range needs none.
  • This is the one GET that can write: passing bitwaveAccountId persists it to exportTxInfo.json on the wallet disklet.
  • An export is unfiltered. The account spam-filter setting applies to a listing, the way the GUI's transaction list applies it, and not to an exportFormat export, the way the GUI's Export button does not — dropping rows from an accounting export is not a display convenience. An explicit spamThreshold applies to both.
  • An export of a range with no transactions in it succeeds and writes an empty CSV — no header row, zero bytes. The CSV exporter takes its column names from the first record, so with no records there is no header to write, and an empty range is a successful export of nothing rather than a failure. QBO differs because its envelope does not depend on the records, so --export-format=csv,qbo over an empty range writes one empty file and one with only an envelope in it.
+
+
+

Count transactions in a wallet.

+
get-num-transactionssrc/cli/engine/routes/transactions.ts
+
+

corewallet.getNumTransactions

+

Cheaper than listing when only the total matters.

+
+
+

Command line

+
get-num-transactions --wallet-id=<walletId> [--token-id=<tokenId>]
+ + + +
+

REST

+

GET/account/{sessionId}/wallet/get-num-transactions

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+
+
Query
+
{
+  walletId: string
+  tokenId?: string | null
+}
+
Example
{
+  "walletId": "FS8xJ2kQ…",
+  "tokenId": "FS8xJ2kQ…"
+}
+ + + + + + + + + +
walletIdstringThe wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns 409 AMBIGUOUS_WALLET_ID with details.candidates.
tokenIdstring | null optionalDefaults to the native asset.
+
+ + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/account/$SESS/wallet/get-num-transactions?walletId=…'
+
+
+

Response 200

+
+
Response body
+
{
+  numTransactions: number
+}
+
Example
{
+  "numTransactions": 0
+}
+ + + + +
numTransactionsnumberEvery transaction the wallet knows of.
+
+
Errors

404TOKEN_NOT_FOUND 404WALLET_NOT_FOUND 409AMBIGUOUS_WALLET_ID 401INVALID_SESSION 401SESSION_EXPIRED

+
+

Notes

  • Unfiltered: spamThreshold, dates and searchString do not apply, so this can exceed total from get-transactions.
+
+
+

Save transaction metadata.

+
save-tx-metadatasrc/cli/engine/routes/transactions.ts
+
+

corewallet.saveTxMetadata

+

One of the paths that write transaction metadata to disk. The others are save-tx-action, which writes savedAction and assetAction to the same file, and save-tx and spend, both of which re-apply the caller's metadata through saveTxAndMetadata after the transaction is saved.

+
+
+

Command line

+
save-tx-metadata --wallet-id=<walletId> --txid=<txid> [--token-id=<tokenId>] --metadata='<json>'
+ + + +
+

REST

+

POST/account/{sessionId}/wallet/save-tx-metadata

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + +
+
Request body
+
{
+  walletId: string
+  txid: string
+  tokenId?: string | null
+  metadata: EdgeMetadataChange
+}
+
Example
{
+  "walletId": "FS8xJ2kQ…",
+  "txid": "FS8xJ2kQ…",
+  "tokenId": "FS8xJ2kQ…",
+  "metadata": "<EdgeMetadataChange>"
+}
+ + + + + + + + + + + + + + + + + + + +
walletIdstringThe wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns 409 AMBIGUOUS_WALLET_ID with details.candidates.
txidstringWhich transaction to tag.
tokenIdstring | null optionalDefaults to the native asset.
metadataEdgeMetadataChangeEdgeMetadataChange: name, category, notes, exchangeAmount, bizId. null on a field deletes it; omitting the field leaves it unchanged.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"walletId":"FS8xJ2kQ…","txid":"FS8xJ2kQ…","tokenId":"FS8xJ2kQ…","metadata":"<EdgeMetadataChange>"}' \
+  'http://localhost/account/$SESS/wallet/save-tx-metadata'
+
+ +

Notes

  • metadata is an EdgeMetadataChange, so an explicit null clears a field while an omitted one is left alone.
+
+
+

Save a transaction action.

+
save-tx-actionsrc/cli/engine/routes/transactions.ts
+
+

corewallet.saveTxAction

+

Records what a transaction was — a swap, a stake — beyond its metadata.

+
+
+

Command line

+
save-tx-action --wallet-id=<walletId> --txid=<txid> [--token-id=<tokenId>] --saved-action='<json>' [--asset-action='<json>']
+ + + +
+

REST

+

POST/account/{sessionId}/wallet/save-tx-action

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + +
+
Request body
+
{
+  walletId: string
+  txid: string
+  tokenId?: string | null
+  savedAction: EdgeTxActionSwap | EdgeTxActionSwapSend | EdgeTxActionStake | EdgeTxActionFiat | EdgeTxActionTokenApproval | EdgeTxActionGiftCard
+  assetAction?: EdgeAssetAction
+}
+
Example
{
+  "walletId": "FS8xJ2kQ…",
+  "txid": "FS8xJ2kQ…",
+  "tokenId": "FS8xJ2kQ…",
+  "savedAction": "<EdgeTxActionSwap>",
+  "assetAction": "<EdgeAssetAction>"
+}
+ + + + + + + + + + + + + + + + + + + + + + + + +
walletIdstringThe wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns 409 AMBIGUOUS_WALLET_ID with details.candidates.
txidstringWhich transaction to annotate.
tokenIdstring | null optionalDefaults to the native asset.
savedActionEdgeTxActionSwap | EdgeTxActionSwapSend | EdgeTxActionStake | EdgeTxActionFiat | EdgeTxActionTokenApproval | EdgeTxActionGiftCardEdgeTxAction describing what happened, discriminated on actionType: swap, stake, fiat, tokenApproval or giftCard.
assetActionEdgeAssetAction optionalEdgeAssetAction: one assetActionType.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"walletId":"FS8xJ2kQ…","txid":"FS8xJ2kQ…","tokenId":"FS8xJ2kQ…","savedAction":"<EdgeTxActionSwap>","assetAction":"<EdgeAssetAction>"}' \
+  'http://localhost/account/$SESS/wallet/save-tx-action'
+
+ +

Notes

  • When assetAction is omitted it defaults to { assetActionType: 'transfer' }.
+

Spending

+

Two ways to send funds. spend does the whole thing in one call; the staged workflow — make-spend, sign-tx, broadcast-tx, save-tx — hands back an object handle at each step so fees can be inspected before committing.

+
+
+
+

Largest sendable amount.

+
get-max-spendablesrc/cli/engine/routes/spend.ts
+
+

corewallet.getMaxSpendable

Differs from core:

  • to — Shorthand the engine expands into spendTargets, so a one-output send needs no nested JSON.
  • nativeAmount — Amount for the to shorthand, in the smallest unit.
  • amount — Alias of nativeAmount for the to shorthand: the chain’s smallest unit, not whole coins.
+

What empties the wallet after fees. A destination is still required, since fees depend on it.

+
+
+

Command line

+
get-max-spendable --wallet-id=<walletId> [--spend-info='<json>'] [--to=<to>] [--native-amount=<nativeAmount>] [--amount=<amount>] [--token-id=<tokenId>] [--metadata='<json>']
+ + + +
+

REST

+

POST/account/{sessionId}/wallet/get-max-spendable

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + +
+
Request body
+
{
+  walletId: string
+  spendInfo?: {
+    spendTargets: {
+      publicAddress: string;
+      nativeAmount: string | undefined;
+      uniqueIdentifier: string | undefined;
+      memo: string | undefined;
+      otherParams: {
+        [keys: string]: unknown
+      } | undefined
+    }[];
+    tokenId: string | null;
+    metadata: EdgeMetadata | undefined;
+    networkFeeOption: string | undefined;
+    customNetworkFee: {
+      [keys: string]: unknown
+    } | undefined;
+    rbfTxid: string | undefined;
+    memos: {
+      [keys: string]: unknown
+    }[] | undefined;
+    assetAction: EdgeAssetAction | undefined;
+    savedAction: EdgeTxAction | undefined;
+    otherParams: {
+      [keys: string]: unknown
+    } | undefined
+  }
+  to?: string
+  nativeAmount?: string
+  amount?: string
+  tokenId?: string | null
+  metadata?: EdgeMetadata
+}
+
Example
{
+  "walletId": "FS8xJ2kQ…",
+  "spendInfo": {},
+  "to": "string",
+  "nativeAmount": "12345",
+  "amount": "12345",
+  "tokenId": "FS8xJ2kQ…",
+  "metadata": "<EdgeMetadata>"
+}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
walletIdstringThe wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns 409 AMBIGUOUS_WALLET_ID with details.candidates.
spendInfo{ spendTargets: { publicAddress: string; nativeAmount: string | undefined; uniqueIdentifier: string | undefined; memo: string | undefined; otherParams: { [keys: string]: unknown; } | undefined; }[]; tokenId: string | null; metadata: EdgeMetadata | undefined; networkFeeOption: string | undefined; customNetworkFee: { [keys: string]: unknown; } | undefined; rbfTxid: string | undefined; memos: { [keys: string]: unknown; }[] | undefined; assetAction: EdgeAssetAction | undefined; savedAction: EdgeTxAction | undefined; otherParams: { [keys: string]: unknown; } | undefined; } optionalA full EdgeSpendInfo, used as-is when present. spendTargets is required.
tostring optionalAddress or BIP21 URI, run through wallet.parseUri.
nativeAmountstring optionalHow much, in native units.
amountstring optionalAlias of nativeAmount.
tokenIdstring | null optionalDefaults to the native asset.
metadataEdgeMetadata optionalWins over anything parsed out of the URI.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"walletId":"FS8xJ2kQ…","spendInfo":{},"to":"string","nativeAmount":"12345","amount":"12345","tokenId":"FS8xJ2kQ…","metadata":"<EdgeMetadata>"}' \
+  'http://localhost/account/$SESS/wallet/get-max-spendable'
+
+
+

Response 200

+
+
Response body
+
{
+  nativeAmount: string
+}
+
Example
{
+  "nativeAmount": "12345"
+}
+ + + + +
nativeAmountstringThe most this wallet can send.
+
+
Errors

404TOKEN_NOT_FOUND 422INSUFFICIENT_FUNDS 400BAD_REQUEST 503NETWORK_ERROR 404WALLET_NOT_FOUND 409AMBIGUOUS_WALLET_ID 401INVALID_SESSION 401SESSION_EXPIRED

+
+ +
+
+

Send funds.

+
spendsrc/cli/engine/routes/spend.ts
+
+

coreGUI composite: makeSpend, signTx, broadcastTx and saveTx together.

+

makeSpend, then signTx, then optionally broadcastTx and saveTx, in one request. broadcast and save both default to true, so a bare body with a destination and an amount moves real money. A completed spend leaves no handle behind.

+
+
+

Command line

+
spend [--use-max[=false]] [--dry-run[=false]] [--broadcast[=false]] [--save[=false]] --wallet-id=<walletId> [--spend-info='<json>'] [--to=<to>] [--native-amount=<nativeAmount>] [--amount=<amount>] [--token-id=<tokenId>] [--metadata='<json>']
+
spend-max — Send a wallet’s entire spendable balance.
spend-max [--dry-run[=false]] [--broadcast[=false]] [--save[=false]] --wallet-id=<walletId> [--spend-info='<json>'] [--to=<to>] [--native-amount=<nativeAmount>] [--amount=<amount>] [--token-id=<tokenId>] [--metadata='<json>']

The same route with useMax preset, so it sends everything.

+
+ + +
+

REST

+

POST/account/{sessionId}/wallet/spend

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + +
+
Request body
+
{
+  useMax?: boolean
+  dryRun?: boolean
+  broadcast?: boolean
+  save?: boolean
+  walletId: string
+  spendInfo?: {
+    spendTargets: {
+      publicAddress: string;
+      nativeAmount: string | undefined;
+      uniqueIdentifier: string | undefined;
+      memo: string | undefined;
+      otherParams: {
+        [keys: string]: unknown
+      } | undefined
+    }[];
+    tokenId: string | null;
+    metadata: EdgeMetadata | undefined;
+    networkFeeOption: string | undefined;
+    customNetworkFee: {
+      [keys: string]: unknown
+    } | undefined;
+    rbfTxid: string | undefined;
+    memos: {
+      [keys: string]: unknown
+    }[] | undefined;
+    assetAction: EdgeAssetAction | undefined;
+    savedAction: EdgeTxAction | undefined;
+    otherParams: {
+      [keys: string]: unknown
+    } | undefined
+  }
+  to?: string
+  nativeAmount?: string
+  amount?: string
+  tokenId?: string | null
+  metadata?: EdgeMetadata
+}
+
Example
{
+  "useMax": true,
+  "dryRun": true,
+  "broadcast": true,
+  "save": true,
+  "walletId": "FS8xJ2kQ…",
+  "spendInfo": {},
+  "to": "string",
+  "nativeAmount": "12345",
+  "amount": "12345",
+  "tokenId": "FS8xJ2kQ…",
+  "metadata": "<EdgeMetadata>"
+}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
useMaxboolean optionalReplace the first target’s amount with the maximum.
dryRunboolean optionalBuild only. Never signs or broadcasts.
broadcastboolean optionalDefaults to true. With false the transaction is signed and not sent, so save then defaults to false as well — recording an unsent transaction marks its inputs spent locally for something the network will never confirm.
saveboolean optionalRecord the transaction in the wallet. Defaults to whatever broadcast is. Setting it true alongside broadcast: false is refused: use --dry-run, or the staged make-spend → sign-tx → broadcast-tx → save-tx flow.
walletIdstringThe wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns 409 AMBIGUOUS_WALLET_ID with details.candidates.
spendInfo{ spendTargets: { publicAddress: string; nativeAmount: string | undefined; uniqueIdentifier: string | undefined; memo: string | undefined; otherParams: { [keys: string]: unknown; } | undefined; }[]; tokenId: string | null; metadata: EdgeMetadata | undefined; networkFeeOption: string | undefined; customNetworkFee: { [keys: string]: unknown; } | undefined; rbfTxid: string | undefined; memos: { [keys: string]: unknown; }[] | undefined; assetAction: EdgeAssetAction | undefined; savedAction: EdgeTxAction | undefined; otherParams: { [keys: string]: unknown; } | undefined; } optionalA full EdgeSpendInfo, used as-is when present. spendTargets is required.
tostring optionalAddress or BIP21 URI, run through wallet.parseUri.
nativeAmountstring optionalHow much, in native units.
amountstring optionalAlias of nativeAmount.
tokenIdstring | null optionalDefaults to the native asset.
metadataEdgeMetadata optionalWins over anything parsed out of the URI.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"useMax":true,"dryRun":true,"broadcast":true,"save":true,"walletId":"FS8xJ2kQ…","spendInfo":{},"to":"string","nativeAmount":"12345","amount":"12345","tokenId":"FS8xJ2kQ…","metadata":"<EdgeMetadata>"}' \
+  'http://localhost/account/$SESS/wallet/spend'
+
+
+

Response 200

+

{ transaction }, plus saveError when the broadcast succeeded but saving failed. With dryRun, a TransactionHandle instead.

+
unknown
+
Errors

404TOKEN_NOT_FOUND 422INSUFFICIENT_FUNDS 422DUST_SPEND 422PENDING_FUNDS 422SPEND_TO_SELF 400NO_AMOUNT_SPECIFIED 400BAD_REQUEST 503NETWORK_ERROR 404WALLET_NOT_FOUND 409AMBIGUOUS_WALLET_ID 401INVALID_SESSION 401SESSION_EXPIRED

+
+

Notes

  • BIP21 label and message from to become metadata name and notes. An explicit metadata object wins.
  • saveError is the case to handle. Once broadcast, the money is gone, so a failure inside saveTx cannot throw — it would hide the txid of a real payment. The response is 200 with the transaction plus saveError.
  • With dryRun, only makeSpend runs and the response is a transaction handle that expires in 5 minutes.
+
+
+

Build an unsigned transaction.

+
make-spendsrc/cli/engine/routes/spend.ts
+
+

corewallet.makeSpend

Differs from core:

  • to — Shorthand the engine expands into spendTargets, so a one-output send needs no nested JSON.
  • nativeAmount — Amount for the to shorthand, in the smallest unit.
  • amount — Alias of nativeAmount for the to shorthand: the chain’s smallest unit, not whole coins.
+

First step of the staged workflow: nothing is signed and no funds move. Inspect transaction.networkFee on the result before signing.

+
+
+

Command line

+
make-spend --wallet-id=<walletId> [--spend-info='<json>'] [--to=<to>] [--native-amount=<nativeAmount>] [--amount=<amount>] [--token-id=<tokenId>] [--metadata='<json>']
+ + + +
+

REST

+

POST/account/{sessionId}/wallet/make-spend

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + +
+
Request body
+
{
+  walletId: string
+  spendInfo?: {
+    spendTargets: {
+      publicAddress: string;
+      nativeAmount: string | undefined;
+      uniqueIdentifier: string | undefined;
+      memo: string | undefined;
+      otherParams: {
+        [keys: string]: unknown
+      } | undefined
+    }[];
+    tokenId: string | null;
+    metadata: EdgeMetadata | undefined;
+    networkFeeOption: string | undefined;
+    customNetworkFee: {
+      [keys: string]: unknown
+    } | undefined;
+    rbfTxid: string | undefined;
+    memos: {
+      [keys: string]: unknown
+    }[] | undefined;
+    assetAction: EdgeAssetAction | undefined;
+    savedAction: EdgeTxAction | undefined;
+    otherParams: {
+      [keys: string]: unknown
+    } | undefined
+  }
+  to?: string
+  nativeAmount?: string
+  amount?: string
+  tokenId?: string | null
+  metadata?: EdgeMetadata
+}
+
Example
{
+  "walletId": "FS8xJ2kQ…",
+  "spendInfo": {},
+  "to": "string",
+  "nativeAmount": "12345",
+  "amount": "12345",
+  "tokenId": "FS8xJ2kQ…",
+  "metadata": "<EdgeMetadata>"
+}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
walletIdstringThe wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns 409 AMBIGUOUS_WALLET_ID with details.candidates.
spendInfo{ spendTargets: { publicAddress: string; nativeAmount: string | undefined; uniqueIdentifier: string | undefined; memo: string | undefined; otherParams: { [keys: string]: unknown; } | undefined; }[]; tokenId: string | null; metadata: EdgeMetadata | undefined; networkFeeOption: string | undefined; customNetworkFee: { [keys: string]: unknown; } | undefined; rbfTxid: string | undefined; memos: { [keys: string]: unknown; }[] | undefined; assetAction: EdgeAssetAction | undefined; savedAction: EdgeTxAction | undefined; otherParams: { [keys: string]: unknown; } | undefined; } optionalA full EdgeSpendInfo, used as-is when present. spendTargets is required.
tostring optionalAddress or BIP21 URI, run through wallet.parseUri.
nativeAmountstring optionalHow much, in native units.
amountstring optionalAlias of nativeAmount.
tokenIdstring | null optionalDefaults to the native asset.
metadataEdgeMetadata optionalWins over anything parsed out of the URI.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"walletId":"FS8xJ2kQ…","spendInfo":{},"to":"string","nativeAmount":"12345","amount":"12345","tokenId":"FS8xJ2kQ…","metadata":"<EdgeMetadata>"}' \
+  'http://localhost/account/$SESS/wallet/make-spend'
+
+
+

Response 200

+
+
Response body
+
{
+  kind: string
+  transaction: unknown
+  objectId: string
+  createdAt: string
+  expiresAt: string
+  sessionId?: string
+  walletId?: string
+}
+
Example
{
+  "kind": "string",
+  "transaction": {},
+  "objectId": "FS8xJ2kQ…",
+  "createdAt": "2026-09-02T16:35:00.000Z",
+  "expiresAt": "2026-09-02T16:35:00.000Z",
+  "sessionId": "FS8xJ2kQ…",
+  "walletId": "FS8xJ2kQ…"
+}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
kindstringWhat the handle refers to, which decides the calls that accept it.
transactionunknownEdgeTransaction as it stands after this step. Unsigned after make-spend, signed after sign-tx, and carrying a txid once broadcast.
objectIdstringHandle for the value the engine is holding. Pass it to the calls that consume it.
createdAtstringWhen the engine took the handle.
expiresAtstringWhen the engine drops the handle. Handles live 5 minutes.
sessionIdstring optionalSession that created the handle; only that session may use it.
walletIdstring optionalWallet the handle is bound to, when it belongs to one.
+
+
Errors

404TOKEN_NOT_FOUND 422INSUFFICIENT_FUNDS 422DUST_SPEND 400NO_AMOUNT_SPECIFIED 400BAD_REQUEST 404WALLET_NOT_FOUND 409AMBIGUOUS_WALLET_ID 401INVALID_SESSION 401SESSION_EXPIRED

+
+ +
+
+

Sign a staged transaction.

+
sign-txsrc/cli/engine/routes/spend.ts
+
+

corewallet.signTx

+

Keeps the same handle and pushes its expiry out another five minutes.

+
+
+

Command line

+
sign-tx <objectId>
+ + + +
+

REST

+

POST/account/{sessionId}/sign-tx/{objectId}

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
objectIdstringFrom make-spend.
+ + + +
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"objectId":"FS8xJ2kQ…"}' \
+  'http://localhost/account/$SESS/sign-tx/$OBJECTID'
+
+
+

Response 200

+
+
Response body
+
{
+  kind: string
+  transaction: unknown
+  objectId: string
+  createdAt: string
+  expiresAt: string
+  sessionId?: string
+  walletId?: string
+}
+
Example
{
+  "kind": "string",
+  "transaction": {},
+  "objectId": "FS8xJ2kQ…",
+  "createdAt": "2026-09-02T16:35:00.000Z",
+  "expiresAt": "2026-09-02T16:35:00.000Z",
+  "sessionId": "FS8xJ2kQ…",
+  "walletId": "FS8xJ2kQ…"
+}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
kindstringWhat the handle refers to, which decides the calls that accept it.
transactionunknownEdgeTransaction as it stands after this step. Unsigned after make-spend, signed after sign-tx, and carrying a txid once broadcast.
objectIdstringHandle for the value the engine is holding. Pass it to the calls that consume it.
createdAtstringWhen the engine took the handle.
expiresAtstringWhen the engine drops the handle. Handles live 5 minutes.
sessionIdstring optionalSession that created the handle; only that session may use it.
walletIdstring optionalWallet the handle is bound to, when it belongs to one.
+
+
Errors

400BAD_REQUEST 404OBJECT_NOT_FOUND 410OBJECT_EXPIRED 400OBJECT_KIND_MISMATCH 400OBJECT_WALLET_MISMATCH 400OBJECT_SESSION_MISMATCH 401INVALID_SESSION 401SESSION_EXPIRED

+
+ +
+
+

Broadcast a signed transaction.

+
broadcast-txsrc/cli/engine/routes/spend.ts
+
+

corewallet.broadcastTx

+

The irreversible step: once this returns, the funds have left the wallet.

+
+
+

Command line

+
broadcast-tx <objectId>
+ + + +
+

REST

+

POST/account/{sessionId}/broadcast-tx/{objectId}

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
objectIdstringFrom sign-tx.
+ + + +
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"objectId":"FS8xJ2kQ…"}' \
+  'http://localhost/account/$SESS/broadcast-tx/$OBJECTID'
+
+
+

Response 200

+

The handle survives, so save-tx can still run.

+
+
Response body
+
{
+  kind: string
+  transaction: unknown
+  objectId: string
+  createdAt: string
+  expiresAt: string
+  sessionId?: string
+  walletId?: string
+}
+
Example
{
+  "kind": "string",
+  "transaction": {},
+  "objectId": "FS8xJ2kQ…",
+  "createdAt": "2026-09-02T16:35:00.000Z",
+  "expiresAt": "2026-09-02T16:35:00.000Z",
+  "sessionId": "FS8xJ2kQ…",
+  "walletId": "FS8xJ2kQ…"
+}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
kindstringWhat the handle refers to, which decides the calls that accept it.
transactionunknownEdgeTransaction as it stands after this step. Unsigned after make-spend, signed after sign-tx, and carrying a txid once broadcast.
objectIdstringHandle for the value the engine is holding. Pass it to the calls that consume it.
createdAtstringWhen the engine took the handle.
expiresAtstringWhen the engine drops the handle. Handles live 5 minutes.
sessionIdstring optionalSession that created the handle; only that session may use it.
walletIdstring optionalWallet the handle is bound to, when it belongs to one.
+
+
Errors

400BAD_REQUEST 503NETWORK_ERROR 404OBJECT_NOT_FOUND 410OBJECT_EXPIRED 400OBJECT_KIND_MISMATCH 400OBJECT_WALLET_MISMATCH 400OBJECT_SESSION_MISMATCH 401INVALID_SESSION 401SESSION_EXPIRED

+
+

Notes

  • Broadcasting does not record the transaction locally. Follow with save-tx, or it stays missing from history until a sync finds it.
+
+
+

Record a transaction and release its handle.

+
save-txsrc/cli/engine/routes/spend.ts
+
+

corewallet.saveTx

+

Final step. The handle is gone afterwards, so a second call is a 404.

+
+
+

Command line

+
save-tx <objectId>
+ + + +
+

REST

+

POST/account/{sessionId}/save-tx/{objectId}

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
objectIdstringThe handle to persist and release.
+ + + +
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"objectId":"FS8xJ2kQ…"}' \
+  'http://localhost/account/$SESS/save-tx/$OBJECTID'
+
+
+

Response 200

+
+
Response body
+
{
+  ok: boolean
+  objectId: string
+}
+
Example
{
+  "ok": true,
+  "objectId": "FS8xJ2kQ…"
+}
+ + + + + + + + + +
okbooleanAlways true; a failure arrives as an error envelope.
objectIdstringThe handle this call consumed. It is now expired.
+
+
Errors

400BAD_REQUEST 404OBJECT_NOT_FOUND 410OBJECT_EXPIRED 400OBJECT_KIND_MISMATCH 400OBJECT_WALLET_MISMATCH 400OBJECT_SESSION_MISMATCH 401INVALID_SESSION 401SESSION_EXPIRED

+
+ +
+
+

Fee-bump a pending transaction.

+
acceleratesrc/cli/engine/routes/spend.ts
+
+

corewallet.accelerate

Differs from core:

  • transaction — Core names the parameter tx. Spelled out here to match the transaction field every staged-transaction response returns.
+

Replace-by-fee, where the plugin supports it. Returns a new unsigned transaction to sign and broadcast.

+
+
+

Command line

+
accelerate --wallet-id=<walletId> [--object-id=<objectId>] [--transaction='<json>']
+ + + +
+

REST

+

POST/account/{sessionId}/wallet/accelerate

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + +
+
Request body
+
{
+  walletId: string
+  objectId?: string
+  transaction?: {
+    txid: string;
+    currencyCode: string | undefined;
+    nativeAmount: string | undefined;
+    networkFee: string | undefined;
+    walletId: string | undefined
+  }
+}
+
Example
{
+  "walletId": "FS8xJ2kQ…",
+  "objectId": "FS8xJ2kQ…",
+  "transaction": {}
+}
+ + + + + + + + + + + + + + +
walletIdstringThe wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns 409 AMBIGUOUS_WALLET_ID with details.candidates.
objectIdstring optionalHandle of the transaction to bump.
transaction{ txid: string; currencyCode: string | undefined; nativeAmount: string | undefined; networkFee: string | undefined; walletId: string | undefined; } optionalOr the transaction itself.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"walletId":"FS8xJ2kQ…","objectId":"FS8xJ2kQ…","transaction":{}}' \
+  'http://localhost/account/$SESS/wallet/accelerate'
+
+
+

Response 200

+

Given objectId the same handle is updated; given a transaction a new one is created.

+
+
Response body
+
{
+  kind: string
+  transaction: unknown
+  objectId: string
+  createdAt: string
+  expiresAt: string
+  sessionId?: string
+  walletId?: string
+}
+
Example
{
+  "kind": "string",
+  "transaction": {},
+  "objectId": "FS8xJ2kQ…",
+  "createdAt": "2026-09-02T16:35:00.000Z",
+  "expiresAt": "2026-09-02T16:35:00.000Z",
+  "sessionId": "FS8xJ2kQ…",
+  "walletId": "FS8xJ2kQ…"
+}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
kindstringWhat the handle refers to, which decides the calls that accept it.
transactionunknownEdgeTransaction as it stands after this step. Unsigned after make-spend, signed after sign-tx, and carrying a txid once broadcast.
objectIdstringHandle for the value the engine is holding. Pass it to the calls that consume it.
createdAtstringWhen the engine took the handle.
expiresAtstringWhen the engine drops the handle. Handles live 5 minutes.
sessionIdstring optionalSession that created the handle; only that session may use it.
walletIdstring optionalWallet the handle is bound to, when it belongs to one.
+
+
Errors

400BAD_REQUEST 404OBJECT_NOT_FOUND 410OBJECT_EXPIRED 400OBJECT_KIND_MISMATCH 400OBJECT_WALLET_MISMATCH 400OBJECT_SESSION_MISMATCH 404WALLET_NOT_FOUND 409AMBIGUOUS_WALLET_ID 401INVALID_SESSION 401SESSION_EXPIRED

+
+

Notes

  • A plugin that cannot accelerate returns 400 rather than a null transaction.
+
+
+

Sweep private keys into this wallet.

+
sweep-private-keyssrc/cli/engine/routes/spend.ts
+
+

corewallet.sweepPrivateKeys

Differs from core:

  • spendInfo — Core names this one edgeSpendInfo while makeSpend names the same type spendInfo. Both are spendInfo here.
+

Builds a transaction moving everything from an external key. Returns an unsigned handle: sign, broadcast and save it like any staged spend.

+
+
+

Command line

+
sweep-private-keys --wallet-id=<walletId> --spend-info='<json>'
+ + + +
+

REST

+

POST/account/{sessionId}/wallet/sweep-private-keys

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + +
+
Request body
+
{
+  walletId: string
+  spendInfo: {
+    privateKeys: string[];
+    tokenId: string | null;
+    spendTargets: {
+      publicAddress: string;
+      nativeAmount: string | undefined;
+      uniqueIdentifier: string | undefined;
+      memo: string | undefined;
+      otherParams: {
+        [keys: string]: unknown
+      } | undefined
+    }[];
+    metadata: EdgeMetadata | undefined;
+    memos: {
+      [keys: string]: unknown
+    }[] | undefined
+  }
+}
+
Example
{
+  "walletId": "FS8xJ2kQ…",
+  "spendInfo": {}
+}
+ + + + + + + + + +
walletIdstringThe wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns 409 AMBIGUOUS_WALLET_ID with details.candidates.
spendInfo{ privateKeys: string[]; tokenId: string | null; spendTargets: { publicAddress: string; nativeAmount: string | undefined; uniqueIdentifier: string | undefined; memo: string | undefined; otherParams: { [keys: string]: unknown; } | undefined; }[]; metadata: EdgeMetadata | undefined; memos: { [keys: string]: unknown; }[] | undefined; }The keys to sweep in privateKeys, plus the optional spendTargets, tokenId, metadata and memos of a spend.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"walletId":"FS8xJ2kQ…","spendInfo":{}}' \
+  'http://localhost/account/$SESS/wallet/sweep-private-keys'
+
+
+

Response 200

+
+
Response body
+
{
+  kind: string
+  transaction: unknown
+  objectId: string
+  createdAt: string
+  expiresAt: string
+  sessionId?: string
+  walletId?: string
+}
+
Example
{
+  "kind": "string",
+  "transaction": {},
+  "objectId": "FS8xJ2kQ…",
+  "createdAt": "2026-09-02T16:35:00.000Z",
+  "expiresAt": "2026-09-02T16:35:00.000Z",
+  "sessionId": "FS8xJ2kQ…",
+  "walletId": "FS8xJ2kQ…"
+}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
kindstringWhat the handle refers to, which decides the calls that accept it.
transactionunknownEdgeTransaction as it stands after this step. Unsigned after make-spend, signed after sign-tx, and carrying a txid once broadcast.
objectIdstringHandle for the value the engine is holding. Pass it to the calls that consume it.
createdAtstringWhen the engine took the handle.
expiresAtstringWhen the engine drops the handle. Handles live 5 minutes.
sessionIdstring optionalSession that created the handle; only that session may use it.
walletIdstring optionalWallet the handle is bound to, when it belongs to one.
+
+
Errors

400BAD_REQUEST 422INSUFFICIENT_FUNDS 503NETWORK_ERROR 404TOKEN_NOT_FOUND 404WALLET_NOT_FOUND 409AMBIGUOUS_WALLET_ID 401INVALID_SESSION 401SESSION_EXPIRED

+
+ +
+
+

Sign arbitrary bytes.

+
sign-bytessrc/cli/engine/routes/spend.ts
+
+

corewallet.signBytes

Differs from core:

  • bytes — Core takes a Uint8Array named buf. JSON cannot carry bytes, so this is base64 text.
+

Message signing and proof-of-ownership, for plugins that support it.

+
+
+

Command line

+
sign-bytes --wallet-id=<walletId> [--bytes=<bytes>] [--other-params='<json>']
+ + + +
+

REST

+

POST/account/{sessionId}/wallet/sign-bytes

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + +
+
Request body
+
{
+  walletId: string
+  bytes?: string
+  otherParams?: {
+    [keys: string]: unknown
+  }
+}
+
Example
{
+  "walletId": "FS8xJ2kQ…",
+  "bytes": "string",
+  "otherParams": {}
+}
+ + + + + + + + + + + + + + +
walletIdstringThe wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns 409 AMBIGUOUS_WALLET_ID with details.candidates.
bytesstring optionalBase64. Defaults to empty when absent.
otherParams{ [keys: string]: unknown; } optionalPlugin-specific options. Bitcoin needs { publicAddress }; other plugins take nothing, or refuse the call entirely.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"walletId":"FS8xJ2kQ…","bytes":"string","otherParams":{}}' \
+  'http://localhost/account/$SESS/wallet/sign-bytes'
+
+
+

Response 200

+
+
Response body
+
{
+  signature: string
+}
+
Example
{
+  "signature": "string"
+}
+ + + + +
signaturestringBase64.
+
+
Errors

400BAD_REQUEST 404WALLET_NOT_FOUND 409AMBIGUOUS_WALLET_ID 401INVALID_SESSION 401SESSION_EXPIRED

+
+

Notes

  • Invalid base64 is refused with BAD_REQUEST rather than signed.
  • Support is per plugin, and failures surface as 500 INTERNAL_ERROR from the plugin rather than as a typed error: litecoin answers "litecoin doesn't support signBytes", and bitcoin requires otherParams.publicAddress naming which address to sign with.
+
+
+

Fetch a BIP70 payment request.

+
get-payment-protocol-infosrc/cli/engine/routes/spend.ts
+
+

corewallet.getPaymentProtocolInfo

+

Feed spendTargets from the result into make-spend to pay it.

+
+
+

Command line

+
get-payment-protocol-info --wallet-id=<walletId> --payment-protocol-url=<paymentProtocolUrl>
+ + + +
+

REST

+

GET/account/{sessionId}/wallet/get-payment-protocol-info

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+
+
Query
+
{
+  walletId: string
+  paymentProtocolUrl: string
+}
+
Example
{
+  "walletId": "FS8xJ2kQ…",
+  "paymentProtocolUrl": "string"
+}
+ + + + + + + + + +
walletIdstringThe wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns 409 AMBIGUOUS_WALLET_ID with details.candidates.
paymentProtocolUrlstringThe payment-request URL.
+
+ + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/account/$SESS/wallet/get-payment-protocol-info?walletId=…&paymentProtocolUrl=…'
+
+
+

Response 200

+

EdgePaymentProtocolInfo: domain, memo, merchant, nativeAmount, spendTargets.

+
unknown
+
Errors

400BAD_REQUEST 503NETWORK_ERROR 404WALLET_NOT_FOUND 409AMBIGUOUS_WALLET_ID 401INVALID_SESSION 401SESSION_EXPIRED

+
+ +

URIs

+

Parsing and building BIP21-style payment URIs through the wallet’s own plugin, so chain-specific quirks are handled for you.

+
+
+
+

Parse a payment URI or address.

+
parse-urisrc/cli/engine/routes/uri.ts
+
+

corewallet.parseUri

+

What the GUI address tile does when you paste or scan something.

+
+
+

Command line

+
parse-uri --wallet-id=<walletId> --uri=<uri> [--currency-code=<currencyCode>]
+ + + +
+

REST

+

POST/account/{sessionId}/wallet/parse-uri

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + +
+
Request body
+
{
+  walletId: string
+  uri: string
+  currencyCode?: string
+}
+
Example
{
+  "walletId": "FS8xJ2kQ…",
+  "uri": "string",
+  "currencyCode": "string"
+}
+ + + + + + + + + + + + + + +
walletIdstringThe wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns 409 AMBIGUOUS_WALLET_ID with details.candidates.
uristringA payment URI or a bare address.
currencyCodestring optionalDisambiguates on chains that carry several assets.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"walletId":"FS8xJ2kQ…","uri":"string","currencyCode":"string"}' \
+  'http://localhost/account/$SESS/wallet/parse-uri'
+
+
+

Response 200

+

EdgeParsedUri: publicAddress, nativeAmount, currencyCode, metadata, paymentProtocolUrl, …

+
unknown
+
Errors

400BAD_REQUEST 404WALLET_NOT_FOUND 409AMBIGUOUS_WALLET_ID 401INVALID_SESSION 401SESSION_EXPIRED

+
+

Notes

  • spend and make-spend run their to field through this same call, so parsing separately is only needed to inspect or confirm first.
+
+
+

Build a payment URI.

+
encode-urisrc/cli/engine/routes/uri.ts
+
+

corewallet.encodeUri

+

For a receive screen or a QR code.

+
+
+

Command line

+
encode-uri --wallet-id=<walletId> --public-address=<publicAddress> [--native-amount=<nativeAmount>] [--label=<label>] [--message=<message>] [--currency-code=<currencyCode>]
+ + + +
+

REST

+

POST/account/{sessionId}/wallet/encode-uri

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + +
+
Request body
+
{
+  walletId: string
+  publicAddress: string
+  nativeAmount?: string
+  label?: string
+  message?: string
+  currencyCode?: string
+}
+
Example
{
+  "walletId": "FS8xJ2kQ…",
+  "publicAddress": "string",
+  "nativeAmount": "12345",
+  "label": "string",
+  "message": "string",
+  "currencyCode": "string"
+}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
walletIdstringThe wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns 409 AMBIGUOUS_WALLET_ID with details.candidates.
publicAddressstringWhere the payment should go.
nativeAmountstring optionalAmount, in the native unit.
labelstring optionalBIP21 label; becomes metadata.name when parsed back.
messagestring optionalBIP21 message; becomes metadata.notes.
currencyCodestring optionalDisambiguates on chains that carry several assets.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"walletId":"FS8xJ2kQ…","publicAddress":"string","nativeAmount":"12345","label":"string","message":"string","currencyCode":"string"}' \
+  'http://localhost/account/$SESS/wallet/encode-uri'
+
+
+

Response 200

+
+
Response body
+
{
+  uri: string
+}
+
Example
{
+  "uri": "string"
+}
+ + + + +
uristringThe encoded URI, ready for a QR code.
+
+
Errors

400BAD_REQUEST 404WALLET_NOT_FOUND 409AMBIGUOUS_WALLET_ID 401INVALID_SESSION 401SESSION_EXPIRED

+
+

Notes

  • Only these five fields are read; a fuller EdgeEncodeUri has its extras ignored.
+

Local settings

+

Device-local account settings, stored outside the synced repos. They follow the account but never leave the machine.

+
+

Local settings

+

Device-local account settings, stored outside the synced repos.

+
+
+
+

Local settings.

+
local-settingssrc/cli/engine/routes/localSettings.ts
+
+

coreGUI code (src/util/localAccountSettings), reached through account.localDisklet.

+

Device-local account settings, stored in Settings.json on account.localDisklet. They are not synced — a phone and a CLI keep separate copies unless they share an Edge data directory.

+
+
+

Command line

+
local-settings
+ + + +
+

REST

+

GET/account/{sessionId}/local-settings

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/account/$SESS/local-settings'
+
+
+

Response 200

+
+
Response body
+
{
+  spamFilterOn: boolean
+}
+
Example
{
+  "spamFilterOn": true
+}
+ + + + +
spamFilterOnbooleanHide spam transactions in get-transactions results. Defaults to true, matching the GUI. The filter hides rows; it never changes stored metadata.
+
+
Errors

401INVALID_SESSION 401SESSION_EXPIRED

+
+ +
+
+

Change local settings.

+
local-settingssrc/cli/engine/routes/localSettings.ts
+
+

coreGUI code (src/util/localAccountSettings).

+

Writes device-local account settings. Every option is a field on the body; spamFilterOn is the only one today, and new options are added alongside it.

+
+
+

Command line

+
local-settings --spam-filter-on=true|false
+ + +

With no flag the command reads; with one it writes.

+
+
+

REST

+

POST/account/{sessionId}/change-local-settings

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
+ + +
+
Request body
+
{
+  spamFilterOn: boolean
+}
+
Example
{
+  "spamFilterOn": true
+}
+ + + + +
spamFilterOnbooleanHide spam transactions in get-transactions results. Defaults to true, matching the GUI. The filter hides rows; it never changes stored metadata.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"spamFilterOn":true}' \
+  'http://localhost/account/$SESS/change-local-settings'
+
+
+

Response 200

+
+
Response body
+
{
+  spamFilterOn: boolean
+}
+
Example
{
+  "spamFilterOn": true
+}
+ + + + +
spamFilterOnbooleanHide spam transactions in get-transactions results. Defaults to true, matching the GUI. The filter hides rows; it never changes stored metadata.
+
+
Errors

401INVALID_SESSION 401SESSION_EXPIRED

+
+

Notes

  • Omitting a field is a 400, not a no-op, so a caller cannot clear a setting by accident.
+

Exchange rates

+

Fiat and crypto pricing, current and historical.

+
+

Exchange rates

+

Historical and current rates through the same batching queue the GUI uses. No session required.

+
+
+
+

Batch crypto and fiat rate lookups.

+
rates-querysrc/cli/engine/routes/rates.ts
+
+

coreGUI code (src/util/exchangeRates): getHistoricalCryptoRate and getHistoricalFiatRate.

+

Concurrent lookups share one rates-server queue, so asking for many rates at once costs a single upstream request.

+
+
+

Command line

+
rates-query [--crypto='<json>'] [--fiat='<json>']
+ + + +
+

REST

+

POST/rates/query

+ + + +
+
Request body
+
{
+  crypto?: {
+    pluginId: string;
+    tokenId: string | null;
+    targetFiat: string | undefined;
+    date: Date | undefined
+  }[]
+  fiat?: {
+    fiatCode: string;
+    targetFiat: string | undefined;
+    date: Date | undefined
+  }[]
+}
+
Example
{
+  "crypto": [
+    {}
+  ],
+  "fiat": [
+    {}
+  ]
+}
+ + + + + + + + + +
crypto{ pluginId: string; tokenId: string | null; targetFiat: string | undefined; date: Date | undefined; }[] optionalCrypto rates to fetch.
fiat{ fiatCode: string; targetFiat: string | undefined; date: Date | undefined; }[] optionalFiat rates to fetch.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"crypto":[{}],"fiat":[{}]}' \
+  'http://localhost/rates/query'
+
+
+

Response 200

+
+
Response body
+
{
+  crypto: {
+    pluginId: string;
+    tokenId: string | null;
+    targetFiat: string;
+    date: string;
+    rate: number
+  }[]
+  fiat: {
+    fiatCode: string;
+    targetFiat: string;
+    date: string;
+    rate: number
+  }[]
+}
+
Example
{
+  "crypto": [
+    {}
+  ],
+  "fiat": [
+    {}
+  ]
+}
+ + + + + + + + + +
crypto{ pluginId: string; tokenId: string | null; targetFiat: string; date: string; rate: number; }[]Always present; empty when no crypto rates were requested.
fiat{ fiatCode: string; targetFiat: string; date: string; rate: number; }[]Always present; empty when no fiat rates were requested.
+
+
Errors

400BAD_REQUEST 503NETWORK_ERROR

+
+

Notes

  • A rate the server cannot supply comes back as 0 rather than an error, so check for zero before dividing.
+
+
+

Convert a USD amount into native units.

+
rates-usd-to-nativesrc/cli/engine/routes/rates.ts
+
+

coreGUI code (src/util/exchangeRates): getHistoricalCryptoRate.

+

Turns a fiat notional into the native amount a spend needs.

+
+
+

Command line

+
rates-usd-to-native --usd-amount=<usdAmount> --plugin-id=<pluginId> [--token-id=<tokenId>] --multiplier=<multiplier> [--date=<date>]
+ + + +
+

REST

+

POST/rates/usd-to-native

+ + + +
+
Request body
+
{
+  usdAmount: string
+  pluginId: string
+  tokenId?: string | null
+  multiplier: string
+  date?: Date
+}
+
Example
{
+  "usdAmount": "12345",
+  "pluginId": "FS8xJ2kQ…",
+  "tokenId": "FS8xJ2kQ…",
+  "multiplier": "string",
+  "date": {
+    "type": "string",
+    "format": "date-time"
+  }
+}
+ + + + + + + + + + + + + + + + + + + + + + + + +
usdAmountstringA positive decimal string.
pluginIdstringWhich chain to price.
tokenIdstring | null optionalDefaults to the native asset.
multiplierstringNative units per whole coin, as a positive decimal string. This route is not session-scoped, so the engine cannot read the asset’s denomination from core and will not guess one.
dateDate optionalISO-8601, or epoch milliseconds. Omitted, the current time is sent to the rates server. An unparseable date is a 400 BAD_REQUEST, not a rate of zero.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"usdAmount":"12345","pluginId":"FS8xJ2kQ…","tokenId":"FS8xJ2kQ…","multiplier":"string","date":{"type":"string","format":"date-time"}}' \
+  'http://localhost/rates/usd-to-native'
+
+
+

Response 200

+
+
Response body
+
{
+  usdAmount: number
+  pluginId: string
+  tokenId: string | null
+  multiplier: string
+  date: string
+  rate: number
+  displayAmount: string
+  nativeAmount: string
+}
+
Example
{
+  "usdAmount": 0,
+  "pluginId": "FS8xJ2kQ…",
+  "tokenId": "FS8xJ2kQ…",
+  "multiplier": "string",
+  "date": "2026-09-02T16:35:00.000Z",
+  "rate": 1,
+  "displayAmount": "12345",
+  "nativeAmount": "12345"
+}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
usdAmountnumberEchoed as a number, though it is sent as a string.
pluginIdstringCurrency plugin the amount was converted for.
tokenIdstring | nullThe asset, or null for the chain’s own coin.
multiplierstringNative units per whole coin, which is what displayAmount was multiplied by to reach nativeAmount.
datestringThe timestamp actually used for the rate.
ratenumberUSD per whole coin at that date.
displayAmountstringWhole coins, to 8 decimal places.
nativeAmountstringWhat a spend actually takes.
+
+
Errors

400BAD_REQUEST 404NOT_FOUND 503NETWORK_ERROR

+
+

Notes

  • displayAmount is rounded to 8 decimals before conversion, so assets with finer precision lose the tail. For an exact figure use rates-query and do the arithmetic yourself.
  • multiplier is required. The engine has no logged-in account here, so it cannot read the asset's denomination from core, and a guessed multiplier would return a nativeAmount wrong by orders of magnitude under a field documented as what a spend takes.
+

Object handles

+

A core value with methods on it cannot cross JSON, so the engine keeps it and hands back an id. These read and release any of them.

+
+

Object handles

+

A core value with methods on it — a staged transaction, a swap quote, a pending login — cannot cross JSON, so the engine keeps it and hands back an id. These read and release any of them.

+
+
+
+

Inspect an object handle.

+
object-getsrc/cli/engine/routes/objects.ts
+
+

coreEngine handle store; core identifies these values by object reference.

+

For the session-scoped kinds: a staged transaction or a swap quote.

+
+
+

Command line

+
object-get <objectId>
+ + + +
+

REST

+

GET/account/{sessionId}/object/{objectId}

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
objectIdstringAn ephemeral object handle id.
+ + + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/account/$SESS/object/$OBJECTID'
+
+
+

Response 200

+

The handle fields, plus a value holding a JSON-safe view of the object. A staged transaction is returned whole; a swap quote is summarised, because the value behind it is a live core object whose properties are getters.

+
+
Response body
+
{
+  value: unknown
+  objectId: string
+  kind: "lobby" | "pendingLogin" | "swap" | "transaction"
+  createdAt: string
+  expiresAt: string
+  sessionId?: string
+  walletId?: string
+}
+
Example
{
+  "value": {},
+  "objectId": "FS8xJ2kQ…",
+  "kind": "lobby",
+  "createdAt": "2026-09-02T16:35:00.000Z",
+  "expiresAt": "2026-09-02T16:35:00.000Z",
+  "sessionId": "FS8xJ2kQ…",
+  "walletId": "FS8xJ2kQ…"
+}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
valueunknownA JSON-safe view of the object, whose shape depends on kind: a staged transaction whole, a swap quote summarised, and null for a kind with no scalar projection.
objectIdstringHandle for the value the engine is holding. Pass it to the calls that consume it.
kind"lobby" | "pendingLogin" | "swap" | "transaction"What the handle refers to, which decides the calls that accept it.
createdAtstringWhen the engine took the handle.
expiresAtstringWhen the engine drops the handle. Handles live 5 minutes.
sessionIdstring optionalSession that created the handle; only that session may use it.
walletIdstring optionalWallet the handle is bound to, when it belongs to one.
+
+
Errors

404OBJECT_NOT_FOUND 410OBJECT_EXPIRED 409OBJECT_IN_USE 400OBJECT_SESSION_MISMATCH 401INVALID_SESSION 401SESSION_EXPIRED

+
+

Notes

  • Reading does not extend the TTL. Only a step that updates the value does.
+
+
+

Release an object handle.

+
object-deletesrc/cli/engine/routes/objects.ts
+
+

coreEngine handle store.

+

Runs the handle's cleanup — closing a swap quote, discarding a staged transaction — instead of waiting out the TTL.

+
+
+

Command line

+
object-delete <objectId>
+ + + +
+

REST

+

POST/account/{sessionId}/object/delete/{objectId}

+
Path
sessionIdstringFrom a successful login. The CLI supplies this from session.json, --session, or EDGE_CLI_SESSION.
objectIdstringAn ephemeral object handle id.
+ + + +
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  'http://localhost/account/$SESS/object/delete/$OBJECTID'
+
+
+

Response 200

+
+
Response body
+
{
+  ok: boolean
+  objectId: string
+}
+
Example
{
+  "ok": true,
+  "objectId": "FS8xJ2kQ…"
+}
+ + + + + + + + + +
okbooleanAlways true; a failure arrives as an error envelope.
objectIdstringThe handle this call consumed. It is now expired.
+
+
Errors

404OBJECT_NOT_FOUND 410OBJECT_EXPIRED 409OBJECT_IN_USE 400OBJECT_SESSION_MISMATCH 401INVALID_SESSION 401SESSION_EXPIRED

+
+ +

Admin

+

The $internalStuff escape hatch: login-server and sync-repo access that no ordinary caller needs.

+
+

Admin

+

Debugging only — not for production apps. These reach into context.$internalStuff, the private surface of edge-core-js, and can corrupt an account’s synced repos. They take no sessionId: they act on the context, not on a logged-in account.

+
+
+
+

Raw login-server request.

+
admin-auth-requestsrc/cli/engine/routes/admin.ts
+
+

corecontext.$internalStuff.authRequest

+

Sends an arbitrary request with the context's credentials attached. Debugging only — this is core's private surface.

+
+
+

Command line

+
admin-auth-request --method=<method> --path=<path> [--body='<json>']
+ + + +
+

REST

+

POST/admin/auth-request

+ + + +
+
Request body
+
{
+  method: string
+  path: string
+  body?: {
+    [keys: string]: unknown
+  }
+}
+
Example
{
+  "method": "string",
+  "path": "string",
+  "body": {}
+}
+ + + + + + + + + + + + + + +
methodstringHTTP method, e.g. GET.
pathstringLogin-server path, not an engine path.
body{ [keys: string]: unknown; } optionalRequest body, when the method takes one.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"method":"string","path":"string","body":{}}' \
+  'http://localhost/admin/auth-request'
+
+
+

Response 200

+

Whatever the login server returned.

+
unknown
+
Errors

400BAD_REQUEST 503NETWORK_ERROR

+
+ +
+
+

Hash a username.

+
admin-hash-usernamesrc/cli/engine/routes/admin.ts
+
+

corecontext.$internalStuff.hashUsername

+

Reproduces the login server's hashing, to derive a login id offline.

+
+
+

Command line

+
admin-hash-username --username=<username>
+ + + +
+

REST

+

GET/admin/hash-username

+ +
+
Query
+
{
+  username: string
+}
+
Example
{
+  "username": "string"
+}
+ + + + +
usernamestringThe name to hash.
+
+ + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/admin/hash-username?username=…'
+
+
+

Response 200

+
+
Response body
+
{
+  loginId: string
+}
+
Example
{
+  "loginId": "FS8xJ2kQ…"
+}
+ + + + +
loginIdstringBase58.
+
+ +
+ +
+
+

Create a lobby.

+
admin-make-lobbysrc/cli/engine/routes/admin.ts
+
+

corecontext.$internalStuff.makeLobby

+

A lobby polls the login server until closed, so the engine parks it under a lobby_ handle and closes it on expiry rather than leaking the poll.

+
+
+

Command line

+
admin-make-lobby [--lobby-request='<json>'] [--period-seconds=<period>]
+ + + +
+

REST

+

POST/admin/make-lobby

+ + + +
+
Request body
+
{
+  lobbyRequest?: {
+    [keys: string]: unknown
+  }
+  period?: number
+}
+
Example
{
+  "lobbyRequest": {},
+  "period": 0
+}
+ + + + + + + + + +
lobbyRequest{ [keys: string]: unknown; } optionalDefaults to {}.
periodnumber optionalPoll interval in seconds. At least 0.25; the engine converts to the milliseconds core takes.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"lobbyRequest":{},"period":0}' \
+  'http://localhost/admin/make-lobby'
+
+
+

Response 200

+
+
Response body
+
{
+  objectId: string
+  expiresAt: string
+  lobbyId: string
+  replies: unknown[]
+}
+
Example
{
+  "objectId": "FS8xJ2kQ…",
+  "expiresAt": "2026-09-02T16:35:00.000Z",
+  "lobbyId": "FS8xJ2kQ…",
+  "replies": [
+    {}
+  ]
+}
+ + + + + + + + + + + + + + + + + + + +
objectIdstringThe parked handle.
expiresAtstringWhen the engine closes the lobby and stops polling.
lobbyIdstringIdentifies the lobby to the party joining it.
repliesunknown[]Empty at creation; re-read to see replies.
+
+
Errors

503NETWORK_ERROR

+
+

Notes

  • Release it with admin-lobby-handle-delete, or the poll runs for the full five minutes.
+
+
+

Close a parked lobby.

+
admin-lobby-handle-deletesrc/cli/engine/routes/admin.ts
+
+

coreEngine handle store for a lobby created via makeLobby.

+ +
+

Command line

+
admin-lobby-handle-delete <objectId>
+ + + +
+

REST

+

POST/admin/lobby-handle/delete/{objectId}

+
Path
objectIdstringAn ephemeral object handle id.
+ + + +
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  'http://localhost/admin/lobby-handle/delete/$OBJECTID'
+
+
+

Response 200

+
+
Response body
+
{
+  ok: boolean
+}
+
Example
{
+  "ok": true
+}
+ + + + +
okbooleanAlways true; a failure arrives as an error envelope.
+
+
Errors

404OBJECT_NOT_FOUND 400OBJECT_KIND_MISMATCH 409OBJECT_IN_USE

+
+

Notes

  • Not under /account/{sessionId}/objects/, because admin lobbies belong to no session.
+
+
+

Read a lobby's contents.

+
admin-fetch-lobby-requestsrc/cli/engine/routes/admin.ts
+
+

corecontext.$internalStuff.fetchLobbyRequest

+ +
+

Command line

+
admin-fetch-lobby-request <lobbyId>
+ + + +
+

REST

+

GET/admin/fetch-lobby-request/{lobbyId}

+
Path
lobbyIdstringWhich lobby to read.
+ + + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/admin/fetch-lobby-request/$LOBBYID?lobbyId=…'
+
+
+

Response 200

+

The raw lobby request.

+
unknown
+
Errors

503NETWORK_ERROR

+
+ +
+
+

Reply to a lobby.

+
admin-send-lobby-replysrc/cli/engine/routes/admin.ts
+
+

corecontext.$internalStuff.sendLobbyReply

+ +
+

Command line

+
admin-send-lobby-reply <lobbyId> --lobby-request='<json>' [--reply-data='<json>']
+ + + +
+

REST

+

POST/admin/send-lobby-reply/{lobbyId}

+
Path
lobbyIdstringWhich lobby to answer.
+ + +
+
Request body
+
{
+  lobbyRequest: {
+    [keys: string]: unknown
+  }
+  replyData?: unknown
+}
+
Example
{
+  "lobbyRequest": {},
+  "replyData": {}
+}
+ + + + + + + + + +
lobbyRequest{ [keys: string]: unknown; }Normally the object from admin-fetch-lobby-request.
replyDataunknown optionalPayload for the requester.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"lobbyId":"FS8xJ2kQ…","lobbyRequest":{},"replyData":{}}' \
+  'http://localhost/admin/send-lobby-reply/$LOBBYID'
+
+
+

Response 204

+

No body.

+
Errors

400BAD_REQUEST 503NETWORK_ERROR

+
+ +
+
+

Sync a repo.

+
admin-sync-reposrc/cli/engine/routes/admin.ts
+
+

corecontext.$internalStuff.syncRepo

+ +
+

Command line

+
admin-sync-repo <syncKey>
+ + + +
+

REST

+

POST/admin/sync-repo/{syncKey}

+
Path
syncKeystringBase58 repo sync key.
+ + + +
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"syncKey":"string"}' \
+  'http://localhost/admin/sync-repo/$SYNCKEY'
+
+
+

Response 200

+

The changeset summary.

+
unknown
+
Errors

400BAD_REQUEST 503NETWORK_ERROR

+
+ +
+
+

List repo contents.

+
admin-repo-listsrc/cli/engine/routes/admin.ts
+
+

corecontext.$internalStuff.getRepoDisklet

+ +
+

Command line

+
admin-repo-list <syncKey> [--path=<path>] --data-key=<dataKey>
+ + + +
+

REST

+

GET/admin/repo-list/{syncKey}

+
Path
syncKeystringBase58 repo sync key.
+
+
Query
+
{
+  path?: string
+  dataKey: string
+}
+
Example
{
+  "path": "string",
+  "dataKey": "string"
+}
+ + + + + + + + + +
pathstring optionalSubdirectory. Defaults to the repo root.
dataKeystringBase58 repo data key.
+
+ + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/admin/repo-list/$SYNCKEY?syncKey=…&dataKey=…'
+
+
+

Response 200

+
+
Response body
+
{
+  listing: unknown
+}
+
Example
{
+  "listing": {}
+}
+ + + + +
listingunknownPath to entry type: file or folder.
+
+
Errors

400BAD_REQUEST

+
+ +
+
+

Read a repo file.

+
admin-repo-getsrc/cli/engine/routes/admin.ts
+
+

corecontext.$internalStuff.getRepoDisklet

+ +
+

Command line

+
admin-repo-get <syncKey> --path=<path> --data-key=<dataKey>
+ + + +
+

REST

+

GET/admin/repo-get/{syncKey}

+
Path
syncKeystringBase58 repo sync key.
+
+
Query
+
{
+  path: string
+  dataKey: string
+}
+
Example
{
+  "path": "string",
+  "dataKey": "string"
+}
+ + + + + + + + + +
pathstringPath within the repo.
dataKeystringBase58 repo data key.
+
+ + +
Example
+
curl --unix-socket "$SOCK" \
+  'http://localhost/admin/repo-get/$SYNCKEY?path=…&syncKey=…&dataKey=…'
+
+
+

Response 200

+
+
Response body
+
{
+  text: string
+}
+
Example
{
+  "text": "string"
+}
+ + + + +
textstringThe file contents.
+
+
Errors

404NOT_FOUND 400BAD_REQUEST

+
+ +
+
+

Write a repo file.

+
admin-repo-setsrc/cli/engine/routes/admin.ts
+
+

corecontext.$internalStuff.getRepoDisklet

+

Writes directly into a synced repo, bypassing every core-level invariant. A malformed write can break the account for real clients.

+
+
+

Command line

+
admin-repo-set <syncKey> --path=<path> --text=<text> --data-key=<dataKey>
+ + + +
+

REST

+

POST/admin/repo-set/{syncKey}

+
Path
syncKeystringBase58 repo sync key.
+ + +
+
Request body
+
{
+  path: string
+  text: string
+  dataKey: string
+}
+
Example
{
+  "path": "string",
+  "text": "string",
+  "dataKey": "string"
+}
+ + + + + + + + + + + + + + +
pathstringPath within the repo.
textstringThe contents to write.
dataKeystringBase58 repo data key.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"path":"string","text":"string","syncKey":"string","dataKey":"string"}' \
+  'http://localhost/admin/repo-set/$SYNCKEY'
+
+
+

Response 204

+

No body.

+
Errors

400BAD_REQUEST

+
+ +
+
+

Delete a repo file.

+
admin-repo-deletesrc/cli/engine/routes/admin.ts
+
+

corecontext.$internalStuff.getRepoDisklet

+

Destructive, and not undoable from this API.

+
+
+

Command line

+
admin-repo-delete <syncKey> --path=<path> --data-key=<dataKey>
+ + + +
+

REST

+

POST/admin/repo-delete/{syncKey}

+
Path
syncKeystringBase58 repo sync key.
+ + +
+
Request body
+
{
+  path: string
+  dataKey: string
+}
+
Example
{
+  "path": "string",
+  "dataKey": "string"
+}
+ + + + + + + + + +
pathstringPath within the repo.
dataKeystringBase58 repo data key.
+
+
Example
+
curl --unix-socket "$SOCK" \
+  -X POST \
+  -H 'Content-Type: application/json' \
+  -d '{"path":"string","syncKey":"string","dataKey":"string"}' \
+  'http://localhost/admin/repo-delete/$SYNCKEY'
+
+
+

Response 204

+

No body.

+
Errors

400BAD_REQUEST

+
+ +
+

Error codes

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
BAD_REQUEST400engineMalformed JSON, or a missing / wrongly typed field.
MISSING_BITWAVE_ACCOUNT_ID400engineBitwave export requested with no account id in the query and none saved in the wallet’s exportTxInfo.json.
OBJECT_KIND_MISMATCH400engineThe handle exists but is a different kind (e.g. a swap quote passed to sign-tx).
OBJECT_SESSION_MISMATCH400engineThe handle belongs to a different session.
OBJECT_WALLET_MISMATCH400engineThe transaction handle belongs to a different wallet.
INVALID_SESSION401engineUnknown sessionId.
SESSION_EXPIRED401engineAuto-logged-out after the account’s idle timeout. An explicitly logged-out session is gone rather than expired, and answers INVALID_SESSION.
NOT_FOUND404engineNo route matched, or a generic missing resource.
NO_LOGIN_REQUEST404engineThe lobby exists but carries no pending login request.
OBJECT_NOT_FOUND404engineNo handle with that objectId.
PENDING_LOGIN_NOT_FOUND404engineNo pending Edge login with that pendingId.
TOKEN_NOT_FOUND404engineUnknown token id for this wallet.
UNAUTHORIZED401engineThe TCP transport requires the bearer token from the engine run file in an X-Edge-Token header. The unix socket needs none: its 0600 mode is the check.
FORBIDDEN403engineA TCP request carried an Origin header, or a Host that is not the address the listener is bound to. Both are refused so a web page cannot reach the engine, directly or by rebinding a name onto its port.
USER_NOT_FOUND404engineNo local user matches that username or login id.
WALLET_NOT_FOUND404engineNo wallet matches that id or prefix.
ENGINE_UNAVAILABLE503clientThe client could not reach or start an engine. Written by the client, so it never arrives over the wire; exit code 7.
USAGE400clientBad argv: an unknown command or flag, a missing flag value, or an unparseable structured flag. Written by the client; exit code 2.
METHOD_NOT_ALLOWED405engineThe path exists but not for this HTTP method.
WALLET_NOT_RUNNING409engineThe wallet exists in the account but has no running engine — archived, paused, or its currency plugin is not loaded — and the call needs one. The display-key routes answer this where the raw-key routes succeed.
AMBIGUOUS_WALLET_ID409engineA wallet id prefix matched more than one wallet. details: details.candidates
OBJECT_EXPIRED410engineThe handle was read after its TTL but before the 15-second sweeper released it. Once swept it is gone, so a handle past its TTL usually answers OBJECT_NOT_FOUND instead.
OBJECT_IN_USE409engineA consuming call on this handle is already in flight. Fund-moving calls outlive the client socket timeout, so a retry is refused rather than sending twice.
PAYLOAD_TOO_LARGE413engineRequest body over 4 MiB.
UNSUPPORTED_MEDIA_TYPE415engineBody present but not application/json.
INTERNAL_ERROR500engineUnmapped engine or plugin failure.
ENGINE_SHUTTING_DOWN503engineIdle or explicit shutdown already in progress.
USERNAME_ERROR400coreUnknown username, or an invalid recovery key.
NO_AMOUNT_SPECIFIED400coreZero-amount spend.
SAME_CURRENCY400coreSwap between identical currencies.
PASSWORD_ERROR401coreWrong password, PIN, or recovery answers. details: details.wait (seconds) when rate-limited
OTP_REQUIRED401coreMissing or wrong 2FA token. details: reason (ip|otp), loginId, resetToken, resetDate, voucherId, voucherAuth, voucherActivates
CHALLENGE_REQUIRED403coreThe login server wants a CAPTCHA. Retry with challengeId. details: challengeId, challengeUri
PIN_DISABLED403corePIN login is not enabled on this device.
SWAP_PERMISSION403coreThe swap plugin refused the request. details: pluginId, reason: geoRestriction | noVerification | needsActivation
INSUFFICIENT_FUNDS422coreNot enough balance to cover amount plus fee. details: tokenId, networkFee
DUST_SPEND422coreAmount below the network dust threshold.
PENDING_FUNDS422coreBalance exists but is unconfirmed.
SPEND_TO_SELF422coreDestination address belongs to the source wallet.
SWAP_ABOVE_LIMIT422coreAmount exceeds the plugin maximum. details: swapPluginId, nativeMax, direction
SWAP_BELOW_LIMIT422coreAmount below the plugin minimum. details: swapPluginId, nativeMin, direction. nativeMin is an empty string when the plugin refused the amount without reporting a limit — core defaults it, and the engine passes it through rather than inventing one.
SWAP_CURRENCY422coreThe plugin does not support that pair. details: pluginId, fromTokenId, toTokenId
SWAP_ADDRESS422coreAddress unusable for this swap. details: swapPluginId, reason: mustMatch | mustBeActivated
OBSOLETE_API426coreThe login server rejected this client version.
NETWORK_ERROR503coreCould not reach an Edge server.
+

CLI exit codes

+
0OKSuccess.
1GENERICAny failure with no more specific mapping.
2USAGEBad argv: unknown flag, missing value, extra positional.
3AUTHINVALID_SESSION, SESSION_EXPIRED, UNAUTHORIZED, FORBIDDEN, PASSWORD_ERROR, OTP_REQUIRED, CHALLENGE_REQUIRED, PIN_DISABLED.
4NOT_FOUNDNOT_FOUND, WALLET_NOT_FOUND, TOKEN_NOT_FOUND.
5VALIDATIONBAD_REQUEST, INSUFFICIENT_FUNDS, DUST_SPEND, PENDING_FUNDS, SPEND_TO_SELF, NO_AMOUNT_SPECIFIED, AMBIGUOUS_WALLET_ID, USERNAME_ERROR.
6NETWORKNETWORK_ERROR, or any response with HTTP status 503.
7ENGINEENGINE_SHUTTING_DOWN, or the client could not connect to or spawn the engine.
+
+
+ + \ No newline at end of file diff --git a/docs/api/dist/openapi.json b/docs/api/dist/openapi.json new file mode 100644 index 00000000000..a1abb2dce07 --- /dev/null +++ b/docs/api/dist/openapi.json @@ -0,0 +1,10796 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "Edge CLI", + "version": "1.0.0", + "description": "The `edge-cli` command line and the `edge-engine` JSON REST API, generated from the route declarations in `src/cli/engine/routes/`." + }, + "servers": [ + { + "url": "http://localhost", + "description": "Unix socket at ~/.edge-cli/run//engine.sock" + }, + { + "url": "http://127.0.0.1:9008", + "description": "Loopback TCP, when started with --tcp=9008. Requires the X-Edge-Token header; see securitySchemes." + } + ], + "security": [ + {}, + { + "edgeTcpToken": [] + } + ], + "tags": [ + { + "name": "Lifecycle", + "description": "Lifecycle and configuration of the `edge-engine` daemon. None of these have an `edge-core-js` equivalent — they describe the daemon itself — and none need a session." + }, + { + "name": "Device and usernames", + "description": "Calls on the shared `EdgeContext`: local device state and login-server queries that do not need a session." + }, + { + "name": "Login methods", + "description": "Every successful login returns a [Session](#schema-Session) and registers it in the engine, so later calls need only the `sessionId`. The CLI writes that id to `session.json` automatically." + }, + { + "name": "Session", + "description": "Calls on a logged-in `EdgeAccount`, addressed by `sessionId`. All of these can also return `401 INVALID_SESSION` or `401 SESSION_EXPIRED`." + }, + { + "name": "Local settings", + "description": "Device-local account settings, stored outside the synced repos." + }, + { + "name": "Credentials", + "description": "Password, PIN, username and recovery changes on a logged-in account." + }, + { + "name": "Two-factor authentication", + "description": "OTP state and the reset flow a user falls back on after losing their authenticator." + }, + { + "name": "Vouchers", + "description": "When 2FA blocks a login, the login server issues a voucher an already-trusted device can approve or reject." + }, + { + "name": "Approving a login", + "description": "The other side of `request-edge-login`: a logged-in account inspecting and approving a login somebody scanned." + }, + { + "name": "Keys", + "description": "Raw key infrastructure beneath the wallet API. Several of these return private key material, so they are the routes to think hardest about who can reach the engine: a process that can read the `0600` socket, or the TCP token, holds every logged-in account. All of them need a session, and a `sessionId` is itself full account authority." + }, + { + "name": "Wallet state", + "description": "Account-level wallet listing and creation, then per-wallet calls. A `{walletId}` segment accepts a unique prefix, so those routes can also return `404 WALLET_NOT_FOUND` or `409 AMBIGUOUS_WALLET_ID`." + }, + { + "name": "Tokens", + "description": "Which tokens a wallet tracks. Enabled tokens are the ones it syncs balances for; detected ones were seen on-chain but are not yet enabled." + }, + { + "name": "Transactions", + "description": "Reading transaction history, exporting it, and editing its metadata." + }, + { + "name": "Object handles", + "description": "A core value with methods on it — a staged transaction, a swap quote, a pending login — cannot cross JSON, so the engine keeps it and hands back an id. These read and release any of them." + }, + { + "name": "Spending", + "description": "Two ways to send funds. `spend` does the whole thing in one call; the staged workflow — `make-spend`, `sign-tx`, `broadcast-tx`, `save-tx` — hands back an object handle at each step so fees can be inspected before committing." + }, + { + "name": "Swap quotes", + "description": "Cross-asset exchange. Quotes are live objects held server-side under a `swap_` handle, so approving one means naming its `objectId` rather than re-uploading the quote." + }, + { + "name": "URIs", + "description": "Parsing and building BIP21-style payment URIs through the wallet’s own plugin, so chain-specific quirks are handled for you." + }, + { + "name": "Exchange rates", + "description": "Historical and current rates through the same batching queue the GUI uses. No session required." + }, + { + "name": "Data store", + "description": "The account’s synced key-value store, where plugins keep their own state. One route per `EdgeDataStore` method." + }, + { + "name": "Admin", + "description": "**Debugging only — not for production apps.** These reach into `context.$internalStuff`, the private surface of `edge-core-js`, and can corrupt an account’s synced repos. They take no `sessionId`: they act on the context, not on a logged-in account." + }, + { + "name": "Event stream", + "description": "A Server-Sent Events feed of engine activity, served outside the router because the response never ends." + } + ], + "paths": { + "/engine/status": { + "get": { + "operationId": "engineStatus", + "summary": "Engine liveness and summary.", + "description": "**Core call:** _none — Engine lifecycle; the daemon is not part of the core API._\n\n**Command line**\n\n```\nengine-status\n```\n\nThe readiness probe the client polls after auto-spawning the engine.", + "tags": [ + "Lifecycle" + ], + "x-cli": { + "command": "engine-status", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": null, + "x-core-note": "Engine lifecycle; the daemon is not part of the core API.", + "x-source": "src/cli/engine/routes/status.ts", + "parameters": [], + "responses": { + "200": { + "description": "Every field is documented on `asEngineStatus`; the two nullable ones, `idleShutdownAt` and `tcpPort`, say there what null means.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "pid": { + "type": "number", + "description": "The daemon process, for `kill` when it will not stop." + }, + "apiVersion": { + "type": "string", + "description": "The API this engine speaks. A client refusing to talk to an older engine checks this." + }, + "uptimeSeconds": { + "type": "number", + "description": "How long the daemon has been running." + }, + "sessionCount": { + "type": "number", + "description": "Logged-in accounts held open right now." + }, + "testMode": { + "type": "boolean", + "description": "True when the engine is not pointed at production: the tester fleet, or the in-process fake world under `--fake`." + }, + "idleShutdownAt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "When the engine will exit for want of work. Null while a client is deliberately holding it open — a logged-in session, a live subscription, or a handle that belongs to no session (a pending edge login, or a parked admin lobby) — and null when the timeout is disabled. A request being served also disarms the timer, but is not reported here: the request asking for this field is itself in flight." + }, + "tcpPort": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "The loopback port, null unless started with `--tcp`." + }, + "socketPath": { + "type": "string", + "description": "Unix socket the CLI connects to." + }, + "rateCachedCount": { + "type": "number", + "description": "Exchange rates held in the engine’s process cache. It is bounded and cleared when the last session goes away, and this is how an operator sees it." + }, + "rateUnpricedCount": { + "type": "number", + "description": "Rate keys the server answered without a price, remembered for a few minutes so a repeated listing does not re-ask for every date. A number that stays high means an asset with no feed on the rates server — a hand-added custom token, a long-tail token, or dates predating its market." + }, + "locale": { + "type": "string", + "description": "Language tag the engine resolved at boot." + }, + "localeMatched": { + "type": "boolean", + "description": "Whether a translation table for that tag was actually found. False means the tag was accepted but the engine is answering in English, which is otherwise indistinguishable from a build that has the language." + }, + "decimalSeparator": { + "type": "string", + "description": "Decimal mark for that locale." + }, + "groupingSeparator": { + "type": "string", + "description": "Thousands mark for that locale." + } + }, + "required": [ + "pid", + "apiVersion", + "uptimeSeconds", + "sessionCount", + "testMode", + "idleShutdownAt", + "tcpPort", + "socketPath", + "rateCachedCount", + "rateUnpricedCount", + "locale", + "localeMatched", + "decimalSeparator", + "groupingSeparator" + ] + } + } + } + }, + "default": { + "description": "ENGINE_SHUTTING_DOWN", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/engine/config": { + "get": { + "operationId": "engineConfig", + "summary": "Configured context options.", + "description": "**Core call:** _none — Reflects the EdgeContextOptions the engine supplied at startup._\n\n**Command line**\n\n```\nengine-config\n```\n\nWhat the engine passed to `makeEdgeContext`. Contains no secrets. Use it to assert tester hosts before a test run.", + "tags": [ + "Lifecycle" + ], + "x-cli": { + "command": "engine-config", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": null, + "x-core-note": "Reflects the EdgeContextOptions the engine supplied at startup.", + "x-source": "src/cli/engine/routes/status.ts", + "parameters": [], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "appId": { + "type": "string", + "description": "Application ID the engine was started with." + }, + "testMode": { + "type": "boolean", + "description": "True when the engine is not pointed at production: the tester fleet, or the in-process fake world under `--fake`. Read `servers` to tell those apart." + }, + "directory": { + "type": "string", + "description": "Working directory holding the core data." + }, + "servers": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string" + } + ] + } + }, + "description": "The URLs this engine talks to, keyed by role. `syncServer` is a list, since core rotates across the sync fleet." + }, + "plugins": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Plugin IDs the engine loaded, sorted." + } + }, + "required": [ + "appId", + "testMode", + "directory", + "servers", + "plugins" + ] + } + } + } + }, + "default": { + "description": "Error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/engine/stop": { + "post": { + "operationId": "engineStop", + "summary": "Stop the engine.", + "description": "**Core call:** _none — Engine lifecycle. Internally calls `context.close()`._\n\n**Command line**\n\n```\nengine-stop\n```\n\nLogs out every session, closes the context, unlinks the socket and run-file, then exits. The engine answers before it starts tearing down, so a response is not proof the process is gone.", + "tags": [ + "Lifecycle" + ], + "x-cli": { + "command": "engine-stop", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": null, + "x-core-note": "Engine lifecycle. Internally calls `context.close()`.", + "x-source": "src/cli/engine/routes/status.ts", + "parameters": [], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ok": { + "type": "boolean", + "description": "Always true; a failure arrives as an error envelope." + } + }, + "required": [ + "ok" + ] + } + } + } + }, + "default": { + "description": "Error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/local-users": { + "get": { + "operationId": "localUsers", + "summary": "List local users on this device.", + "description": "**Core call:** `context.localUsers`\n\n**Command line**\n\n```\nlocal-users\n```\n\n", + "tags": [ + "Device and usernames" + ], + "x-cli": { + "command": "local-users", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "context.localUsers", + "x-source": "src/cli/engine/routes/context.ts", + "parameters": [], + "responses": { + "200": { + "description": "Everything `context.localUsers` reports, including which login methods each user has enabled on this device.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "localUsers": { + "type": "array", + "items": {}, + "description": "`EdgeUserInfo[]`: one entry per account cached on this device." + } + }, + "required": [ + "localUsers" + ] + } + } + } + }, + "default": { + "description": "Error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/forget-account": { + "post": { + "operationId": "forgetAccount", + "summary": "Forget an account on this device.", + "description": "**Core call:** `context.forgetAccount`\n\n**Command line**\n\n```\nforget-account --root-login-id=\n```\n\nRemoves locally cached credentials. The remote account is untouched.", + "tags": [ + "Device and usernames" + ], + "x-cli": { + "command": "forget-account", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "context.forgetAccount", + "x-source": "src/cli/engine/routes/context.ts", + "parameters": [], + "responses": { + "204": { + "description": "No content." + }, + "default": { + "description": "USER_NOT_FOUND, BAD_REQUEST", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "rootLoginId": { + "type": "string", + "description": "Core takes a `rootLoginId`. A username is also accepted and resolved against `localUsers` first, so callers need not hash it." + } + }, + "required": [ + "rootLoginId" + ] + } + } + } + } + } + }, + "/username-available": { + "get": { + "operationId": "usernameAvailable", + "summary": "Check whether a username is free.", + "description": "**Core call:** `context.usernameAvailable`\n\n**Command line**\n\n```\nusername-available --username= [--challenge-id=]\n```\n\n", + "tags": [ + "Device and usernames" + ], + "x-cli": { + "command": "username-available", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "context.usernameAvailable", + "x-source": "src/cli/engine/routes/context.ts", + "parameters": [ + { + "name": "username", + "in": "query", + "required": true, + "description": "The name to check.", + "schema": { + "type": "string" + } + }, + { + "name": "challengeId", + "in": "query", + "required": false, + "description": "Supply after solving a CAPTCHA to retry the same check.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "username": { + "type": "string", + "description": "The name that was checked, echoed back." + }, + "available": { + "type": "boolean", + "description": "True when nobody holds this name. It is not reserved by asking." + } + }, + "required": [ + "username", + "available" + ] + } + } + } + }, + "default": { + "description": "USERNAME_ERROR, CHALLENGE_REQUIRED, NETWORK_ERROR", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/fix-username": { + "get": { + "operationId": "fixUsername", + "summary": "Normalize a username.", + "description": "**Core call:** `context.fixUsername`\n\n**Command line**\n\n```\nfix-username --username=\n```\n\nApplies the same rules the login server does, so a caller can show the user what their name will actually be before creating an account.", + "tags": [ + "Device and usernames" + ], + "x-cli": { + "command": "fix-username", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "context.fixUsername", + "x-source": "src/cli/engine/routes/context.ts", + "parameters": [ + { + "name": "username", + "in": "query", + "required": true, + "description": "The name to normalize.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "username": { + "type": "string", + "description": "The normalized value. The input is not echoed." + } + }, + "required": [ + "username" + ] + } + } + } + }, + "default": { + "description": "Error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/check-password-rules": { + "get": { + "operationId": "checkPasswordRules", + "summary": "Score a candidate password.", + "description": "**Core call:** `context.checkPasswordRules`\n\n**Command line**\n\n```\ncheck-password-rules --password=\n```\n\n", + "tags": [ + "Device and usernames" + ], + "x-cli": { + "command": "check-password-rules", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "context.checkPasswordRules", + "x-source": "src/cli/engine/routes/context.ts", + "parameters": [ + { + "name": "password", + "in": "query", + "required": true, + "description": "The candidate password to score.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "`EdgePasswordRules` from core: passed, tooShort, noNumber, noLowerCase, noUpperCase, secondsToCrack.", + "content": { + "application/json": { + "schema": {} + } + } + }, + "default": { + "description": "Error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/fetch-login-messages": { + "get": { + "operationId": "fetchLoginMessages", + "summary": "Fetch login-server messages for every local user.", + "description": "**Core call:** `context.fetchLoginMessages`\n\n**Command line**\n\n```\nfetch-login-messages\n```\n\n", + "tags": [ + "Device and usernames" + ], + "x-cli": { + "command": "fetch-login-messages", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "context.fetchLoginMessages", + "x-source": "src/cli/engine/routes/context.ts", + "parameters": [], + "responses": { + "200": { + "description": "An `EdgeLoginMessage[]` from core: one entry per local login, each with its own `loginId`, `otpResetPending`, `pendingVouchers`, `recovery2Corrupt` and, where the stash has one, `username`.", + "content": { + "application/json": { + "schema": {} + } + } + }, + "default": { + "description": "NETWORK_ERROR", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/request-otp-reset": { + "post": { + "operationId": "requestOtpReset", + "summary": "Request a 2FA reset.", + "description": "**Core call:** `context.requestOtpReset`\n\n**Command line**\n\n```\nrequest-otp-reset --username= --otp-reset-token=\n```\n\nStarts the timed reset a user falls back on after losing their authenticator.", + "tags": [ + "Device and usernames" + ], + "x-cli": { + "command": "request-otp-reset", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "context.requestOtpReset", + "x-source": "src/cli/engine/routes/context.ts", + "parameters": [], + "responses": { + "200": { + "description": "When the reset completes if nobody cancels it.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "resetDate": { + "type": "string", + "description": "When 2FA will actually come off. The login server enforces a waiting period so the real owner has time to cancel." + } + }, + "required": [ + "resetDate" + ] + } + } + } + }, + "default": { + "description": "USERNAME_ERROR, BAD_REQUEST, NETWORK_ERROR", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "username": { + "type": "string", + "description": "Whose 2FA to reset." + }, + "otpResetToken": { + "type": "string", + "description": "From `details.resetToken` on an `OTP_REQUIRED` error." + } + }, + "required": [ + "username", + "otpResetToken" + ] + } + } + } + } + } + }, + "/fetch-recovery-questions": { + "get": { + "operationId": "fetchRecoveryQuestions", + "summary": "Fetch a user’s recovery questions.", + "description": "**Core call:** `context.fetchRecovery2Questions`\n\n**Command line**\n\n```\nfetch-recovery-questions --recovery-key= --username=\n```\n\n", + "tags": [ + "Device and usernames" + ], + "x-cli": { + "command": "fetch-recovery-questions", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "context.fetchRecovery2Questions", + "x-core-note": "Our surface drops the `2` from the path, command and `recoveryKey` parameter; a future Recovery1 would be suffixed `V1`.", + "x-source": "src/cli/engine/routes/context.ts", + "parameters": [ + { + "name": "recoveryKey", + "in": "query", + "required": true, + "description": "From `change-recovery`, stored by the user out of band.", + "schema": { + "type": "string" + } + }, + { + "name": "username", + "in": "query", + "required": true, + "description": "Whose questions to fetch.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "questions": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The questions in the order `login-with-recovery` expects the answers." + } + }, + "required": [ + "questions" + ] + } + } + } + }, + "default": { + "description": "USERNAME_ERROR, NETWORK_ERROR", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/fetch-challenge": { + "post": { + "operationId": "fetchChallenge", + "summary": "Pre-fetch a CAPTCHA challenge.", + "description": "**Core call:** `context.fetchChallenge`\n\n**Command line**\n\n```\nfetch-challenge\n```\n\nLets a client solve a challenge before it hits `403 CHALLENGE_REQUIRED` mid-flow.", + "tags": [ + "Device and usernames" + ], + "x-cli": { + "command": "fetch-challenge", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "context.fetchChallenge", + "x-source": "src/cli/engine/routes/context.ts", + "parameters": [], + "responses": { + "200": { + "description": "`challengeUri` is absent when the server considers the challenge already satisfied.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "challengeId": { + "type": "string", + "description": "Pass to the call that demanded a challenge once the user has solved it." + }, + "challengeUri": { + "type": "string", + "description": "Where to send the user to solve the CAPTCHA. Absent when the server issued a challenge that needs no interaction." + } + }, + "required": [ + "challengeId" + ] + } + } + } + }, + "default": { + "description": "NETWORK_ERROR", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/currency-configs": { + "get": { + "operationId": "currencyConfigs", + "summary": "List plugin ids usable for wallet creation.", + "description": "**Core call:** _none — Engine view of the enabled plugin set; core exposes `account.currencyConfig` per plugin instead._\n\n**Command line**\n\n```\ncurrency-configs\n```\n\nCurrency and accountbased plugins only — swap plugins are excluded.", + "tags": [ + "Device and usernames" + ], + "x-cli": { + "command": "currency-configs", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": null, + "x-core-note": "Engine view of the enabled plugin set; core exposes `account.currencyConfig` per plugin instead.", + "x-source": "src/cli/engine/routes/context.ts", + "parameters": [], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "pluginIds": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Currency plugins this engine loaded." + } + }, + "required": [ + "pluginIds" + ] + } + } + } + }, + "default": { + "description": "Error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/login-with-password": { + "post": { + "operationId": "loginWithPassword", + "summary": "Log in with a password.", + "description": "**Core call:** `context.loginWithPassword`\n\n**Command line**\n\n```\nlogin-with-password [--otp=] [--otp-key=] [--challenge-id=] --username= --password=\n```\n\n", + "tags": [ + "Login methods" + ], + "x-cli": { + "command": "login-with-password", + "flags": [], + "extra": [], + "custom": true, + "preset": {} + }, + "x-core-call": "context.loginWithPassword", + "x-source": "src/cli/engine/routes/login.ts", + "parameters": [], + "responses": { + "200": { + "description": "A session with `loginMethod: \"password\"`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "sessionId": { + "type": "string", + "description": "Identifies this login. Every account-scoped call carries it, and the CLI stores the most recent one so commands can omit it." + }, + "username": { + "type": "string", + "description": "Absent for a light account, which has no username." + }, + "rootLoginId": { + "type": "string", + "description": "The account root, stable across appIds. Two sessions sharing it are the same account." + }, + "loginMethod": { + "type": "string", + "enum": [ + "create", + "edge", + "key", + "password", + "pin", + "recovery" + ], + "description": "How this session was established." + }, + "autoLogoutSeconds": { + "type": "number", + "description": "Idle time before the engine logs the account out. 0 disables it." + }, + "expiresAt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "When auto-logout will fire, or null when it is disabled." + }, + "lastActivityAt": { + "type": "string", + "description": "Last call on this session, which is what auto-logout measures from." + }, + "createdAt": { + "type": "string", + "description": "When the login completed." + } + }, + "required": [ + "sessionId", + "rootLoginId", + "loginMethod", + "autoLogoutSeconds", + "expiresAt", + "lastActivityAt", + "createdAt" + ] + } + } + } + }, + "default": { + "description": "PASSWORD_ERROR, USERNAME_ERROR, OTP_REQUIRED, CHALLENGE_REQUIRED, NETWORK_ERROR", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "otp": { + "type": "string", + "description": "A current 2FA code." + }, + "otpKey": { + "type": "string", + "description": "The 2FA secret itself, instead of a code." + }, + "challengeId": { + "type": "string", + "description": "Supply after solving a CAPTCHA to retry the same request." + }, + "username": { + "type": "string", + "description": "The account name." + }, + "password": { + "type": "string", + "description": "The account password." + } + }, + "required": [ + "username", + "password" + ] + } + } + } + } + } + }, + "/login-with-pin": { + "post": { + "operationId": "loginWithPin", + "summary": "Log in with a device PIN.", + "description": "**Core call:** `context.loginWithPIN`\n\n**Command line**\n\n```\nlogin-with-pin [--otp=] [--otp-key=] [--challenge-id=] --username-or-login-id= --pin= [--use-login-id[=false]]\n```\n\nOnly works on a device that has already saved a PIN for the account.", + "tags": [ + "Login methods" + ], + "x-cli": { + "command": "login-with-pin", + "flags": [], + "extra": [], + "custom": true, + "preset": {} + }, + "x-core-call": "context.loginWithPIN", + "x-source": "src/cli/engine/routes/login.ts", + "parameters": [], + "responses": { + "200": { + "description": "A session with `loginMethod: \"pin\"`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "sessionId": { + "type": "string", + "description": "Identifies this login. Every account-scoped call carries it, and the CLI stores the most recent one so commands can omit it." + }, + "username": { + "type": "string", + "description": "Absent for a light account, which has no username." + }, + "rootLoginId": { + "type": "string", + "description": "The account root, stable across appIds. Two sessions sharing it are the same account." + }, + "loginMethod": { + "type": "string", + "enum": [ + "create", + "edge", + "key", + "password", + "pin", + "recovery" + ], + "description": "How this session was established." + }, + "autoLogoutSeconds": { + "type": "number", + "description": "Idle time before the engine logs the account out. 0 disables it." + }, + "expiresAt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "When auto-logout will fire, or null when it is disabled." + }, + "lastActivityAt": { + "type": "string", + "description": "Last call on this session, which is what auto-logout measures from." + }, + "createdAt": { + "type": "string", + "description": "When the login completed." + } + }, + "required": [ + "sessionId", + "rootLoginId", + "loginMethod", + "autoLogoutSeconds", + "expiresAt", + "lastActivityAt", + "createdAt" + ] + } + } + } + }, + "default": { + "description": "PASSWORD_ERROR, PIN_DISABLED, USERNAME_ERROR, BAD_REQUEST, NETWORK_ERROR", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "otp": { + "type": "string", + "description": "A current 2FA code." + }, + "otpKey": { + "type": "string", + "description": "The 2FA secret itself, instead of a code." + }, + "challengeId": { + "type": "string", + "description": "Supply after solving a CAPTCHA to retry the same request." + }, + "usernameOrLoginId": { + "type": "string", + "description": "A username, or a login id." + }, + "pin": { + "type": "string", + "description": "The device PIN." + }, + "useLoginId": { + "type": "boolean", + "description": "Treat the value as a login id." + } + }, + "required": [ + "usernameOrLoginId", + "pin" + ] + } + } + } + } + } + }, + "/login-with-key": { + "post": { + "operationId": "loginWithKey", + "summary": "Log in with an account login key.", + "description": "**Core call:** `context.loginWithKey`\n\n**Command line**\n\n```\nlogin-with-key [--otp=] [--otp-key=] [--challenge-id=] --username-or-login-id= --login-key= [--use-login-id[=false]]\n```\n\nThe key comes from `get-login-key` on an already-authenticated session.", + "tags": [ + "Login methods" + ], + "x-cli": { + "command": "login-with-key", + "flags": [], + "extra": [], + "custom": true, + "preset": {} + }, + "x-core-call": "context.loginWithKey", + "x-source": "src/cli/engine/routes/login.ts", + "parameters": [], + "responses": { + "200": { + "description": "A session with `loginMethod: \"key\"`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "sessionId": { + "type": "string", + "description": "Identifies this login. Every account-scoped call carries it, and the CLI stores the most recent one so commands can omit it." + }, + "username": { + "type": "string", + "description": "Absent for a light account, which has no username." + }, + "rootLoginId": { + "type": "string", + "description": "The account root, stable across appIds. Two sessions sharing it are the same account." + }, + "loginMethod": { + "type": "string", + "enum": [ + "create", + "edge", + "key", + "password", + "pin", + "recovery" + ], + "description": "How this session was established." + }, + "autoLogoutSeconds": { + "type": "number", + "description": "Idle time before the engine logs the account out. 0 disables it." + }, + "expiresAt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "When auto-logout will fire, or null when it is disabled." + }, + "lastActivityAt": { + "type": "string", + "description": "Last call on this session, which is what auto-logout measures from." + }, + "createdAt": { + "type": "string", + "description": "When the login completed." + } + }, + "required": [ + "sessionId", + "rootLoginId", + "loginMethod", + "autoLogoutSeconds", + "expiresAt", + "lastActivityAt", + "createdAt" + ] + } + } + } + }, + "default": { + "description": "PASSWORD_ERROR, USERNAME_ERROR, NETWORK_ERROR", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "otp": { + "type": "string", + "description": "A current 2FA code." + }, + "otpKey": { + "type": "string", + "description": "The 2FA secret itself, instead of a code." + }, + "challengeId": { + "type": "string", + "description": "Supply after solving a CAPTCHA to retry the same request." + }, + "usernameOrLoginId": { + "type": "string", + "description": "A username, or a login id." + }, + "loginKey": { + "type": "string", + "description": "From `get-login-key`." + }, + "useLoginId": { + "type": "boolean", + "description": "Treat the value as a login id." + } + }, + "required": [ + "usernameOrLoginId", + "loginKey" + ] + } + } + } + } + } + }, + "/login-with-recovery": { + "post": { + "operationId": "loginWithRecovery", + "summary": "Log in with recovery answers.", + "description": "**Core call:** `context.loginWithRecovery2`\n\n**Command line**\n\n```\nlogin-with-recovery [--otp=] [--otp-key=] [--challenge-id=] --recovery-key= --username= --answer= …\n```\n\nNeeds both the recovery key and the answers; neither works alone.", + "tags": [ + "Login methods" + ], + "x-cli": { + "command": "login-with-recovery", + "flags": [ + { + "name": "answer", + "maps": "answers", + "repeat": true + } + ], + "extra": [], + "custom": true, + "preset": {} + }, + "x-core-call": "context.loginWithRecovery2", + "x-core-note": "Our surface drops the `2` from core's recovery2 naming, and calls the key `recoveryKey` to match what `change-recovery` returns.", + "x-source": "src/cli/engine/routes/login.ts", + "parameters": [], + "responses": { + "200": { + "description": "A session with `loginMethod: \"recovery\"`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "sessionId": { + "type": "string", + "description": "Identifies this login. Every account-scoped call carries it, and the CLI stores the most recent one so commands can omit it." + }, + "username": { + "type": "string", + "description": "Absent for a light account, which has no username." + }, + "rootLoginId": { + "type": "string", + "description": "The account root, stable across appIds. Two sessions sharing it are the same account." + }, + "loginMethod": { + "type": "string", + "enum": [ + "create", + "edge", + "key", + "password", + "pin", + "recovery" + ], + "description": "How this session was established." + }, + "autoLogoutSeconds": { + "type": "number", + "description": "Idle time before the engine logs the account out. 0 disables it." + }, + "expiresAt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "When auto-logout will fire, or null when it is disabled." + }, + "lastActivityAt": { + "type": "string", + "description": "Last call on this session, which is what auto-logout measures from." + }, + "createdAt": { + "type": "string", + "description": "When the login completed." + } + }, + "required": [ + "sessionId", + "rootLoginId", + "loginMethod", + "autoLogoutSeconds", + "expiresAt", + "lastActivityAt", + "createdAt" + ] + } + } + } + }, + "default": { + "description": "PASSWORD_ERROR, USERNAME_ERROR, NETWORK_ERROR", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "otp": { + "type": "string", + "description": "A current 2FA code." + }, + "otpKey": { + "type": "string", + "description": "The 2FA secret itself, instead of a code." + }, + "challengeId": { + "type": "string", + "description": "Supply after solving a CAPTCHA to retry the same request." + }, + "recoveryKey": { + "type": "string", + "description": "From `change-recovery`." + }, + "username": { + "type": "string", + "description": "The account name." + }, + "answers": { + "type": "array", + "items": { + "type": "string" + }, + "description": "In the same order as the questions." + } + }, + "required": [ + "recoveryKey", + "username", + "answers" + ] + } + } + } + } + } + }, + "/create-account": { + "post": { + "operationId": "createAccount", + "summary": "Create an account.", + "description": "**Core call:** `context.createAccount`\n\n**Command line**\n\n```\ncreate-account [--otp=] [--otp-key=] [--challenge-id=] [--username=] [--password=] [--pin=]\n```\n\nEvery credential is optional over REST: omitting all three creates a light account with no username.", + "tags": [ + "Login methods" + ], + "x-cli": { + "command": "create-account", + "flags": [], + "extra": [], + "custom": true, + "preset": {} + }, + "x-core-call": "context.createAccount", + "x-source": "src/cli/engine/routes/login.ts", + "parameters": [], + "responses": { + "200": { + "description": "A session with `loginMethod: \"create\"`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "sessionId": { + "type": "string", + "description": "Identifies this login. Every account-scoped call carries it, and the CLI stores the most recent one so commands can omit it." + }, + "username": { + "type": "string", + "description": "Absent for a light account, which has no username." + }, + "rootLoginId": { + "type": "string", + "description": "The account root, stable across appIds. Two sessions sharing it are the same account." + }, + "loginMethod": { + "type": "string", + "enum": [ + "create", + "edge", + "key", + "password", + "pin", + "recovery" + ], + "description": "How this session was established." + }, + "autoLogoutSeconds": { + "type": "number", + "description": "Idle time before the engine logs the account out. 0 disables it." + }, + "expiresAt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "When auto-logout will fire, or null when it is disabled." + }, + "lastActivityAt": { + "type": "string", + "description": "Last call on this session, which is what auto-logout measures from." + }, + "createdAt": { + "type": "string", + "description": "When the login completed." + } + }, + "required": [ + "sessionId", + "rootLoginId", + "loginMethod", + "autoLogoutSeconds", + "expiresAt", + "lastActivityAt", + "createdAt" + ] + } + } + } + }, + "default": { + "description": "USERNAME_ERROR, CHALLENGE_REQUIRED, BAD_REQUEST, NETWORK_ERROR", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "otp": { + "type": "string", + "description": "A current 2FA code." + }, + "otpKey": { + "type": "string", + "description": "The 2FA secret itself, instead of a code." + }, + "challengeId": { + "type": "string", + "description": "Supply after solving a CAPTCHA to retry the same request." + }, + "username": { + "type": "string", + "description": "The name to claim." + }, + "password": { + "type": "string", + "description": "The account password." + }, + "pin": { + "type": "string", + "description": "A device PIN to save." + } + } + } + } + } + } + } + }, + "/request-edge-login": { + "post": { + "operationId": "requestEdgeLogin", + "summary": "Start a QR login.", + "description": "**Core call:** `context.requestEdgeLogin`\n\n**Command line**\n\n```\nrequest-edge-login [--no-wait]\n```\n\nAsks the login server for a lobby another logged-in Edge device can approve. The returned `lobbyId` is what goes in the QR code.", + "tags": [ + "Login methods" + ], + "x-cli": { + "command": "request-edge-login", + "flags": [], + "extra": [ + { + "name": "no-wait", + "kind": "boolean", + "required": false, + "doc": "Print the lobby and exit instead of polling, so the QR can be displayed while `poll-edge-login` watches the same handle from another process." + } + ], + "custom": true, + "preset": {}, + "notes": "Prints the pending login, then polls every 2s for up to 5 minutes. On `done` it stores the session. With `--no-wait` it returns immediately and `poll-edge-login` takes over." + }, + "x-core-call": "context.requestEdgeLogin", + "x-source": "src/cli/engine/routes/login.ts", + "parameters": [], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "objectId": { + "type": "string", + "description": "Handle for the value the engine is holding. Pass it to the calls that consume it." + }, + "pendingId": { + "type": "string", + "description": "Same value as `objectId`, under the name the poll command takes." + }, + "kind": { + "type": "string", + "description": "What the handle refers to, which decides the calls that accept it." + }, + "expiresAt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "When the lobby closes and the QR code stops working." + }, + "lobbyId": { + "type": "string", + "description": "Lobby the phone connects to." + }, + "uri": { + "type": "string", + "description": "The `edge://` URI to render as a QR code for the phone to scan." + }, + "state": { + "type": "string", + "description": "How far the login has got: `pending` before the phone scans, `started` once it has, and `done` when `session` is filled in." + }, + "username": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Account that approved the login, known once the phone has scanned." + }, + "session": { + "anyOf": [ + { + "type": "object", + "properties": { + "sessionId": { + "type": "string" + }, + "username": { + "type": "string" + }, + "rootLoginId": { + "type": "string" + }, + "loginMethod": { + "type": "string", + "enum": [ + "password", + "pin", + "edge", + "key", + "create", + "recovery" + ] + }, + "autoLogoutSeconds": { + "type": "number" + }, + "expiresAt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "lastActivityAt": { + "type": "string" + }, + "createdAt": { + "type": "string" + } + }, + "required": [ + "sessionId", + "rootLoginId", + "loginMethod", + "autoLogoutSeconds", + "expiresAt", + "lastActivityAt", + "createdAt" + ] + }, + { + "type": "null" + } + ], + "description": "The session, null until `state` is `done`." + }, + "error": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Why the login failed, set only when `state` is `error`." + } + }, + "required": [ + "objectId", + "pendingId", + "kind", + "expiresAt", + "lobbyId", + "uri", + "state", + "username", + "session", + "error" + ] + } + } + } + }, + "default": { + "description": "NETWORK_ERROR", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/pending-edge-login/{pendingId}": { + "get": { + "operationId": "pollEdgeLogin", + "summary": "Poll a pending QR login.", + "description": "**Core call:** _none — Engine state for an in-flight requestEdgeLogin; core exposes it as EdgePendingEdgeLogin properties._\n\n**Command line**\n\n```\npoll-edge-login \n```\n\nOnce `state` reaches `done` the engine has already created the session, so the response carries one ready to use.", + "tags": [ + "Login methods" + ], + "x-cli": { + "command": "poll-edge-login", + "positional": "pendingId", + "flags": [], + "extra": [], + "custom": true, + "preset": {} + }, + "x-core-call": null, + "x-core-note": "Engine state for an in-flight requestEdgeLogin; core exposes it as EdgePendingEdgeLogin properties.", + "x-source": "src/cli/engine/routes/login.ts", + "parameters": [ + { + "name": "pendingId", + "in": "path", + "required": true, + "description": "The `pendingId` returned when the QR login was requested.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "objectId": { + "type": "string", + "description": "Handle for the value the engine is holding. Pass it to the calls that consume it." + }, + "pendingId": { + "type": "string", + "description": "Same value as `objectId`, under the name the poll command takes." + }, + "kind": { + "type": "string", + "description": "What the handle refers to, which decides the calls that accept it." + }, + "expiresAt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "When the lobby closes and the QR code stops working." + }, + "lobbyId": { + "type": "string", + "description": "Lobby the phone connects to." + }, + "uri": { + "type": "string", + "description": "The `edge://` URI to render as a QR code for the phone to scan." + }, + "state": { + "type": "string", + "description": "How far the login has got: `pending` before the phone scans, `started` once it has, and `done` when `session` is filled in." + }, + "username": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Account that approved the login, known once the phone has scanned." + }, + "session": { + "anyOf": [ + { + "type": "object", + "properties": { + "sessionId": { + "type": "string" + }, + "username": { + "type": "string" + }, + "rootLoginId": { + "type": "string" + }, + "loginMethod": { + "type": "string", + "enum": [ + "password", + "pin", + "edge", + "key", + "create", + "recovery" + ] + }, + "autoLogoutSeconds": { + "type": "number" + }, + "expiresAt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "lastActivityAt": { + "type": "string" + }, + "createdAt": { + "type": "string" + } + }, + "required": [ + "sessionId", + "rootLoginId", + "loginMethod", + "autoLogoutSeconds", + "expiresAt", + "lastActivityAt", + "createdAt" + ] + }, + { + "type": "null" + } + ], + "description": "The session, null until `state` is `done`." + }, + "error": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Why the login failed, set only when `state` is `error`." + } + }, + "required": [ + "objectId", + "pendingId", + "kind", + "expiresAt", + "lobbyId", + "uri", + "state", + "username", + "session", + "error" + ] + } + } + } + }, + "default": { + "description": "PENDING_LOGIN_NOT_FOUND, OBJECT_EXPIRED, OBJECT_KIND_MISMATCH", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/pending-edge-login/cancel-request/{pendingId}": { + "post": { + "operationId": "cancelEdgeLogin", + "summary": "Cancel a pending QR login.", + "description": "**Core call:** `EdgePendingEdgeLogin.cancelRequest`\n\n**Command line**\n\n```\ncancel-request \n```\n\n", + "tags": [ + "Login methods" + ], + "x-cli": { + "command": "cancel-request", + "positional": "pendingId", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "EdgePendingEdgeLogin.cancelRequest", + "x-source": "src/cli/engine/routes/login.ts", + "parameters": [ + { + "name": "pendingId", + "in": "path", + "required": true, + "description": "The `pendingId` returned when the QR login was requested.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content." + }, + "default": { + "description": "PENDING_LOGIN_NOT_FOUND", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/engine/sessions": { + "get": { + "operationId": "engineSessions", + "summary": "List active sessions.", + "description": "**Core call:** _none — The session registry is an engine construct; core has no multi-account session concept._\n\n**Command line**\n\n```\nengine-sessions\n```\n\n", + "tags": [ + "Login methods" + ], + "x-cli": { + "command": "engine-sessions", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": null, + "x-core-note": "The session registry is an engine construct; core has no multi-account session concept.", + "x-source": "src/cli/engine/routes/login.ts", + "parameters": [], + "responses": { + "200": { + "description": "A bare array, not wrapped in a key. Each `sessionId` is truncated: this route needs no session, so a usable id here would be a credential anyone who can reach the engine could collect.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "sessionId": { + "type": "string" + }, + "username": { + "type": "string" + }, + "rootLoginId": { + "type": "string" + }, + "loginMethod": { + "type": "string", + "enum": [ + "password", + "pin", + "edge", + "key", + "create", + "recovery" + ] + }, + "autoLogoutSeconds": { + "type": "number" + }, + "expiresAt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "lastActivityAt": { + "type": "string" + }, + "createdAt": { + "type": "string" + } + }, + "required": [ + "sessionId", + "rootLoginId", + "loginMethod", + "autoLogoutSeconds", + "expiresAt", + "lastActivityAt", + "createdAt" + ] + } + } + } + } + }, + "default": { + "description": "Error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}": { + "get": { + "operationId": "accountInfo", + "summary": "Account and session summary.", + "description": "**Core call:** _none — Engine composite of the session record plus EdgeAccount properties._\n\n**Command line**\n\n```\naccount-info\n```\n\nSession fields are spread at the top level alongside the account's own properties — there is no nested `session` object.", + "tags": [ + "Session" + ], + "x-cli": { + "command": "account-info", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": null, + "x-core-note": "Engine composite of the session record plus EdgeAccount properties.", + "x-source": "src/cli/engine/routes/account.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "appId": { + "type": "string", + "description": "Application this session logged into." + }, + "created": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "When the account was created, null for accounts predating the field." + }, + "lastLogin": { + "type": "string", + "description": "The previous login, not this one." + }, + "loggedIn": { + "type": "boolean", + "description": "False once the account has been logged out; the session object outlives it briefly." + }, + "recoveryKey": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Present only while recovery is configured." + }, + "otpEnabled": { + "type": "boolean", + "description": "2FA is on for this account." + }, + "otpResetPending": { + "type": "boolean", + "description": "True while somebody has a reset pending against this account." + }, + "canDuressLogin": { + "type": "boolean", + "description": "A duress PIN is configured, so this account can be opened in duress mode." + }, + "isDuressAccount": { + "type": "boolean", + "description": "True when this very session is the duress account rather than the real one." + }, + "edgeLogin": { + "type": "boolean", + "description": "This account was reached by QR login." + }, + "keyLogin": { + "type": "boolean", + "description": "This session was reached with a login key." + }, + "newAccount": { + "type": "boolean", + "description": "This session created the account rather than logging into an existing one." + }, + "passwordLogin": { + "type": "boolean", + "description": "This session was reached with a password." + }, + "pinLogin": { + "type": "boolean", + "description": "This session was reached with a PIN." + }, + "recoveryLogin": { + "type": "boolean", + "description": "This session was reached by answering recovery questions." + } + }, + "required": [ + "appId", + "created", + "lastLogin", + "loggedIn", + "recoveryKey", + "otpEnabled", + "otpResetPending", + "canDuressLogin", + "isDuressAccount", + "edgeLogin", + "keyLogin", + "newAccount", + "passwordLogin", + "pinLogin", + "recoveryLogin" + ] + } + } + } + }, + "default": { + "description": "INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}/logout": { + "post": { + "operationId": "logout", + "summary": "Log out.", + "description": "**Core call:** `account.logout`\n\n**Command line**\n\n```\nlogout\n```\n\nEnds the session and drops it from the engine. Any subscription scoped to this account or its wallets is closed with it.", + "tags": [ + "Session" + ], + "x-cli": { + "command": "logout", + "flags": [], + "extra": [], + "custom": true, + "preset": {}, + "notes": "Also clears the stored id from `session.json`." + }, + "x-core-call": "account.logout", + "x-source": "src/cli/engine/routes/account.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content." + }, + "default": { + "description": "INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}/touch": { + "post": { + "operationId": "touchSession", + "summary": "Keepalive.", + "description": "**Core call:** _none — Engine auto-logout timer; core has no idle concept._\n\n**Command line**\n\n```\ntouch\n```\n\nResets the idle auto-logout timer without doing any other work.", + "tags": [ + "Session" + ], + "x-cli": { + "command": "touch", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": null, + "x-core-note": "Engine auto-logout timer; core has no idle concept.", + "x-source": "src/cli/engine/routes/account.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "The session, with a refreshed `expiresAt`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "sessionId": { + "type": "string", + "description": "Identifies this login. Every account-scoped call carries it, and the CLI stores the most recent one so commands can omit it." + }, + "username": { + "type": "string", + "description": "Absent for a light account, which has no username." + }, + "rootLoginId": { + "type": "string", + "description": "The account root, stable across appIds. Two sessions sharing it are the same account." + }, + "loginMethod": { + "type": "string", + "enum": [ + "create", + "edge", + "key", + "password", + "pin", + "recovery" + ], + "description": "How this session was established." + }, + "autoLogoutSeconds": { + "type": "number", + "description": "Idle time before the engine logs the account out. 0 disables it." + }, + "expiresAt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "When auto-logout will fire, or null when it is disabled." + }, + "lastActivityAt": { + "type": "string", + "description": "Last call on this session, which is what auto-logout measures from." + }, + "createdAt": { + "type": "string", + "description": "When the login completed." + } + }, + "required": [ + "sessionId", + "rootLoginId", + "loginMethod", + "autoLogoutSeconds", + "expiresAt", + "lastActivityAt", + "createdAt" + ] + } + } + } + }, + "default": { + "description": "INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}/get-login-key": { + "get": { + "operationId": "getLoginKey", + "summary": "Read the account login key.", + "description": "**Core call:** `account.getLoginKey`\n\n**Command line**\n\n```\nget-login-key\n```\n\nThe key `login-with-key` takes. It grants full account access, so treat the output as secret.", + "tags": [ + "Session" + ], + "x-cli": { + "command": "get-login-key", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "account.getLoginKey", + "x-source": "src/cli/engine/routes/account.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "loginKey": { + "type": "string", + "description": "base58. Full account access — keep it safe." + } + }, + "required": [ + "loginKey" + ] + } + } + } + }, + "default": { + "description": "INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}/sync": { + "post": { + "operationId": "accountSync", + "summary": "Force an account data sync.", + "description": "**Core call:** `account.sync`\n\n**Command line**\n\n```\nsync\n```\n\nPushes and pulls the account repos immediately rather than waiting for the next scheduled sync.", + "tags": [ + "Session" + ], + "x-cli": { + "command": "sync", + "flags": [], + "extra": [], + "custom": false, + "preset": {}, + "notes": "Named `sync` for the account; the wallet one is `wallet-sync`." + }, + "x-core-call": "account.sync", + "x-source": "src/cli/engine/routes/account.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content." + }, + "default": { + "description": "NETWORK_ERROR, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}/delete-remote-account": { + "post": { + "operationId": "deleteRemoteAccount", + "summary": "Permanently delete the remote account.", + "description": "**Core call:** `account.deleteRemoteAccount`\n\n**Command line**\n\n```\ndelete-remote-account --yes\n```\n\nIrreversible. The account is removed from the login server, and funds in its wallets are unrecoverable without the keys. The session is logged out afterwards.", + "tags": [ + "Session" + ], + "x-cli": { + "command": "delete-remote-account", + "flags": [], + "extra": [ + { + "name": "yes", + "kind": "boolean", + "required": true, + "doc": "Confirms intent. Without it the command refuses to run." + } + ], + "custom": true, + "preset": {} + }, + "x-core-call": "account.deleteRemoteAccount", + "x-source": "src/cli/engine/routes/account.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content." + }, + "default": { + "description": "NETWORK_ERROR, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}/wait-for-all-wallets": { + "post": { + "operationId": "waitForAllWallets", + "summary": "Wait for every wallet to finish loading.", + "description": "**Core call:** `account.waitForAllWallets`\n\n**Command line**\n\n```\nwait-for-all-wallets\n```\n\nWallets load in the background after login, so a list taken straight afterwards can be short. This resolves once each active wallet has either loaded or failed — balances may still be syncing afterwards.", + "tags": [ + "Session" + ], + "x-cli": { + "command": "wait-for-all-wallets", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "account.waitForAllWallets", + "x-source": "src/cli/engine/routes/account.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content." + }, + "default": { + "description": "INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}/currency-wallets": { + "get": { + "operationId": "currencyWallets", + "summary": "List the account's loaded wallets.", + "description": "**Core call:** `account.currencyWallets`\n\n**Command line**\n\n```\ncurrency-wallets\n```\n\nEvery wallet core has built an API for, which is the account's active set: `activeWalletIds` is `ids.filter(id => !archived)`, so an archived or deleted wallet is not here. Paused wallets are.\n\nThis took a `filter` of `active`, `archived`, `hidden` or `all`, and three of those four could not work. The handler resolved each id through `account.currencyWallets`, which core builds from `activeWalletIds` **alone**, so the archived and hidden lists were disjoint from the resolvable set by construction: every id mapped to `undefined`, the `filter(wallet != null)` dropped it, and `?filter=archived` answered `{\"currencyWallets\":[]}` on every account, always. `all` concatenated the three lists, so it was `active` with each hidden wallet — which is active and hidden at once, core deriving the two flags independently — listed twice. Nor could the missing wallets be served in this shape: an archived wallet has no loaded API, so `name`, `currencyCode`, `blockHeight`, `syncStatus` and `paused` do not exist for it, and seven required response fields would have had to become nullable for every caller to carry three values that never worked.\n\n`all-keys` is the route for that, and already was: it returns `EdgeWalletInfoFull[]` — id, type, `archived`, `deleted`, `hidden`, `sortIndex` — which is everything knowable about a wallet core has not loaded.", + "tags": [ + "Session" + ], + "x-cli": { + "command": "currency-wallets", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "account.currencyWallets", + "x-core-note": "account.currencyWallets is keyed by activeWalletIds.", + "x-source": "src/cli/engine/routes/account.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "currencyWallets": { + "type": "array", + "items": { + "type": "object", + "properties": { + "walletId": { + "type": "string" + }, + "id": { + "type": "string" + }, + "type": { + "type": "string" + }, + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "pluginId": { + "type": "string" + }, + "currencyCode": { + "type": "string" + }, + "fiatCurrencyCode": { + "type": "string" + }, + "blockHeight": { + "type": "number" + }, + "syncStatus": {}, + "syncRatio": { + "type": "string" + }, + "paused": { + "type": "boolean" + }, + "imported": { + "type": "boolean" + }, + "created": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "enabledTokenIds": { + "type": "array", + "items": { + "type": "string" + } + }, + "detectedTokenIds": { + "type": "array", + "items": { + "type": "string" + } + }, + "unactivatedTokenIds": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "walletId", + "id", + "type", + "name", + "pluginId", + "currencyCode", + "fiatCurrencyCode", + "blockHeight", + "syncStatus", + "paused", + "created", + "enabledTokenIds", + "detectedTokenIds", + "unactivatedTokenIds" + ] + }, + "description": "Every wallet core has loaded for this account, including paused ones. Archived, deleted and hidden wallets are not loaded; `all-keys` lists those." + } + }, + "required": [ + "currencyWallets" + ] + } + } + } + }, + "default": { + "description": "INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}/create-currency-wallet": { + "post": { + "operationId": "createCurrencyWallet", + "summary": "Create a currency wallet.", + "description": "**Core call:** `account.createCurrencyWallet`\n\n**Command line**\n\n```\ncreate-currency-wallet --wallet-type= [--name=] [--import-text=]\n```\n\n", + "tags": [ + "Session" + ], + "x-cli": { + "command": "create-currency-wallet", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "account.createCurrencyWallet", + "x-source": "src/cli/engine/routes/account.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "walletId": { + "type": "string", + "description": "The full wallet id. Commands taking a wallet accept any unique prefix." + }, + "id": { + "type": "string", + "description": "Same value as `walletId`, under the name `edge-core-js` uses. Core’s `EdgeCurrencyWallet` has only `id`; `walletId` is the name every route’s parameter takes, so both are published and a caller can use whichever half of the API it is reading." + }, + "type": { + "type": "string", + "description": "Key type, such as `wallet:bitcoin`." + }, + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "User-assigned name, null until one is set." + }, + "pluginId": { + "type": "string", + "description": "Currency plugin backing this wallet." + }, + "currencyCode": { + "type": "string", + "description": "Ticker for the native asset." + }, + "fiatCurrencyCode": { + "type": "string", + "description": "Fiat the wallet reports value in, as `iso:USD`." + }, + "blockHeight": { + "type": "number", + "description": "Chain height this wallet has seen." + }, + "syncStatus": { + "description": "`EdgeWalletSyncStatus` from core." + }, + "syncRatio": { + "type": "string", + "description": "Sync progress as a percentage, for display." + }, + "paused": { + "type": "boolean", + "description": "True while the engine is not syncing this wallet." + }, + "imported": { + "type": "boolean", + "description": "True when the keys came from an import rather than being generated here." + }, + "created": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "When the wallet was created, null for wallets predating the field." + }, + "enabledTokenIds": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Tokens the user turned on." + }, + "detectedTokenIds": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Tokens found on-chain that are not enabled yet." + }, + "unactivatedTokenIds": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Enabled tokens still awaiting on-chain activation." + } + }, + "required": [ + "walletId", + "id", + "type", + "name", + "pluginId", + "currencyCode", + "fiatCurrencyCode", + "blockHeight", + "syncStatus", + "paused", + "created", + "enabledTokenIds", + "detectedTokenIds", + "unactivatedTokenIds" + ] + } + } + } + }, + "default": { + "description": "BAD_REQUEST, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "walletType": { + "type": "string", + "description": "From `currency-configs`, e.g. `wallet:bitcoin`." + }, + "name": { + "type": "string", + "description": "Display name." + }, + "importText": { + "type": "string", + "description": "Seed or key text to import instead of generating." + } + }, + "required": [ + "walletType" + ] + } + } + } + } + } + }, + "/account/{sessionId}/create-currency-wallets": { + "post": { + "operationId": "createCurrencyWallets", + "summary": "Create several wallets at once.", + "description": "**Core call:** `account.createCurrencyWallets`\n\n**Command line**\n\n```\ncreate-currency-wallets --create-wallets=''\n```\n\nPartial success is normal: each entry reports its own outcome, and one failure does not roll back the others.", + "tags": [ + "Session" + ], + "x-cli": { + "command": "create-currency-wallets", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "account.createCurrencyWallets", + "x-source": "src/cli/engine/routes/account.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": {}, + "description": "Mirrors core’s EdgeResult[]: `{ ok, wallet }` or `{ ok: false, error }`." + } + }, + "required": [ + "results" + ] + } + } + } + }, + "default": { + "description": "BAD_REQUEST, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "createWallets": { + "type": "array", + "items": { + "type": "object", + "properties": { + "walletType": { + "type": "string" + }, + "name": { + "type": "string" + }, + "fiatCurrencyCode": { + "type": "string" + } + }, + "required": [ + "walletType" + ] + }, + "description": "`EdgeCreateCurrencyWallet[]`: walletType, plus optional name and fiatCurrencyCode." + } + }, + "required": [ + "createWallets" + ] + } + } + } + } + } + }, + "/account/{sessionId}/local-settings": { + "get": { + "operationId": "localSettings", + "summary": "Local settings.", + "description": "**Core call:** _none — GUI code (src/util/localAccountSettings), reached through account.localDisklet._\n\n**Command line**\n\n```\nlocal-settings\n```\n\nDevice-local account settings, stored in `Settings.json` on `account.localDisklet`. They are not synced — a phone and a CLI keep separate copies unless they share an Edge data directory.", + "tags": [ + "Local settings" + ], + "x-cli": { + "command": "local-settings", + "flags": [], + "extra": [], + "custom": true, + "preset": {} + }, + "x-core-call": null, + "x-core-note": "GUI code (src/util/localAccountSettings), reached through account.localDisklet.", + "x-source": "src/cli/engine/routes/localSettings.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "spamFilterOn": { + "type": "boolean", + "description": "Hide spam transactions in `get-transactions` results. Defaults to `true`, matching the GUI. The filter hides rows; it never changes stored metadata." + } + }, + "required": [ + "spamFilterOn" + ] + } + } + } + }, + "default": { + "description": "INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}/change-local-settings": { + "post": { + "operationId": "changeLocalSettings", + "summary": "Change local settings.", + "description": "**Core call:** _none — GUI code (src/util/localAccountSettings)._\n\n**Command line**\n\n```\nlocal-settings --spam-filter-on=true|false\n```\n\nWrites device-local account settings. Every option is a field on the body; `spamFilterOn` is the only one today, and new options are added alongside it.", + "tags": [ + "Local settings" + ], + "x-cli": { + "command": "local-settings", + "flags": [], + "extra": [], + "custom": true, + "preset": {}, + "notes": "With no flag the command reads; with one it writes." + }, + "x-core-call": null, + "x-core-note": "GUI code (src/util/localAccountSettings).", + "x-source": "src/cli/engine/routes/localSettings.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "spamFilterOn": { + "type": "boolean", + "description": "Hide spam transactions in `get-transactions` results. Defaults to `true`, matching the GUI. The filter hides rows; it never changes stored metadata." + } + }, + "required": [ + "spamFilterOn" + ] + } + } + } + }, + "default": { + "description": "INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "spamFilterOn": { + "type": "boolean", + "description": "Hide spam transactions in `get-transactions` results. Defaults to `true`, matching the GUI. The filter hides rows; it never changes stored metadata." + } + }, + "required": [ + "spamFilterOn" + ] + } + } + } + } + } + }, + "/account/{sessionId}/change-password": { + "post": { + "operationId": "changePassword", + "summary": "Set or change the password.", + "description": "**Core call:** `account.changePassword`\n\n**Command line**\n\n```\nchange-password --password=\n```\n\nThe login server enforces its own rules; `check-password-rules` scores a candidate first.", + "tags": [ + "Credentials" + ], + "x-cli": { + "command": "change-password", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "account.changePassword", + "x-source": "src/cli/engine/routes/credentials.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content." + }, + "default": { + "description": "BAD_REQUEST, NETWORK_ERROR, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "password": { + "type": "string", + "description": "The new password." + } + }, + "required": [ + "password" + ] + } + } + } + } + } + }, + "/account/{sessionId}/delete-password": { + "post": { + "operationId": "deletePassword", + "summary": "Remove password login.", + "description": "**Core call:** `account.deletePassword`\n\n**Command line**\n\n```\ndelete-password\n```\n\nThe account keeps its other login methods; only the password stops working.", + "tags": [ + "Credentials" + ], + "x-cli": { + "command": "delete-password", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "account.deletePassword", + "x-source": "src/cli/engine/routes/credentials.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content." + }, + "default": { + "description": "BAD_REQUEST, NETWORK_ERROR, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}/check-password": { + "post": { + "operationId": "checkPassword", + "summary": "Verify a password.", + "description": "**Core call:** `account.checkPassword`\n\n**Command line**\n\n```\ncheck-password --password=\n```\n\nChecks without changing anything, which is how a caller gates a destructive action behind a re-entry prompt.", + "tags": [ + "Credentials" + ], + "x-cli": { + "command": "check-password", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "account.checkPassword", + "x-source": "src/cli/engine/routes/credentials.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ok": { + "type": "boolean", + "description": "False for a wrong password — not an error response." + } + }, + "required": [ + "ok" + ] + } + } + } + }, + "default": { + "description": "INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "password": { + "type": "string", + "description": "The account password." + } + }, + "required": [ + "password" + ] + } + } + } + } + } + }, + "/account/{sessionId}/get-pin": { + "get": { + "operationId": "getPin", + "summary": "Read the account PIN.", + "description": "**Core call:** `account.getPin`\n\n**Command line**\n\n```\nget-pin\n```\n\nReturns the PIN itself, not a status flag, so treat the output as secret.", + "tags": [ + "Credentials" + ], + "x-cli": { + "command": "get-pin", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "account.getPin", + "x-source": "src/cli/engine/routes/credentials.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "pin": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Null when no PIN is set." + } + }, + "required": [ + "pin" + ] + } + } + } + }, + "default": { + "description": "INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}/change-pin": { + "post": { + "operationId": "changePin", + "summary": "Set or change the PIN.", + "description": "**Core call:** `account.changePin`\n\n**Command line**\n\n```\nchange-pin --pin= [--enable-login[=false]] [--for-duress-account[=false]]\n```\n\n", + "tags": [ + "Credentials" + ], + "x-cli": { + "command": "change-pin", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "account.changePin", + "x-source": "src/cli/engine/routes/credentials.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "pin2Key": { + "type": "string", + "description": "The new PIN login key core returns." + } + }, + "required": [ + "pin2Key" + ] + } + } + } + }, + "default": { + "description": "BAD_REQUEST, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "pin": { + "type": "string", + "description": "The new PIN." + }, + "enableLogin": { + "type": "boolean", + "description": "Allow logging in with this PIN on this device." + }, + "forDuressAccount": { + "type": "boolean", + "description": "Act on the duress account rather than the real one." + } + }, + "required": [ + "pin" + ] + } + } + } + } + } + }, + "/account/{sessionId}/delete-pin": { + "post": { + "operationId": "deletePin", + "summary": "Remove the PIN.", + "description": "**Core call:** `account.deletePin`\n\n**Command line**\n\n```\ndelete-pin\n```\n\nPIN login stops working on this device; other methods are untouched.", + "tags": [ + "Credentials" + ], + "x-cli": { + "command": "delete-pin", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "account.deletePin", + "x-source": "src/cli/engine/routes/credentials.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content." + }, + "default": { + "description": "INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}/check-pin": { + "post": { + "operationId": "checkPin", + "summary": "Verify a PIN.", + "description": "**Core call:** `account.checkPin`\n\n**Command line**\n\n```\ncheck-pin --pin= [--for-duress-account[=false]]\n```\n\n", + "tags": [ + "Credentials" + ], + "x-cli": { + "command": "check-pin", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "account.checkPin", + "x-source": "src/cli/engine/routes/credentials.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ok": { + "type": "boolean", + "description": "False for a wrong PIN — not an error response." + } + }, + "required": [ + "ok" + ] + } + } + } + }, + "default": { + "description": "INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "pin": { + "type": "string", + "description": "The device PIN, usually four digits." + }, + "forDuressAccount": { + "type": "boolean", + "description": "Act on the duress account rather than the real one." + } + }, + "required": [ + "pin" + ] + } + } + } + } + } + }, + "/account/{sessionId}/change-username": { + "post": { + "operationId": "changeUsername", + "summary": "Change the username.", + "description": "**Core call:** `account.changeUsername`\n\n**Command line**\n\n```\nchange-username --username= [--password=]\n```\n\nThe old name is released, so it becomes available to anyone else.", + "tags": [ + "Credentials" + ], + "x-cli": { + "command": "change-username", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "account.changeUsername", + "x-source": "src/cli/engine/routes/credentials.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content." + }, + "default": { + "description": "USERNAME_ERROR, BAD_REQUEST, NETWORK_ERROR, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "username": { + "type": "string", + "description": "The new username." + }, + "password": { + "type": "string", + "description": "Required by core when the account has a password." + } + }, + "required": [ + "username" + ] + } + } + } + } + } + }, + "/account/{sessionId}/change-recovery": { + "post": { + "operationId": "changeRecovery", + "summary": "Set recovery questions and answers.", + "description": "**Core call:** `account.changeRecovery`\n\n**Command line**\n\n```\nchange-recovery --question= … --answer= …\n```\n\nThe returned key is half of the credential: without it the answers alone cannot recover the account, so it has to be stored somewhere else.", + "tags": [ + "Credentials" + ], + "x-cli": { + "command": "change-recovery", + "flags": [ + { + "name": "question", + "maps": "questions", + "repeat": true + }, + { + "name": "answer", + "maps": "answers", + "repeat": true + } + ], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "account.changeRecovery", + "x-core-note": "Our surface drops the `2` from core's recovery2 naming; a future Recovery1 would be suffixed `V1`.", + "x-source": "src/cli/engine/routes/credentials.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "recoveryKey": { + "type": "string", + "description": "Store this out of band. `login-with-recovery` needs it alongside the answers." + } + }, + "required": [ + "recoveryKey" + ] + } + } + } + }, + "default": { + "description": "BAD_REQUEST, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "questions": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The questions to ask." + }, + "answers": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Same length and order as `questions`." + } + }, + "required": [ + "questions", + "answers" + ] + } + } + } + } + } + }, + "/account/{sessionId}/delete-recovery": { + "post": { + "operationId": "deleteRecovery", + "summary": "Disable recovery login.", + "description": "**Core call:** `account.deleteRecovery`\n\n**Command line**\n\n```\ndelete-recovery\n```\n\nThe existing recovery key stops working.", + "tags": [ + "Credentials" + ], + "x-cli": { + "command": "delete-recovery", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "account.deleteRecovery", + "x-source": "src/cli/engine/routes/credentials.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content." + }, + "default": { + "description": "INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}/otp-key": { + "get": { + "operationId": "otpKey", + "summary": "Read the 2FA secret and reset state.", + "description": "**Core call:** `account.otpKey`\n\n**Command line**\n\n```\notp-key\n```\n\n", + "tags": [ + "Two-factor authentication" + ], + "x-cli": { + "command": "otp-key", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "account.otpKey", + "x-core-note": "Also carries account.otpResetDate.", + "x-source": "src/cli/engine/routes/otp.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "otpKey": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Null when 2FA is off. The 2FA secret itself. Secret material — record it safely." + }, + "otpResetDate": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Set once somebody has requested a reset; cancel it with `cancel-otp-reset`." + } + }, + "required": [ + "otpKey", + "otpResetDate" + ] + } + } + } + }, + "default": { + "description": "INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}/enable-otp": { + "post": { + "operationId": "enableOtp", + "summary": "Enable 2FA.", + "description": "**Core call:** `account.enableOtp`\n\n**Command line**\n\n```\nenable-otp [--timeout=]\n```\n\nRecord the returned key before leaving the terminal: it is the only copy.", + "tags": [ + "Two-factor authentication" + ], + "x-cli": { + "command": "enable-otp", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "account.enableOtp", + "x-source": "src/cli/engine/routes/otp.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "otpKey": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "The new secret. The 2FA secret itself. Secret material — record it safely." + } + }, + "required": [ + "otpKey" + ] + } + } + } + }, + "default": { + "description": "INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "timeout": { + "type": "number", + "description": "How long a reset request must wait before it completes, in seconds. Core supplies the default when omitted." + } + } + } + } + } + } + } + }, + "/account/{sessionId}/disable-otp": { + "post": { + "operationId": "disableOtp", + "summary": "Disable 2FA.", + "description": "**Core call:** `account.disableOtp`\n\n**Command line**\n\n```\ndisable-otp\n```\n\nLogins stop requiring a code immediately.", + "tags": [ + "Two-factor authentication" + ], + "x-cli": { + "command": "disable-otp", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "account.disableOtp", + "x-source": "src/cli/engine/routes/otp.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content." + }, + "default": { + "description": "INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}/cancel-otp-reset": { + "post": { + "operationId": "cancelOtpReset", + "summary": "Cancel a pending 2FA reset.", + "description": "**Core call:** `account.cancelOtpReset`\n\n**Command line**\n\n```\ncancel-otp-reset\n```\n\nThe defence against somebody else requesting a reset on your account: as long as you cancel before the timer runs out, their reset never lands.", + "tags": [ + "Two-factor authentication" + ], + "x-cli": { + "command": "cancel-otp-reset", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "account.cancelOtpReset", + "x-source": "src/cli/engine/routes/otp.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content." + }, + "default": { + "description": "INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}/repair-otp": { + "post": { + "operationId": "repairOtp", + "summary": "Re-point the account at a known 2FA secret.", + "description": "**Core call:** `account.repairOtp`\n\n**Command line**\n\n```\nrepair-otp --otp-key=\n```\n\nFor a device whose stored secret has drifted from the server's.", + "tags": [ + "Two-factor authentication" + ], + "x-cli": { + "command": "repair-otp", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "account.repairOtp", + "x-source": "src/cli/engine/routes/otp.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content." + }, + "default": { + "description": "OTP_REQUIRED, BAD_REQUEST, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "otpKey": { + "type": "string", + "description": "The secret the account should use." + } + }, + "required": [ + "otpKey" + ] + } + } + } + } + } + }, + "/account/{sessionId}/pending-vouchers": { + "get": { + "operationId": "pendingVouchers", + "summary": "List pending 2FA vouchers.", + "description": "**Core call:** `account.pendingVouchers`\n\n**Command line**\n\n```\npending-vouchers\n```\n\nWhen 2FA blocks a login, the login server issues a voucher that an already-trusted device can approve or reject.", + "tags": [ + "Vouchers" + ], + "x-cli": { + "command": "pending-vouchers", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "account.pendingVouchers", + "x-source": "src/cli/engine/routes/vouchers.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "pendingVouchers": { + "type": "array", + "items": {}, + "description": "`EdgePendingVoucher[]`: voucherId, activates, created, deviceDescription, ipDescription." + } + }, + "required": [ + "pendingVouchers" + ] + } + } + } + }, + "default": { + "description": "INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}/approve-voucher": { + "post": { + "operationId": "approveVoucher", + "summary": "Approve a voucher.", + "description": "**Core call:** `account.approveVoucher`\n\n**Command line**\n\n```\napprove-voucher --voucher-id=\n```\n\nLets the waiting device finish logging in.", + "tags": [ + "Vouchers" + ], + "x-cli": { + "command": "approve-voucher", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "account.approveVoucher", + "x-source": "src/cli/engine/routes/vouchers.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content." + }, + "default": { + "description": "BAD_REQUEST, NETWORK_ERROR, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "voucherId": { + "type": "string", + "description": "From `pending-vouchers`, or an `OTP_REQUIRED` error’s `details.voucherId`." + } + }, + "required": [ + "voucherId" + ] + } + } + } + } + } + }, + "/account/{sessionId}/reject-voucher": { + "post": { + "operationId": "rejectVoucher", + "summary": "Reject a voucher.", + "description": "**Core call:** `account.rejectVoucher`\n\n**Command line**\n\n```\nreject-voucher --voucher-id=\n```\n\nDenies the waiting device. The login it was issued for cannot complete.", + "tags": [ + "Vouchers" + ], + "x-cli": { + "command": "reject-voucher", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "account.rejectVoucher", + "x-source": "src/cli/engine/routes/vouchers.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content." + }, + "default": { + "description": "BAD_REQUEST, NETWORK_ERROR, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "voucherId": { + "type": "string", + "description": "From `pending-vouchers`, or an `OTP_REQUIRED` error’s `details.voucherId`." + } + }, + "required": [ + "voucherId" + ] + } + } + } + } + } + }, + "/account/{sessionId}/fetch-lobby/{lobbyId}": { + "get": { + "operationId": "fetchLobby", + "summary": "Inspect a login request.", + "description": "**Core call:** `account.fetchLobby`\n\n**Command line**\n\n```\nfetch-lobby \n```\n\nThe other side of `request-edge-login`: shows who is asking, so a human can decide before approving.", + "tags": [ + "Approving a login" + ], + "x-cli": { + "command": "fetch-lobby", + "positional": "lobbyId", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "account.fetchLobby", + "x-source": "src/cli/engine/routes/lobby.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + }, + { + "name": "lobbyId", + "in": "path", + "required": true, + "description": "From the QR code, or an `edge://edge/` link.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "lobbyId": { + "type": "string", + "description": "The lobby that was fetched, echoed back." + }, + "loginRequest": { + "anyOf": [ + { + "type": "object", + "properties": { + "appId": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "displayImageDarkUrl": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "displayImageLightUrl": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "appId", + "displayName", + "displayImageDarkUrl", + "displayImageLightUrl" + ] + }, + { + "type": "null" + } + ], + "description": "Null when the lobby carries no pending login request." + } + }, + "required": [ + "lobbyId", + "loginRequest" + ] + } + } + } + }, + "default": { + "description": "BAD_REQUEST, NETWORK_ERROR, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}/approve-login-request/{lobbyId}": { + "post": { + "operationId": "approveLoginRequest", + "summary": "Approve a login request.", + "description": "**Core call:** `EdgeLoginRequest.approve`\n\n**Command line**\n\n```\napprove-login-request \n```\n\nGrants the requesting device access to this account.", + "tags": [ + "Approving a login" + ], + "x-cli": { + "command": "approve-login-request", + "positional": "lobbyId", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "EdgeLoginRequest.approve", + "x-core-note": "Reached through account.fetchLobby(lobbyId).loginRequest.", + "x-source": "src/cli/engine/routes/lobby.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + }, + { + "name": "lobbyId", + "in": "path", + "required": true, + "description": "From the QR code, or an `edge://edge/` link.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ok": { + "type": "boolean", + "description": "Always true; a failure arrives as an error envelope." + } + }, + "required": [ + "ok" + ] + } + } + } + }, + "default": { + "description": "NO_LOGIN_REQUEST, BAD_REQUEST, NETWORK_ERROR, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}/all-keys": { + "get": { + "operationId": "allKeys", + "summary": "List every key in the account.", + "description": "**Core call:** `account.allKeys`\n\n**Command line**\n\n```\nall-keys\n```\n\nIncludes archived and deleted keys, unlike `currency-wallets`.", + "tags": [ + "Keys" + ], + "x-cli": { + "command": "all-keys", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "account.allKeys", + "x-source": "src/cli/engine/routes/keys.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "allKeys": { + "type": "array", + "items": {}, + "description": "`EdgeWalletInfoFull[]`: id, type, keys, archived, deleted, hidden, sortIndex." + } + }, + "required": [ + "allKeys" + ] + } + } + } + }, + "default": { + "description": "INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}/create-wallet": { + "post": { + "operationId": "createWallet", + "summary": "Create a wallet from raw key JSON.", + "description": "**Core call:** `account.createWallet`\n\n**Command line**\n\n```\ncreate-wallet --type= [--keys='']\n```\n\nThe import path. Use `create-currency-wallet` to make a fresh wallet with generated keys.", + "tags": [ + "Keys" + ], + "x-cli": { + "command": "create-wallet", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "account.createWallet", + "x-source": "src/cli/engine/routes/keys.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "walletId": { + "type": "string", + "description": "The new wallet. Its keys are already saved." + } + }, + "required": [ + "walletId" + ] + } + } + } + }, + "default": { + "description": "BAD_REQUEST, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "Wallet type, e.g. `wallet:bitcoin`." + }, + "keys": { + "type": "object", + "additionalProperties": {}, + "description": "Plugin key material. Omit to let core generate it." + } + }, + "required": [ + "type" + ] + } + } + } + } + } + }, + "/account/{sessionId}/get-wallet-info": { + "get": { + "operationId": "getWalletInfo", + "summary": "Read one wallet's key info.", + "description": "**Core call:** `account.getWalletInfo`\n\n**Command line**\n\n```\nget-wallet-info --id=\n```\n\n", + "tags": [ + "Keys" + ], + "x-cli": { + "command": "get-wallet-info", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "account.getWalletInfo", + "x-source": "src/cli/engine/routes/keys.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + }, + { + "name": "id", + "in": "query", + "required": true, + "description": "The key id, from `all-keys`. Base64, like a wallet id.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "`EdgeWalletInfoFull`, verbatim from core — including the `keys` object.", + "content": { + "application/json": { + "schema": {} + } + } + }, + "default": { + "description": "WALLET_NOT_FOUND, AMBIGUOUS_WALLET_ID, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}/get-raw-private-key": { + "get": { + "operationId": "getRawPrivateKey", + "summary": "Read raw private key material.", + "description": "**Core call:** `account.getRawPrivateKey`\n\n**Command line**\n\n```\nget-raw-private-key --wallet-id=\n```\n\nSecret. Whatever the plugin stores — seed, mnemonic, xpriv.", + "tags": [ + "Keys" + ], + "x-cli": { + "command": "get-raw-private-key", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "account.getRawPrivateKey", + "x-source": "src/cli/engine/routes/keys.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + }, + { + "name": "walletId", + "in": "query", + "required": true, + "description": "The wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns `409 AMBIGUOUS_WALLET_ID` with `details.candidates`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "The plugin’s key object, at the top level.", + "content": { + "application/json": { + "schema": {} + } + } + }, + "default": { + "description": "WALLET_NOT_FOUND, AMBIGUOUS_WALLET_ID, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}/get-raw-public-key": { + "get": { + "operationId": "getRawPublicKey", + "summary": "Read raw public key material.", + "description": "**Core call:** `account.getRawPublicKey`\n\n**Command line**\n\n```\nget-raw-public-key --wallet-id=\n```\n\n", + "tags": [ + "Keys" + ], + "x-cli": { + "command": "get-raw-public-key", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "account.getRawPublicKey", + "x-source": "src/cli/engine/routes/keys.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + }, + { + "name": "walletId", + "in": "query", + "required": true, + "description": "The wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns `409 AMBIGUOUS_WALLET_ID` with `details.candidates`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "The plugin’s public key object.", + "content": { + "application/json": { + "schema": {} + } + } + }, + "default": { + "description": "WALLET_NOT_FOUND, AMBIGUOUS_WALLET_ID, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}/get-display-private-key": { + "get": { + "operationId": "getDisplayPrivateKey", + "summary": "Export the private key for display.", + "description": "**Core call:** `account.getDisplayPrivateKey`\n\n**Command line**\n\n```\nget-display-private-key --wallet-id=\n```\n\nSecret. The human-facing form — WIF, seed phrase, whatever the plugin shows on its export screen.", + "tags": [ + "Keys" + ], + "x-cli": { + "command": "get-display-private-key", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "account.getDisplayPrivateKey", + "x-source": "src/cli/engine/routes/keys.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + }, + { + "name": "walletId", + "in": "query", + "required": true, + "description": "The wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns `409 AMBIGUOUS_WALLET_ID` with `details.candidates`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "The displayable private key." + } + }, + "required": [ + "key" + ] + } + } + } + }, + "default": { + "description": "WALLET_NOT_FOUND, AMBIGUOUS_WALLET_ID, WALLET_NOT_RUNNING, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}/get-display-public-key": { + "get": { + "operationId": "getDisplayPublicKey", + "summary": "Export the public key for display.", + "description": "**Core call:** `account.getDisplayPublicKey`\n\n**Command line**\n\n```\nget-display-public-key --wallet-id=\n```\n\nThe xpub or equivalent — safe to share for watch-only use.", + "tags": [ + "Keys" + ], + "x-cli": { + "command": "get-display-public-key", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "account.getDisplayPublicKey", + "x-source": "src/cli/engine/routes/keys.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + }, + { + "name": "walletId", + "in": "query", + "required": true, + "description": "The wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns `409 AMBIGUOUS_WALLET_ID` with `details.candidates`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "The displayable public key." + } + }, + "required": [ + "key" + ] + } + } + } + }, + "default": { + "description": "WALLET_NOT_FOUND, AMBIGUOUS_WALLET_ID, WALLET_NOT_RUNNING, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}/list-splittable-wallet-types": { + "get": { + "operationId": "listSplittableWalletTypes", + "summary": "List chains a wallet can split into.", + "description": "**Core call:** `account.listSplittableWalletTypes`\n\n**Command line**\n\n```\nlist-splittable-wallet-types --wallet-id=\n```\n\nForked-chain support: which wallet types can be derived from these keys.", + "tags": [ + "Keys" + ], + "x-cli": { + "command": "list-splittable-wallet-types", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "account.listSplittableWalletTypes", + "x-source": "src/cli/engine/routes/keys.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + }, + { + "name": "walletId", + "in": "query", + "required": true, + "description": "The wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns `409 AMBIGUOUS_WALLET_ID` with `details.candidates`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "walletTypes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Types valid for `split`." + } + }, + "required": [ + "walletTypes" + ] + } + } + } + }, + "default": { + "description": "WALLET_NOT_FOUND, AMBIGUOUS_WALLET_ID, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}/change-wallet-states": { + "post": { + "operationId": "changeWalletStates", + "summary": "Archive, delete, hide, or reorder wallets.", + "description": "**Core call:** `account.changeWalletStates`\n\n**Command line**\n\n```\nchange-wallet-states [--wallet-states=''] --wallet-id= [--archived=] [--deleted=] [--hidden=] [--sort-index=]\n```\n\nThe canonical backend for every wallet flag; there are no separate archive, unarchive or undelete verbs.", + "tags": [ + "Keys" + ], + "x-cli": { + "command": "change-wallet-states", + "flags": [], + "extra": [ + { + "name": "wallet-id", + "kind": "string", + "required": true, + "doc": "The wallet to change. The command makes it the key of a single-entry `walletStates` map." + }, + { + "name": "archived", + "kind": "boolstr", + "required": false, + "doc": "Hide from the active list." + }, + { + "name": "deleted", + "kind": "boolstr", + "required": false, + "doc": "Mark deleted." + }, + { + "name": "hidden", + "kind": "boolstr", + "required": false, + "doc": "Hide from the wallet picker." + }, + { + "name": "sort-index", + "kind": "string", + "required": false, + "doc": "Position in the wallet list." + } + ], + "custom": true, + "preset": {}, + "notes": "The command builds a single-wallet `walletStates` map from these flags, and needs at least one." + }, + "x-core-call": "account.changeWalletStates", + "x-source": "src/cli/engine/routes/keys.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content." + }, + "default": { + "description": "BAD_REQUEST, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "walletStates": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "archived": { + "type": "boolean" + }, + "deleted": { + "type": "boolean" + }, + "hidden": { + "type": "boolean" + }, + "sortIndex": { + "type": "number" + } + } + }, + "description": "`EdgeWalletStates`: wallet ids to the flags being changed." + } + } + } + } + } + } + } + }, + "/account/{sessionId}/wallet": { + "get": { + "operationId": "walletInfo", + "summary": "Wallet detail.", + "description": "**Core call:** _none — Engine composite of EdgeCurrencyWallet properties plus its EdgeCurrencyConfig token map._\n\n**Command line**\n\n```\nwallet-info --wallet-id=\n```\n\n", + "tags": [ + "Wallet state" + ], + "x-cli": { + "command": "wallet-info", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": null, + "x-core-note": "Engine composite of EdgeCurrencyWallet properties plus its EdgeCurrencyConfig token map.", + "x-source": "src/cli/engine/routes/wallets.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + }, + { + "name": "walletId", + "in": "query", + "required": true, + "description": "The wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns `409 AMBIGUOUS_WALLET_ID` with `details.candidates`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Every WalletSummary field, plus denominations and walletSettings. `allTokens` is not here — `wallet-tokens` exists to carry it, and returning it from both sent the same map twice in a session that calls both.", + "content": { + "application/json": { + "schema": {} + } + } + }, + "default": { + "description": "WALLET_NOT_FOUND, AMBIGUOUS_WALLET_ID, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}/wallet/rename-wallet": { + "post": { + "operationId": "renameWallet", + "summary": "Rename a wallet.", + "description": "**Core call:** `wallet.renameWallet`\n\n**Command line**\n\n```\nrename-wallet --wallet-id= --name=\n```\n\n", + "tags": [ + "Wallet state" + ], + "x-cli": { + "command": "rename-wallet", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "wallet.renameWallet", + "x-source": "src/cli/engine/routes/wallets.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content." + }, + "default": { + "description": "BAD_REQUEST, WALLET_NOT_FOUND, AMBIGUOUS_WALLET_ID, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "walletId": { + "type": "string", + "description": "The wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns `409 AMBIGUOUS_WALLET_ID` with `details.candidates`." + }, + "name": { + "type": "string", + "description": "The new display name." + } + }, + "required": [ + "walletId", + "name" + ] + } + } + } + } + } + }, + "/account/{sessionId}/wallet/set-fiat-currency-code": { + "post": { + "operationId": "setFiatCurrencyCode", + "summary": "Change a wallet's fiat currency.", + "description": "**Core call:** `wallet.setFiatCurrencyCode`\n\n**Command line**\n\n```\nset-fiat-currency-code --wallet-id= --fiat-currency-code=\n```\n\nAffects how balances and history are priced, not the asset itself.", + "tags": [ + "Wallet state" + ], + "x-cli": { + "command": "set-fiat-currency-code", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "wallet.setFiatCurrencyCode", + "x-source": "src/cli/engine/routes/wallets.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content." + }, + "default": { + "description": "BAD_REQUEST, WALLET_NOT_FOUND, AMBIGUOUS_WALLET_ID, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "walletId": { + "type": "string", + "description": "The wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns `409 AMBIGUOUS_WALLET_ID` with `details.candidates`." + }, + "fiatCurrencyCode": { + "type": "string", + "description": "e.g. `iso:EUR`." + } + }, + "required": [ + "walletId", + "fiatCurrencyCode" + ] + } + } + } + } + } + }, + "/account/{sessionId}/wallet/change-paused": { + "post": { + "operationId": "changePaused", + "summary": "Pause or resume a wallet engine.", + "description": "**Core call:** `wallet.changePaused`\n\n**Command line**\n\n```\nchange-paused --wallet-id= --paused=true|false\n```\n\nA paused wallet stops syncing, which is how a caller quiets a chain it does not currently care about.", + "tags": [ + "Wallet state" + ], + "x-cli": { + "command": "change-paused", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "wallet.changePaused", + "x-source": "src/cli/engine/routes/wallets.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content." + }, + "default": { + "description": "BAD_REQUEST, WALLET_NOT_FOUND, AMBIGUOUS_WALLET_ID, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "walletId": { + "type": "string", + "description": "The wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns `409 AMBIGUOUS_WALLET_ID` with `details.candidates`." + }, + "paused": { + "type": "boolean", + "description": "True to stop syncing." + } + }, + "required": [ + "walletId", + "paused" + ] + } + } + } + } + } + }, + "/account/{sessionId}/wallet/sync": { + "post": { + "operationId": "walletSync", + "summary": "Nudge one wallet to sync.", + "description": "**Core call:** `wallet.sync`\n\n**Command line**\n\n```\nwallet-sync --wallet-id=\n```\n\n", + "tags": [ + "Wallet state" + ], + "x-cli": { + "command": "wallet-sync", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "wallet.sync", + "x-source": "src/cli/engine/routes/wallets.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content." + }, + "default": { + "description": "WALLET_NOT_FOUND, AMBIGUOUS_WALLET_ID, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "walletId": { + "type": "string", + "description": "The wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns `409 AMBIGUOUS_WALLET_ID` with `details.candidates`." + } + }, + "required": [ + "walletId" + ] + } + } + } + } + } + }, + "/account/{sessionId}/wallet/resync-blockchain": { + "post": { + "operationId": "resyncBlockchain", + "summary": "Rescan the blockchain from scratch.", + "description": "**Core call:** `wallet.resyncBlockchain`\n\n**Command line**\n\n```\nresync-blockchain --wallet-id=\n```\n\nDrops cached chain state and re-scans. Expensive, and the wallet reports an incomplete balance until it finishes.", + "tags": [ + "Wallet state" + ], + "x-cli": { + "command": "resync-blockchain", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "wallet.resyncBlockchain", + "x-source": "src/cli/engine/routes/wallets.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content." + }, + "default": { + "description": "WALLET_NOT_FOUND, AMBIGUOUS_WALLET_ID, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "walletId": { + "type": "string", + "description": "The wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns `409 AMBIGUOUS_WALLET_ID` with `details.candidates`." + } + }, + "required": [ + "walletId" + ] + } + } + } + } + } + }, + "/account/{sessionId}/wallet/split": { + "post": { + "operationId": "splitWallet", + "summary": "Split a wallet into another chain.", + "description": "**Core call:** `wallet.split`\n\n**Command line**\n\n```\nsplit --wallet-id= --split-wallets=''\n```\n\nForked-chain support: derive a wallet of a different type from the same keys. `list-splittable-wallet-types` says which are valid.", + "tags": [ + "Wallet state" + ], + "x-cli": { + "command": "split", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "wallet.split", + "x-source": "src/cli/engine/routes/wallets.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": {}, + "description": "Per-entry outcomes, like batch create." + } + }, + "required": [ + "results" + ] + } + } + } + }, + "default": { + "description": "BAD_REQUEST, WALLET_NOT_FOUND, AMBIGUOUS_WALLET_ID, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "walletId": { + "type": "string", + "description": "The wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns `409 AMBIGUOUS_WALLET_ID` with `details.candidates`." + }, + "splitWallets": { + "type": "array", + "items": { + "type": "object", + "properties": { + "walletType": { + "type": "string" + }, + "name": { + "type": "string" + }, + "fiatCurrencyCode": { + "type": "string" + } + }, + "required": [ + "walletType" + ] + }, + "description": "`EdgeSplitCurrencyWallet[]`: walletType, plus optional name and fiatCurrencyCode." + } + }, + "required": [ + "walletId", + "splitWallets" + ] + } + } + } + } + } + }, + "/account/{sessionId}/wallet/dump-data": { + "get": { + "operationId": "dumpData", + "summary": "Dump wallet engine state.", + "description": "**Core call:** `wallet.dumpData`\n\n**Command line**\n\n```\ndump-data --wallet-id=\n```\n\nPlugin-defined debug output. Shape varies by plugin and can be very large.", + "tags": [ + "Wallet state" + ], + "x-cli": { + "command": "dump-data", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "wallet.dumpData", + "x-source": "src/cli/engine/routes/wallets.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + }, + { + "name": "walletId", + "in": "query", + "required": true, + "description": "The wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns `409 AMBIGUOUS_WALLET_ID` with `details.candidates`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "`EdgeDataDump`, straight from the plugin.", + "content": { + "application/json": { + "schema": {} + } + } + }, + "default": { + "description": "WALLET_NOT_FOUND, AMBIGUOUS_WALLET_ID, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}/wallet/balance-map": { + "get": { + "operationId": "balanceMap", + "summary": "Balances for every asset in the wallet.", + "description": "**Core call:** `wallet.balanceMap`\n\n**Command line**\n\n```\nbalance-map --wallet-id= [--token-id=]\n```\n\nThe native currency plus every enabled token.", + "tags": [ + "Wallet state" + ], + "x-cli": { + "command": "balance-map", + "flags": [], + "extra": [ + { + "name": "token-id", + "kind": "string", + "required": false, + "doc": "Client-side filter; core has no single-balance accessor." + } + ], + "custom": true, + "preset": {} + }, + "x-core-call": "wallet.balanceMap", + "x-core-note": "Rendered as an array, with currencyCode and displayAmount added from the wallet's denominations.", + "x-source": "src/cli/engine/routes/wallets.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + }, + { + "name": "walletId", + "in": "query", + "required": true, + "description": "The wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns `409 AMBIGUOUS_WALLET_ID` with `details.candidates`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "balances": { + "type": "array", + "items": { + "type": "object", + "properties": { + "tokenId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "currencyCode": { + "type": "string" + }, + "nativeAmount": { + "type": "string" + }, + "displayAmount": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "unknownToken": { + "type": "boolean" + } + }, + "required": [ + "tokenId", + "currencyCode", + "nativeAmount", + "displayAmount", + "unknownToken" + ] + }, + "description": "One entry per asset the wallet holds, native coin first: `tokenId`, `currencyCode`, `nativeAmount`, `displayAmount` and `unknownToken`. A token the plugin reports a balance for but whose config it no longer carries is listed with `unknownToken: true` and a null `displayAmount`, rather than failing the whole wallet." + } + }, + "required": [ + "balances" + ] + } + } + } + }, + "default": { + "description": "WALLET_NOT_FOUND, AMBIGUOUS_WALLET_ID, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}/wallet/get-addresses": { + "get": { + "operationId": "getAddresses", + "summary": "Receive addresses.", + "description": "**Core call:** `wallet.getAddresses`\n\n**Command line**\n\n```\nget-addresses --wallet-id= [--token-id=] [--force-index=]\n```\n\n", + "tags": [ + "Wallet state" + ], + "x-cli": { + "command": "get-addresses", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "wallet.getAddresses", + "x-source": "src/cli/engine/routes/wallets.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + }, + { + "name": "walletId", + "in": "query", + "required": true, + "description": "The wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns `409 AMBIGUOUS_WALLET_ID` with `details.candidates`.", + "schema": { + "type": "string" + } + }, + { + "name": "tokenId", + "in": "query", + "required": false, + "description": "Defaults to the native asset.", + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + { + "name": "forceIndex", + "in": "query", + "required": false, + "description": "Derive at a specific index.", + "schema": { + "type": "number" + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "addresses": { + "type": "array", + "items": {}, + "description": "`EdgeAddress[]`: addressType, publicAddress, nativeBalance." + } + }, + "required": [ + "addresses" + ] + } + } + } + }, + "default": { + "description": "TOKEN_NOT_FOUND, WALLET_NOT_FOUND, AMBIGUOUS_WALLET_ID, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}/wallet/tokens": { + "get": { + "operationId": "walletTokens", + "summary": "List a wallet's tokens.", + "description": "**Core call:** _none — Engine composite of the EdgeCurrencyConfig token maps plus wallet.enabledTokenIds and wallet.detectedTokenIds._\n\n**Command line**\n\n```\nwallet-tokens --wallet-id=\n```\n\n\"Enabled\" tokens are the ones the wallet syncs balances for; \"detected\" ones were seen on-chain but are not yet enabled.", + "tags": [ + "Tokens" + ], + "x-cli": { + "command": "wallet-tokens", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": null, + "x-core-note": "Engine composite of the EdgeCurrencyConfig token maps plus wallet.enabledTokenIds and wallet.detectedTokenIds.", + "x-source": "src/cli/engine/routes/tokens.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + }, + { + "name": "walletId", + "in": "query", + "required": true, + "description": "The wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns `409 AMBIGUOUS_WALLET_ID` with `details.candidates`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "allTokens": { + "type": "object", + "additionalProperties": {}, + "description": "`EdgeToken` by tokenId: everything the plugin ships with, plus this account’s own. `builtinTokens` is not returned separately — it is this map minus `customTokens`, and sending both wrote the whole built-in list down the socket twice." + }, + "customTokens": { + "type": "object", + "additionalProperties": {}, + "description": "`EdgeToken` by tokenId: tokens this account added by hand." + }, + "enabledTokenIds": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Which of the above the wallet is actually tracking." + }, + "detectedTokenIds": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Seen on-chain but not enabled, so their balances are not synced." + } + }, + "required": [ + "allTokens", + "customTokens", + "enabledTokenIds", + "detectedTokenIds" + ] + } + } + } + }, + "default": { + "description": "WALLET_NOT_FOUND, AMBIGUOUS_WALLET_ID, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}/wallet/change-enabled-token-ids": { + "post": { + "operationId": "changeEnabledTokenIds", + "summary": "Set the enabled token set.", + "description": "**Core call:** `wallet.changeEnabledTokenIds`\n\n**Command line**\n\n```\nchange-enabled-token-ids --wallet-id= --token-ids='' [--add=] [--remove=]\n```\n\nAbsolute: anything missing from `tokenIds` is disabled. Core has only this setter, so there is no add or remove call.", + "tags": [ + "Tokens" + ], + "x-cli": { + "command": "change-enabled-token-ids", + "flags": [], + "extra": [ + { + "name": "add", + "kind": "repeat", + "required": false, + "doc": "Read the current set, add this id, write it back." + }, + { + "name": "remove", + "kind": "repeat", + "required": false, + "doc": "Read the current set, drop this id, write it back." + } + ], + "custom": true, + "preset": {} + }, + "x-core-call": "wallet.changeEnabledTokenIds", + "x-source": "src/cli/engine/routes/tokens.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "enabledTokenIds": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The wallet’s enabled tokens after the change, not just what changed." + } + }, + "required": [ + "enabledTokenIds" + ] + } + } + } + }, + "default": { + "description": "BAD_REQUEST, WALLET_NOT_FOUND, AMBIGUOUS_WALLET_ID, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "walletId": { + "type": "string", + "description": "The wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns `409 AMBIGUOUS_WALLET_ID` with `details.candidates`." + }, + "tokenIds": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The complete desired set." + } + }, + "required": [ + "walletId", + "tokenIds" + ] + } + } + } + } + } + }, + "/account/{sessionId}/wallet/get-transactions": { + "get": { + "operationId": "getTransactions", + "summary": "List or export a wallet's transactions.", + "description": "**Core call:** `wallet.getTransactions`\n\n**Command line**\n\n```\nget-transactions --wallet-id= [--token-id=] [--limit=] [--offset=] [--save-export-prefs[=false]] [--start-date=] [--end-date=] [--search-string=] [--spam-threshold=] [--fiat=] [--export-format=] [--bitwave-account=] [--out=]\n```\n\nReads history, overlays the display metadata the GUI shows, fills historical fiat, and optionally formats the result — all on this one call.", + "tags": [ + "Transactions" + ], + "x-cli": { + "command": "get-transactions", + "flags": [ + { + "name": "bitwave-account", + "maps": "bitwaveAccountId", + "repeat": false + } + ], + "extra": [ + { + "name": "out", + "kind": "string", + "required": false, + "requiredWith": "exportFormat", + "doc": "Where to write the returned files. One format: the path. Several: a stem, plus .csv / .qbo / .bitwave.csv." + } + ], + "custom": true, + "preset": {} + }, + "x-core-call": "wallet.getTransactions", + "x-source": "src/cli/engine/routes/transactions.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + }, + { + "name": "walletId", + "in": "query", + "required": true, + "description": "The wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns `409 AMBIGUOUS_WALLET_ID` with `details.candidates`.", + "schema": { + "type": "string" + } + }, + { + "name": "tokenId", + "in": "query", + "required": false, + "description": "Defaults to the native asset.", + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "How many to return. Defaults to 99; pass `0` for every transaction from `offset` on. `total` in the response says how many matched, so a caller can page with `offset`.", + "schema": { + "type": "number" + } + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "Where to start. Defaults to 0.", + "schema": { + "type": "number" + } + }, + { + "name": "saveExportPrefs", + "in": "query", + "required": false, + "description": "Save `bitwaveAccountId` and the chosen formats into the wallet’s synced `exportTxInfo.json`, which the GUI export scene reads back. Off by default: a read does not change saved preferences.", + "schema": { + "type": "boolean" + } + }, + { + "name": "startDate", + "in": "query", + "required": false, + "description": "ISO-8601, or epoch milliseconds.", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "endDate", + "in": "query", + "required": false, + "description": "ISO-8601, or epoch milliseconds.", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "searchString", + "in": "query", + "required": false, + "description": "Matches payee, category, notes and txid.", + "schema": { + "type": "string" + } + }, + { + "name": "spamThreshold", + "in": "query", + "required": false, + "description": "Native-amount floor. Omitted, the account spam-filter setting applies to a listing and nothing is filtered from an `exportFormat` export; a value always overrides both, and `0` shows everything. An empty value reads as omitted, like every other query parameter.", + "schema": { + "type": "string" + } + }, + { + "name": "fiat", + "in": "query", + "required": false, + "description": "Three-letter ISO 4217 code. Defaults to the account defaultIsoFiat.", + "schema": { + "type": "string" + } + }, + { + "name": "exportFormat", + "in": "query", + "required": false, + "description": "Comma list of `csv`, `qbo`, `bitwave`.", + "schema": { + "type": "string" + } + }, + { + "name": "bitwaveAccountId", + "in": "query", + "required": false, + "description": "A 400 unless `exportFormat` includes `bitwave`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "`{ transactions, total, isoFiat }`, or `{ ok, isoFiat, total, files }` when exportFormat is set. Each transaction carries `metadata.name`, `metadata.category` and `metadata.notes` as **localized prose** in the engine’s boot locale — the engine has no per-request locale, so a second shell with a different `LANG` reuses the first engine and gets its language. `displayInfo` beside them carries the machine values the prose was derived from (`direction`, `assetActionType`, `actionType`, and the `category` split whose `category` member is one of `transfer`, `exchange`, `expense`, `income`), so a caller can render its own text.", + "content": { + "application/json": { + "schema": {} + } + } + }, + "default": { + "description": "BAD_REQUEST, MISSING_BITWAVE_ACCOUNT_ID, TOKEN_NOT_FOUND, WALLET_NOT_FOUND, AMBIGUOUS_WALLET_ID, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}/wallet/get-num-transactions": { + "get": { + "operationId": "getNumTransactions", + "summary": "Count transactions in a wallet.", + "description": "**Core call:** `wallet.getNumTransactions`\n\n**Command line**\n\n```\nget-num-transactions --wallet-id= [--token-id=]\n```\n\nCheaper than listing when only the total matters.", + "tags": [ + "Transactions" + ], + "x-cli": { + "command": "get-num-transactions", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "wallet.getNumTransactions", + "x-source": "src/cli/engine/routes/transactions.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + }, + { + "name": "walletId", + "in": "query", + "required": true, + "description": "The wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns `409 AMBIGUOUS_WALLET_ID` with `details.candidates`.", + "schema": { + "type": "string" + } + }, + { + "name": "tokenId", + "in": "query", + "required": false, + "description": "Defaults to the native asset.", + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "numTransactions": { + "type": "number", + "description": "Every transaction the wallet knows of." + } + }, + "required": [ + "numTransactions" + ] + } + } + } + }, + "default": { + "description": "TOKEN_NOT_FOUND, WALLET_NOT_FOUND, AMBIGUOUS_WALLET_ID, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}/wallet/save-tx-metadata": { + "post": { + "operationId": "saveTxMetadata", + "summary": "Save transaction metadata.", + "description": "**Core call:** `wallet.saveTxMetadata`\n\n**Command line**\n\n```\nsave-tx-metadata --wallet-id= --txid= [--token-id=] --metadata=''\n```\n\nOne of the paths that write transaction metadata to disk. The others are `save-tx-action`, which writes `savedAction` and `assetAction` to the same file, and `save-tx` and `spend`, both of which re-apply the caller's metadata through `saveTxAndMetadata` after the transaction is saved.", + "tags": [ + "Transactions" + ], + "x-cli": { + "command": "save-tx-metadata", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "wallet.saveTxMetadata", + "x-source": "src/cli/engine/routes/transactions.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content." + }, + "default": { + "description": "BAD_REQUEST, TOKEN_NOT_FOUND, WALLET_NOT_FOUND, AMBIGUOUS_WALLET_ID, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "walletId": { + "type": "string", + "description": "The wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns `409 AMBIGUOUS_WALLET_ID` with `details.candidates`." + }, + "txid": { + "type": "string", + "description": "Which transaction to tag." + }, + "tokenId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Defaults to the native asset." + }, + "metadata": { + "description": "`EdgeMetadataChange`: name, category, notes, exchangeAmount, bizId. `null` on a field deletes it; omitting the field leaves it unchanged." + } + }, + "required": [ + "walletId", + "txid", + "metadata" + ] + } + } + } + } + } + }, + "/account/{sessionId}/wallet/save-tx-action": { + "post": { + "operationId": "saveTxAction", + "summary": "Save a transaction action.", + "description": "**Core call:** `wallet.saveTxAction`\n\n**Command line**\n\n```\nsave-tx-action --wallet-id= --txid= [--token-id=] --saved-action='' [--asset-action='']\n```\n\nRecords what a transaction *was* — a swap, a stake — beyond its metadata.", + "tags": [ + "Transactions" + ], + "x-cli": { + "command": "save-tx-action", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "wallet.saveTxAction", + "x-source": "src/cli/engine/routes/transactions.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content." + }, + "default": { + "description": "BAD_REQUEST, TOKEN_NOT_FOUND, WALLET_NOT_FOUND, AMBIGUOUS_WALLET_ID, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "walletId": { + "type": "string", + "description": "The wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns `409 AMBIGUOUS_WALLET_ID` with `details.candidates`." + }, + "txid": { + "type": "string", + "description": "Which transaction to annotate." + }, + "tokenId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Defaults to the native asset." + }, + "savedAction": { + "anyOf": [ + { + "description": "EdgeTxActionSwap" + }, + { + "description": "EdgeTxActionSwapSend" + }, + { + "description": "EdgeTxActionStake" + }, + { + "description": "EdgeTxActionFiat" + }, + { + "description": "EdgeTxActionTokenApproval" + }, + { + "description": "EdgeTxActionGiftCard" + } + ], + "description": "`EdgeTxAction` describing what happened, discriminated on `actionType`: swap, stake, fiat, tokenApproval or giftCard." + }, + "assetAction": { + "description": "`EdgeAssetAction`: one `assetActionType`." + } + }, + "required": [ + "walletId", + "txid", + "savedAction" + ] + } + } + } + } + } + }, + "/account/{sessionId}/object/{objectId}": { + "get": { + "operationId": "getObject", + "summary": "Inspect an object handle.", + "description": "**Core call:** _none — Engine handle store; core identifies these values by object reference._\n\n**Command line**\n\n```\nobject-get \n```\n\nFor the session-scoped kinds: a staged transaction or a swap quote.", + "tags": [ + "Object handles" + ], + "x-cli": { + "command": "object-get", + "positional": "objectId", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": null, + "x-core-note": "Engine handle store; core identifies these values by object reference.", + "x-source": "src/cli/engine/routes/objects.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + }, + { + "name": "objectId", + "in": "path", + "required": true, + "description": "An ephemeral object handle id.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "The handle fields, plus a `value` holding a JSON-safe view of the object. A staged transaction is returned whole; a swap quote is summarised, because the value behind it is a live core object whose properties are getters.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "value": { + "description": "A JSON-safe view of the object, whose shape depends on `kind`: a staged transaction whole, a swap quote summarised, and null for a kind with no scalar projection." + }, + "objectId": { + "type": "string", + "description": "Handle for the value the engine is holding. Pass it to the calls that consume it." + }, + "kind": { + "type": "string", + "enum": [ + "lobby", + "pendingLogin", + "swap", + "transaction" + ], + "description": "What the handle refers to, which decides the calls that accept it." + }, + "createdAt": { + "type": "string", + "description": "When the engine took the handle." + }, + "expiresAt": { + "type": "string", + "description": "When the engine drops the handle. Handles live 5 minutes." + }, + "sessionId": { + "type": "string", + "description": "Session that created the handle; only that session may use it." + }, + "walletId": { + "type": "string", + "description": "Wallet the handle is bound to, when it belongs to one." + } + }, + "required": [ + "value", + "objectId", + "kind", + "createdAt", + "expiresAt" + ] + } + } + } + }, + "default": { + "description": "OBJECT_NOT_FOUND, OBJECT_EXPIRED, OBJECT_IN_USE, OBJECT_SESSION_MISMATCH, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}/object/delete/{objectId}": { + "post": { + "operationId": "deleteObject", + "summary": "Release an object handle.", + "description": "**Core call:** _none — Engine handle store._\n\n**Command line**\n\n```\nobject-delete \n```\n\nRuns the handle's cleanup — closing a swap quote, discarding a staged transaction — instead of waiting out the TTL.", + "tags": [ + "Object handles" + ], + "x-cli": { + "command": "object-delete", + "positional": "objectId", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": null, + "x-core-note": "Engine handle store.", + "x-source": "src/cli/engine/routes/objects.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + }, + { + "name": "objectId", + "in": "path", + "required": true, + "description": "An ephemeral object handle id.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ok": { + "type": "boolean", + "description": "Always true; a failure arrives as an error envelope." + }, + "objectId": { + "type": "string", + "description": "The handle this call consumed. It is now expired." + } + }, + "required": [ + "ok", + "objectId" + ] + } + } + } + }, + "default": { + "description": "OBJECT_NOT_FOUND, OBJECT_EXPIRED, OBJECT_IN_USE, OBJECT_SESSION_MISMATCH, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}/wallet/get-max-spendable": { + "post": { + "operationId": "getMaxSpendable", + "summary": "Largest sendable amount.", + "description": "**Core call:** `wallet.getMaxSpendable`\n\n**Command line**\n\n```\nget-max-spendable --wallet-id= [--spend-info=''] [--to=] [--native-amount=] [--amount=] [--token-id=] [--metadata='']\n```\n\nWhat empties the wallet after fees. A destination is still required, since fees depend on it.", + "tags": [ + "Spending" + ], + "x-cli": { + "command": "get-max-spendable", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "wallet.getMaxSpendable", + "x-source": "src/cli/engine/routes/spend.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "nativeAmount": { + "type": "string", + "description": "The most this wallet can send." + } + }, + "required": [ + "nativeAmount" + ] + } + } + } + }, + "default": { + "description": "TOKEN_NOT_FOUND, INSUFFICIENT_FUNDS, BAD_REQUEST, NETWORK_ERROR, WALLET_NOT_FOUND, AMBIGUOUS_WALLET_ID, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "walletId": { + "type": "string", + "description": "The wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns `409 AMBIGUOUS_WALLET_ID` with `details.candidates`." + }, + "spendInfo": { + "type": "object", + "properties": { + "spendTargets": { + "type": "array", + "items": { + "type": "object", + "properties": { + "publicAddress": { + "type": "string" + }, + "nativeAmount": { + "type": "string" + }, + "uniqueIdentifier": { + "type": "string" + }, + "memo": { + "type": "string" + }, + "otherParams": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "publicAddress" + ] + } + }, + "tokenId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "metadata": { + "description": "EdgeMetadata" + }, + "networkFeeOption": { + "type": "string" + }, + "customNetworkFee": { + "type": "object", + "additionalProperties": {} + }, + "rbfTxid": { + "type": "string" + }, + "memos": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": {} + } + }, + "assetAction": { + "description": "EdgeAssetAction" + }, + "savedAction": { + "description": "EdgeTxAction" + }, + "otherParams": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "spendTargets", + "tokenId" + ], + "description": "A full `EdgeSpendInfo`, used as-is when present. `spendTargets` is required." + }, + "to": { + "type": "string", + "description": "Address or BIP21 URI, run through `wallet.parseUri`." + }, + "nativeAmount": { + "type": "string", + "description": "How much, in native units." + }, + "amount": { + "type": "string", + "description": "Alias of `nativeAmount`." + }, + "tokenId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Defaults to the native asset." + }, + "metadata": { + "description": "Wins over anything parsed out of the URI." + } + }, + "required": [ + "walletId" + ] + } + } + } + } + } + }, + "/account/{sessionId}/wallet/spend": { + "post": { + "operationId": "spend", + "summary": "Send funds.", + "description": "**Core call:** _none — GUI composite: makeSpend, signTx, broadcastTx and saveTx together._\n\n**Command line**\n\n```\nspend [--use-max[=false]] [--dry-run[=false]] [--broadcast[=false]] [--save[=false]] --wallet-id= [--spend-info=''] [--to=] [--native-amount=] [--amount=] [--token-id=] [--metadata='']\n```\n\n`makeSpend`, then `signTx`, then optionally `broadcastTx` and `saveTx`, in one request. `broadcast` and `save` both default to true, so a bare body with a destination and an amount moves real money. A completed spend leaves no handle behind.", + "tags": [ + "Spending" + ], + "x-cli": { + "command": "spend", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-cli-alternates": [ + { + "command": "spend-max", + "flags": [], + "extra": [], + "custom": false, + "preset": { + "useMax": true + }, + "notes": "The same route with `useMax` preset, so it sends everything.", + "summary": "Send a wallet’s entire spendable balance." + } + ], + "x-core-call": null, + "x-core-note": "GUI composite: makeSpend, signTx, broadcastTx and saveTx together.", + "x-source": "src/cli/engine/routes/spend.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "`{ transaction }`, plus `saveError` when the broadcast succeeded but saving failed. With dryRun, a TransactionHandle instead.", + "content": { + "application/json": { + "schema": {} + } + } + }, + "default": { + "description": "TOKEN_NOT_FOUND, INSUFFICIENT_FUNDS, DUST_SPEND, PENDING_FUNDS, SPEND_TO_SELF, NO_AMOUNT_SPECIFIED, BAD_REQUEST, NETWORK_ERROR, WALLET_NOT_FOUND, AMBIGUOUS_WALLET_ID, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "useMax": { + "type": "boolean", + "description": "Replace the first target’s amount with the maximum." + }, + "dryRun": { + "type": "boolean", + "description": "Build only. Never signs or broadcasts." + }, + "broadcast": { + "type": "boolean", + "description": "Defaults to **true**. With `false` the transaction is signed and not sent, so `save` then defaults to false as well — recording an unsent transaction marks its inputs spent locally for something the network will never confirm." + }, + "save": { + "type": "boolean", + "description": "Record the transaction in the wallet. Defaults to whatever `broadcast` is. Setting it true alongside `broadcast: false` is refused: use `--dry-run`, or the staged `make-spend` → `sign-tx` → `broadcast-tx` → `save-tx` flow." + }, + "walletId": { + "type": "string", + "description": "The wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns `409 AMBIGUOUS_WALLET_ID` with `details.candidates`." + }, + "spendInfo": { + "type": "object", + "properties": { + "spendTargets": { + "type": "array", + "items": { + "type": "object", + "properties": { + "publicAddress": { + "type": "string" + }, + "nativeAmount": { + "type": "string" + }, + "uniqueIdentifier": { + "type": "string" + }, + "memo": { + "type": "string" + }, + "otherParams": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "publicAddress" + ] + } + }, + "tokenId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "metadata": { + "description": "EdgeMetadata" + }, + "networkFeeOption": { + "type": "string" + }, + "customNetworkFee": { + "type": "object", + "additionalProperties": {} + }, + "rbfTxid": { + "type": "string" + }, + "memos": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": {} + } + }, + "assetAction": { + "description": "EdgeAssetAction" + }, + "savedAction": { + "description": "EdgeTxAction" + }, + "otherParams": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "spendTargets", + "tokenId" + ], + "description": "A full `EdgeSpendInfo`, used as-is when present. `spendTargets` is required." + }, + "to": { + "type": "string", + "description": "Address or BIP21 URI, run through `wallet.parseUri`." + }, + "nativeAmount": { + "type": "string", + "description": "How much, in native units." + }, + "amount": { + "type": "string", + "description": "Alias of `nativeAmount`." + }, + "tokenId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Defaults to the native asset." + }, + "metadata": { + "description": "Wins over anything parsed out of the URI." + } + }, + "required": [ + "walletId" + ] + } + } + } + } + } + }, + "/account/{sessionId}/wallet/make-spend": { + "post": { + "operationId": "makeSpend", + "summary": "Build an unsigned transaction.", + "description": "**Core call:** `wallet.makeSpend`\n\n**Command line**\n\n```\nmake-spend --wallet-id= [--spend-info=''] [--to=] [--native-amount=] [--amount=] [--token-id=] [--metadata='']\n```\n\nFirst step of the staged workflow: nothing is signed and no funds move. Inspect `transaction.networkFee` on the result before signing.", + "tags": [ + "Spending" + ], + "x-cli": { + "command": "make-spend", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "wallet.makeSpend", + "x-source": "src/cli/engine/routes/spend.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "description": "What the handle refers to, which decides the calls that accept it." + }, + "transaction": { + "description": "`EdgeTransaction` as it stands after this step. Unsigned after `make-spend`, signed after `sign-tx`, and carrying a txid once broadcast." + }, + "objectId": { + "type": "string", + "description": "Handle for the value the engine is holding. Pass it to the calls that consume it." + }, + "createdAt": { + "type": "string", + "description": "When the engine took the handle." + }, + "expiresAt": { + "type": "string", + "description": "When the engine drops the handle. Handles live 5 minutes." + }, + "sessionId": { + "type": "string", + "description": "Session that created the handle; only that session may use it." + }, + "walletId": { + "type": "string", + "description": "Wallet the handle is bound to, when it belongs to one." + } + }, + "required": [ + "kind", + "transaction", + "objectId", + "createdAt", + "expiresAt" + ] + } + } + } + }, + "default": { + "description": "TOKEN_NOT_FOUND, INSUFFICIENT_FUNDS, DUST_SPEND, NO_AMOUNT_SPECIFIED, BAD_REQUEST, WALLET_NOT_FOUND, AMBIGUOUS_WALLET_ID, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "walletId": { + "type": "string", + "description": "The wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns `409 AMBIGUOUS_WALLET_ID` with `details.candidates`." + }, + "spendInfo": { + "type": "object", + "properties": { + "spendTargets": { + "type": "array", + "items": { + "type": "object", + "properties": { + "publicAddress": { + "type": "string" + }, + "nativeAmount": { + "type": "string" + }, + "uniqueIdentifier": { + "type": "string" + }, + "memo": { + "type": "string" + }, + "otherParams": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "publicAddress" + ] + } + }, + "tokenId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "metadata": { + "description": "EdgeMetadata" + }, + "networkFeeOption": { + "type": "string" + }, + "customNetworkFee": { + "type": "object", + "additionalProperties": {} + }, + "rbfTxid": { + "type": "string" + }, + "memos": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": {} + } + }, + "assetAction": { + "description": "EdgeAssetAction" + }, + "savedAction": { + "description": "EdgeTxAction" + }, + "otherParams": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "spendTargets", + "tokenId" + ], + "description": "A full `EdgeSpendInfo`, used as-is when present. `spendTargets` is required." + }, + "to": { + "type": "string", + "description": "Address or BIP21 URI, run through `wallet.parseUri`." + }, + "nativeAmount": { + "type": "string", + "description": "How much, in native units." + }, + "amount": { + "type": "string", + "description": "Alias of `nativeAmount`." + }, + "tokenId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Defaults to the native asset." + }, + "metadata": { + "description": "Wins over anything parsed out of the URI." + } + }, + "required": [ + "walletId" + ] + } + } + } + } + } + }, + "/account/{sessionId}/sign-tx/{objectId}": { + "post": { + "operationId": "signTx", + "summary": "Sign a staged transaction.", + "description": "**Core call:** `wallet.signTx`\n\n**Command line**\n\n```\nsign-tx \n```\n\nKeeps the same handle and pushes its expiry out another five minutes.", + "tags": [ + "Spending" + ], + "x-cli": { + "command": "sign-tx", + "positional": "objectId", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "wallet.signTx", + "x-source": "src/cli/engine/routes/spend.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + }, + { + "name": "objectId", + "in": "path", + "required": true, + "description": "From `make-spend`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "description": "What the handle refers to, which decides the calls that accept it." + }, + "transaction": { + "description": "`EdgeTransaction` as it stands after this step. Unsigned after `make-spend`, signed after `sign-tx`, and carrying a txid once broadcast." + }, + "objectId": { + "type": "string", + "description": "Handle for the value the engine is holding. Pass it to the calls that consume it." + }, + "createdAt": { + "type": "string", + "description": "When the engine took the handle." + }, + "expiresAt": { + "type": "string", + "description": "When the engine drops the handle. Handles live 5 minutes." + }, + "sessionId": { + "type": "string", + "description": "Session that created the handle; only that session may use it." + }, + "walletId": { + "type": "string", + "description": "Wallet the handle is bound to, when it belongs to one." + } + }, + "required": [ + "kind", + "transaction", + "objectId", + "createdAt", + "expiresAt" + ] + } + } + } + }, + "default": { + "description": "BAD_REQUEST, OBJECT_NOT_FOUND, OBJECT_EXPIRED, OBJECT_KIND_MISMATCH, OBJECT_WALLET_MISMATCH, OBJECT_SESSION_MISMATCH, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}/broadcast-tx/{objectId}": { + "post": { + "operationId": "broadcastTx", + "summary": "Broadcast a signed transaction.", + "description": "**Core call:** `wallet.broadcastTx`\n\n**Command line**\n\n```\nbroadcast-tx \n```\n\nThe irreversible step: once this returns, the funds have left the wallet.", + "tags": [ + "Spending" + ], + "x-cli": { + "command": "broadcast-tx", + "positional": "objectId", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "wallet.broadcastTx", + "x-source": "src/cli/engine/routes/spend.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + }, + { + "name": "objectId", + "in": "path", + "required": true, + "description": "From `sign-tx`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "The handle survives, so `save-tx` can still run.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "description": "What the handle refers to, which decides the calls that accept it." + }, + "transaction": { + "description": "`EdgeTransaction` as it stands after this step. Unsigned after `make-spend`, signed after `sign-tx`, and carrying a txid once broadcast." + }, + "objectId": { + "type": "string", + "description": "Handle for the value the engine is holding. Pass it to the calls that consume it." + }, + "createdAt": { + "type": "string", + "description": "When the engine took the handle." + }, + "expiresAt": { + "type": "string", + "description": "When the engine drops the handle. Handles live 5 minutes." + }, + "sessionId": { + "type": "string", + "description": "Session that created the handle; only that session may use it." + }, + "walletId": { + "type": "string", + "description": "Wallet the handle is bound to, when it belongs to one." + } + }, + "required": [ + "kind", + "transaction", + "objectId", + "createdAt", + "expiresAt" + ] + } + } + } + }, + "default": { + "description": "BAD_REQUEST, NETWORK_ERROR, OBJECT_NOT_FOUND, OBJECT_EXPIRED, OBJECT_KIND_MISMATCH, OBJECT_WALLET_MISMATCH, OBJECT_SESSION_MISMATCH, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}/save-tx/{objectId}": { + "post": { + "operationId": "saveTx", + "summary": "Record a transaction and release its handle.", + "description": "**Core call:** `wallet.saveTx`\n\n**Command line**\n\n```\nsave-tx \n```\n\nFinal step. The handle is gone afterwards, so a second call is a 404.", + "tags": [ + "Spending" + ], + "x-cli": { + "command": "save-tx", + "positional": "objectId", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "wallet.saveTx", + "x-source": "src/cli/engine/routes/spend.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + }, + { + "name": "objectId", + "in": "path", + "required": true, + "description": "The handle to persist and release.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ok": { + "type": "boolean", + "description": "Always true; a failure arrives as an error envelope." + }, + "objectId": { + "type": "string", + "description": "The handle this call consumed. It is now expired." + } + }, + "required": [ + "ok", + "objectId" + ] + } + } + } + }, + "default": { + "description": "BAD_REQUEST, OBJECT_NOT_FOUND, OBJECT_EXPIRED, OBJECT_KIND_MISMATCH, OBJECT_WALLET_MISMATCH, OBJECT_SESSION_MISMATCH, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}/wallet/accelerate": { + "post": { + "operationId": "accelerate", + "summary": "Fee-bump a pending transaction.", + "description": "**Core call:** `wallet.accelerate`\n\n**Command line**\n\n```\naccelerate --wallet-id= [--object-id=] [--transaction='']\n```\n\nReplace-by-fee, where the plugin supports it. Returns a new unsigned transaction to sign and broadcast.", + "tags": [ + "Spending" + ], + "x-cli": { + "command": "accelerate", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "wallet.accelerate", + "x-source": "src/cli/engine/routes/spend.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Given objectId the same handle is updated; given a transaction a new one is created.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "description": "What the handle refers to, which decides the calls that accept it." + }, + "transaction": { + "description": "`EdgeTransaction` as it stands after this step. Unsigned after `make-spend`, signed after `sign-tx`, and carrying a txid once broadcast." + }, + "objectId": { + "type": "string", + "description": "Handle for the value the engine is holding. Pass it to the calls that consume it." + }, + "createdAt": { + "type": "string", + "description": "When the engine took the handle." + }, + "expiresAt": { + "type": "string", + "description": "When the engine drops the handle. Handles live 5 minutes." + }, + "sessionId": { + "type": "string", + "description": "Session that created the handle; only that session may use it." + }, + "walletId": { + "type": "string", + "description": "Wallet the handle is bound to, when it belongs to one." + } + }, + "required": [ + "kind", + "transaction", + "objectId", + "createdAt", + "expiresAt" + ] + } + } + } + }, + "default": { + "description": "BAD_REQUEST, OBJECT_NOT_FOUND, OBJECT_EXPIRED, OBJECT_KIND_MISMATCH, OBJECT_WALLET_MISMATCH, OBJECT_SESSION_MISMATCH, WALLET_NOT_FOUND, AMBIGUOUS_WALLET_ID, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "walletId": { + "type": "string", + "description": "The wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns `409 AMBIGUOUS_WALLET_ID` with `details.candidates`." + }, + "objectId": { + "type": "string", + "description": "Handle of the transaction to bump." + }, + "transaction": { + "type": "object", + "properties": { + "txid": { + "type": "string" + }, + "currencyCode": { + "type": "string" + }, + "nativeAmount": { + "type": "string" + }, + "networkFee": { + "type": "string" + }, + "walletId": { + "type": "string" + } + }, + "required": [ + "txid" + ], + "description": "Or the transaction itself." + } + }, + "required": [ + "walletId" + ] + } + } + } + } + } + }, + "/account/{sessionId}/wallet/sweep-private-keys": { + "post": { + "operationId": "sweepPrivateKeys", + "summary": "Sweep private keys into this wallet.", + "description": "**Core call:** `wallet.sweepPrivateKeys`\n\n**Command line**\n\n```\nsweep-private-keys --wallet-id= --spend-info=''\n```\n\nBuilds a transaction moving everything from an external key. Returns an unsigned handle: sign, broadcast and save it like any staged spend.", + "tags": [ + "Spending" + ], + "x-cli": { + "command": "sweep-private-keys", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "wallet.sweepPrivateKeys", + "x-source": "src/cli/engine/routes/spend.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "description": "What the handle refers to, which decides the calls that accept it." + }, + "transaction": { + "description": "`EdgeTransaction` as it stands after this step. Unsigned after `make-spend`, signed after `sign-tx`, and carrying a txid once broadcast." + }, + "objectId": { + "type": "string", + "description": "Handle for the value the engine is holding. Pass it to the calls that consume it." + }, + "createdAt": { + "type": "string", + "description": "When the engine took the handle." + }, + "expiresAt": { + "type": "string", + "description": "When the engine drops the handle. Handles live 5 minutes." + }, + "sessionId": { + "type": "string", + "description": "Session that created the handle; only that session may use it." + }, + "walletId": { + "type": "string", + "description": "Wallet the handle is bound to, when it belongs to one." + } + }, + "required": [ + "kind", + "transaction", + "objectId", + "createdAt", + "expiresAt" + ] + } + } + } + }, + "default": { + "description": "BAD_REQUEST, INSUFFICIENT_FUNDS, NETWORK_ERROR, TOKEN_NOT_FOUND, WALLET_NOT_FOUND, AMBIGUOUS_WALLET_ID, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "walletId": { + "type": "string", + "description": "The wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns `409 AMBIGUOUS_WALLET_ID` with `details.candidates`." + }, + "spendInfo": { + "type": "object", + "properties": { + "privateKeys": { + "type": "array", + "items": { + "type": "string" + } + }, + "tokenId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "spendTargets": { + "type": "array", + "items": { + "type": "object", + "properties": { + "publicAddress": { + "type": "string" + }, + "nativeAmount": { + "type": "string" + }, + "uniqueIdentifier": { + "type": "string" + }, + "memo": { + "type": "string" + }, + "otherParams": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "publicAddress" + ] + } + }, + "metadata": { + "description": "EdgeMetadata" + }, + "memos": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": {} + } + } + }, + "required": [ + "privateKeys", + "tokenId", + "spendTargets" + ], + "description": "The keys to sweep in `privateKeys`, plus the optional `spendTargets`, `tokenId`, `metadata` and `memos` of a spend." + } + }, + "required": [ + "walletId", + "spendInfo" + ] + } + } + } + } + } + }, + "/account/{sessionId}/wallet/sign-bytes": { + "post": { + "operationId": "signBytes", + "summary": "Sign arbitrary bytes.", + "description": "**Core call:** `wallet.signBytes`\n\n**Command line**\n\n```\nsign-bytes --wallet-id= [--bytes=] [--other-params='']\n```\n\nMessage signing and proof-of-ownership, for plugins that support it.", + "tags": [ + "Spending" + ], + "x-cli": { + "command": "sign-bytes", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "wallet.signBytes", + "x-source": "src/cli/engine/routes/spend.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "signature": { + "type": "string", + "description": "Base64." + } + }, + "required": [ + "signature" + ] + } + } + } + }, + "default": { + "description": "BAD_REQUEST, WALLET_NOT_FOUND, AMBIGUOUS_WALLET_ID, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "walletId": { + "type": "string", + "description": "The wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns `409 AMBIGUOUS_WALLET_ID` with `details.candidates`." + }, + "bytes": { + "type": "string", + "description": "Base64. Defaults to empty when absent." + }, + "otherParams": { + "type": "object", + "additionalProperties": {}, + "description": "Plugin-specific options. Bitcoin needs `{ publicAddress }`; other plugins take nothing, or refuse the call entirely." + } + }, + "required": [ + "walletId" + ] + } + } + } + } + } + }, + "/account/{sessionId}/wallet/get-payment-protocol-info": { + "get": { + "operationId": "getPaymentProtocolInfo", + "summary": "Fetch a BIP70 payment request.", + "description": "**Core call:** `wallet.getPaymentProtocolInfo`\n\n**Command line**\n\n```\nget-payment-protocol-info --wallet-id= --payment-protocol-url=\n```\n\nFeed `spendTargets` from the result into `make-spend` to pay it.", + "tags": [ + "Spending" + ], + "x-cli": { + "command": "get-payment-protocol-info", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "wallet.getPaymentProtocolInfo", + "x-source": "src/cli/engine/routes/spend.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + }, + { + "name": "walletId", + "in": "query", + "required": true, + "description": "The wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns `409 AMBIGUOUS_WALLET_ID` with `details.candidates`.", + "schema": { + "type": "string" + } + }, + { + "name": "paymentProtocolUrl", + "in": "query", + "required": true, + "description": "The payment-request URL.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "`EdgePaymentProtocolInfo`: domain, memo, merchant, nativeAmount, spendTargets.", + "content": { + "application/json": { + "schema": {} + } + } + }, + "default": { + "description": "BAD_REQUEST, NETWORK_ERROR, WALLET_NOT_FOUND, AMBIGUOUS_WALLET_ID, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}/fetch-swap-quotes": { + "post": { + "operationId": "fetchSwapQuotes", + "summary": "Fetch swap quotes.", + "description": "**Core call:** `account.fetchSwapQuotes`\n\n**Command line**\n\n```\nfetch-swap-quotes --from-wallet-id= --to-wallet-id= --native-amount= [--from-token-id=] [--to-token-id=] [--quote-for=from|max|to] [--plugin-id=]\n```\n\nPolls every enabled swap plugin and parks each result under its own `swap_` handle with a 5 minute TTL.", + "tags": [ + "Swap quotes" + ], + "x-cli": { + "command": "fetch-swap-quotes", + "flags": [ + { + "name": "plugin-id", + "maps": "preferPluginId", + "repeat": false + } + ], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "account.fetchSwapQuotes", + "x-source": "src/cli/engine/routes/swap.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "quoteCount": { + "type": "number", + "description": "How many plugins answered." + }, + "quotes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "objectId": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "expiresAt": { + "type": "string" + }, + "pluginId": { + "type": "string" + }, + "isEstimate": { + "type": "boolean" + }, + "canBePartial": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ] + }, + "maxFulfillmentSeconds": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "minReceiveAmount": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "fromNativeAmount": { + "type": "string" + }, + "toNativeAmount": { + "type": "string" + }, + "networkFee": { + "type": "object", + "properties": { + "nativeAmount": { + "type": "string" + }, + "tokenId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "nativeAmount", + "tokenId" + ] + }, + "quoteExpirationDate": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "swapInfo": { + "type": "object", + "properties": { + "pluginId": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "supportEmail": { + "type": "string" + }, + "isDex": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "pluginId", + "displayName", + "supportEmail", + "isDex" + ] + }, + "request": { + "type": "object", + "properties": { + "fromTokenId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "toTokenId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "nativeAmount": { + "type": "string" + }, + "quoteFor": { + "type": "string", + "enum": [ + "from", + "to", + "max" + ] + }, + "fromWalletId": { + "type": "string" + }, + "toWalletId": { + "type": "string" + } + }, + "required": [ + "fromTokenId", + "toTokenId", + "nativeAmount", + "quoteFor", + "fromWalletId", + "toWalletId" + ] + } + }, + "required": [ + "objectId", + "kind", + "expiresAt", + "pluginId", + "isEstimate", + "canBePartial", + "maxFulfillmentSeconds", + "minReceiveAmount", + "fromNativeAmount", + "toNativeAmount", + "networkFee", + "quoteExpirationDate", + "swapInfo", + "request" + ] + }, + "description": "One quote per plugin that answered, each already parked under its own handle. Plugins that failed or had nothing to offer are simply absent." + } + }, + "required": [ + "quoteCount", + "quotes" + ] + } + } + } + }, + "default": { + "description": "BAD_REQUEST, SWAP_BELOW_LIMIT, SWAP_ABOVE_LIMIT, SWAP_CURRENCY, SWAP_PERMISSION, SWAP_ADDRESS, SAME_CURRENCY, INSUFFICIENT_FUNDS, WALLET_NOT_FOUND, TOKEN_NOT_FOUND, NETWORK_ERROR, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "fromWalletId": { + "type": "string", + "description": "Source wallet. Accepts a unique prefix." + }, + "toWalletId": { + "type": "string", + "description": "Destination wallet." + }, + "nativeAmount": { + "type": "string", + "description": "How much, in native units." + }, + "fromTokenId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Defaults to the native asset." + }, + "toTokenId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Defaults to the native asset." + }, + "quoteFor": { + "type": "string", + "enum": [ + "from", + "max", + "to" + ], + "description": "`from` spends this much of the source, `to` receives this much at the destination, `max` sends everything. Defaults to `from`." + }, + "preferPluginId": { + "type": "string", + "description": "Restrict to one exchange." + } + }, + "required": [ + "fromWalletId", + "toWalletId", + "nativeAmount" + ] + } + } + } + } + } + }, + "/account/{sessionId}/swap-quote/{objectId}": { + "get": { + "operationId": "getSwapQuote", + "summary": "Re-read a quote.", + "description": "**Core call:** _none — Engine handle store; the quote is a live EdgeSwapQuote held server-side._\n\n**Command line**\n\n```\nswap-quote-get \n```\n\n", + "tags": [ + "Swap quotes" + ], + "x-cli": { + "command": "swap-quote-get", + "positional": "objectId", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": null, + "x-core-note": "Engine handle store; the quote is a live EdgeSwapQuote held server-side.", + "x-source": "src/cli/engine/routes/swap.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + }, + { + "name": "objectId", + "in": "path", + "required": true, + "description": "An ephemeral object handle id.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "objectId": { + "type": "string", + "description": "Handle for the value the engine is holding. Pass it to the calls that consume it." + }, + "kind": { + "type": "string", + "description": "What the handle refers to, which decides the calls that accept it." + }, + "expiresAt": { + "type": "string", + "description": "When the engine drops the handle. Handles live 5 minutes." + }, + "pluginId": { + "type": "string", + "description": "Swap provider that produced this quote." + }, + "isEstimate": { + "type": "boolean", + "description": "True when the provider may settle at a different rate than quoted." + }, + "canBePartial": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "description": "True when the provider may fill only part of the order. Null when it does not say." + }, + "maxFulfillmentSeconds": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Longest the provider expects a partial fill to take." + }, + "minReceiveAmount": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Least the provider guarantees to deliver, in the destination’s native units." + }, + "fromNativeAmount": { + "type": "string", + "description": "Amount leaving the source wallet." + }, + "toNativeAmount": { + "type": "string", + "description": "Amount arriving in the destination wallet." + }, + "networkFee": { + "type": "object", + "properties": { + "nativeAmount": { + "type": "string" + }, + "tokenId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "nativeAmount", + "tokenId" + ], + "description": "On-chain fee for the sending transaction. It is not the provider’s own spread, which is already in the rate." + }, + "quoteExpirationDate": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "When the provider stops honouring the rate. Null when it does not expire." + }, + "swapInfo": { + "type": "object", + "properties": { + "pluginId": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "supportEmail": { + "type": "string" + }, + "isDex": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "pluginId", + "displayName", + "supportEmail", + "isDex" + ], + "description": "`EdgeSwapInfo`: how to name the provider and where to send complaints." + }, + "request": { + "type": "object", + "properties": { + "fromTokenId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "toTokenId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "nativeAmount": { + "type": "string" + }, + "quoteFor": { + "type": "string", + "enum": [ + "from", + "to", + "max" + ] + }, + "fromWalletId": { + "type": "string" + }, + "toWalletId": { + "type": "string" + } + }, + "required": [ + "fromTokenId", + "toTokenId", + "nativeAmount", + "quoteFor", + "fromWalletId", + "toWalletId" + ], + "description": "The `EdgeSwapRequest` this quote answers, echoed back so quotes from different plugins can be compared without tracking what was asked." + } + }, + "required": [ + "objectId", + "kind", + "expiresAt", + "pluginId", + "isEstimate", + "canBePartial", + "maxFulfillmentSeconds", + "minReceiveAmount", + "fromNativeAmount", + "toNativeAmount", + "networkFee", + "quoteExpirationDate", + "swapInfo", + "request" + ] + } + } + } + }, + "default": { + "description": "OBJECT_NOT_FOUND, OBJECT_EXPIRED, OBJECT_KIND_MISMATCH, OBJECT_SESSION_MISMATCH, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}/swap-quote/approve/{objectId}": { + "post": { + "operationId": "approveSwapQuote", + "summary": "Execute a quote.", + "description": "**Core call:** `EdgeSwapQuote.approve`\n\n**Command line**\n\n```\napprove-swap-quote \n```\n\nMoves funds. The handle is released afterwards whether or not the response is read, so record `orderId` from it.", + "tags": [ + "Swap quotes" + ], + "x-cli": { + "command": "approve-swap-quote", + "positional": "objectId", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "EdgeSwapQuote.approve", + "x-source": "src/cli/engine/routes/swap.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + }, + { + "name": "objectId", + "in": "path", + "required": true, + "description": "An ephemeral object handle id.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ok": { + "description": "True once the swap is submitted and the send broadcast." + }, + "objectId": { + "type": "string", + "description": "The handle that was consumed." + }, + "orderId": { + "description": "The exchange’s order reference, when it gives one." + }, + "destinationAddress": { + "description": "Address the funds were sent to, when the exchange reports one." + }, + "transaction": { + "description": "The on-chain send to the exchange." + } + }, + "required": [ + "ok", + "objectId", + "orderId", + "destinationAddress", + "transaction" + ] + } + } + } + }, + "default": { + "description": "OBJECT_NOT_FOUND, OBJECT_EXPIRED, OBJECT_KIND_MISMATCH, OBJECT_SESSION_MISMATCH, OBJECT_IN_USE, INSUFFICIENT_FUNDS, NETWORK_ERROR, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}/swap-quote/close/{objectId}": { + "post": { + "operationId": "closeSwapQuote", + "summary": "Discard a quote.", + "description": "**Core call:** `EdgeSwapQuote.close`\n\n**Command line**\n\n```\nclose-swap-quote \n```\n\nCloses the plugin object without executing, freeing whatever the exchange was holding.", + "tags": [ + "Swap quotes" + ], + "x-cli": { + "command": "close-swap-quote", + "positional": "objectId", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "EdgeSwapQuote.close", + "x-source": "src/cli/engine/routes/swap.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + }, + { + "name": "objectId", + "in": "path", + "required": true, + "description": "An ephemeral object handle id.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ok": { + "type": "boolean", + "description": "Always true; a failure arrives as an error envelope." + }, + "objectId": { + "type": "string", + "description": "The handle this call consumed. It is now expired." + } + }, + "required": [ + "ok", + "objectId" + ] + } + } + } + }, + "default": { + "description": "OBJECT_NOT_FOUND, OBJECT_EXPIRED, OBJECT_KIND_MISMATCH, OBJECT_SESSION_MISMATCH, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + } + } + }, + "/account/{sessionId}/wallet/parse-uri": { + "post": { + "operationId": "parseUri", + "summary": "Parse a payment URI or address.", + "description": "**Core call:** `wallet.parseUri`\n\n**Command line**\n\n```\nparse-uri --wallet-id= --uri= [--currency-code=]\n```\n\nWhat the GUI address tile does when you paste or scan something.", + "tags": [ + "URIs" + ], + "x-cli": { + "command": "parse-uri", + "flags": [], + "extra": [], + "custom": false, + "preset": {} + }, + "x-core-call": "wallet.parseUri", + "x-source": "src/cli/engine/routes/uri.ts", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "From a successful login. The CLI supplies this from `session.json`, `--session`, or `EDGE_CLI_SESSION`.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "`EdgeParsedUri`: publicAddress, nativeAmount, currencyCode, metadata, paymentProtocolUrl, …", + "content": { + "application/json": { + "schema": {} + } + } + }, + "default": { + "description": "BAD_REQUEST, WALLET_NOT_FOUND, AMBIGUOUS_WALLET_ID, INVALID_SESSION, SESSION_EXPIRED", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorEnvelope" + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "walletId": { + "type": "string", + "description": "The wallet to act on. A full wallet id, or any unique prefix of one. An ambiguous prefix returns `409 AMBIGUOUS_WALLET_ID` with `details.candidates`." + }, + "uri": { + "type": "string", + "description": "A payment URI or a bare address." + }, + "currencyCode": { + "type": "string", + "description": "Disambiguates on chains that carry several assets." + } + }, + "required": [ + "walletId", + "uri" + ] + } + } + } + } + } + }, + "/account/{sessionId}/wallet/encode-uri": { + "post": { + "operationId": "encodeUri", + "summary": "Build a payment URI.", + "description": "**Core call:** `wallet.encodeUri`\n\n**Command line**\n\n```\nencode-uri --wallet-id= --public-address= [--native-amount=] [--label=