# Arcus API Guide: Markets, Funding and Fee-Tier Endpoints with Working Examples

> A hands-on guide to the Arcus API: markets, fee tiers, candles and funding endpoints, with curl and JavaScript examples, real responses and the units explained.

*Source: https://arcusguide.com/guides/trading/arcus-api-guide*

> **Note:** The public Arcus API lives at `https://api.arcus.xyz`. Market data needs no key: `/v1/markets`, `/v1/feetiers`, `/v1/candles` and `/v1/fundingRates` all answer a plain GET. Watch the units. Fees are in ppm (225 = 0.0225%), volume thresholds are in 1e-9 USD, and candle and funding timestamps are in microseconds. Candles came back newest-first in testing, even though the docs say oldest-first. Every response below is real, captured on October 2, 2026.

 Arcus runs perpetual futures and Stock Tokens on Robinhood Chain, and its perps exchange has a full REST and WebSocket API. The [official docs](https://docs.arcus.xyz/api-reference/introduction) are detailed, down to a full OpenAPI spec. They are also long, and the unit conventions are spread across dozens of pages. This page pulls together the four endpoints you need for a dashboard, a backtest or a funding tracker, shows what each one returned on a real call, and spells out the conversions. If you just want to trade, the [Arcus app](https://app.arcus.xyz/ref/ARCUSGUIDE) is the easier route.

![Arcus API docs page for GET /v1/candles, showing the endpoint, a curl sample and a static example response](/images/trading/arcus-api-guide/arcus-docs-candles-endpoint.webp)

*The `GET /v1/candles` page in the [Arcus API reference](https://docs.arcus.xyz/api-reference/public/get-ohlcv-candles). Its response panel is a static sample; the live API is what this guide quotes.*

> **Key takeaway:** You don't need an account or an API key to read Arcus market data. A plain GET to `api.arcus.xyz` returns markets, fees, candles and funding as JSON. The units are where scripts go wrong, so the table below lists each one.

## What the public Arcus API covers

Arcus's introduction page says the REST and WebSocket APIs "cover Arcus **perpetuals**." Mainnet REST is `https://api.arcus.xyz` and the WebSocket is `wss://api.arcus.xyz/v1/ws`. A testnet with identical paths runs at `https://api.testnet.arcus.xyz`, and Arcus notes that new features reach testnet first. A GET to the root returned `{"docs":"https://docs.arcus.xyz","status":"running","version":"production-v0.5.0-1"}` when I checked.

The endpoints this guide uses, plus a few related ones, with the request weight the [rate-limit page](https://docs.arcus.xyz/api-reference/rate-limits) assigns to each:

| Endpoint | What it returns | Key? | Weight |
|---|---|---|---|
| `GET /v1/markets` | Every perp market: prices, 24h stats, open interest, funding, margin fractions | No | 20 |
| `GET /v1/feetiers` | The exchange-wide maker/taker fee table by tier | No | 2 |
| `GET /v1/candles` | OHLCV bars for one market and timeframe | No | 20 + rows/60 |
| `GET /v1/fundingRates` | Historical hourly funding rates for one market | No | 20 + rows/20 |
| `GET /v1/commissionrates` | The referral commission schedule in basis points | No | not listed |
| `GET /v1/bbo/BTC-USD`, `/v1/mids`, `/v1/prices` | Best bid/offer for one market, mids for all, oracle and mark prices | No | 2 to 20 |
| `POST /v1/placeOrder` and other writes | Orders, cancels, leverage, withdrawals (signed) | Yes | 0 to 125 |

Account reads such as `/v1/positions` and `/v1/account` take an `address` query parameter, and their docs pages say no authentication header is required. The pToken vaults have their own host, `https://api.vaults.arcus.xyz/v1/vaults`, which returned 16 vaults with TVL, NAV and fee fields on October 2. That host is not in the API reference index, so treat its shape as unofficial. Stock Token spot trading goes through a separate system, the [Spot Router](https://docs.arcus.xyz/concepts/spot-router), with its own integration docs. For background on the vaults, see [Arcus pTokens explained](/ecosystem/arcus-ptokens).

## Units and gotchas

The API uses at least five different units across its fields. I checked every row below against a live response on October 2, 2026.

| Field | Unit | Live example | Convert |
|---|---|---|---|
| `maker_fee_ppm`, `taker_fee_ppm` | Parts per million of notional | `225` | ÷ 10,000 = 0.0225% |
| `volume_threshold` (fee tiers) | USD × 1e9 | `5000000000000000` | ÷ 1e9 = $5,000,000 |
| `openTime` (candles), `time` (funding) | Unix microseconds | `1790953200000000` | ÷ 1,000 for JS `Date` |
| `to` / `from` query params | Unix microseconds | `1790956638949693` | `Date.now() * 1000` |
| `nextFundingAt`, `addedTimestamp` (markets) | Unix seconds | `1790956800` | × 1,000 for JS `Date` |
| `fundingRate` | Hourly rate as a fraction | `"0.0000125"` | × 100 = 0.00125% per hour |
| `priceChange24h` | Fraction | `"0.0133"` | × 100 = +1.33% |
| `openInterest` | Base-asset units | `"138.52231637"` | × `markPrice` = USD |
| `initialMarginFraction` | Fraction | `"0.025"` | 1 ÷ value = 40x max leverage |
| `timeNs` (`/v1/time`), `X-Timestamp` header | Unix nanoseconds | `1790956663172516503` | ÷ 1e6 for JS `Date` |

Two more things to know. Prices, sizes and rates arrive as **strings** (`"85245.5"`, not `85245.5`), which keeps decimal precision intact but means you have to parse them before doing math. And the time units don't match across endpoints: `nextFundingAt` on `/v1/markets` is in seconds, while candle and funding times are in microseconds.

> **Warning:** The candles docs say "Bars are returned oldest-first; the last entry is the in-progress bar." On October 2, 2026 the live API returned them **newest-first**, with the in-progress bar (`isFinal: false`) at index 0. That held for both the `countback` and the `from` forms. Sort by `openTime` yourself and your code works either way.

---

## Example 1: list markets with GET /v1/markets

With no parameters you get every market, sorted by `marketId`. Pass `market` (a display name like `BTC-USD`, case-insensitive, or a numeric ID) to get just one.

```bash
curl "https://api.arcus.xyz/v1/markets?market=BTC-USD"
```

Response, captured 2026-10-02 15:57 UTC (trimmed to the most useful fields):

```json
{
 "markets": [
 {
 "marketDisplayName": "BTC-USD",
 "marketId": 1,
 "status": "ONLINE",
 "tickSize": "0.1",
 "stepSize": "0.00000001",
 "minOrderNotional": "5",
 "oraclePrice": "85266.1",
 "markPrice": "85245.5",
 "fundingRate": "0.0000125",
 "nextFundingRate": "0.0000125",
 "nextFundingAt": 1790956800,
 "priceChange24h": "0.0133",
 "volume24hNotional": "269836888.8",
 "openInterest": "138.52231637",
 "initialMarginFraction": "0.025",
 "regularTradingHours": null,
 "isOutsideRth": false,
 "category": "CRYPTO",
 "pythId": "1"
 }
 ]
}
```

That reads as: BTC marked at $85,245.50, funding of 0.00125% for the hour with the next payment at 16:00 UTC, about $11.8M of open interest (138.52 BTC × the mark), and 40x max leverage. Equity, index and commodity markets also fill `regularTradingHours` and flip `isOutsideRth` when the underlying is closed. The full list returned 67 markets that day, 60 of them `ONLINE`.

The same call in JavaScript, ranked by open interest:

```js
const res = await fetch('https://api.arcus.xyz/v1/markets');
const { markets } = await res.json();

const rows = markets
 .filter((m) => m.status === 'ONLINE')
 .map((m) => ({
 market: m.marketDisplayName,
 mark: Number(m.markPrice),
 maxLev: Math.round(1 / Number(m.initialMarginFraction)),
 oiUsd: Number(m.openInterest) * Number(m.markPrice),
 fundingPctHr: Number(m.fundingRate) * 100,
 }))
 .sort((a, b) => b.oiUsd - a.oiUsd);

console.table(rows.slice(0, 5));
```

At 15:58 UTC that printed BTC-USD (about $11.7M OI, 40x), ETH-USD (about $5.4M, 25x), SPY-USD (about $3.8M, 50x), SOL-USD and NVDA-USD. Our [markets page](/markets) and the [open interest tracker](/tools/open-interest) are built on this same endpoint.

## Example 2: read fee tiers with GET /v1/feetiers

```bash
curl https://api.arcus.xyz/v1/feetiers
```

Response, captured 2026-10-02 15:57 UTC (first two and last two tiers):

```json
{
 "tiers": [
 { "level": 0, "name": "Base", "volume_threshold": 0,
 "maker_fee_ppm": 0, "taker_fee_ppm": 225 },
 { "level": 1, "name": "Bronze", "volume_threshold": 5000000000000000,
 "maker_fee_ppm": 0, "taker_fee_ppm": 190 },
 { "level": 5, "name": "VIP", "volume_threshold": 1000000000000000000,
 "maker_fee_ppm": -20, "taker_fee_ppm": 100 },
 { "level": 6, "name": "Max", "volume_threshold": 3000000000000000000,
 "maker_fee_ppm": -30, "taker_fee_ppm": 95 }
 ]
}
```

The schema describes `volume_threshold` as the "30-day notional volume threshold (in USD×1e9)" and the fee fields as "parts-per-million of notional," where a negative maker value is a rebate. Converting and pricing a trade:

```js
const { tiers } = await (
 await fetch('https://api.arcus.xyz/v1/feetiers')
).json();

const pct = (ppm) => ppm / 10_000; // 225 -> 0.0225 (%)
const usd = (raw) => raw / 1e9; // 5e15 -> 5,000,000
const feeUsd = (notional, ppm) => (notional * ppm) / 1e6;

for (const t of tiers) {
 console.log(t.name, usd(t.volume_threshold),
 `maker ${pct(t.maker_fee_ppm)}%`, `taker ${pct(t.taker_fee_ppm)}%`);
}

feeUsd(10_000, tiers[0].taker_fee_ppm); // 2.25
```

A $10,000 taker fill at the Base tier costs $2.25, and a maker fill costs nothing. These are Arcus's beta rates. The help center lists higher standard rates for after the beta (0.045% taker and 0.015% maker at Base), so read the table live instead of hard-coding it. Arcus's [fees concept page](https://docs.arcus.xyz/concepts/perpetuals/fees) gives the same advice. Our [fee calculator](/tools/fee-calculator) does this math for every tier, and [Arcus fees explained](/guides/fees/arcus-fees-explained) covers how tiers are earned.

Traders who join with a referral code such as Concept211 see "Receive 5% off on all trading fees" in Arcus's app, and Arcus's API changelog for July 22, 2026 lists a "5% referral fee discount for referred users." A code can only be added early in an account's life, and ArcusGuide earns a share of fees from accounts that use its code. The [referral code guide](/guides/getting-started/arcus-referral-code) has the details.

[Get 5% Off Arcus Fees](https://app.arcus.xyz/ref/ARCUSGUIDE)

## Example 3: pull candles with GET /v1/candles

`market`, `timeframe` and `to` are required. `to` must be in Unix **microseconds**: the spec says seconds- or millisecond-scale values are rejected, and the server requires at least `1e14`. Add either `countback` (up to 1,500 bars) or `from`, but not both. Timeframes run from `1m` to `1w`: `1m`, `3m`, `5m`, `15m`, `30m`, `1h`, `2h`, `4h`, `8h`, `12h`, `1d`, `3d` and `1w`.

```bash
TO=$(( $(date +%s) * 1000000 ))
curl "https://api.arcus.xyz/v1/candles?market=BTC-USD&timeframe=1h&to=$TO&countback=3"
```

Response, captured 2026-10-02 15:57 UTC (two of three bars, some fields trimmed):

```json
{
 "candles": [
 {
 "marketDisplayName": "BTC-USD",
 "timeframe": "1h",
 "openTime": 1790953200000000,
 "open": "85657.5", "high": "85727.9",
 "low": "85123.3", "close": "85252.9",
 "volume": "272.42586708",
 "notionalVolume": "23263333.211866306",
 "tradeCount": 11456,
 "isFinal": false
 },
 {
 "marketDisplayName": "BTC-USD",
 "timeframe": "1h",
 "openTime": 1790949600000000,
 "open": "86541.2", "high": "86802.9",
 "low": "85364.2", "close": "85657.5",
 "volume": "364.28365959",
 "notionalVolume": "31317915.408049955",
 "tradeCount": 14243,
 "isFinal": true
 }
 ],
 "total": 3
}
```

The first bar is the open 15:00 UTC hour, so the order is newest-first. With `countback=3` I got three rows, the in-progress bar included. Sending `to` in seconds returned HTTP 400 with `{"error":"invalid or missing 'to' query parameter (unix microseconds)"}`.

If you plan to backtest, note that the docs say bar prices "are derived from the market's oracle price feed, not from executed trades," while volume and trade counts come from actual fills. Empty buckets are gap-filled flat at the previous close. Arcus's help center names Pyth Pro as the primary source for its oracle prices.

```js
async function getCandles(market, timeframe, countback = 100) {
 const url = new URL('https://api.arcus.xyz/v1/candles');
 url.search = new URLSearchParams({
 market,
 timeframe,
 to: Date.now() * 1000, // microseconds
 countback,
 });
 const res = await fetch(url);
 if (!res.ok) throw new Error((await res.json()).error);
 const { candles } = await res.json();
 return candles
 .map((c) => ({ ...c, date: new Date(c.openTime / 1000) }))
 .sort((a, b) => a.openTime - b.openTime); // oldest first
}

const bars = await getCandles('ETH-USD', '4h', 6);
```

> **Key takeaway:** Send `to` in microseconds, convert `openTime` back with `/ 1000`, and always sort candles yourself. Do that, and your code works whichever order the API uses.

## Example 4: read funding with GET /v1/fundingRates

This endpoint returns past hourly funding rates for one market, newest-first, up to `limit=1000` rows per call. Optional `from` and `to` are microseconds and inclusive, matching the `time` field, so a `time` from one page works as the bound for the next.

```bash
curl "https://api.arcus.xyz/v1/fundingRates?market=SPY-USD&limit=3"
```

Response, captured 2026-10-02 15:57 UTC:

```json
{
 "fundingRates": [
 { "marketId": 23, "marketDisplayName": "SPY-USD",
 "fundingRate": "-0.000029976454494208", "time": 1790953200000000 },
 { "marketId": 23, "marketDisplayName": "SPY-USD",
 "fundingRate": "-0.000018348220132205", "time": 1790949600000000 },
 { "marketId": 23, "marketDisplayName": "SPY-USD",
 "fundingRate": "0.00000505787037037", "time": 1790946000000000 }
 ],
 "total": 3
}
```

Per Arcus's [funding docs](https://docs.arcus.xyz/concepts/perpetuals/funding), a positive rate means longs pay shorts, and the hourly payment is `funding_rate_hourly × position_size × oracle_price`. The 15:00 UTC SPY rate was negative, so shorts paid longs that hour. On BTC, `0.0000125` per hour works out to 0.01% per 8 hours, which is the crypto base rate in the docs, or about 10.95% a year if it held constant. On a $10,000 BTC position that is $0.125 an hour. Equity, index and commodity perps lock funding to SOFR + 0.5% a year while the underlying is fully closed overnight and on weekends.

To build a longer history, page backwards:

```js
async function fundingHistory(market, hours = 24 * 30) {
 const out = [];
 let to;
 while (out.length < hours) {
 const url = new URL('https://api.arcus.xyz/v1/fundingRates');
 url.searchParams.set('market', market);
 url.searchParams.set('limit', '1000');
 if (to) url.searchParams.set('to', String(to));
 const { fundingRates } = await (await fetch(url)).json();
 if (!fundingRates.length) break;
 out.push(...fundingRates);
 to = fundingRates.at(-1).time - 1; // just below the oldest row
 await new Promise((r) => setTimeout(r, 3000)); // a full page costs 70 weight
 }
 return out.slice(0, hours);
}

const rows = await fundingHistory('SPY-USD', 1500);
const avg = rows.reduce((s, r) => s + Number(r.fundingRate), 0) / rows.length;
console.log(`${(avg * 100).toFixed(5)}%/hr, ${(avg * 24 * 365 * 100).toFixed(1)}%/yr`);
```

Run on October 2, 2026, that returned 1,500 unique hourly rows for SPY-USD going back to August 1. The average was about 0.00096% per hour, roughly 8.4% a year paid by longs over that window. The same lookup in Python:

```python

from datetime import datetime, timezone

r = requests.get(
 "https://api.arcus.xyz/v1/fundingRates",
 params={"market": "BTC-USD", "limit": 24},
 timeout=10,
)
r.raise_for_status()
for row in r.json()["fundingRates"]:
 t = datetime.fromtimestamp(row["time"] / 1_000_000, tz=timezone.utc)
 print(t.isoformat(), f"{float(row['fundingRate']) * 100:.5f}%/hr")
```

In my capture, `fundingRate` on `/v1/markets` matched the newest row here, and `nextFundingRate` is the predicted rate for the next hour. For a live view across every market without writing code, use the [funding rates tool](/tools/funding-rates).

## Rate limits and CORS

The [rate-limit page](https://docs.arcus.xyz/api-reference/rate-limits) describes two independent layers. Every IP gets a bucket of **1,500 weight** that refills at 1,500 per minute (25 per second). Each request deducts its weight first. Arcus says limits are enforced across all gateway nodes, "so spreading requests across connections or hitting different nodes does not raise your effective budget."

![Arcus API docs rate limits page describing per-IP weight limits and per-subaccount trading limits](/images/trading/arcus-api-guide/arcus-docs-rate-limits.webp)

*The [rate limits page](https://docs.arcus.xyz/api-reference/rate-limits) in Arcus's API reference.*

The sustained rates that follow from a 25-per-second refill, using the docs' own figures:

| Weight | Sustained rate from one IP | Example endpoints |
|---|---|---|
| 2 | 12.5 requests/second | `feeTiers`, `bbo`, `mids`, `positions` |
| 20 | 1.25 requests/second | `markets`, `candles`, `fundingRates`, `prices` |
| 125 | 0.2 requests/second | `setLeverage`, `withdraw`, `transfer` |

Big pages cost extra. List endpoints add `floor(items / N)`, where N is 20 for most lists and 60 for candles, so a full 1,500-bar candle page costs 45 and a 1,000-row funding page costs 70. Go over budget and you get HTTP 429 with a `Retry-After` header in whole seconds and `{"error":"rate limited"}` in the body. Arcus does not send `X-RateLimit-*` headers, so you have to track the cost yourself. Order writes cost 0 IP weight and draw on per-subaccount pools instead (20,000 orders and 40,000 cancels to start, growing with lifetime volume).

If you poll hard, the docs suggest a WebSocket subscription. The socket has channels for the order book, trades, BBO, candles, markets and predicted funding.

I couldn't find anything about CORS in the docs. In testing on October 2, `api.arcus.xyz` returned `access-control-allow-origin: *` on 200 responses, on 400 errors and on the `OPTIONS` preflight, and the vaults host did the same. Browser `fetch` calls worked directly with no proxy. Arcus hasn't documented that, so it could change.

[Get 5% Off Arcus Fees](https://app.arcus.xyz/ref/ARCUSGUIDE)

## What needs an API key

Anything that changes account state needs a signed request. According to the [authentication docs](https://docs.arcus.xyz/api-reference/authentication), an Arcus API key is the public half of an Ed25519 key pair. You register it with `POST /v1/createApiKey`, which your Ethereum wallet signs as EIP-712 typed data, or you let the Arcus web app generate and register one for you.

Each signed request then carries three headers: `X-API-Key`, `X-Timestamp` and `X-Signature`. The timestamp is in Unix **nanoseconds** and has to be within ±30 seconds of server time, not the microseconds the data endpoints use. Orders, cancels and modifies sign a compact, key-sorted payload with prices in ticks and sizes in quantums. Leverage changes, cancel-all and referral calls use an older scheme that signs `timestamp + action + body`.

Three rules are easy to miss. Order writes are asynchronous and usually return `202 Accepted`; the docs say the final state arrives on the WebSocket `orders` and `userFills` channels. Every order needs a `goodTilTime` at least one month out, IOC and FOK included, because the field doubles as replay protection. And keys made through the public `createApiKey` are trade-only. The docs say a key that can withdraw is only granted through the operator's provisioning flow.

The docs include step-by-step guides for [trading over REST](https://docs.arcus.xyz/guides/rest-trading) and over WebSocket, plus a latency guide. Start on testnet. A new testnet key starts with no balance, and the docs point to a **Testnet Deposit** button that credits about $1,000 of paper collateral. Arcus also runs an MCP server so AI coding assistants can read its docs.

Two limits apply before mainnet. Arcus says it is not available in the United States, Canada, the United Kingdom or other restricted jurisdictions, and its terms bar those persons from perps; if that's you, the [regional availability guide](/troubleshooting/arcus-not-available-in-my-country) covers what that means. Elsewhere, perps trading needs access through an invite code or a waitlist slot, and an API key does not get around that. The [invite code and waitlist guide](/guides/getting-started/arcus-invite-code-and-waitlist) explains how access works.

## Where to go next

Arcus is built by dYdX Labs (the trade name of dYdX Trading Inc.), and its API is Arcus's own `/v1` design. The dYdX v4 indexer path `/v4/perpetualMarkets` returns 404 on `api.arcus.xyz`, so code written for dYdX Chain needs rewriting. For more on the platform itself, start with [what Arcus is](/ecosystem/what-is-arcus) and [how it compares with dYdX Chain](/compare/arcus-vs-dydx). For the official reference, bookmark the [API introduction](https://docs.arcus.xyz/api-reference/introduction), the [changelog](https://docs.arcus.xyz/changelog) and the machine-readable index at [docs.arcus.xyz/llms.txt](https://docs.arcus.xyz/llms.txt).

*ArcusGuide is independent and not affiliated with Arcus. Responses quoted here were captured on October 2, 2026 and will have changed since. This is not financial advice.*
