πŸ”Œ QL Agent API

Programmatically create, run, and manage AI market agents via REST API. Grounded reasoning with live data, citations, and verified track records. ← Back to Agents Β· ⚑ Studio Β· 🌐 Network
Pro+ Required REST API JavaScript SDK OpenAPI 3.1 Webhooks

Quick Start

Get an API key from your profile (Pro+ subscription required), then:

1. Install the SDK

# Node.js β€” download it, then import the local file
curl -o quantlogix-agents.mjs https://quantlogix.ai/sdk/quantlogix-agents.mjs

import { QuantLogix } from './quantlogix-agents.mjs';

# Browser ESM β€” import straight from the URL
import { QuantLogix } from 'https://quantlogix.ai/sdk/quantlogix-agents.mjs';

Node can't import over https: β€” its ESM loader only accepts file: and data: URLs, so download the file first. Browsers import the URL directly.

2. Create your first agent

const ql = new QuantLogix('ql_your_key_here');

// Create a daily pre-market digest agent
const { agent } = await ql.agents.create({
  name: 'NVDA Pre-Market Digest',
  mode: 'digest',
  task_prompt: 'Summarize the overnight setup for NVDA. Include key support/resistance, any catalyst headlines, and the 5-factor signal read.',
  tickers: ['NVDA'],
  cadence: 'premarket',
});

// Run it now (synchronous β€” returns the AI result inline)
const result = await ql.agents.run(agent.id);
console.log(result.result.headline);
console.log(result.result.detail);
console.log('Confidence:', result.result.confidence);
console.log('Tokens used:', result.usage.total_tokens);

3. One-shot Q&A (no saved agent needed)

const { answer } = await ql.ask({
  question: 'Is NVDA oversold right now?',
  tickers: ['NVDA'],
});

console.log(answer.headline);    // "NVDA RSI at 28 β€” technically oversold"
console.log(answer.sources);     // [{ label, asof? }] β€” see Sources below
console.log(answer.confidence);  // 0.82

Authentication

All requests require a ql_ API key, passed via header (never query params β€” they leak in logs):

# Preferred: Bearer token
curl -H "Authorization: Bearer ql_your_key" \
     https://quantlogix.ai/api/v1/agents

# Alternative: X-API-Key header
curl -H "X-API-Key: ql_your_key" \
     https://quantlogix.ai/api/v1/agents

Get your key at /profile. Keys are tier-scoped: Pro unlocks the Agent API and every market-data endpoint, including day/swing signals and microstructure. Institutional covers the same endpoints with higher ceilings β€” unlimited monthly calls (vs 10,000), 300 req/min (vs 60), a 1M/day AI-token cap (vs 250K), and a fuller /signal payload.

Endpoints

Agent Lifecycle

GET/api/v1/agentsPro+
POST/api/v1/agentsPro+
PATCH/api/v1/agentsPro+
DELETE/api/v1/agents?id=<uuid>Pro+
POST/api/v1/agents/runPro+

Run History

GET/api/v1/runsPro+
GET/api/v1/runs/<run_id>Pro+

Connectors & Memory

GET/api/v1/agents/connectorsPro+
POST/api/v1/agents/connectorsPro+
PATCH/api/v1/agents/connectorsPro+
DELETE/api/v1/agents/connectors?id=<uuid>Pro+
POST/api/v1/agents/connectors?action=testPro+
GET/api/v1/agents/memory?agent_id=<uuid>Pro+
DELETE/api/v1/agents/memory?agent_id=<uuid>&note_id=<uuid>Pro+
DELETE/api/v1/agents/memory?agent_id=<uuid>&all=1Pro+

AI Q&A

POST/api/v1/askPro+

Market Data

GET/api/v1/signal?ticker=NVDAFree
GET/api/v1/news?ticker=NVDA&limit=10Free
GET/api/v1/verified?dataset=convictionAll tiers

/signal's payload depends on tier: every key gets the composite score, label, confidence and price, Pro adds the 5-factor scores and indicators breakdown, and Institutional adds microstructure. /news takes limit (default 10, max 50). These are the four market-data endpoints most agents need β€” the OpenAPI spec lists every gated endpoint, including screener, seasonality, promise-ledger, gap-stats, institutional flow, private companies and CRM; the full REST reference lives at /api-docs.

