@melaya/sdk
v0.4.0
Published
Official SDK for Melaya Agent Builder and Mobile Device Control. Includes preview Melaya Trading namespaces that are not yet generally available.
Maintainers
Readme
@melaya/sdk
Current production scope: Agent Builder and Mobile Device Control are available now. Melaya Trading namespaces are preview-only and not generally available; do not use them with real funds.
Official SDK for the Melaya Agent Builder and flagship Mobile Device Control APIs. Build agents from 8,000+ scoped tools, 100+ specialized subagents, and 48 AI providers; pair an Android phone and let an authorized agent operate approved apps through the visible interface. Trading namespaces are included only as a preview of a later product.
Melaya products: Melaya Agents · Melaya Assistant · Device Control · Browser Control · MCP Server · Melaya Marketing
- Zero runtime dependencies (uses the platform
fetch+WebSocket). - Isomorphic: works in Node 18+ and the browser.
- Fully typed, from pipeline configs to real-time run events.
- Agent Builder, Device Control, and platform management from one client — plus preview trading namespaces.
Install
npm install @melaya/sdkQuick start: pair a phone
import { Melaya } from "@melaya/sdk";
const melaya = new Melaya({ apiKey: process.env.MELAYA_API_KEY! }); // keys are prefixed `mk_`
const { code, expiresInSeconds } = await melaya.agents.phone.pair();
console.log({ code, expiresInSeconds });
const devices = await melaya.agents.phone.listDevices();
const apps = await melaya.agents.phone.listApps();
await melaya.agents.phone.setAllowedApps(["com.android.chrome"]);A Melaya platform key is required. "No app API required" means Device Control operates the target app through its user interface; it does not mean the Melaya SDK is unauthenticated.
Quick start: run an agent pipeline
Configure provider credentials through Melaya Connectors first. Never include a provider key in pipeline configuration or per-run overrides.
// The run is generated ONLY from `steps[]` — a top-level `agents[]` array
// with no matching steps produces an EMPTY pipeline. There is no `prompt`
// field on an agent: `instruction` is the task.
await melaya.agents.pipelines.create({
name: "mobile-review",
project: "Operations",
steps: [{
kind: "agent",
agent: {
name: "mobile-operator",
role: "Careful mobile operator",
instruction: "Read before acting. Never send, publish, or delete.",
model: { provider: "anthropic", name: "claude-sonnet-4-6" },
agent_tools: [
"phone_get_screen_tree",
"phone_current_app",
"phone_open_app",
"phone_click_text",
"phone_back",
"phone_wait"
],
human_approval_tools: []
}
}],
maxCostUsd: 1.00
});
const { run_id } = await melaya.agents.pipelines.run("mobile-review", { project: "Operations" });
await melaya.agents.phone.registerActiveRun(run_id);
const status = await melaya.agents.pipelines.runStatus("mobile-review", run_id);
// Real-time run progress + HITL notifications over Socket.IO
melaya.platform.events.onRunUpdate(run_id, (e) => console.log(e.event_type, e.status));
melaya.platform.events.onHitlApproval((e) => console.log("HITL:", e.type, e.requestId));API surface (generally available)
Namespaced access is the primary API (melaya.agents.*, melaya.platform.*); flat aliases (melaya.pipelines, melaya.hitl, ...) remain for backwards compatibility.
| Area | Methods |
|---|---|
| Auth | platform.auth.login, verifyMfa, register, verifySignup, resendVerification, forgotPassword, resetPassword, changePassword, me, check, refresh, myPermissions, createMobileHandoff |
| MFA | platform.mfa.status, setup, confirm |
| Accounts | platform.accounts.exportMyData, updateProfile, removeKey, credits, aiCredits, portfolioIdeasCredits, riskMonitoringCredits, resendEmailVerification, verifyEmail |
| Projects | platform.projects.list, create, rename, runnerProjects |
| Connectors | platform.connectors.connectedServices, set, delete, envHandle, googleOAuthStart, applyPersonal, sharedBy, googleStatus, googleSetDefault, googleDisconnect, dbTestStart, dbTestStatus |
| Credentials | platform.credentials.list, connectedServices, get, set, delete, test, getOperatorProfile, setOperatorProfile, listModels, RAG + connect flows (ragIngestStart, ragRetrieveStart, linkedinConnectStart, telegramAuthStart, ...), googleStatus, googleSetDefault, googleDisconnect, dbTestStart, dbTestStatus, telegramQrStart, telegramQrPoll, whatsappSignupConfig, whatsappSignupExchange, tiktokCreatorInfo, substackEmailLinkSend, substackEmailLinkRedeem |
| Pipelines | agents.pipelines.listPipelines, create, get, update, remove, run, runIds, runStatus, cancelRun, outputs, output, previewCode, tools, subagents, catalogCounts, instantiateTemplate, buildWithAI, overview, count, list, recent, traces, trace, traceStats, deleteTraces, listSchedules, getSchedule, upsertSchedule, pauseSchedule, resumeSchedule, uploadRunFile, runInputs, runInputFile, runActive, listDocs, uploadDoc, deleteDoc, uploadRetrievalDoc, ingestRetrieval, deleteRetrievalDoc, projectToolCalls, projectToolCallFacets, toolCallDetail |
| Templates | platform.templates.list, listGlobal, listValidated, save, update, duplicate, delete, share, shareTargets, listAssignments, assign(templateId, { userId \| projectId }), unassign(templateId, { userId \| projectId }) |
| Phone (Device Control) | agents.phone.pair, listDevices, revokeDevice, screenTree, listApps, setAllowedApps, registerActiveRun, grantApp, requestCast |
| HITL | agents.hitl.pending, history, approve, reject, bulkDecide, runToolStats, runToolStatsByAgent, runMessages, runToolCalls |
| Evals | agents.evals.listRuns, summary, runDetail, compare, benchmarks |
| Memory | agents.memory.graph, runMemory, crew, editEntry, deleteEntry |
| Event triggers | agents.triggers.list, get, deliveries, stats, pendingApprovals, test, events, pollStatus, pollTest, pollNow, pollSync, presets, limits, sources |
| Models | agents.models.listModels |
| Connector tools | agents.connectorTools.services, search, describe, test, connect, call, callStatus, callAndWait |
| Assistant | agents.assistant.getProfile, setProfile |
| Events (real-time) | platform.events.onRunUpdate, onInitPhase, onProjectEvent, onHitlApproval, onPipelineCreated, onPipelineUpdated, onPipelineDeleted, leaveRun, leaveProject, close |
| Billing | platform.billing.subscription, plans, createCheckout, createPortal, ambassadorPerk, redeemCode, reservedPromo |
| Runner | platform.runner.createToken, listTokens, revokeToken |
| Team | platform.team.listMembers, invite, createInviteLink, acceptInvite, updateMemberRole, removeMember, getPipelineVisibility, setPipelineVisibility, transferOwnership |
| Bugs | platform.bugs.create, listMine, get, addComment, listNotifications, markNotificationsRead |
Authentication
Create an API key in the dashboard (melaya.org → Settings → API Keys). Keys are prefixed mk_. On the wire:
- REST — the key is sent only as an
Authorization: Bearer mk_...header, never in the URL. - Public WebSocket streams (preview market data) — the key rides as
?apiKey=in thewss://URL (server protocol; redact it from proxy/APM/WebSocket query logs). - Private WebSocket streams — the SDK first mints a short-lived ticket over REST, and only
?wsTicket=appears in the URL; the API key itself is never in a private stream URL.
Alternatively, pass a sessionToken from platform.auth.login() instead of an API key.
Connector tools
agents.connectorTools (flat alias: melaya.connectorTools) calls the same connector tools the Melaya Assistant and MCP server use — over plain REST, without a running pipeline. This is distinct from platform.connectors / platform.credentials, which store the underlying service credentials; no method here ever accepts or returns a credential value.
- Read tools run immediately.
- Writes default to
approval: "required", which stages the same approval card the Assistant raises and returns HTTP 202 (a success, not an error) with arequestId; the write runs once the user approves it in the Melaya app. Passapproval: "none"to run a write immediately — it is still audit-logged. - Tools that move money or trade are refused under both approval modes.
const { tools } = await melaya.agents.connectorTools.search("unread email");
// Fire-and-forget: poll callStatus(requestId) yourself.
const staged = await melaya.agents.connectorTools.call("gmail_send", { to: "[email protected]" });
if (staged.status === "pending_approval") {
const outcome = await melaya.agents.connectorTools.callStatus(staged.requestId);
}
// Or block until the approval is decided (or it times out).
const outcome = await melaya.agents.connectorTools.callAndWait("gmail_send", { to: "[email protected]" });
if (outcome.status === "done" && outcome.ok) console.log(outcome.result);Event triggers
agents.triggers (flat alias: melaya.triggers) reads and diagnoses the triggers that start your pipelines from outside events. Create, edit and delete triggers in the Agent Builder or the MCP server; they are not on REST.
deliveriesandstatsshow what happened to each event: verdict, decision, action, run id and timings.eventsis the live log, including events that never got a receipt (filtered, shed, rejected at ingress).testdry-runs an event: prefilter, decide and routing run for real, the action never executes.pollTestcalls a poll trigger's tool now and shows what it would publish, publishing nothing.pollNowmakes a real poll due now.
const [trigger] = await melaya.agents.triggers.list({ project: "acme" });
await melaya.agents.triggers.test(trigger.id, { amount: 120 });
const { events } = await melaya.agents.triggers.events({ triggerId: trigger.id, verdicts: ["filtered", "rejected"] });Trading (preview — not generally available)
Everything below this line belongs to the Melaya Trading preview. It is not a production commitment and must not be used with real funds.
Market data
// REST — normalized ticker from any of 70+ venues
const ticker = await melaya.market.ticker({ exchange: "binance", symbol: "BTC/USDT", market: "spot" });
console.log(ticker.last, ticker.bid, ticker.ask);
// REST — order book
const book = await melaya.market.orderbook({ exchange: "bybit", symbol: "BTC/USDT", market: "spot", limit: 20 });
// REST — candles
const candles = await melaya.market.ohlcv({ exchange: "okx", symbol: "ETH/USDT", timeframe: "1h", limit: 200 });Streaming
// Live ticker
const stream = melaya.stream.ticker({ exchange: "binance", symbol: "BTC/USDT", market: "spot" });
for await (const t of stream) {
console.log(t.last);
}
// Or event-style
const liq = melaya.stream.liquidations({ exchange: "binance" });
liq.on("message", (e) => console.log(e.side, e.notional));
liq.on("close", () => console.log("stream closed"));
// liq.close();Trading
The same client covers the preview authenticated surface: your account, paper trading, strategies, and backtests. Reads need only your mk_ key; live order placement needs a connected exchange key (melaya.account.keys()).
// Account: connected keys, tier limits, usage
const keys = await melaya.account.keys(); // [{ apiKeyId: "BINANCEUSDM_0", exchange, market, ... }]
const usage = await melaya.account.usage();
// Strategies — create() launches immediately. Paper (dryRun) needs no exchange key.
// SDK-launchable strategies are `custom` definitions: a Rhai script with an
// `evaluate()` that emits signals (emit_long / emit_short / emit_close).
const { strategyId } = await melaya.strategies.create({
name: "My first bot",
strategyType: "custom",
exchange: "binanceusdm",
symbol: "BTC/USDT:USDT",
market: "FUTURES",
dryRun: true, // paper. dryRun:false + apiKeyId places real orders.
params: {
language: "rhai",
definition: `fn evaluate() { emit_long(param("qty")); }`,
qty: 0.001,
},
});
await melaya.strategies.pause(strategyId);
await melaya.strategies.resume(strategyId);
const trades = await melaya.strategies.trades(strategyId);
// Paper trading (sim broker) — synthetic fills, no venue state
const bal = await melaya.sim.balance({ strategyId });
const fill = await melaya.sim.createOrder({
strategyId, exchange: "binanceusdm", symbol: "BTC/USDT:USDT",
side: "buy", type: "market", amount: 0.001, market: "FUTURES",
});
// Backtest on the Rust engine
const { job_id } = await melaya.backtest.start({
strategyType: "custom", exchange: "binance", symbol: "BTC/USDT", timeframe: "1h",
since_ms: Date.now() - 90 * 864e5, until_ms: Date.now(),
language: "rhai", definition: `fn evaluate() { emit_long(param("qty")); }`, params: { qty: 0.001 },
});
let job; do { job = await melaya.backtest.job(job_id); } while (!["done","error"].includes(job.status));
const result = await melaya.backtest.results(job_id); // metrics, equity_curve, ohlcv
// Live private feeds (ticket-minted automatically)
const events = await melaya.stream.strategies();
for await (const ev of events) console.log(ev.type, ev.strategyId);Trading API surface (preview)
| Area | Methods |
|---|---|
| Reference | market.listExchanges(), catalogCounts() |
| Market data | market.ticker, orderbook, ohlcv, ohlcvMulti, trades, markets, currencies, marketConstraints, status, time |
| Batch / derivatives | market.tickers, fundingRates, fundingRateHistory, fundingRateHistoryMulti, openInterest, openInterestHistory, openInterestHistoryMulti, instruments, liquidationEvents |
| Prediction markets | market.predictionMarkets (polymarket, kalshi, drift_pm, sxbet, azuro, overtime) |
| Account | account.keys, usage, apiKeyStatus |
| Strategies | strategies.create, list, get, pause, resume, stop, delete, updateParams, status, performance, executions, trades, logs |
| AI optimizer | strategies.aiOptStart, aiOptStatus, aiOptApprove, aiOptStop, aiOptRuns |
| Paper trading | sim.balance, positions, openOrders, myTrades, createOrder, cancelOrder, listAccounts |
| Backtesting | backtest.start, job, results, trades, sweep, list, favorites, fundingRange, cancel, delete, deleteAll |
| Public streaming | stream.ticker, orderbook, ohlcv, trades, liquidations |
| Private streaming | stream.strategies, stream.private |
| Live trading | trade.balance, positions, openOrders, orders, closedOrders, myTrades, myTradesHistory, planOrders, positionsHistory, leverage, leverageTiers, createOrder, cancelOrder, amendOrder, cancelAllOrders, cancelPlanOrders, closePosition, setLeverage, setMarginMode, setPositionMode |
Older runtimes
// Node < 18 (no global fetch) and/or Node < 22 (no global WebSocket):
import { Melaya } from "@melaya/sdk";
import fetch from "node-fetch";
import WebSocket from "ws";
const melaya = new Melaya({ apiKey: "mk_...", fetch: fetch as any, WebSocket: WebSocket as any });Full docs: melaya.org/documentation.
