On this page
Batch account value
POST /v1/accounts/batch resolves the complete, mode-aware account value
for a bounded list of wallets in a single call — the same number
/v1/portfolio and /v1/me/stats report per wallet,
just fanned out. It exists so a fund / copy-trade aggregator can read "total
amount + total unrealized PnL across N wallets" without making N round-trips.
Artery is the stateless per-address provider here: it computes current state on demand. Snapshots, time series, and cross-wallet aggregation are owned by the caller — Artery does not store a "fund" concept.
When to use this
- Fund / copy-trade dashboards — sum current NAV across a follower set.
- Risk monitoring — watch unrealized PnL across many wallets at once.
- For a single wallet, prefer
/v1/portfolio(it also returns the rawclearinghouseState+ Polymarket positions).
Request
bashcurl -X POST -H "Authorization: Bearer art_live_<your-key>" \
-H "Content-Type: application/json" \
-d '{"users":["0x1111…","0x2222…"]}' \
"https://api.artery.questflow.ai/v1/accounts/batch"Body
| Field | Type | Required | Description |
|---|---|---|---|
users | string[] | yes | EVM addresses, 0x + 40 hex each. Max 50 per call — page client-side for larger sets. Must be unique. |
Each address is lowercased server-side. More than 50 addresses, an empty
array, a malformed address, or a duplicate address each return 400
(duplicates are rejected so the fund total can't silently double-count).
Response
jsonc{
"results": [
{
"address": "0x1111111111111111111111111111111111111111",
"valueUsd": 12450.78,
"buckets": [
{ "kind": "perp", "usd": 12450.78 },
{ "kind": "spot", "usd": 0 },
{ "kind": "vault", "usd": 0 },
{ "kind": "staking", "usd": 0 }
],
"unrealizedPnlUsd": 350.78,
"accountMode": "unifiedAccount"
},
{
"address": "0x2222222222222222222222222222222222222222",
"valueUsd": null,
"unrealizedPnlUsd": null,
"note": "account state unavailable"
}
],
"summary": {
"totalValueUsd": 12450.78,
"addressesReporting": 1,
"addressesRequested": 2
},
"fetchedAt": "2026-06-16T07:00:00.000Z"
}Semantics
results[i]is in the same order as the requestusers— one row per requested address (which are unique).valueUsdis the complete account value —spot_available + Σ dex accountValue + vault + staking, with unrealized PnL counted exactly once. Mode-aware exactly like/v1/portfolio: onunifiedAccount/portfolioMarginwallets the USDC margin hold is excluded from thespotbucket (it already lives inside each dex'saccountValue).buckets/accountModemirror the portfolio endpoint's Hyperliquid slice.unrealizedPnlUsdis mark-to-market open-position PnL summed across every perp dex. It is already insidevalueUsd— surfaced separately for PnL consumers.- Per-address failure isolation — when an address can't be resolved
upstream, its row has
valueUsd: nulland anote; the rest of the batch still returns200. Failed addresses are excluded fromsummary.totalValueUsdbut still counted inaddressesRequested.
Deriving fund totals
total amount = Σ results[i].valueUsd (across all pages)
total unrealized = Σ results[i].unrealizedPnlUsd
For lifetime total PnL, the correct derivation is
Σ valueUsd − Σ netDeposits (deposits − withdrawals). v1 does not return
netDepositsUsd / realizedPnlUsd: the node-local sources are windowed
(hl_fills ~14-day retention) or only cover the node's indexing history, so a
lifetime figure can't be guaranteed from them yet. Those fields are planned for
a later revision once a lifetime history source is settled.
Error responses
| HTTP | When |
|---|---|
| 400 | users missing / not an array / empty, more than 50 addresses, a malformed address, or a duplicate address. |
| 401 | Missing / invalid Authorization header. |
| 403 | Key lacks the read scope. |
| 429 | Over the portfolio.read SKU quota (this endpoint shares it). |
Quota & performance
Metered under the portfolio.read SKU (1 unit per call). Each address triggers
one full account fan-out (~1s, ~12 node info calls); the batch runs them with
bounded internal concurrency and reuses the shared 15s coalescing cache, so
overlapping batches for the same wallets are cheap. Keep batches at or below 50
so a synchronous call stays responsive — loop pages for larger sets.