Quota

GET/api/v1/usageAll tiers
GET/api/v1/openapiPublic

Create Agent

POST /api/v1/agents

FieldTypeRequiredDescription
namestringYesAgent name (max 80 chars)
task_promptstringYesWhat the AI runs each cycle (max 1200 chars)
modestringDefault: digestdigest (always send), alert (fire only when condition met), research (deep-dive, Starter+), committee (multi-perspective panel, Starter+)
tickersstring[]OptionalTicker symbols (max 12, uppercase). Extras are dropped and unrecognized symbols silently ignored β€” not rejected.
cadencestringDefault: premarketpremarket, close, weekly, intraday (~every 30 min in market hours), once (requires run_at), custom (requires schedule_days + schedule_time)
run_atstringRequired for onceISO datetime for the one-shot delivery. Omitting it on create returns 400 run_at_required. The agent auto-disables after it fires.
schedule_daysstring[]Required for customWeekdays the agent runs, e.g. ["mon","wed","fri"]
schedule_timestringRequired for customET time of day, e.g. "09:45"
contextstringOptionalStanding background for the AI (max 800 chars)
channelsobjectDefault: push+email{ push: true, email: true, telegram: false, discord: false }
webhook_urlstringStarter+HTTPS URL fired on agent results (POST with JSON body)
connector_idsstring[]Starter+Up to 3 outbound connectors (your REST APIs / MCP servers) the agent may call during its run
enabledbooleanDefault: trueEnable/disable the agent

Over-limit values truncate, they don't error. A 200-char name, a 20-symbol tickers list, or a 5th connector is silently trimmed to the documented maximum and the request still returns 201. Read the returned agent back if you need to confirm what was stored.

Tier-gated fields are dropped silently too. Without the agents_advanced entitlement (Starter+), webhook_url and connector_ids are stripped and mode: research/committee falls back to digest β€” you still get 201 with a working but reduced agent, never a 403.

Example: Create an alert agent

const { agent } = await ql.agents.create({
  name: 'NVDA Oversold Alert',
  mode: 'alert',  // only fires when the condition is met
  task_prompt: 'Alert me if NVDA RSI drops below 30 AND volume spikes above 1.5x the 20-day average.',
  tickers: ['NVDA'],
  cadence: 'intraday',  // checks every ~30 min during market hours
  webhook_url: 'https://your-app.com/webhook/ql-agent',
});

Run an Agent

POST /api/v1/agents/run β€” runs synchronously (≀50s) and returns the AI result inline, or in the background with async: true. Never sends push/email. Spends the account's AI token budget.

FieldTypeDescription
idstringRun a saved agent by UUID
agentobjectRun an inline draft (same shape as create, no save)
asyncbooleanReturn 202 { run_id, poll_url } immediately and execute in the background with a ~5-minute budget β€” deep research runs that would time out synchronously. Requires a saved agent id. Poll GET /api/v1/runs/<run_id> until status leaves pending.

One of id or agent is required.

Response shape

{
  "ok": true,
  "fired": true,
  "reason": null,                // why an alert stayed silent, when ok:true & fired:false
  "result": {
    "headline": "NVDA RSI at 28 β€” technically oversold with volume 1.8x average",
    "detail": "NVDA closed at $425.30, down 3.2%...",
    "confidence": 0.82,
    "sources": [
      { "label": "NVDA RSI(14) 28.3", "asof": "20:00 ET" }
    ],
    "as_of": "2026-07-11T22:30:00Z"
  },
  "models": "fast+deep",        // reasoning tiers used: "fast", "deep", or "fast+deep"
  "tool_calls": 4,
  "grounding": null,
  "usage": { "total_tokens": 3420, "cost_usd": 0.008912 },
  "api_version": "v1"
}

Sources. Each citation is { label, asof? } β€” label is a human-readable datum (max 120 chars), asof an as-of stamp (max 48 chars) present only when the model supplied one. Up to 8 per result, deduplicated. There are no ticker/metric/value fields.

