On this page
- Events
- List universal events
- Event detail + composite health
- Spread / arbitrage / arb-decay
- Trades, anomaly history, dispute history, settlement
- Me (org-scoped)
- Wallet bindings
- Wallet history backfill (Polymarket)
- Platform attributions (builder codes)
- Fills, PnL, anomaly feed
- Backtest
- Webhooks
- Account activity — trades + deposits/withdrawals (unified feed)
- Wallet deposit / withdraw events
- Wallet liquidation events
- API keys
- Admin
- Stream — quick test from CLI
- See also
Examples
A working request + a representative response for every endpoint family.
Replace art_live_<your-key> with a real key from
POST /keys and evt_01HMPK... with
a real universal event id from GET /v1/events.
All examples use the production base URL https://api.artery.questflow.ai. For local dev, swap
in https://api.artery.questflow.ai — the wire shape is identical.
Events
List universal events
bash# All multi-provider BTC events resolving in 2026 (page size 50)
curl -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/events?underlying=BTC&resolution_after=2026-01-01T00:00:00Z&min_providers=2&limit=50"jsonc{
"events": [
{
"universalId": "evt_01HMPKW8YJ5C0F8H4Q4ZE7VX9D",
"canonicalName": "BTC ≥ $120,000 by 2026-12-31",
"kind": "price_binary",
"underlying": "BTC",
"threshold": 120000,
"op": "gte",
"resolutionDate": "2026-12-31T23:59:59Z",
"status": "open",
"firstSeenAt": "2026-04-02T14:11:08.412Z",
"lastSeenAt": "2026-05-13T08:42:13.991Z",
"links": [
{ "provider": "polymarket", "providerMarketId": "0xCONDITION_ID", "confidence": 0.97, "source": "ner" },
{ "provider": "kalshi", "providerMarketId": "KXBTC-26DEC31-T120K", "confidence": 0.95, "source": "ner" }
]
}
],
"meta": { "total": 142, "lastReconcile": { "at": "2026-05-13T08:00:00Z", "ingested": 1422 } }
}Event detail + composite health
bash# One-shot dashboard fetch — spread + arbitrage + settlement + disputes + half-life
curl -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/events/evt_01HMPKW8YJ5C0F8H4Q4ZE7VX9D/health"jsonc{
"universalId": "evt_01HMPKW8YJ5C0F8H4Q4ZE7VX9D",
"event": { "...": "ArteryEvent shape" },
"spread": {
"universalId": "evt_01HMPKW8...",
"legs": [
{ "provider": "polymarket", "yesBid": 0.42, "yesAsk": 0.45, "yesMid": 0.435 },
{ "provider": "kalshi", "yesBid": 0.39, "yesAsk": 0.42, "yesMid": 0.405 }
],
"fetchedAt": "2026-05-13T08:42:18.220Z"
},
"arbitrage": {
"crossProvider": { "buyProvider": "kalshi", "sellProvider": "polymarket", "grossEdge": 0.015, "netEdge": 0.008 },
"negationPair": null
},
"consensusScore": null,
"settlement": [],
"disputeHistory": [],
"edgeHalfLife": { "medianHalfLifeSec": 84, "decayedSignals": 32, "computedAt": "2026-05-13T03:00:00Z" },
"recentAnomalies": [],
"recentTrades": [],
"fetchedAt": "2026-05-13T08:42:18.224Z"
}Spread / arbitrage / arb-decay
bashcurl -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/events/evt_01HMPKW8YJ5C0F8H4Q4ZE7VX9D/spread"
curl -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/events/evt_01HMPKW8YJ5C0F8H4Q4ZE7VX9D/arbitrage"
curl -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/events/evt_01HMPKW8YJ5C0F8H4Q4ZE7VX9D/arb-decay"jsonc// /arb-decay response
{
"universalId": "evt_01HMPKW8YJ5C0F8H4Q4ZE7VX9D",
"signalsConsidered": 87,
"decayedSignals": 64,
"medianHalfLifeMs": 84_312,
"medianHalfLifeSec": 84,
"computedAt": "2026-05-13T03:00:00Z"
}Trades, anomaly history, dispute history, settlement
bash# Last 100 fills observed for this event (cross-provider)
curl -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/events/evt_01HMPKW8YJ5C0F8H4Q4ZE7VX9D/trades?limit=100"
# Anomalies in the trailing 7d (default), filter to spread-widening
curl -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/events/evt_01HMPKW8YJ5C0F8H4Q4ZE7VX9D/anomaly-history?kind=spread_widening&limit=50"
curl -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/events/evt_01HMPKW8YJ5C0F8H4Q4ZE7VX9D/dispute-history"
curl -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/events/evt_01HMPKW8YJ5C0F8H4Q4ZE7VX9D/settlement"jsonc// /anomaly-history response
{
"universalId": "evt_01HMPKW8YJ5C0F8H4Q4ZE7VX9D",
"anomalies": [
{
"id": "an_01HMQ0J7P5G2N7VK0H9F6X3M2A",
"kind": "spread_widening",
"universalId": "evt_01HMPKW8YJ5C0F8H4Q4ZE7VX9D",
"provider": "polymarket",
"marketId": "0xCONDITION_ID",
"ts": "2026-05-13T07:11:42.000Z",
"bucketVol": null,
"baselineMean": 0.012,
"baselineStddev": 0.004,
"zScore": 6.4,
"raw": null
}
]
}Me (org-scoped)
Wallet bindings
bash# Bind a public address — only the address is stored, never keys
curl -X POST -H "Authorization: Bearer art_live_<your-key>" \
-H "Content-Type: application/json" \
-d '{"provider":"polymarket","address":"0xabc123...","label":"prod-trader-1"}' \
"https://api.artery.questflow.ai/v1/me/wallets"
# List bindings
curl -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/me/wallets"
# Remove a binding
curl -X DELETE -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/me/wallets/wal_01HMQ1XYZ..."jsonc// POST response
{
"id": "wal_01HMQ1XYZ8K5C0F8H4Q4ZE7VX9D",
"provider": "polymarket",
"address": "0xabc123...",
"label": "prod-trader-1",
"createdAt": "2026-05-13T08:43:01.140Z"
}Wallet history backfill (Polymarket)
bash# On-demand backfill of historical Polymarket fills for one bound wallet
curl -X POST -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/me/wallets/wal_01HMQ1XYZ.../history-backfill?lookback_days=7"jsonc// 200 response
{
"requestId": "req_01HMQ7K...",
"fills": 12,
"scanned": { "fromBlock": "63250000", "toBlock": "63552500" }
}Use this after binding a new wallet or after a long WebSocket disconnect
to gap-fill on-chain fills the live listener missed. The endpoint runs a
maker-OR-taker filtered eth_getLogs against both Polymarket V2 CTF Exchange
contracts and decodes each OrderFilled event into event_trades.
- Polymarket only. Bindings under any other provider return
400. The HL wallet families have their own listener path and don't need this surface. - No body. Caller is identified by the path
:id; the wallet must belong to the caller org or you get404. lookback_daysis clamped to[1, 30], default7. Older windows are capped by an internal RPC-budget guard (default1_000chunks ×2_000blocks per contract) — when the request would exceed it, the actualfromBlockis clamped forward and the server logs aclampedwarning.- Idempotent. Persistence is keyed on
(provider, tx_hash, log_index), so repeated calls within the same window safely no-op without double-counting. - Yield. Expect 5-50 rows per N-day window for a typical wallet. The
endpoint is metered under the standard
api.requestquota.
Platform attributions (builder codes)
Register the on-chain builder identifiers your platform's order flow uses, then ask PnL queries to scope to "trades that came through my UI" instead of "anything my bound wallets ever did anywhere."
bash# Register an HL builder (40-char EVM address — the wallet that signs through
# the HL builder API). provider is just `hyperliquid` — you don't pick
# perp/hip4/dex; one HL builder identity covers them all.
curl -X POST -H "Authorization: Bearer art_live_<your-key>" \
-H "Content-Type: application/json" \
-d '{"provider":"hyperliquid","builder":"0x5a3bc60b0a99a7f4fbf0d15554fa5fe88e7628c2","label":"Main app"}' \
"https://api.artery.questflow.ai/v1/me/platform-attributions"
# Register a Polymarket builder (V2 OrderFilled.builder, 64-char bytes32)
curl -X POST -H "Authorization: Bearer art_live_<your-key>" \
-H "Content-Type: application/json" \
-d '{"provider":"polymarket","builder":"0x0000000000000000000000000000000000000000000000000000000000abc123","label":"prod-relayer"}' \
"https://api.artery.questflow.ai/v1/me/platform-attributions"
# List
curl -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/me/platform-attributions"
# Remove
curl -X DELETE -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/me/platform-attributions/<id>"jsonc// POST response — `hyperliquid` is canonicalized to `hyperliquid_perp` on store
{
"id": "9f3c1d6e-...",
"orgId": "<your-org-id>",
"provider": "hyperliquid_perp",
"builder": "0x5a3bc60b0a99a7f4fbf0d15554fa5fe88e7628c2",
"label": "Main app",
"createdByUserId": null,
"createdAt": "2026-05-27T08:43:01.140Z"
}- Provider is a family value. Pass
hyperliquidorpolymarket— you do NOT pickhyperliquid_perp/_hip4/_dex(one HL builder identity covers them;hyperliquidis canonicalized tohyperliquid_perpon store, mirroring wallet binding). The fine-grained sub-providers are still accepted for back-compat. - Format. Hyperliquid builders are a 40-char EVM address; Polymarket is a
64-char
bytes32(taken verbatim from the V2OrderFilled.builderfield, never translated). The endpoint validates length +0xprefix. - Scopes.
GETrequiresread;POST/DELETErequireadmin. - Idempotent. Unique on
(org_id, provider, builder)— re-posting the same triple returns the existing row. - Audit-logged as
me.platform_attribution.{create,delete}against the caller's API key. - Sentinel builder
0x0…0is always excluded fromplatform=minematching and fromgroupBy=builderbucketing — it represents "no attribution" on chain. - You never pass a builder in a PnL query. Register it once here; then
query
/v1/me/pnl?platform=mineand the server resolves your registered builder(s) for the queried provider(s). platform=minewith nothing registered →400 no_platform_attribution. Requestingplatform=minefor a provider you have no builder registered for is rejected (not silently0), so you can tell "no builder configured" apart from "wallet genuinely has no platform fills". Register a builder, or useplatform=all(the default) to query every fill.
The portal exposes the same surface as a form on /wallets. The internal
admin tool also has a per-org section under
/customers/{orgId} — useful for ops to register a builder on the
customer's behalf during onboarding.
Fills, PnL, anomaly feed
bash# Every observed trade where one of your bound wallets is maker or taker
curl -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/me/fills?limit=200"
# Realized + unrealized PnL across all your wallets, grouped by event
curl -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/me/pnl"
# Same data, grouped by wallet (top wallets by realized first)
curl -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/me/pnl?groupBy=wallet"
# Rolled up by provider — Hyperliquid family rolls perp + hip4 + dex
curl -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/me/pnl?groupBy=provider"
# Filter to a single provider family or sub-provider
curl -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/me/pnl?provider=hyperliquid"
# ---- Platform-scoped PnL (Phase A) ----
# Narrow to fills tagged with one of YOUR registered builders.
# Requires at least one row in /v1/me/platform-attributions.
curl -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/me/pnl?platform=mine"
# Group by builder — one row per (provider, builder) bucket, with label.
# Unattributed fills (no builder) and the 0x0…0 sentinel are excluded.
curl -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/me/pnl?groupBy=builder"
# Window by ingest timestamp
curl -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/me/pnl?groupBy=builder&since=2026-05-01T00:00:00Z&until=2026-05-20T00:00:00Z"
# Anomalies on events you care about (default trailing 24h, max 30d)
curl -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/me/anomaly-feed?since=2026-05-12T00:00:00Z&limit=100"jsonc// /me/pnl response — note: `byEvent` / `byWallet` / `byProvider` are
// phase-1 legacy aliases for `data`, removed in phase 3.
{
"total": { "realized": 1843.22, "unrealized": -120.55, "totalNotional": 42_188.0 },
"data": [
{
"universalId": "evt_01HMPKW8YJ5C0F8H4Q4ZE7VX9D",
"canonicalName": "BTC ≥ $120,000 by 2026-12-31",
"realized": 510.0,
"unrealized": -22.5,
"openPosition": 250,
"avgCost": 0.421,
"currentPrice": 0.412,
"tradeCount": 18,
"legs": [
{ "provider": "polymarket", "marketId": "0xCONDITION_ID", "realized": 510.0, "unrealized": -22.5, "openPosition": 250, "avgCost": 0.421, "currentPrice": 0.412, "tradeCount": 18 }
]
}
],
"pagination": { "nextCursor": null, "hasMore": false },
"applied": { "walletIds": [], "provider": null, "groupBy": "event", "sort": "-realized" },
"byEvent": [ /* same rows as `data`, deprecated */ ]
}groupBy=builder response
jsonc// /me/pnl?groupBy=builder
{
"total": { "realized": 700.0, "unrealized": 18.5, "totalNotional": 33953.5 },
"data": [
{
"provider": "hyperliquid_perp",
"builder": "0x5a3bc60b0a99a7f4fbf0d15554fa5fe88e7628c2",
"label": "Main app", // null if no registered label
"walletCount": 1,
"realized": 412.0,
"unrealized": 18.5,
"openPosition": 0.5,
"totalNotional": 33850.5,
"tradeCount": 22
},
{
"provider": "polymarket",
"builder": "0x0000000000000000000000000000000000000000000000000000000000abc123",
"label": null,
"walletCount": 2,
"realized": 288.0,
"unrealized": 0.0,
"openPosition": 0,
"totalNotional": 103.0,
"tradeCount": 6
}
],
"applied": {
"walletIds": [],
"provider": null,
"groupBy": "builder",
"sort": "-realized"
}
}- Bucketing. PnL is bucketed on
(wallet × market × builder)so that the same wallet trading the same market across two builders gets two independent FIFO books — no cost-basis bleed between platforms. walletCountis the number of distinct bound wallets that have fills attributed to this builder in the result window.labelis whatever the org registered via/v1/me/platform-attributions(ornullif the trade matches a builder that's never been registered — happens when scanning historical data before the binding was added).
platform=mine filter
Layered on top of any other groupBy. Drops every fill whose
raw.builder is not in the caller org's
/v1/me/platform-attributions list:
bash# Per-event PnL, only counting fills routed through your platform
curl -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/me/pnl?platform=mine&groupBy=event"
# Per-wallet ranking, only counting your-platform fills
curl -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/me/pnl?platform=mine&groupBy=wallet"If no platform attributions are registered, platform=mine returns
an empty data array with total zeroed — not an error.
Hyperliquid family rollup
When a caller passes provider=hyperliquid, the server expands the filter to
all three HL sub-providers (hyperliquid_perp, hyperliquid_hip4,
hyperliquid_dex) and echoes the family label back in applied.provider:
jsonc// /me/pnl?groupBy=provider&provider=hyperliquid won't return — combining
// groupBy=provider with a single provider filter is rejected as a
// degenerate 1-row view. Drop the provider filter to see the rollup.
//
// /me/pnl?groupBy=provider — applied.provider is null, data rolls all HL
// sub-providers up by their stored enum value (one row per fine-grained
// provider in the response; the portal collapses the visible rows into a
// single "Hyperliquid" entry).
{
"data": [
{ "provider": "hyperliquid_perp", "walletCount": 1, "realized": 412.0, "unrealized": 18.5, "openPosition": 0.5, "totalNotional": 33850.5, "tradeCount": 22 },
{ "provider": "hyperliquid_hip4", "walletCount": 1, "realized": 188.0, "unrealized": 0.0, "openPosition": 0, "totalNotional": 0.0, "tradeCount": 6 },
{ "provider": "polymarket", "walletCount": 2, "realized": 510.0, "unrealized": -22.5, "openPosition": 250, "totalNotional": 103.0, "tradeCount": 18 }
],
"applied": { "walletIds": [], "provider": null, "groupBy": "provider", "sort": "-realized" }
}PnL curve
bash# Cumulative realized PnL curve, last 7d (default), 1 point per sell event
curl -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/me/pnl/curve?days=7"
# Same chart, filtered to Hyperliquid wallets only
curl -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/me/pnl/curve?days=14&provider=hyperliquid"jsonc// /me/pnl/curve response
{
"total": { "realized": 1843.22, "unrealized": -120.55, "totalNotional": 42_188.0 },
"curve": [
{ "ts": "2026-05-12T00:00:00.000Z", "cumulativeRealized": 0 },
{ "ts": "2026-05-12T03:14:22.000Z", "cumulativeRealized": 42.10 },
{ "ts": "2026-05-12T11:08:45.000Z", "cumulativeRealized": 188.30 },
{ "ts": "2026-05-19T00:00:00.000Z", "cumulativeRealized": 1843.22 }
],
"window": { "from": "2026-05-12T00:00:00.000Z", "to": "2026-05-19T00:00:00.000Z", "days": 7 }
}days is clamped to [1, 30] (default 7). One point per realized sell
event, plus the window's first + last timestamps so the chart spans the full
requested range. walletId / walletAddress (repeat) and provider
filters are honored; sort / groupBy / cursor are NOT accepted — the
curve is implicitly time-ordered.
Per-wallet NAV + PnL snapshot
Two per-wallet reads under your own /v1/me/wallets/{id} namespace. Unlike
/me/pnl (which aggregates fills across every wallet from event_trades),
these proxy each provider's authoritative live source and cache the result.
bash# Current NAV (net asset value) of one bound wallet — real-time liquidation equity
curl -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/me/wallets/wal_01HMQ1XYZ.../nav"
# Cumulative PnL curve for one wallet (window = 1D | 1W | 1M | ALL, default ALL)
curl -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/me/wallets/wal_01HMQ1XYZ.../pnl-curve?window=1M"jsonc// /nav response — Polymarket wallet (cash + positions split populated)
{
"walletId": "wal_01HMQ1XYZ...",
"provider": "polymarket",
"address": "0x3e08…ff1c",
"navUsdc": 1284.55, // unified cross-provider number = cash + positions
"cashUsdc": 312.40, // on-chain USDC balance of the proxy Safe
"positionsValueUsdc": 972.15, // data-api /value (marks every holding)
"asOf": "2026-06-04T09:31:00.000Z"
}
// /nav response — Hyperliquid wallet (margin account: split is null, only navUsdc)
{
"walletId": "wal_01HMQ2ABC...",
"provider": "hyperliquid_perp",
"address": "0xa154…5b9f",
"navUsdc": 5012.88, // clearinghouseState.marginSummary.accountValue
"cashUsdc": null,
"positionsValueUsdc": null,
"asOf": "2026-06-04T09:31:00.000Z"
}
// /pnl-curve response — { t (unix seconds), pnl (cumulative USD) }, ascending
{
"walletId": "wal_01HMQ1XYZ...",
"provider": "polymarket",
"address": "0x3e08…ff1c",
"window": "1M",
"source": "pm_user_pnl", // hl_portfolio for Hyperliquid wallets
"points": [
{ "t": 1748000000, "pnl": 0 },
{ "t": 1748086400, "pnl": 42.1 },
{ "t": 1750560000, "pnl": 188.3 }
],
"asOf": "2026-06-04T09:31:00.000Z"
}navUsdcis the unified, cross-provider quantity — "close everything and withdraw → this much USD". ThecashUsdc/positionsValueUsdcsplit is provider-specific and populated for Polymarket only (a Hyperliquid margin account has no clean spot split).sourceof the curve: Hyperliquid viainfo portfolio.pnlHistory, Polymarket via the officialuser-pnlseries (the same Profit/Loss curve Polymarket's own profile UI draws — registry-independent, so it marks even unregistered short-lived markets).- This is PnL, not NAV. The curve starts at
0and is unaffected by deposits/withdrawals. To reconstruct a NAV-over-time curve, combine with cumulative net deposits:NAV(t) = cumulativeNetDeposits(t) + pnl(t). - Windows:
1D/1W/1M/ALL(the set both providers serve natively).1H/1Yare not offered in this phase. - Cached ~20s (NAV) / ~60s (curve) per wallet with request coalescing, so a burst of reads for the same wallet collapses to one upstream call.
NAV curve (net-asset-value time-series)
Unlike /pnl-curve (a live proxy), /nav-curve reads persisted hourly
snapshots — the wallet's NAV plus cumulative net external deposits over time.
bash# NAV time-series for one wallet (window = 1D | 1W | 1M | ALL, default ALL)
curl -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/me/wallets/wal_01HMQ1XYZ.../nav-curve?window=1M"jsonc// /nav-curve response — { t (unix seconds), navUsdc, netDepositsUsdc }, ascending
{
"walletId": "wal_01HMQ1XYZ...",
"provider": "hyperliquid_perp",
"address": "0xa154…5b9f",
"window": "1M",
"source": "snapshot",
"points": [
{ "t": 1748000000, "navUsdc": 1000.0, "netDepositsUsdc": 1000.0 },
{ "t": 1748003600, "navUsdc": 1042.1, "netDepositsUsdc": 1000.0 },
{ "t": 1750560000, "navUsdc": 1188.3, "netDepositsUsdc": 1100.0 }
],
"asOf": "2026-06-04T10:00:00.000Z"
}navUsdcis the wallet's NAV (liquidation equity);netDepositsUsdcis cumulative external deposits − withdrawals at that instant.- Flow-adjusted performance: trading PnL =
navUsdc − netDepositsUsdc, so a subscriber deposit (which bumps both) doesn't read as a gain. Compute time-weighted return / drawdown on the consumer from these two series. source=snapshot(our persisted hourly NAV — authoritative) or, for Polymarket until its snapshot pipeline lands,pm_user_pnl(the curve is reconstructed from the official PM PnL series + our ledger flows, anchored to the current NAV — accurate within recent windows; theALLwindow degrades before ledger coverage). Snapshot wins once ≥2 points exist, so the fallback auto-retires.- Hyperliquid NAV comes from
clearinghouseStateand deposits fromuserNonFundingLedgerUpdates— both served by our self-hosted node (no rate cost, exact per-flow timestamps), so HL issnapshot. Polymarket uses thepm_user_pnlreconstruction today and switches tosnapshotonce the dedicated Polygon node lands. A wallet covered by neither returns emptypoints. - Hourly snapshots, aligned to the top of the hour. On a wallet's first
appearance the series is seeded from
portfolio.accountValueHistoryso it isn't empty until the next tick.
By-address fan-out — one call per user (HL + PM)
A user usually has two addresses — a Hyperliquid EOA and a Polymarket
deposit-wallet. Instead of storing each wallet's opaque id and calling the
{id} endpoints once per provider, query the same three reads by address:
pass walletAddress (repeatable) and get both providers in one response.
No wallet id to store — the address is the handle. Each address resolves all
of its bindings, so pass a user's HL + PM addresses to get both — and if the
same address is bound under both providers, one walletAddress value returns
both. The addresses must already be bound to your org
(POST /v1/me/wallets); an unbound one comes back in warnings, not as an
error. (Rule of thumb: pass one walletAddress per distinct address — one if
HL and PM share an address, two if they differ.)
bash# Current NAV for a user's HL + PM addresses in one call
curl -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/me/wallets/nav?walletAddress=0xa154…5b9f&walletAddress=0x3e08…ff1c"
# NAV curve / PnL curve fan-out (window = 1D | 1W | 1M | ALL, default ALL)
curl -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/me/wallets/nav-curve?walletAddress=0xa154…5b9f&walletAddress=0x3e08…ff1c&window=1M"
curl -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/me/wallets/pnl-curve?walletAddress=0xa154…5b9f&window=ALL"jsonc// /nav response (fan-out) — one slice per resolved provider, SAME shape as the
// {id} endpoint; unbound / failed addresses land in `warnings`.
{
"slices": [
{ "walletId": "wal_…", "provider": "hyperliquid_perp", "address": "0xa154…5b9f",
"navUsdc": 5012.88, "cashUsdc": null, "positionsValueUsdc": null,
"asOf": "2026-06-04T09:31:00.000Z" },
{ "walletId": "wal_…", "provider": "polymarket", "address": "0x3e08…ff1c",
"navUsdc": 1284.55, "cashUsdc": 312.40, "positionsValueUsdc": 972.15,
"asOf": "2026-06-04T09:31:00.000Z" }
],
"warnings": [],
"asOf": "2026-06-04T09:31:00.000Z"
}- Same numbers as the
{id}endpoints — the fan-out reuses the exact per-wallet compute (persisted snapshots + ledger flow-adjustment); it only changes the addressing. Sumslices[i].navUsdcfor the user's Total Assets, or add the curves point-wise for a combined curve. walletAddressis provider-agnostic — the binding already records the provider, so you never name it. Omit nothing / add more addresses to widen; at least one is required. (user/hlUser/pmUserare accepted as deprecated aliases — preferwalletAddress.) This is the same selector as/v1/me/pnland/v1/me/activity.- Failure ≠ empty — if a provider's upstream is briefly unavailable (e.g. an
HL rate-limit), that slice comes back with
navUsdc: null(and awarningsentry), NOT0. Treatnullas "unknown, retry / keep last known"; only a real0means an empty account. Likewise the curves refuse to serve a$0series they can't confirm — a failed provider surfaces inwarningsrather than as a misleading flat-zero curve. Don't coerce a missing/nullprovider to0when summing Total Assets. - Stateful path — these read your bound wallets' history. For an
arbitrary / unbound address (a visitor or leader profile) use the
stateless
GET /v1/portfolio(current value, withnavUsd/totalNavUsd) andGET /v1/portfolio/nav(curve) instead — no binding, but no ledger flow-adjustment.
Trading ROI — MWR + TWR (/roi)
Return on a bound wallet's whole-account NAV (perp + spot + vault + staking)
between from and to. Both measures are returned because they answer
different questions:
mwrPct— money-weighted (modified Dietz). The actual return on the user's capital; affected by deposit/withdraw timing.twrPct— time-weighted (chain-linked at each cash flow). The strategy/trading return, comparable across users regardless of their flows.simpleRoiPct— simple (un-weighted) = PnL ÷ capital deployed (startNav + netDeposits). Ignores flow timing, so it's the most robust + intuitive "ROI on deposited capital" and the recommended default to display — it stays stable on freshly-funded wallets (capital that arrived near the window edge) wheremwrPct/twrPctcan destabilise.
All are fractions (0.1 = +10%) and are equal when there were no cash flows.
netPnlUsd— net PnL in USD =endNavUsd − startNavUsd − netDepositsUsd= account value change minus net deposits. This is Hyperliquid’s official portfolio-PnL (net of all fees + funding, spot included) — the canonical true PnL for leaderboards. Use it instead ofrealizedPnlUsd + unrealizedPnlUsd.
bash# from / to accept ISO-8601 or epoch milliseconds; to defaults to now
curl -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/me/wallets/wal_01HMQ1XYZ.../roi?from=2026-05-01T00:00:00Z&to=2026-06-01T00:00:00Z"jsonc// /roi response (fractions; null when not computable)
{
"address": "0xa154…5b9f",
"fromMs": 1746057600000,
"toMs": 1748736000000,
"startNavUsd": 1000.0,
"endNavUsd": 1188.3,
"netDepositsUsd": 100.0, // Σ deposits − withdrawals in the window
"grossRealizedPnlUsd": 96.4, // ── reconciliation breakdown (already in NAV) ──
"tradingFeeUsd": 3.1,
"builderFeeUsd": 1.2,
"fundingUsd": -0.8,
"mwrPct": 0.0852,
"twrPct": 0.0911,
"simpleRoiPct": 0.0863,
"netPnlUsd": 152.40,
"accuracy": "exact-end", // exact-end (live equity) | interpolated
"navSource": "snapshot", // snapshot (our hourly series) | hl_portfolio (fallback) | none (no anchor → null metrics)
"provider": "hyperliquid",
"note": null
}- NAV-based, so fees & funding are already included in the returns. The
grossRealizedPnlUsd/tradingFeeUsd/builderFeeUsd/fundingUsdbreakdown is for reconciliation only — do not add it on top of the returns. - NAV anchors use our own persisted hourly NAV series whenever the wallet has
one (
navSource: "snapshot"— whole-account, basis-consistent with the live end anchor, finer than HL's, and it already includes backfilled history). For a window that predates the series the start is clamped to inception (since-inception — see below), still on the snapshot. Only a wallet with no usable snapshot at all falls back to Hyperliquid'sportfoliohistory (navSource: "hl_portfolio"; coarser →accuracy: "interpolated")./me/stats?roi=trueuses the same snapshot basis, so the two surfaces agree. When the window ends ~now the live account value is the exact end anchor (accuracy: "exact-end"). - Provider coverage. Hyperliquid supports retrospective history
(reconstructable from HL's portfolio curve + event-level fills/funding/ledger).
Polymarket is going-forward only: its MWR/TWR come from our own NAV
snapshots (
navSource: "snapshot") + persisted deposit/withdraw flows that accrue from when the wallet is first observed — windows predating that returnnullmetrics with a going-forwardnote, and PM carries no realized/fee breakdown (it has no per-fill closed PnL).mwrPct/twrPctare alsonullwhen there is no NAV anchor for the window start or the capital base is non-positive. - New wallets / since-inception: if
frompredates the wallet's first funding, the start is clamped to inception (anotesays so) and returns are measured since then — so a freshly-funded wallet still gets a realtwrPctinstead ofnull. NotemwrPct(money-weighted) can be below −100% when capital was added to a losing position mid-window; that is correct, not a bug. - Cached ~60s with request coalescing; metered under the
portfolio.readSKU.
Trading-competition leaderboard (/me/stats)
Per the caller org's wallets: realized PnL + volume + account value + unrealized PnL + trade count over a custom window, sortable for a ranked leaderboard. Full field reference, sorting, and per-venue semantics live on the dedicated Competition stats page.
bash# Per-wallet leaderboard, ranked by realized PnL (default sort)
curl -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/me/stats?from=2026-06-01T00:00:00Z&to=2026-06-16T00:00:00Z&groupBy=wallet&sort=-realizedPnlUsd"jsonc// /me/stats response (groupBy=wallet, sort=-realizedPnlUsd)
{
"total": {
"accountValueUsd": 6012.88, "volumeUsd": 21841.5,
"realizedPnlUsd": 412.07, "unrealizedPnlUsd": 88.4,
"tradingFeeUsd": 9.12, "builderFeeUsd": 1.83
},
"data": [
{ "address": "0xab…01", "accountValueUsd": 5012.88, "volumeUsd": 18402.1,
"realizedPnlUsd": 380.55, "unrealizedPnlUsd": 64.2,
"tradingFeeUsd": 7.71, "builderFeeUsd": 1.54, "tradeCount": 142 },
{ "address": "0xcd…02", "accountValueUsd": 1000.0, "volumeUsd": 3439.4,
"realizedPnlUsd": 31.52, "unrealizedPnlUsd": 24.2,
"tradingFeeUsd": 1.41, "builderFeeUsd": 0.29, "tradeCount": 17 }
],
"window": { "from": "2026-06-01T00:00:00.000Z", "to": "2026-06-16T00:00:00.000Z" },
"applied": { "groupBy": "wallet", "sort": { "field": "realizedPnlUsd", "dir": "desc" } }
}realizedPnlUsd/volumeUsd/tradeCount/tradingFeeUsd/builderFeeUsd— for Hyperliquid all come from the wallet's own exchange fills (Σ closedPnl+Σ px·sz+Σ fee+Σ builderFee, one pull, shared with/roi), so they agree with HL's UI and cover perp + HIP-3 + spot.realizedPnlUsdis gross of fees — net = realized − tradingFee − builderFee.nullfor non-HL rows,platform=mine, or on upstream error.accountValueUsd= current NAV (perp + spot + vault + staking), point-in-time.unrealizedPnlUsd= open-position mark PnL, point-in-time.mode=snapshot(default) vsmode=live(nav=is a deprecated alias) — by defaultaccountValueUsdandunrealizedPnlUsdare read from the latest hourly NAV snapshot (fast over many wallets, ≤1h stale). Addmode=livefor real-time account value + unrealized PnL (onegetStateper wallet — prefer for small sets).sort= a field name with optional-prefix for descending; nulls always sort last. Default-realizedPnlUsd.groupBy=walletfor the per-wallet leaderboard,groupBy=providerto roll up by venue.- For PnL drill-down by market, use
/v1/me/pnl.
PnL CSV export
bash# Streamed CSV of every fill on the caller org's wallets
curl -OJ -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/me/pnl/export?format=csv&since=2026-01-01T00:00:00Z&until=2026-05-15T00:00:00Z"
# Same filters as /me/pnl: walletId / walletAddress / provider
curl -OJ -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/me/pnl/export?format=csv&provider=polymarket"format=csvis required. JSON / JSONL are not supported today.- Streamed row-by-row via
Transfer-Encoding: chunked; pipe the response body straight to disk. The browser is nudged withContent-Disposition: attachment; filename="artery-pnl-YYYY-MM-DD.csv". - Cap at 50,000 rows post-filter. Exceeding it returns
400 row_cap_exceededBEFORE any row is written — narrow[since, until]and retry. The body includesrowCount+capso a client can do that adaptively. - Sort is ignored — exports are always chronological. The file ends with
a
# summaryblock (total_realized,total_unrealized,total_notional,trade_count) for quick spot-checks against/v1/me/pnl.
Backtest
Backtest endpoints are expensive — each call walks every historic signal in the requested
window against real trade flow. They consume the backtest.compute_min quota in addition to
api.request. Stick to ≤ 7-day windows for interactive use; for 30-day backtests, expect
multi-second response times.
bash# Replay every cross-provider arbitrage signal in [from, to] vs real trades
curl -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/me/backtest/replay-arbitrage?from=2026-05-01T00:00:00Z&to=2026-05-08T00:00:00Z&min_net_edge=0.005&fill_window_ms=3600000¬ional=1000"
# Same idea, but for negation-pair (YES on A + NO on B, sum <$1) signals
curl -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/me/backtest/replay-negation?from=2026-05-01T00:00:00Z&to=2026-05-08T00:00:00Z&min_net_edge=0.005¬ional=1000"jsonc// /backtest/replay-arbitrage response shape
{
"range": { "from": "2026-05-01T00:00:00Z", "to": "2026-05-08T00:00:00Z" },
"parameters": { "minNetEdge": 0.005, "fillWindowMs": 3_600_000, "notionalPerSignal": 1000 },
"totals": {
"signalsConsidered": 412,
"filledSignals": 287,
"cumulativePnl": 1842.7,
"maxDrawdown": -212.4,
"sharpe": 1.83
},
"curve": [
{ "ts": "2026-05-01T01:14:22Z", "pnl": 0, "cumulative": 0 },
{ "ts": "2026-05-01T01:14:48Z", "pnl": 5.2, "cumulative": 5.2 }
]
}Window constraints: to - from ≤ 30d, fill_window_ms ≤ 24h,
notional ∈ [0.01, 1_000_000].
Webhooks
bash# Create a webhook — the signing secret is returned exactly once
curl -X POST -H "Authorization: Bearer art_live_<your-admin-key>" \
-H "Content-Type: application/json" \
-d '{
"url": "https://app.example.com/artery-hooks",
"channels": ["artery:stream:anomaly:*"],
"universalIds": ["evt_01HMPKW8YJ5C0F8H4Q4ZE7VX9D"],
"description": "Anomaly alerts for our BTC binary"
}' \
"https://api.artery.questflow.ai/v1/webhooks"
# List webhooks (secret is omitted from list output)
curl -H "Authorization: Bearer art_live_<your-admin-key>" \
"https://api.artery.questflow.ai/v1/webhooks"
# Update channels / status / universalIds
curl -X PATCH -H "Authorization: Bearer art_live_<your-admin-key>" \
-H "Content-Type: application/json" \
-d '{"status":"paused"}' \
"https://api.artery.questflow.ai/v1/webhooks/wh_01HMQ2..."
# Delete
curl -X DELETE -H "Authorization: Bearer art_live_<your-admin-key>" \
"https://api.artery.questflow.ai/v1/webhooks/wh_01HMQ2..."jsonc// POST response — store the secret IMMEDIATELY, it cannot be re-derived
{
"id": "wh_01HMQ2ABCDEFGHIJKLMNOPQRST",
"url": "https://app.example.com/artery-hooks",
"channels": ["artery:stream:anomaly:*"],
"secret": "whsec_<base64url-32-byte-secret>",
"createdAt": "2026-05-13T08:50:21.001Z"
}Account activity — trades + deposits/withdrawals (unified feed)
GET /v1/me/activity is one merged, newest-first timeline of everything that
happened on your bound wallets: fills (from event_trades, side in your
wallet's perspective) and external deposits / withdrawals (the same
flows the wallet.deposit / wallet.withdraw webhook pushes — this is the
pull/query counterpart). It's the superset of /v1/me/fills; use that when you
only want trades.
bash# Everything, newest first (default: all types, all providers, ALL window)
curl -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/me/activity?limit=100"
# One user's activity (their HL + PM addresses), deposits/withdrawals only, last 30d
curl -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/me/activity?types=deposit,withdraw&walletAddress=0xHL&walletAddress=0xPM&window=1M"
# Hyperliquid trades in an explicit unix-seconds window, page 2 via cursor
curl -H "Authorization: Bearer art_live_<your-key>" \
"https://api.artery.questflow.ai/v1/me/activity?types=trade&provider=hyperliquid&from=1749000000&to=1751600000&cursor=<nextCursor>"jsonc// /me/activity response — discriminated on `type`
{
"data": [
{
"type": "withdraw", // ← ledger flow
"ts": 1751883000, // unix seconds
"provider": "polymarket",
"address": "0x3e08…ff1c",
"amountUsd": 250.0, // always ≥ 0; direction is on `direction`/`type`
"txHash": "0xabc…",
"direction": "withdraw"
},
{
"type": "trade", // ← fill (side in YOUR wallet's perspective)
"ts": 1751880000,
"provider": "hyperliquid_perp",
"address": "0xa154…5b9f",
"amountUsd": 302.5, // trade notional = price × size
"txHash": null,
"side": "buy",
"role": "taker",
"price": 60.5,
"size": 5,
"marketId": "BTC"
},
{
"type": "trade", // ← a FORCED close (liquidation)
"ts": 1751879000,
"provider": "hyperliquid_perp",
"address": "0xa154…5b9f",
"amountUsd": 119.5,
"txHash": "0xdead…",
"side": "sell",
"role": "taker",
"price": 14.059,
"size": 8.5,
"marketId": "NOK",
"closeReason": "liquidation", // ← liquidation | adl (absent on normal fills)
"liquidationMethod": "market", // ← HL method (liquidation only)
"dir": "Close Long" // ← HL raw label (e.g. "Liquidated Isolated Short")
}
],
"pagination": { "nextCursor": "eyJrI…", "hasMore": true },
"applied": { "sort": "-ts", "types": ["trade", "deposit", "withdraw"] }
}- Filters:
types(trade/deposit/withdraw, comma-separated),provider(hyperliquid/polymarketfamilies),walletAddress(repeatable — omit for all your wallets), and a window (window=1D|1W|1M|ALL, or explicitfrom/toin unix seconds;from/tooverridewindow). - Sort is fixed to
-ts(newest first); page withlimit(default 100, max 500) + the opaquecursorfrompagination.nextCursor. - Forced closes are marked — a HL fill closed by the system carries
closeReason:liquidation(强平) oradl(auto-deleverage — a distinct mechanism, not conflated).liquidationfills also carryliquidationMethod(market/backstop) and HL's rawdir(e.g.Liquidated Isolated Short). Absent on normal manual fills — so you can show aLiquidated/Auto-Deleveragingbadge instead of a plain Sell. HL only (Polymarket has no such mechanism). Same fields appear on/v1/me/fills. - Binding required — only wallets bound via
POST /v1/me/walletsappear (this reads your persisted trade + ledger history). Trades-only? Use/v1/me/fills. Cumulative PnL? Use/v1/me/pnl.
Wallet deposit / withdraw events
Subscribe to wallet.deposit / wallet.withdraw to be notified within ~minutes
when one of your bound wallets has an external USDC deposit or
withdrawal. Covers Hyperliquid and Polymarket (provider field tells
them apart). Only true deposits/withdrawals fire — internal / trade-related
movement is excluded (HL: vault / sub-account / spot transfers, sends;
Polymarket: USDC moving between your Safe and Polymarket's own exchange
contracts when you buy/sell shares).
bashcurl -X POST -H "Authorization: Bearer art_live_<your-admin-key>" \
-H "Content-Type: application/json" \
-d '{
"url": "https://app.example.com/artery-hooks",
"channels": ["wallet.deposit", "wallet.withdraw"],
"description": "Fund movement alerts"
}' \
"https://api.artery.questflow.ai/v1/webhooks"jsonc// Delivered payload (signed via X-Artery-Signature, same as every webhook)
{
"event": "wallet.deposit", // or "wallet.withdraw"
"provider": "hyperliquid", // or "polymarket"
"wallet": "0x1234…", // bound wallet (lowercased; Polymarket = the proxy Safe)
"direction": "deposit", // "deposit" | "withdraw"
"amountUsd": 2500.0, // USDC amount; null if unparseable
"txHash": "0xabc…", // tx / event hash
"logIndex": 17, // Polymarket only (a tx can hold several transfers); null for HL
"occurredAt": "2026-06-16T07:00:00.000Z",
"deliveredAt": "2026-06-16T07:00:42.000Z",
"native": { "type": "deposit", "usdc": "2500.0" } // raw upstream blob
}Delivery is at-least-once — dedupe on txHash (Hyperliquid) or
txHash + logIndex (Polymarket, where one transaction can carry several
transfers). Hyperliquid events are checkpointed node-side; Polymarket events
come from an on-chain Moralis stream — both resume without dropping events
across a transient outage.
Wallet liquidation events
Subscribe to wallet.liquidation to be notified in real time (typically a few seconds) when one
of your bound Hyperliquid wallets is force-closed — either a margin
liquidation or an auto-deleverage (ADL). One channel covers both;
closeReason (liquidation | adl) tells them apart, and the fields match the
markers on /v1/me/fills + /v1/me/activity so you map one field set
everywhere. HL only (Polymarket has no such mechanism).
bashcurl -X POST -H "Authorization: Bearer art_live_<your-admin-key>" \
-H "Content-Type: application/json" \
-d '{
"url": "https://app.example.com/artery-hooks",
"channels": ["wallet.liquidation"],
"description": "Liquidation alerts"
}' \
"https://api.artery.questflow.ai/v1/webhooks"jsonc// Delivered payload (signed via X-Artery-Signature, same as every webhook)
{
"event": "wallet.liquidation",
"provider": "hyperliquid",
"wallet": "0x1234…", // bound wallet that was force-closed (lowercased)
"closeReason": "liquidation", // "liquidation" (margin call) | "adl" (auto-deleveraged)
"liquidationMethod": "market", // "market" | "backstop" — present for a liquidation, absent for ADL
"dir": "Close Long", // HL raw mechanism label (e.g. "Liquidated Isolated Short", "Auto-Deleveraging")
"coin": "BTC",
"size": 0.5, // fill size (base units)
"price": "59000",
"markPx": "59097", // HL mark price at liquidation; absent for ADL
"closedPnl": "-1234.5", // realized PnL on this fill
"txHash": "0xabc…",
"tid": 1040287331733280, // HL per-fill trade id
"occurredAt": "2026-06-25T07:00:00.000Z",
"deliveredAt": "2026-06-25T07:00:42.000Z",
"native": { /* raw fill blob */ }
}Delivery is at-least-once — dedupe on txHash + tid (a single order
matching several counterparties yields multiple fills that share txHash but
differ by tid). Delivery is real-time (a live node userFills stream), with a
60-second reconciliation sweep as a backstop, so a forced close is never missed
even across a transient stream drop. Events are checkpointed node-side, so a transient node or
worker restart resumes without dropping liquidations.
API keys
bash# Mint a key for the caller's organization
curl -X POST -H "Authorization: Bearer art_live_<your-admin-key>" \
-H "Content-Type: application/json" \
-d '{
"name": "trader-bot",
"userId": "u-001",
"scopes": ["read","stream"]
}' \
"https://api.artery.questflow.ai/keys"
# Edit scopes on an existing key (caller may only grant scopes they hold)
curl -X PATCH -H "Authorization: Bearer art_live_<your-admin-key>" \
-H "Content-Type: application/json" \
-d '{ "scopes": ["read","stream","trade"] }' \
"https://api.artery.questflow.ai/keys/<id>"
# Revoke
curl -X DELETE -H "Authorization: Bearer art_live_<your-admin-key>" \
"https://api.artery.questflow.ai/keys/<id>"jsonc// POST /keys response — plaintext shown ONCE
{
"id": "key_01HMQ3...",
"plaintext": "art_live_01HMQ3....s_kJ8a4Kv2qBpH...",
"name": "trader-bot",
"orgId": "org_01HMP0...",
"scopes": ["read", "stream"],
"environment": "live",
"createdAt": "2026-05-13T08:51:08.700Z"
}Quota comes from the key's organization subscription plan. The legacy
rateLimitTier key field is deprecated and ignored; omit it from new requests.
PATCH /keys/:id replaces a non-revoked key's scopes in place — the caller
may only grant scopes they themselves hold, empty scopes are rejected (revoke
to disable instead), and the change takes effect on the very next request
(scopes are resolved fresh per call — no cache). Returns the key's updated
metadata (no plaintext).
Admin
All /v1/admin/* routes require the admin scope. They're stable but intentionally undocumented
in the public OpenAPI export — use them through the internal admin app, not customer SDKs.
bash# Every wallet binding across every tenant
curl -H "Authorization: Bearer art_live_<your-admin-key>" \
"https://api.artery.questflow.ai/v1/admin/wallets"
# Recent webhook deliveries (pull-based observability)
curl -H "Authorization: Bearer art_live_<your-admin-key>" \
"https://api.artery.questflow.ai/v1/admin/webhook-deliveries?limit=100"
# Force a single delivery retry
curl -X POST -H "Authorization: Bearer art_live_<your-admin-key>" \
"https://api.artery.questflow.ai/v1/admin/webhook-deliveries/whd_01HMQ4.../retry"
# Promote / demote an org's plan
curl -X PATCH -H "Authorization: Bearer art_live_<your-admin-key>" \
-H "Content-Type: application/json" \
-d '{"planId":"pro"}' \
"https://api.artery.questflow.ai/v1/admin/orgs/org_01HMP0.../plan"Stream — quick test from CLI
bashTOKEN=art_live_<your-key>
websocat "wss://api.artery.questflow.ai/v1/stream?token=$TOKEN" \
-x '{"type":"subscribe","channels":["artery:stream:event:evt_01HMPKW8YJ5C0F8H4Q4ZE7VX9D"]}'
# → {"type":"subscribed","subscription_id":"sub_01HMQ5..."}
# → {"type":"event","subscription_id":"sub_01HMQ5...","data":{...spread snapshot...}}See the WebSocket channel catalog for every channel name + envelope shape.
See also
- Authentication — token format and scopes
- Error code reference — error.code enum
- Rate limits & quotas — organization plan/SKU quotas and headers
- WebSocket channel catalog — channel names, envelopes