> For the complete documentation index, see [llms.txt](https://docs.viperexecution.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.viperexecution.com/api-reference/overview.md).

# Overview

Overview of the Viper Execution v1 API: base URL, authentication model, resource families, quick start and the design principles behind the contract.

## What this is

The Viper Execution v1 API is a programmatic interface for running institutional-grade execution algorithms on Hyperliquid. It provides the same execution algorithms available in the Viper dashboard (Glidemaker, Pacemaker, GhostSweep, FlowScale, FlowBand, Smart Exit) through a REST surface suitable for trading bots.

The reference is organized by resource family — account, connections, positions & leverage, orders & fills, execute & executions, monitors, webhooks, baskets, and market data — each in its own section of the navigation. Real-time data (account, execution, and monitor streams) lives under **Streams**.

## Status

v1 is live and stable. The core surface — `/v1/execute`, `/v1/executions/*`, authentication, and the error envelope — is in production and won't break under you.

* Scope enforcement is **active** at the gateway. A key calling an endpoint outside its scope returns 403 `insufficient_scope` with `required_scope` in the error details. Provision each key with only the scopes your bot actually uses.
* The full v1 family set is shipped — account, connections, positions & leverage, orders & fills, execute & executions, monitors, webhooks, baskets, market data, and streams.
* Example payloads in this reference are authoritative for response shape.

## Base URL

```
https://api.viperexecution.com
```

All v1 endpoints live under the `/v1/` path prefix. The legacy `/api/` prefix is the dashboard's internal API — bots should never use it.

## Authentication model

v1 uses **HMAC-SHA256 request signing** with per-request timestamps. Every request carries three headers:

* `X-Viper-Api-Key-Id` — your public key identifier (`vk_…`)
* `X-Viper-Timestamp` — UNIX epoch seconds, recent
* `X-Viper-Signature` — HMAC-SHA256(secret, `{timestamp}{method}{path}{body}`)

Full signing details are in **Authentication**. The dashboard's JWT auth does NOT work here; these are separate systems.

Key properties:

* Agent-wallet architecture — your API key signs with a server-side agent key. Agents can place/cancel orders but cannot withdraw funds. The agent never holds withdrawal permission — funds stay under your control.
* Scope-limited — each key is provisioned with specific scopes (`read`, `trade`, `algo`). `algo` covers execution launch and management.
* Rate-limited per key — you get a per-minute weight budget based on your tier. Launch endpoints are weight-20 (heavy); reads are weight-1 to weight-5.

## The v1 surface

| Family                   | What it covers                                                                                                                              |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- |
| **Account**              | Account state, balances, P\&L, fills, fee tier, referrals                                                                                   |
| **Connections**          | API-key → venue connection bindings (`GET /v1/connections`)                                                                                 |
| **Positions & Leverage** | Open positions, entry-fee analytics, leverage config, position close                                                                        |
| **Orders & Fills**       | Manual order placement, cancel/modify, order history, fills                                                                                 |
| **Execute & Executions** | Algorithmic execution launch, lifecycle, introspection — the six algos (Glidemaker, Pacemaker, GhostSweep, FlowScale, FlowBand, Smart Exit) |
| **Monitors**             | Trade monitors — conditional automation                                                                                                     |
| **Webhooks**             | Inbound TradingView-style webhook receivers                                                                                                 |
| **Baskets**              | Execution Baskets — grouped multi-leg executions                                                                                            |
| **Market Data**          | Instruments, markets, orderbook, price, candles, server-side indicators                                                                     |
| **Streams & Utility**    | WebSocket + SSE streams, health, status, rate limits                                                                                        |

## Market data: candles and server-side indicators

The market-data family serves OHLCV candles (`GET /v1/candles/{symbol}`) and batch technical-indicator evaluation (`POST /v1/indicators/evaluate`) — the same fourteen indicator types the dashboard chart draws, computed server-side from one contract, so the number your bot reads is the number the chart draws. Two properties matter for strategy code:

* **Closed bars by default.** The `candles` array only ever contains closed bars, and indicator values bind to the last closed bar — stamped in `bar_time`. The in-progress bar is available only by explicit opt-in (`include_forming=true`); repainting semantics are always your choice, never an accident.
* **Cache honesty.** Candle data is served from a per-account, per-symbol/interval cache refreshed on bar boundaries. Every response reports `cached` and `age_seconds`, so you always know whether a venue fetch happened and how old the served data is.

**Reads yield to execution.** Market-data reads are admission-refused before your venue-weight budget is exhausted, so execution always finds protected headroom — a read can never starve your running algorithms of the budget they need to place orders. A refused read returns `429 rate_limited` with `bucket_exceeded: "read_headroom_gate"` and `retry_after_seconds` in the error details; cached serves are unaffected, since the gate protects venue weight, not cached reads. See **Authentication → 429 envelope** for the full rate-limit contract.

## Indicator-driven TP/SL

**Offset legs.** Anywhere a TP/SL accepts an absolute price, it also accepts an indicator offset — the trigger is computed server-side as `anchor ± mult × indicator_value`, using the same fourteen-indicator engine as `POST /v1/indicators/evaluate` and the dashboard chart, evaluated on the last closed bar at resolution time. A volatility-scaled stop on an order:

```json
{
  "symbol": "HYPE", "side": "buy", "size": 4.4,
  "order_type": "limit", "price": 56.10,
  "stop_loss": {
    "offset": { "mult": 1.8, "indicator": "atr",
                "params": { "period": 14 }, "interval": "1h" }
  }
}
```

On a filled entry the leg resolves immediately, anchored to your actual average fill price; on a resting entry the response reports `queued: true` and the leg places the moment the entry fills — the watch is durable across restarts and follows the order through modifies. Every resolved leg echoes its full resolution:

```json
"resolution": {
  "indicator": "atr", "interval": "1h", "series": "atr",
  "indicator_value": 0.3733, "bar_time": 1786553100000,
  "anchor": "entry", "anchor_price": 55.869, "trigger_price": 55.197
}
```

— the number your stop was built from is the number the chart draws for that bar. Multi-series indicators select a line with `series` (`bollinger` → `upper`/`mid`/`lower`); `mult` scales the indicator's value into a price distance. The same leg shape applies on `POST /v1/execute` (resolved when the execution reaches a terminal state with fills — a completed run, or one stopped early with a partial position — anchored to the algo's average fill, sized to the accumulated fill) and on `POST /v1/positions/{symbol}/tpsl` (resolved immediately for an open position; orders are venue-coupled — they track position size and auto-cancel on close).