When the run fails (ok: false) the body carries reason, models, usage and api_version β€” but no result and no fired. Always branch on ok before reading result.

Run History & Async Runs

GET /api/v1/runs β€” everything your agents produced, newest first: scheduled cron runs (with summaries) and API-triggered runs (with the full structured result). Filter with ?agent_id=<uuid>, page with ?limit= (max 100). A client that misses a webhook can always recover results here.

GET /api/v1/runs/<run_id> β€” one run; the poll target for async runs. status is pending while an async run executes, then fired / clear / skipped / error.

// Deep research run in the background, then wait for it
const { run_id } = await ql.agents.runAsync(agent.id);
const run = await ql.runs.wait(run_id);   // polls every 5s, up to 5.5 min
console.log(run.status, run.result?.headline);

// What did my overnight agents find?
const { runs } = await ql.runs.list({ limit: 20 });
for (const r of runs) console.log(r.ran_at, r.status, r.summary);

Outbound Connectors (REST / MCP)

Pro+. Register your own platforms β€” an HTTPS JSON API or a remote MCP server β€” and attach them to agents via connector_ids. During a run the agent gets an extra ext_* tool per connector and can pull your data into its reasoning, fully grounded and cited like every other source.

FieldTypeRequiredDescription
namestringYesDisplay name (also names the agent-side tool, e.g. ext_my_research_api)
typestringYesrest (HTTPS JSON API) or mcp (JSON-RPC 2.0 over Streamable HTTP). Required on create β€” the SDK does not validate it client-side, so omitting it fails server-side with 400 type_invalid.
base_urlstringYesHTTPS only, public DNS hosts only β€” IP literals, ports, credentials, query strings, and private/internal names are rejected
auth_modestringOptionalnone, bearer (Authorization: Bearer), or header (custom header via auth_header)
auth_headerstringWith headerCustom header name, [A-Za-z0-9-], max 64 chars
secretstringOptionalWrite-only. Encrypted at rest (AES-256-GCM), never returned by any read β€” views carry has_secret only
allowed_pathsstring[]OptionalREST: path-prefix allowlist, max 20. Empty = nothing callable (fail closed). Each must start with /; malformed entries return 400 allowed_path_invalid.
allowed_toolsstring[]OptionalMCP: tool-name allowlist, max 20. Empty = every listed tool
enabledbooleanDefault: trueEnable/disable the connector without deleting it

Testing a connector. POST /api/v1/agents/connectors?action=test (SDK: connectors.test(id)) probes it live. A failed probe still returns HTTP 200 with { ok: false, error } β€” check the ok field, not the status. Success returns { ok: true, tools: [...] } for MCP and { ok: true, sample: "..." } for REST.

// Register a connector, verify it answers, attach it to an agent
const { connector } = await ql.connectors.create({
  name: 'My Research API', type: 'rest',
  base_url: 'https://api.example.com',
  auth_mode: 'bearer', secret: process.env.MY_API_TOKEN,
  allowed_paths: ['/v1/notes', '/v1/positions'],
});
await ql.connectors.test(connector.id);                      // live connectivity probe
await ql.agents.update(agent.id, { connector_ids: [connector.id] });

Security posture: per-run call budget (5 external calls), 10s timeout and 256 KB response cap per call, redirects never followed, hostnames re-resolved against public IP space at call time (DNS-rebind guard), and every response is delivered to the model wrapped as untrusted external data β€” instructions inside connector output are never followed. Up to 10 connectors per account, 3 per agent.

Per-Agent Memory

