26 KiB
Qortal External Auth
Minimal local daemon + shared auth core for Qortal-compatible account import and signing.
Getting Started
Install
npm install
Dev mode
npm run dev
Build + start (production)
npm run build
npm run start
Docker
Build image:
docker build -t qortal-external-auth:latest .
Run container (bind host port 3191, persist data):
docker run --rm -p 3191:3191 \
-e QORTAL_AUTH_HOST=0.0.0.0 \
-e QORTAL_AUTH_DATA_DIR=/data \
-v qortal-auth-data:/data \
qortal-external-auth:latest
Note: use a named volume (as shown) instead of a host bind-mount to keep wallet files out of your normal filesystem paths. Named volumes live under Docker's internal data directory and are less likely to be accessed accidentally.
If the Qortal node runs on the Docker host, set QORTAL_AUTH_NODE_URL and add the host gateway:
docker run --rm -p 3191:3191 \
-e QORTAL_AUTH_HOST=0.0.0.0 \
-e QORTAL_AUTH_DATA_DIR=/data \
-e QORTAL_AUTH_NODE_URL=http://host.docker.internal:12391 \
--add-host=host.docker.internal:host-gateway \
-v qortal-auth-data:/data \
qortal-external-auth:latest
Docker Compose (multi-repo)
This repo includes a compose.yaml that builds the local auth daemon and
pulls/builds the Nextcloud integration from your Gitea repo.
docker compose up --build
Notes:
- The Compose file uses a named volume for wallet data (
qortal-auth-data). - Services can reach each other by service name on the default network, e.g.
http://qortal-external-auth:3191. - If you need a Qortal node container, add it and set
QORTAL_AUTH_NODE_URL=http://<service-name>:12391.
Environment
Optional environment variables:
QORTAL_AUTH_PORT(default3191)QORTAL_AUTH_HOST(default127.0.0.1)QORTAL_AUTH_NODE_URL(default node URL if not provided in requests)QORTAL_AUTH_NODE_API_KEY(optional API key for restricted node endpoints, sent asX-API-KEY)QORTAL_AUTH_NODE_API_KEY_MODE(auto|always|never|paths|retry, defaultauto)QORTAL_AUTH_NODE_API_KEY_PATHS(comma-separated path patterns when usingpaths, e.g./admin/stop,/render/{service}/{name}. Use/to match all node paths.)QORTAL_AUTH_BODY_LIMIT_BYTES(max JSON request body size in bytes; default262144000/ 250 MiB)QORTAL_AUTH_SAVE_FILE_DIR(base directory forqortalRequest('SAVE_FILE'); default<dataDir>/saved-files)
retry mode attempts a request without the key and retries once on 401/403
with the key, so the key is only sent when required.
Regardless of mode, if the node explicitly responds with a missing
X-API-KEY error, the daemon retries once with the configured key.
auto mode uses a built-in list of Qortal endpoints that call
Security.checkApiCallAllowed or declare @SecurityRequirement(name = "apiKey")
in core. paths supports simple {param} placeholders and {path:.*} wildcards.
QORTAL_AUTH_DATA_DIR(default./data)QORTAL_AUTH_STORAGE_PASSPHRASE(used when encrypted backups are enabled)QORTAL_AUTH_DOCS_HOST(override Swagger server host if needed)
Large inline base64 publishes use JSON request bodies and can exceed this limit.
For very large content, prefer uploadType: "file" with filePath so data is
streamed to the node in chunks instead of being sent as one large JSON payload.
You can also create a .env file in the repo root (it is gitignored).
API
All endpoints are JSON over HTTP. By default the daemon listens on 127.0.0.1:3191.
API docs (interactive)
Swagger UI (try calls in browser):
http://127.0.0.1:3191/docs
OpenAPI JSON:
http://127.0.0.1:3191/openapi.json
Authentication
Most endpoints require a bearer token:
Authorization: Bearer <TOKEN>
If a permission is in ask mode, include:
X-Qortal-Approval-Token: <TOKEN>
Unauthenticated endpoints:
GET /(redirect to docs)GET /healthGET /openapi.jsonGET /docs
Typical flow
POST /apps/registerto create an app id/secret.POST /sessionsto create a bearer token.- Import a wallet (
/wallet/import-seedor/wallet/import-backup). - Set permissions (
/permissions/set) or issue one-time approvals (/permissions/approve). - Unlock a wallet (
/wallet/unlock) or passpasswordper request. - Call signing or QDN endpoints.
Endpoint reference (daemon)
Apps:
POST /apps/register
Body: {"name":"my-app"}
Response: {"appId":"...","appSecret":"..."}
Sessions:
POST /sessions
Body: {"appId":"...","appSecret":"..."}
Response: {"token":"...","expiresAt":<unix ms>}
Wallets:
POST /wallet/import-seed
Body: {"seedPhrase":"...","password":"..."}
POST /wallet/create
Body: {"password":"..."}
POST /wallet/import-backup
Body: {"password":"...","backup":{...}}
POST /wallet/backup/export
Body: {"walletId":"...","password":"...","reEncryptPassword":"..."}
POST /wallet/backup-json
Body: {"walletId":"...","password":"...","newPassword":"..."}
POST /wallet/backup
Body: {"walletId":"...","password":"..."}
GET /wallets
GET /wallet/<WALLET_ID>
POST /wallet/unlock
Body: {"walletId":"...","password":"...","ttlSeconds":1200}
GET /wallet/unlock/status?walletId=<WALLET_ID>
POST /wallet/lock
Body: {"walletId":"..."}
Permissions:
POST /permissions/set
Body: {"walletId":"...","scope":"SIGN_TRANSACTION","mode":"ask|session|persistent","ttlSeconds":3600}
POST /permissions/approve
Body: {"walletId":"...","scope":"SIGN_TRANSACTION"}
GET /permissions?walletId=<WALLET_ID>
Settings:
GET /settings?walletId=<WALLET_ID>
POST /settings
Body: {"scope":"global|app|wallet","walletId":"<WALLET_ID>","settings":{...}}
Transactions and QDN:
POST /tx/sign
Body: {"walletId":"...","unsignedBytes":"<BASE58>","process":false,"password":"...","nodeUrl":"..."}
POST /qdn/publish
Body: {"walletId":"...","data":"<BASE64>","service":"DOCUMENT","registeredName":"name","identifier":"id","password":"..."}
Qortal request router and node proxy:
POST /qortal/request
Body: {"requestType":"GET_NODE_INFO","payload":{...}}
POST /node/proxy
Body: {"method":"GET","path":"/admin/info","query":{...},"body":{...}}
GET /node/proxy?path=/addresses/balance/<ADDRESS>&assetId=0
System endpoints
Health check:
curl -s http://127.0.0.1:3191/health
OpenAPI JSON:
curl -s http://127.0.0.1:3191/openapi.json
Node proxy (generic)
Use /node/proxy to pass through a Qortal node call. This is useful for
endpoints not yet exposed via /qortal/request.
curl -s -X POST http://127.0.0.1:3191/node/proxy \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Content-Type: application/json' \
-d '{"method":"GET","path":"/admin/info"}'
GET shortcut for simple read-only requests:
curl -s -X GET 'http://127.0.0.1:3191/node/proxy?path=/addresses/balance/QiNKXRfnX9mTodSed1yRQexhL1HA42RHHo&assetId=0' \
-H 'Authorization: Bearer <TOKEN>'
Swagger UI authorization (quick)
- Register app:
curl -s -X POST http://127.0.0.1:3191/apps/register \
-H 'Content-Type: application/json' \
-d '{"name":"my-app"}'
- Create session:
curl -s -X POST http://127.0.0.1:3191/sessions \
-H 'Content-Type: application/json' \
-d '{"appId":"<APP_ID>","appSecret":"<APP_SECRET>"}'
- In Swagger UI (
/docs), click Authorize and paste:
Bearer <TOKEN>
End-to-end example (import backup + sign)
Scenario: An external app wants to import a Qortal backup file, unlock it for 20 minutes, and sign a transaction.
-
Register app + create session (same as above).
-
Import a backup file:
curl -s -X POST http://127.0.0.1:3191/wallet/import-backup \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Content-Type: application/json' \
-d '{"password":"backup-password","backup":{...}}'
- Set permissions for signing (session mode):
curl -s -X POST http://127.0.0.1:3191/permissions/set \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Content-Type: application/json' \
-d '{"walletId":"<WALLET_ID>","scope":"SIGN_TRANSACTION","mode":"session","ttlSeconds":3600}'
- Unlock the wallet for 20 minutes:
curl -s -X POST http://127.0.0.1:3191/wallet/unlock \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Content-Type: application/json' \
-d '{"walletId":"<WALLET_ID>","password":"backup-password","ttlSeconds":1200}'
- Sign a transaction (wallet already unlocked):
curl -s -X POST http://127.0.0.1:3191/tx/sign \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Content-Type: application/json' \
-d '{"walletId":"<WALLET_ID>","unsignedBytes":"<BASE58>","process":false,"nodeUrl":"http://127.0.0.1:12391"}'
Register app
curl -s -X POST http://127.0.0.1:3191/apps/register \
-H 'Content-Type: application/json' \
-d '{"name":"my-app"}'
Create session
curl -s -X POST http://127.0.0.1:3191/sessions \
-H 'Content-Type: application/json' \
-d '{"appId":"<APP_ID>","appSecret":"<APP_SECRET>"}'
Import seed phrase (creates and saves Qortal backup file)
curl -s -X POST http://127.0.0.1:3191/wallet/import-seed \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Content-Type: application/json' \
-d '{"seedPhrase":"seed words here","password":"backup-password"}'
Create new wallet (random seed + save backup)
curl -s -X POST http://127.0.0.1:3191/wallet/create \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Content-Type: application/json' \
-d '{"password":"backup-password"}'
Import Qortal backup file
curl -s -X POST http://127.0.0.1:3191/wallet/import-backup \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Content-Type: application/json' \
-d '{"password":"backup-password","backup":{...}}'
Export backup file (password required)
curl -s -X POST http://127.0.0.1:3191/wallet/backup/export \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Content-Type: application/json' \
-d '{"walletId":"<WALLET_ID>","password":"backup-password"}'
Optional compatibility fields for re-encryption on export:
reEncryptPasswordnewPassword
If both are provided, they must match. Alias routes /wallet/backup-json and
/wallet/backup use the same behavior.
List wallets
curl -s -X GET http://127.0.0.1:3191/wallets \
-H 'Authorization: Bearer <TOKEN>'
Get wallet
curl -s -X GET http://127.0.0.1:3191/wallet/<WALLET_ID> \
-H 'Authorization: Bearer <TOKEN>'
Permissions
Permissions gate sensitive operations. Modes:
ask: requires a one-time approval tokensession: auto-approve until TTL expirespersistent: always approved for this app + wallet + scope
Set permission mode
curl -s -X POST http://127.0.0.1:3191/permissions/set \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Content-Type: application/json' \
-d '{"walletId":"<WALLET_ID>","scope":"SIGN_TRANSACTION","mode":"session","ttlSeconds":3600}'
Approve once (for ask mode)
curl -s -X POST http://127.0.0.1:3191/permissions/approve \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Content-Type: application/json' \
-d '{"walletId":"<WALLET_ID>","scope":"SIGN_TRANSACTION"}'
Use the returned approvalToken as header:
-H 'X-Qortal-Approval-Token: <TOKEN>'
List persistent permissions
curl -s -X GET http://127.0.0.1:3191/permissions?walletId=<WALLET_ID> \
-H 'Authorization: Bearer <TOKEN>'
Settings
Settings are stored in data/settings.json and can be configured at:
- global defaults
- per-app
- per-wallet
These settings control the default auth mode and the default node URL if a request does not supply nodeUrl.
When authMode is session, the daemon will auto‑unlock the wallet when a signing request includes a password.
Get settings (resolved)
curl -s -X GET 'http://127.0.0.1:3191/settings?walletId=<WALLET_ID>' \
-H 'Authorization: Bearer <TOKEN>'
Set global defaults
curl -s -X POST http://127.0.0.1:3191/settings \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Content-Type: application/json' \
-d '{\"scope\":\"global\",\"settings\":{\"authMode\":\"ask\",\"sessionTtlSeconds\":3600,\"approvalTtlSeconds\":300,\"unlockTtlSeconds\":1200,\"nodeUrl\":\"http://127.0.0.1:12391\"}}'
Set app defaults
curl -s -X POST http://127.0.0.1:3191/settings \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Content-Type: application/json' \
-d '{\"scope\":\"app\",\"settings\":{\"authMode\":\"session\"}}'
Set per-wallet defaults
curl -s -X POST http://127.0.0.1:3191/settings \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Content-Type: application/json' \
-d '{\"scope\":\"wallet\",\"walletId\":\"<WALLET_ID>\",\"settings\":{\"authMode\":\"persistent\",\"nodeUrl\":\"http://127.0.0.1:12391\"}}'
Note: If authMode is set to session or persistent, the daemon will auto‑grant permissions for that scope without requiring approval tokens.
Encrypted backups (optional)
If encryptedBackups is enabled in global settings, wallet backups are encrypted at rest using a storage passphrase.
On startup the daemon will prompt for the passphrase if QORTAL_AUTH_STORAGE_PASSPHRASE is not set.
Enable via settings:
curl -s -X POST http://127.0.0.1:3191/settings \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Content-Type: application/json' \
-d '{\"scope\":\"global\",\"settings\":{\"encryptedBackups\":true}}'
Then start the daemon with:
QORTAL_AUTH_STORAGE_PASSPHRASE='your-passphrase' npm run dev
Wallet Unlock
Unlocking decrypts the backup once, then stores an encrypted private key in memory with a TTL. Signing calls can omit password while unlocked.
Unlock
curl -s -X POST http://127.0.0.1:3191/wallet/unlock \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Content-Type: application/json' \
-d '{\"walletId\":\"<WALLET_ID>\",\"password\":\"backup-password\",\"ttlSeconds\":1200}'
Unlock status
curl -s -X GET 'http://127.0.0.1:3191/wallet/unlock/status?walletId=<WALLET_ID>' \
-H 'Authorization: Bearer <TOKEN>'
Lock
curl -s -X POST http://127.0.0.1:3191/wallet/lock \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Content-Type: application/json' \
-d '{\"walletId\":\"<WALLET_ID>\"}'
Signing
Sign transaction (Base58 unsigned bytes)
If the wallet is unlocked, you can omit password.
curl -s -X POST http://127.0.0.1:3191/tx/sign \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Content-Type: application/json' \
-d '{"walletId":"<WALLET_ID>","password":"backup-password","unsignedBytes":"<BASE58>","process":false,"nodeUrl":"http://127.0.0.1:12391"}'
QDN publish (base64)
This is a minimal base64 publish flow (mirrors Hub’s PUBLISH_QDN_RESOURCE). If the wallet is unlocked, you can omit password.
curl -s -X POST http://127.0.0.1:3191/qdn/publish \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Content-Type: application/json' \
-d '{"walletId":"<WALLET_ID>","password":"backup-password","registeredName":"name","service":"DOCUMENT_PRIVATE","identifier":"example","data":"<BASE64>","nodeUrl":"http://127.0.0.1:12391"}'
Qortal request router
A compatibility endpoint that mirrors Hub-style request types.
Request format:
curl -s -X POST http://127.0.0.1:3191/qortal/request \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Content-Type: application/json' \
-d '{"requestType":"GET_NODE_INFO","payload":{}}'
Node and status
GET_NODE_INFOGET_NODE_STATUSGET_DAY_SUMMARY
Accounts and names
GET_ACCOUNT_DATA(payload:addressorwalletId)GET_ACCOUNT_NAMES(payload:addressorwalletId)GET_PRIMARY_NAME(payload:addressorwalletId)GET_NAME_DATA(payload:name)SEARCH_NAMES(payload:query,limit,offset,reverse)
Balances
GET_BALANCE(payload:addressorwalletId, returns QORT balance)GET_WALLET_BALANCE(payload:coin+addressorwalletId)
coin supports QORT, an asset name, or an assetId.
Wallet metadata
GET_USER_WALLET(payload:walletId)GET_USER_WALLET_INFO(payload:walletId)GET_USER_ACCOUNT(payload: optionalwalletId)WHICH_UI(payload optional, returnsHUB_WEBby default; setQORTAL_AUTH_UI=HUB_ELECTRONto override)
Encryption
ENCRYPT_DATA(payload:walletIdoraddress,data64|base64, optionalpublicKeys[])DECRYPT_DATA(payload:walletIdoraddress,encryptedData|data64|base64, andpublicKeywhen decrypting legacyqortalEncryptedData)ENCRYPT_QORTAL_GROUP_DATA(payload:walletIdoraddress,data64|base64, and eithersecretKeyObjectorgroupId, optionalisAdmins, optionaltypeNumber, optionalrefreshCache)DECRYPT_QORTAL_GROUP_DATA(payload:walletIdoraddress,data64|base64, and eithersecretKeyObjectorgroupId, optionalisAdmins, optionalrefreshCache)
If secretKeyObject is omitted and groupId is provided, the daemon mirrors Hub behavior:
it fetches group admins, resolves their primary names, finds the latest matching key publish
(symmetric-qchat-group-<groupId> or admins-symmetric-qchat-group-<groupId>), decrypts it
with the caller wallet key, and uses the resulting key object. Resolved keys are cached for
20 minutes unless refreshCache=true.
Transactions and blocks
GET_USER_WALLET_TRANSACTIONS(payload:walletId,limit,offset,reverse,confirmationStatus,txType)SEARCH_TRANSACTIONS(payload:limit,offset,reverse, plus any/transactions/searchparams)DECODE_TRANSACTION(payload:bytes|unsignedBytes|signedBytes, optionalignoreValidityChecks)FETCH_BLOCK(payload:heightorsignature, empty = latest)FETCH_BLOCK_RANGE(payload:start,end)
Groups and ATs
LIST_GROUPS(payload:limit,offset,reverse)LIST_ATS(payload:limit,offset,reverse)GET_AT(payload:atoratAddress)GET_AT_DATA(payload:atoratAddress)
QDN and hosted data
PUBLISH_QDN_RESOURCE(payload:walletId,registeredName,service, plus one ofdata(base64),stringData/text(plain), orfilePath(chunked), optionaluploadType, optionalencrypt+publicKeys[],identifier,title,description,category,tags,fee)PUBLISH_MULTIPLE_QDN_RESOURCES(payload:walletId,resources[], optionalcontinueOnError, optional top-levelencrypt+publicKeys[], each resource can includeuploadType/filePathand optionaldisableEncrypt)GET_QDN_RESOURCE_URL(payload:service,name, optionalidentifier)LINK_TO_QDN_RESOURCE(alias ofGET_QDN_RESOURCE_URL)FETCH_QDN_RESOURCE(payload:service,name, optionalidentifier,build,attempt,timeoutMs,nodeUrl)GET_QDN_RESOURCE_STATUS(payload:service,name,identifier, optionalbuild)GET_QDN_RESOURCE_PROPERTIES(payload:service,name,identifier, optionalbuild)GET_QDN_RESOURCE_METADATA(payload:service,name, optionalidentifier)LIST_QDN_RESOURCES(payload:service,name,identifier,limit,offset,reverse)SEARCH_QDN_RESOURCES(payload:query,service,name,identifier,limit,offset,reverse)GET_HOSTED_DATA(payload:limit,offset,query)DELETE_HOSTED_DATA(payload:walletId,hostedData[])SAVE_FILE(payload:filenameor relativefilePath, plustextorbase64|data64|data; writes underQORTAL_AUTH_SAVE_FILE_DIR)
Crosschain
GET_CROSSCHAIN_SERVER_INFO(payload:coin)GET_SERVER_CONNECTION_HISTORY(payload:coin)GET_TX_ACTIVITY_SUMMARY(payload:coin)GET_FOREIGN_FEE(payload:coin,typewheretypeisfeekborfeerequired)GET_PRICE(payload:blockchain, optionallimit,offset,reverse,inverse)GET_ARRR_SYNC_STATUS(payload optional)ADD_FOREIGN_SERVER(payload:coin,serverInfo)REMOVE_FOREIGN_SERVER(payload:coin,serverInfo)SET_CURRENT_FOREIGN_SERVER(payload:coin,serverInfo)UPDATE_FOREIGN_FEE(payload:coin,feeType=feekb|feerequired,fee)SIGN_FOREIGN_FEES(payload:signedFees[])
Lists
GET_LIST_ITEMS(payload:listName)ADD_LIST_ITEMS(payload:listName,items[])DELETE_LIST_ITEM(payload:listName,items[])
Chat
SEARCH_CHAT_MESSAGES(payload:txGroupIdorinvolving[], plus search params)SEND_CHAT_MESSAGE(build + sign + process; see transaction build list below)
Transaction build + sign + process
These build a raw unsigned tx via the Qortal node, sign it with your wallet, and (by default) process it.
All require payload.walletId and a node-format payload.tx unless noted.
Set payload.process=false to return signed bytes without processing.
If timestamp, reference (or lastReference), fee, or the required *PublicKey field are omitted, the daemon will fill them using the wallet and node.
REGISTER_NAME(can auto-build fromname+ optionaldescription)UPDATE_NAMESELL_NAMECANCEL_SELL_NAMEBUY_NAMECREATE_GROUPUPDATE_GROUPADD_GROUP_ADMINREMOVE_GROUP_ADMINBAN_FROM_GROUPCANCEL_GROUP_BANKICK_FROM_GROUPINVITE_TO_GROUPCANCEL_GROUP_INVITEJOIN_GROUPLEAVE_GROUPCREATE_POLLVOTE_ON_POLLSEND_COIN(PAYMENT)TRANSFER_ASSETCREATE_ASSET_ORDERCANCEL_ASSET_ORDERDEPLOY_ATSEND_CHAT_MESSAGE(includes/chat/computePoW step)
REGISTER_NAME (auto-build)
curl -s -X POST http://127.0.0.1:3191/qortal/request \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Content-Type: application/json' \
-d '{"requestType":"REGISTER_NAME","payload":{"walletId":"<WALLET_ID>","name":"my-name","description":"Registered Name"}}'
SIGN_TRANSACTION
curl -s -X POST http://127.0.0.1:3191/qortal/request \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Content-Type: application/json' \
-d '{"requestType":"SIGN_TRANSACTION","payload":{"walletId":"<WALLET_ID>","password":"backup-password","unsignedBytes":"<BASE58>","process":false}}'
DECODE_TRANSACTION
curl -s -X POST http://127.0.0.1:3191/qortal/request \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Content-Type: application/json' \
-d '{"requestType":"DECODE_TRANSACTION","payload":{"bytes":"<BASE58_TX_BYTES>"}}'
Approvals (Hub-style consent)
If a request needs approval and the wallet/app is in ask mode, the daemon returns:
{ "error": "approval_required", "scope": "<SCOPE>", "walletId": "<WALLET_ID>" }
To approve once, request a short-lived approval token and retry with
X-Qortal-Approval-Token:
curl -s -X POST http://127.0.0.1:3191/permissions/approve \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Content-Type: application/json' \
-d '{"walletId":"<WALLET_ID>","scope":"SIGN_TRANSACTION"}'
Then retry the original request with:
-H 'X-Qortal-Approval-Token: <APPROVAL_TOKEN>'
To auto-approve for a scope, set a mode:
curl -s -X POST http://127.0.0.1:3191/permissions/set \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Content-Type: application/json' \
-d '{"walletId":"<WALLET_ID>","scope":"SIGN_TRANSACTION","mode":"persistent"}'
PUBLISH_QDN_RESOURCE
uploadType is optional and auto-detected. filePath is read from the daemon filesystem (mount it if running in Docker). For zip files with filePath, set isZip: true.
fileiffilePathis provided (chunked upload)stringifstringDataortextis providedzipifuploadTypeis explicitlyzip(base64 zip viadata)base64otherwise (datais base64)
curl -s -X POST http://127.0.0.1:3191/qortal/request \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Content-Type: application/json' \
-d '{"requestType":"PUBLISH_QDN_RESOURCE","payload":{"walletId":"<WALLET_ID>","password":"backup-password","registeredName":"name","service":"DOCUMENT_PRIVATE","identifier":"example","data":"<BASE64>"}}'
curl -s -X POST http://127.0.0.1:3191/qortal/request \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Content-Type: application/json' \
-d '{"requestType":"PUBLISH_QDN_RESOURCE","payload":{"walletId":"<WALLET_ID>","password":"backup-password","registeredName":"name","service":"DOCUMENT_PRIVATE","identifier":"example","uploadType":"file","filePath":"/data/uploads/document.pdf"}}'
PUBLISH_MULTIPLE_QDN_RESOURCES
curl -s -X POST http://127.0.0.1:3191/qortal/request \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Content-Type: application/json' \
-d '{"requestType":"PUBLISH_MULTIPLE_QDN_RESOURCES","payload":{"walletId":"<WALLET_ID>","password":"backup-password","continueOnError":false,"resources":[{"registeredName":"name","service":"DOCUMENT_PRIVATE","identifier":"one","data":"<BASE64>"},{"registeredName":"name","service":"DOCUMENT_PRIVATE","identifier":"two","data":"<BASE64>"}]}}'
GET_USER_ACCOUNT
curl -s -X POST http://127.0.0.1:3191/qortal/request \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Content-Type: application/json' \
-d '{"requestType":"GET_USER_ACCOUNT","payload":{"walletId":"<WALLET_ID>"}}'
WHICH_UI
curl -s -X POST http://127.0.0.1:3191/qortal/request \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Content-Type: application/json' \
-d '{"requestType":"WHICH_UI","payload":{}}'
Response is a string (HUB_WEB or HUB_ELECTRON).
SAVE_FILE
Writes a file on the daemon host filesystem. Relative paths are resolved under
QORTAL_AUTH_SAVE_FILE_DIR (default data/saved-files). Absolute paths are only
accepted if they are inside that same base directory.
curl -s -X POST http://127.0.0.1:3191/qortal/request \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Content-Type: application/json' \
-d '{"requestType":"SAVE_FILE","payload":{"filename":"example.txt","text":"hello world"}}'
Storage
Data is stored under ./data/ by default:
data/state.json(apps + wallet metadata + persistent permissions)data/settings.json(global/app/wallet settings)data/audit.log(JSONL audit trail)data/wallets/qortal_backup_<address>.json(Qortal-compatible backups)
Override with QORTAL_AUTH_DATA_DIR if needed. Use QORTAL_AUTH_NODE_URL to set the default node URL.
Testing
Run the backup parity test
Put a Qortal backup file in TEST-FILES/ and run:
QORTAL_TEST_BACKUP_PASSWORD='Password1234x!' \
QORTAL_TEST_BACKUP_FILE='not-used-test-2.json' \
npm run test
If you omit QORTAL_TEST_BACKUP_FILE, it will use the default file already referenced in the test.