**At-level legs.** Where an offset expresses a *distance*, `at` makes the trigger *the level itself* — "SL at the lower Bollinger", "TP at the Donchian upper":

```json
"stop_loss": {
  "at": { "indicator": "bollinger", "series": "lower",
          "params": { "period": 20 }, "interval": "1h" }
}
```

At-level triggers are restricted to price-dimensioned series (moving averages, VWAP, band lines) — an oscillator value is not a price and is rejected with a 422. Side-sanity is enforced at resolution — never clamped, and the resolved value is returned in the error detail. On order and algo launches the anchor is the entry: an SL resolving above a long's entry (or a TP below it) is rejected. On position attach the anchor is the current price, so breakeven-plus stops on a winning position are accepted; only legs that would trigger immediately are refused.

**Trailing legs.** `trail` hands the leg to the tracking engine, which re-evaluates it on every closed bar of the leg's interval and moves the resting trigger — the order's identity (`client_order_id`) is preserved across every move. Three forms:

```json
"stop_loss": { "trail": { "pct": 2.0, "interval": "1m" } }
"stop_loss": { "trail": { "indicator": "atr", "mult": 6.0,
                          "params": { "period": 14 }, "interval": "1m" } }
"stop_loss": { "trail": { "indicator": "ema",
                          "params": { "period": 20 }, "interval": "1h" } }
```

— percent behind the favorable extreme, a volatility distance behind it, or riding a price-level line. SL trails are protective-direction monotone (they only ever tighten); TP trails follow their level both ways. A venue write is issued only when the new trigger moves at least `min_move_bps` (default 5) from the resting one. Distance trails are ATR-only; level trails share the at-level price-dimensioned whitelist. Trailing works on all three surfaces — on the position surface the trailing stop stays venue-coupled through every move (position-size tracked, auto-cancelled on close). Each wallet can run up to 20 trailing legs; beyond that the request is rejected with a 422. Trail lifecycle (each move, and retirement when the trigger fills or is cancelled) streams on the `tpsl.watch` channel.

**Indicator-priced entries.** The same resolve-once machinery prices limit entries: `price_from` on `POST /v1/order` resolves the level at submission and the response echoes `price_from_resolution`:

```json
{ "symbol": "HYPE", "side": "buy", "size": 4.4,
  "order_type": "limit",
  "price_from": { "indicator": "ema", "params": { "period": 50 },
                  "interval": "1h" } }
```

**Tracked entries.** Add `track: true` and the entry FOLLOWS the level — the tracking engine re-resolves it on each closed bar and moves the resting limit with it, both ways:

```json
"price_from": { "indicator": "bollinger", "series": "lower",
                "interval": "1m", "track": true }
```

The response carries an `entry_track` ack naming the watch; the watch streams on `tpsl.watch` (`watch_kind: entry`, `entry_moved` per move). The same spec tracks a GhostSweep trigger — `trigger_from` on a GhostSweep launch. A tracked GhostSweep takes `sweep_limit_bps` (relative to the live trigger, sign per side); an absolute `sweep_limit` is refused 422.

Field contract: only the price/trigger moves; `min_move_bps` (default 5) throttles writes; `max_drift_bps` (default 500) — exceeded, tracking stops and the order stands (`entry_tracking_stopped`); a level that would cross the book holds (`entry_hold`, order targets). Tracked entries count against the 20-row maintain limit; modifies of a tracked entry are refused 409, cancels retire the watch.