Each agent keeps a private memory: at the end of a run it may save one short note (a level it's watching, a dated expectation) that is injected into its future runs. Notes are isolated per agent β€” two agents on the same ticker never see each other's memory β€” and capped at the newest 30.

const { notes } = await ql.agents.memory(agent.id);   // [{ id, note, created_at }]
await ql.agents.deleteMemoryNote(agent.id, notes[0].id);
await ql.agents.clearMemory(agent.id);                 // wipe it entirely

Agent-Scoped API Keys

Mint keys restricted to specific agents β€” hand a teammate, a bot, or a third-party integration a key that can read and run only the agents you list (max 20), and nothing else: out-of-scope agents 404, creating agents and managing connectors are denied. Pass agent_ids when creating the key on your profile (POST /api/v1/keys), or PATCH an existing key; agent_ids: [] clears the scope.

Grounded Q&A

POST /api/v1/ask β€” one-shot market question through the same reasoning pipeline as agents. No saved agent needed.

FieldTypeRequiredDescription
questionstringYesYour market question. Minimum 3 chars (shorter returns 400 question_required); anything past 600 chars is silently truncated, so long prompts are answered on their first 600 characters.
tickersstring[]OptionalTickers to scope the analysis (max 8). A comma-separated string is also accepted. Unrecognized symbols are dropped silently, never rejected.
modestringDefault: digestdigest (fast) or research (deep-dive). Any other value falls back to digest without an error.

Webhooks

When an agent has a webhook_url set, each fired scheduled run POSTs this JSON payload to that URL. Note the fields are flat β€” there is no result wrapper:

{
  "event": "agent.run",
  "agent": { "id": "uuid", "name": "NVDA Oversold Alert", "mode": "alert" },
  "headline": "NVDA RSI at 28 β€” oversold",
  "detail": "...",
  "sources": [ { "label": "NVDA RSI(14) 28.3", "asof": "20:00 ET" } ],
  "artifact_url": "https://quantlogix.ai/...",  // or null
  "ran_at": "2026-07-11T14:30:00Z"
}

Two headers accompany every delivery: X-QL-Event: agent.run and X-QL-Agent-Id. There is no confidence field in the webhook body β€” poll GET /api/v1/runs if you need it.

Only scheduled (cron) runs deliver webhooks. Runs you trigger yourself via POST /api/v1/agents/run never fire one β€” they return the result inline instead, and deliberately send no push or email either.

Your endpoint should respond with 200 OK within 10 seconds. Failed deliveries are not retried β€” recover the full structured result from GET /api/v1/runs (GET /api/v1/agents carries only last_status and a truncated last_summary, never the result body).

Verifying the signature

When you have a webhook signing secret configured on your profile, every delivery is signed: X-QL-Signature: sha256=<hmac> is the HMAC-SHA256 of <X-QL-Timestamp>.<raw body>. The timestamp is part of the signed bytes, so the 5-minute replay window is enforceable β€” resending a captured delivery with a rewritten X-QL-Timestamp breaks the signature. Verify with the SDK β€” pass the raw body string, never a re-serialized object:

import { QuantLogix } from './quantlogix-agents.mjs';

// e.g. an Express handler mounted with express.raw({ type: '*/*' })
const valid = await QuantLogix.verifyWebhook({
  body: req.body.toString('utf8'),        // the RAW bytes, exactly as received
  signature: req.headers['x-ql-signature'],   // HMAC of `<timestamp>.<body>`
  timestamp: req.headers['x-ql-timestamp'],  // required β€” it is signed; >5 min old is rejected
  secret: process.env.QL_WEBHOOK_SECRET,       // qlw_… from your profile
});
if (!valid) return res.status(401).end();

Verification is constant-time and isomorphic (WebCrypto β€” Node 18+, browsers, edge). Deliveries are unsigned only when no signing secret is configured.

Rate Limits & Quotas

TierMonthly CallsPer MinuteAgent APIAI Tokens
Free1005β€”β€”
Starter1,00020β€”β€”
Pro10,00060βœ“250K/day per key
EnterpriseUnlimited300βœ“1M/day per key

Pro and Enterprise keys carry the same market-data endpoints β€” the ceilings differ. One exception: POST /v1/simulate (QL Simulator) is exclusive to the enterprise API tier, which Institutional plans mint. See the API reference.

Check your remaining quota at any time β€” GET /api/v1/usage never consumes quota:

const usage = await ql.usage();
console.log(usage.requests.remaining);  // 9421
console.log(usage.ai_tokens.remaining);  // 187432 (daily)
console.log(usage.tier);                 // "pro"

SDK Reference

The JavaScript SDK (quantlogix-agents.mjs) is a single ESM file with zero dependencies:

MethodDescription
ql.agents.list()List all your agents
ql.agents.create(agent)Create a new agent
ql.agents.update(id, patch)Update an agent (partial)
ql.agents.delete(id)Delete an agent
ql.agents.run(idOrDraft)Run a saved agent (by id) or inline draft, synchronously
ql.agents.runAsync(id)Start a background run (~5 min budget) β€” returns run_id
ql.runs.list({ agent_id?, limit? })Run history, newest first
ql.runs.get(id)One run by id (async-run poll target)
ql.runs.wait(id, opts?)Poll a run until it finishes
ql.agents.memory(id)The agent's private memory notes
ql.agents.deleteMemoryNote(id, noteId) / ql.agents.clearMemory(id)Prune or wipe an agent's memory
ql.connectors.list() / create(spec) / update(id, patch) / delete(id) / test(id)Outbound REST/MCP connectors (Pro+)
ql.ask({ question, tickers, mode })One-shot grounded Q&A
ql.usage()Quota introspection (free, no quota consumed)
ql.signal(ticker)5-factor composite signal
ql.news(ticker, limit?)Curated news with sentiment
ql.backtest({ ticker, strategy, years? })Point-in-time strategy backtest vs buy-and-hold (Starter+)
ql.backtestLibrary()Catalog of runnable strategy-backtest IDs (free, unmetered)
ql.portfolio({ holdings })5-factor roll-up over a book (Pro+)
ql.portfolioOverlay({ holdings })Portfolio Alpha overlay β€” verdicts + action queue (every tier, holdings-capped)
ql.verified({ dataset, ticker?, handle?, limit? })QL Oracle verified data β€” conviction / index / creators / record
ql.signals(tickers)Batch 5-factor snapshot, up to 25 tickers (Pro+)
ql.daySignal(ticker) / ql.swingSignal(ticker)Intraday QDTSS score Β· 2d–4wk A–D conviction grade (Pro+)
ql.microstructure(ticker)OFI / VPIN / smart-money order-flow read (Pro+)
ql.earningsSetup(ticker)Pre-earnings setup β€” implied move + positioning (Pro+)
ql.screener(filters?)Programmatic universe screener (Pro+)
ql.seasonality(ticker) / ql.gapStats(ticker)Calendar-month seasonality Β· overnight-gap profile (Free)
ql.promiseLedger(ticker)Management Promise Ledger β€” kept / missed / walked-back receipts (Free)
ql.dca({ ticker, monthly?, years? }) / ql.streetGrades(params?)DCA analytics Β· analyst-grades-vs-tape leaderboard (Free)
ql.simulate({ positions })QL Simulator β€” regime-switching Monte Carlo on a what-if book (Institutional)
ql.institutional(ticker | { cik })13F holders of a ticker, or a fund's book by CIK (Institutional)
ql.engineModelCard()The live self-test behind /proof β€” factor ICs, decay, durability (Pro+)
ql.researchRequest({ subject })Commission a QL Research thesis (Pro+)
ql.privateCompanies.list / get / news / financials / comparables / ipoReadiness / liquidity / spacesPrivate-markets data β€” the 2,000+-company pre-IPO roster with comps, financials, IPO readiness, secondary liquidity, and the Emerging Spaces board (Pro+)
ql.privateCompanies.marks(params?) / ql.privateCompanies.markTape(slug | params?)QL Private Marks β€” one model-marked valuation per tracked company (last round blended with the daily secondary print and a comps-implied valuation, freshness-weighted, with per-component provenance and a confidence grade) Β· QL Mark Tape β€” SEC-filed mutual-fund marks: price per share as a fiduciary carries it, the matched-basis quarterly re-mark, and cross-manager dispersion on the same security (Institutional)
ql.datasets.catalog / manifest / versions / rowsQL Datasets β€” versioned, hash-verified bulk data in json, jsonl or csv. Every version is an archived QL Rewind snapshot, SHA-256-stamped and publicly chain-verifiable, so rows(slug, { version: '2026-06-01' }) reproduces exactly what that day held. The catalog and manifests are public; full rows are Pro+ (mark-tape is Institutional). Pass ifNoneMatch a previous sha256 and an unchanged version answers { not_modified: true } instead of re-sending the rows β€” which is what makes polling cheap.
ql.collab.sheet.read / write Β· ql.collab.note.sections / read / writeQL Collab β€” documents as APIs: one A1 range of a QL Sheet, one section of a QL Note. Everything outside the addressed range is left untouched, and a write that races another editor returns 409 rather than clobbering it. The key's account must belong to the document's team; anything else is a 404 (Pro+)
ql.investors.list / get / fitVC/PE investor directory + startup↔investor fit scoring (Pro+)
ql.crm.contacts / deals / tasks / interactions (.list/.create/.update) Β· ql.crm.report()QL CRM over your key (Pro+)
ql.webhooks.list / create / test / deleteOutbound webhooks β€” signed event push to your endpoint. test(id) fires a signed webhook.test delivery so you can prove your verifier works before a real event depends on it (Pro+)
ql.mesh.publish / subscribe / directory / mine / grant / revoke / call / calls / unpublishAgent Mesh β€” publish a native QL agent or connector, subscribe to public listings (QL Deep Dive, VC Deal Scout), grant other accounts scoped access, call via listing_id or qlm_ token. Every call is QL-brokered: endpoints & secrets never cross accounts, payloads are credential-redacted both directions, results arrive untrusted-wrapped, per-grant daily budgets enforced, full audit trail. Grant tokens (qlm_…) are returned exactly once (Pro+)
QuantLogix.verifyWebhook({ body, signature, timestamp, secret, toleranceMs? })Verify an inbound webhook signature. timestamp is required β€” it is part of the signed bytes, and omitting it returns false rather than throwing. toleranceMs defaults to 5 min. Returns a boolean (static; safe to await).

Error Handling

try {
  const result = await ql.agents.run(agentId);
} catch (err) {
  if (err.status === 401) console.log('Invalid API key');
  if (err.status === 402) console.log('AI budget exhausted, agent cap, or tier upgrade needed');
  if (err.status === 403) console.log('Tier too low, key not bound, or out of key scope');
  if (err.status === 429) console.log('Rate limit or daily token cap');
  if (err.status === 503) console.log('Server timeout or storage unavailable');
  if (err.code === 'timeout') console.log('Client timeout β€” use runAsync() or raise the timeout');
}

Statuses are overloaded β€” branch on err.code, not status alone. 402 is ai_budget_exhausted (run), cap_reached (agent-count cap on create) or tier_required (connectors). 403 is a tier gate, key_not_bound_to_account, or scoped_key. 503 is timeout or storage_unavailable.

Caveat on err.code: endpoint-level failures return snake_case codes (id_required, cap_reached, not_found, run_failed…), but authentication and quota failures (401 / 403 tier / 429) return a human-readable English sentence in that field instead. For those three, switch on err.status and treat err.code as a display message, not an identifier.

Every failure throws a typed QuantLogixError carrying err.status (HTTP code), err.code (see caveat above), and err.data (the server's JSON body). Transient failures retry automatically with exponential backoff honoring Retry-After β€” 429/503 for every method, plus 502/504 and network errors for reads; tune with new QuantLogix(key, { maxRetries: 4 }) or disable with { maxRetries: 0 }. A client-side timeout throws err.code === 'timeout' once a request outruns the SDK's timeout (default 55s) and is never retried (the run may still be executing server-side). Deep research runs can exceed that β€” either raise it (new QuantLogix(key, { timeout: 120000 })) or, better, start the run in the background with agents.runAsync(id) and poll with runs.wait(run_id). Non-ASCII agent names and prompts (em-dashes, accents, emoji) are fully supported.

Ready to build?

Get your API key and start creating agents programmatically.

Get API Key β†’ See Plans OpenAPI Spec
QL Agents produce educational market commentary, not investment advice. API usage is subject to the QuantLogix Terms of Service. AI token costs are billed against the account's monthly budget.