AArtery
On this page

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.

Note

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 get 404.
  • lookback_days is clamped to [1, 30], default 7. Older windows are capped by an internal RPC-budget guard (default 1_000 chunks × 2_000 blocks per contract) — when the request would exceed it, the actual fromBlock is clamped forward and the server logs a clamped warning.
  • 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.request quota.

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 hyperliquid or polymarket — you do NOT pick hyperliquid_perp / _hip4 / _dex (one HL builder identity covers them; hyperliquid is canonicalized to hyperliquid_perp on 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 V2 OrderFilled.builder field, never translated). The endpoint validates length + 0x prefix.
  • Scopes. GET requires read; POST / DELETE require admin.
  • 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…0 is always excluded from platform=mine matching and from groupBy=builder bucketing — 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=mine and the server resolves your registered builder(s) for the queried provider(s).
  • platform=mine with nothing registered → 400 no_platform_attribution. Requesting platform=mine for a provider you have no builder registered for is rejected (not silently 0), so you can tell "no builder configured" apart from "wallet genuinely has no platform fills". Register a builder, or use platform=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.
  • walletCount is the number of distinct bound wallets that have fills attributed to this builder in the result window.
  • label is whatever the org registered via /v1/me/platform-attributions (or null if 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"
}
  • navUsdc is the unified, cross-provider quantity — "close everything and withdraw → this much USD". The cashUsdc / positionsValueUsdc split is provider-specific and populated for Polymarket only (a Hyperliquid margin account has no clean spot split).
  • source of the curve: Hyperliquid via info portfolio.pnlHistory, Polymarket via the official user-pnl series (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 0 and 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 / 1Y are 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.

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"
}
  • navUsdc is the wallet's NAV (liquidation equity); netDepositsUsdc is 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; the ALL window degrades before ledger coverage). Snapshot wins once ≥2 points exist, so the fallback auto-retires.
  • Hyperliquid NAV comes from clearinghouseState and deposits from userNonFundingLedgerUpdates — both served by our self-hosted node (no rate cost, exact per-flow timestamps), so HL is snapshot. Polymarket uses the pm_user_pnl reconstruction today and switches to snapshot once the dedicated Polygon node lands. A wallet covered by neither returns empty points.
  • Hourly snapshots, aligned to the top of the hour. On a wallet's first appearance the series is seeded from portfolio.accountValueHistory so 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. Sum slices[i].navUsdc for the user's Total Assets, or add the curves point-wise for a combined curve.
  • walletAddress is 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/pmUser are accepted as deprecated aliases — prefer walletAddress.) This is the same selector as /v1/me/pnl and /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 a warnings entry), NOT 0. Treat null as "unknown, retry / keep last known"; only a real 0 means an empty account. Likewise the curves refuse to serve a $0 series they can't confirm — a failed provider surfaces in warnings rather than as a misleading flat-zero curve. Don't coerce a missing/null provider to 0 when 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, with navUsd/totalNavUsd) and GET /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) where mwrPct/twrPct can 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 of realizedPnlUsd + 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 / fundingUsd breakdown 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's portfolio history (navSource: "hl_portfolio"; coarser → accuracy: "interpolated"). /me/stats?roi=true uses 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 return null metrics with a going-forward note, and PM carries no realized/fee breakdown (it has no per-fill closed PnL). mwrPct / twrPct are also null when there is no NAV anchor for the window start or the capital base is non-positive.
  • New wallets / since-inception: if from predates the wallet's first funding, the start is clamped to inception (a note says so) and returns are measured since then — so a freshly-funded wallet still gets a real twrPct instead of null. Note mwrPct (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.read SKU.

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. realizedPnlUsd is gross of fees — net = realized − tradingFee − builderFee. null for 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) vs mode=live (nav= is a deprecated alias) — by default accountValueUsd and unrealizedPnlUsd are read from the latest hourly NAV snapshot (fast over many wallets, ≤1h stale). Add mode=live for real-time account value + unrealized PnL (one getState per wallet — prefer for small sets).
  • sort = a field name with optional - prefix for descending; nulls always sort last. Default -realizedPnlUsd. groupBy=wallet for the per-wallet leaderboard, groupBy=provider to 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=csv is 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 with Content-Disposition: attachment; filename="artery-pnl-YYYY-MM-DD.csv".
  • Cap at 50,000 rows post-filter. Exceeding it returns 400 row_cap_exceeded BEFORE any row is written — narrow [since, until] and retry. The body includes rowCount + cap so a client can do that adaptively.
  • Sort is ignored — exports are always chronological. The file ends with a # summary block (total_realized,total_unrealized,total_notional,trade_count) for quick spot-checks against /v1/me/pnl.

Backtest

Warning

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&notional=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&notional=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/polymarket families), walletAddress (repeatable — omit for all your wallets), and a window (window=1D|1W|1M|ALL, or explicit from/to in unix seconds; from/to override window).
  • Sort is fixed to -ts (newest first); page with limit (default 100, max 500) + the opaque cursor from pagination.nextCursor.
  • Forced closes are marked — a HL fill closed by the system carries closeReason: liquidation (强平) or adl (auto-deleverage — a distinct mechanism, not conflated). liquidation fills also carry liquidationMethod (market / backstop) and HL's raw dir (e.g. Liquidated Isolated Short). Absent on normal manual fills — so you can show a Liquidated / Auto-Deleveraging badge 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/wallets appear (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

Note

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

Edit this page on GitHubLast updated
Examples · Artery API Docs