**Algo-unwind exits.** On the position surface, a leg may choose HOW it exits: `exit_style: "native"` (default) places a venue trigger order; `exit_style: "algo_unwind"` places no venue trigger — the platform compiles the leg into a reduce-only monitor that launches the chosen algorithm when the level is crossed, sized to the position at attach:

```json
"stop_loss": {
  "offset": { "mult": 2.0, "indicator": "atr",
              "params": { "period": 14 }, "interval": "1h" },
  "exit_style": "algo_unwind",
  "unwind_algo": "pacemaker",
  "unwind_params": { "duration_seconds": 600 }
}
```

At-level legs compile to an `indicator_level` monitor on the same spec (the level keeps tracking closed bars); price and offset legs compile to a `price_level` monitor at the resolved trigger. The leg result returns the compiled monitor's id, and when both legs of one attach compile to monitors the pair is stamped as an `oco` group — the first to fire successfully stops the other. Constraints: a trail cannot be an algo unwind; GhostSweep cannot serve an at-level leg; Glidemaker and Smart Exit require the position's order value at the trigger to meet the instrument's algo minimum (Pacemaker and GhostSweep have no minimum). The compiled monitor freezes side, size and level at attach: rearming it later does not re-resolve them against the current position.

Full field reference: `TpSlOffsetSpec`, `TpSlAtSpec`, `TpSlTrailSpec` and `UnwindParamsSpec` in the API reference below. To read indicator values directly into strategy logic, use `POST /v1/indicators/evaluate`.

## Quick start

A minimal Python client that signs a request and launches a Glidemaker:

```python
import os, hashlib, hmac as hmac_mod, httpx, time, json, uuid

API_KEY = os.environ['VIPER_API_KEY']       # opaque vk_… id
API_SECRET = os.environ['VIPER_API_SECRET'] # opaque vs_… secret
BASE = 'https://api.viperexecution.com'


def call(method, path, body=None, idem_key=None):
    ts = str(int(time.time()))
    body_bytes = json.dumps(body).encode() if body is not None else b''
    payload = f'{ts}{method}{path}{body_bytes.decode()}'.encode()
    sig = hmac_mod.new(API_SECRET.encode(), payload, hashlib.sha256).hexdigest()
    headers = {
        'X-Viper-Api-Key-Id': API_KEY,
        'X-Viper-Signature': sig,
        'X-Viper-Timestamp': ts,
    }
    if idem_key: headers['Idempotency-Key'] = idem_key
    if body is not None: headers['Content-Type'] = 'application/json'
    r = httpx.request(method, f'{BASE}{path}', headers=headers, content=body_bytes, timeout=30)
    return r


# Launch a Glidemaker
r = call('POST', '/v1/execute',
    body={
        'algo': 'glidemaker',
        'symbol': 'BTC',
        'side': 'buy',
        'total_size': 0.001,
        'params': {'strategy': 'neutral', 'limit_price': 65000}
    },
    idem_key=str(uuid.uuid4())
)
print(r.status_code, r.json())
```

That's \~30 lines of real code. The raw REST + HMAC surface is fully supported and always will be — build your client in whatever language and framework fits your infrastructure, with full control over timeouts, retries, connection pooling, and error handling.

If you'd rather not hand-roll the signing and WebSocket plumbing, an **official Python SDK** is available — a typed convenience wrapper over these same endpoints, never a gate, never required:

```bash
pip install viper-execution
```

It signs every request, auto-generates idempotency keys, maps the error envelope to typed exceptions, and ships a resilient WebSocket client — while every response stays a plain dict. See the [Python SDK](/api-reference/python-sdk.md) page for the quickstart, the client surface, and the runnable example catalog.

## Design principles worth knowing

A few design principles shape the v1 surface. The ones bot devs most need to know:

1. **Respect user input** — what you send is what we record. We don't silently rewrite fields. If a request produces incoherent behavior, we reject with 422; we don't "correct" it for you.
2. **Preflight validates shape, not state** — we check size/price decimals and field consistency, but position/balance/leverage validation happens at order placement time with live data.
3. **Algos enforce semantic correctness** — when you send `reduce_only: true`, that's an intent. The algo makes it real by checking live state and using Hyperliquid's native reduce\_only flag on every order.
4. **Idempotency is mandatory on `/v1/execute`** — double-launch from a retry loop is the most damaging bot bug class. Always include an `Idempotency-Key` header. See **Authentication → Idempotency**.

## Key provisioning

API keys are created **self-serve in the dashboard** — Settings → API Keys → Create — and the secret is shown only once at creation. You must have a wallet linked to your account before a key can be created (creation is refused with a 409 otherwise). See **Authentication → Creating your API key** for the full procedure, the available options, and the link-wallet-first precondition.

Keys carry a handle (your account identifier) and a scope set (`read`, `trade`, `algo`). Request the minimum scope your bot needs — narrowed scopes limit blast radius if a key leaks.

## Support

For API bugs and questions, reach out at <support@viperexecution.com>.

When reporting an issue with a specific request, include the `request_id` from the response headers — it's the lookup key for our logs.
