# Market Data v1 (Archived)

:::warning
This documentation is archived for reference only. These APIs have been replaced by the [**new WebSocket API**](/websocket/introduction). **Start new integrations with the new WebSocket API.**
:::

# WebSocket API v1 Overview

## Introduction

Using WebSockets provides several advantages:

- Receive notifications in real time
- Reduce the amount of data you have to transfer over the network
- Reduce latency introduced by polling interval

For example, to keep track of your orders, you might be requesting the [Get Active Orders](/rest/order-status-apis#get-active-orders) endpoint every five seconds. Using the private [Order Events](/websocket/archived/order-events) API, you would subscribe once and receive real time notifications of all order activity.

### WebSocket Protocol Resources

- [About HTML5 WebSocket](https://websocket.org/aboutwebsocket.html)
- [RFC 6455 The WebSocket Protocol](https://tools.ietf.org/html/rfc6455)

---

## Requests

Both public and private WebSocket API requests begin with a GET request that includes headers asking for an upgrade to the WebSocket protocol. The private API WebSocket request also includes the standard private API headers.

### Public API Request Headers

```
GET wss://api.gemini.com/v1/marketdata/BTCUSD
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Key: uRovscZjNol/umbTt5uKmw==
Sec-WebSocket-Version: 13
```

### Private API Request Headers

```
GET wss://api.gemini.com/v1/order/events
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Key: uRovscZjNol/umbTt5uKmw==
Sec-WebSocket-Version: 13
X-GEMINI-APIKEY: qOfnZJDZTTBsxdM3bVRP
X-GEMINI-PAYLOAD: eyJyZXF1ZXN0IjoiL3YxL29yZGVyL2V2ZW50cyIsIm5vbmNlIjoxNDc3OTYzMjQwNzQxMDgzMzA3fQ==
X-GEMINI-SIGNATURE: 88cd6f391d8f920a76a2060d613b519a8e8b4b3fb5bff089ea826d49ac73888bd479c0c2e2062ba60ba7afbe273132e3
```

---

## Private API Invocation

Gemini uses API keys to allow access to private APIs. You can obtain these by logging on and creating a key in [Settings/API](https://exchange.gemini.com/settings/api). This will give you both an "API Key" that will serve as your user name, and an "API Secret" that you will use to sign messages.

All requests must contain a nonce, a number that will never be repeated and must increase between requests. This is to prevent an attacker who has captured a previous request from simply replaying that request. We recommend using a timestamp at millisecond or higher precision.

### Payload

The payload of the requests will be a JSON object. Rather than being sent as the body of the POST request, it will be base-64 encoded and stored as a header in the request.

:::info
Authenticated APIs do **not** submit their payload as POSTed data, but instead put it in the X-GEMINI-PAYLOAD header.
:::

### Headers

| Header             | Value                                               |
| ------------------ | --------------------------------------------------- |
| Content-Length     | `0`                                                 |
| Content-Type       | `text/plain`                                        |
| X-GEMINI-APIKEY    | Your Gemini API key                                 |
| X-GEMINI-PAYLOAD   | The base64-encoded JSON payload                     |
| X-GEMINI-SIGNATURE | `hex(HMAC_SHA384(base64(payload), key=api_secret))` |
| Cache-Control      | no-cache                                            |

### Example

```json
{
    "request": "/v1/order/events",
    "nonce": <nonce>
}
```

Base64 encode, then sign with HMAC-SHA384:

```python
import base64, hmac, hashlib, json, time

gemini_api_key = "mykey"
gemini_api_secret = "1234abcd".encode()

payload = {"request": "/v1/order/events", "nonce": time.time()}
encoded_payload = json.dumps(payload).encode()
b64 = base64.b64encode(encoded_payload)
signature = hmac.new(gemini_api_secret, b64, hashlib.sha384).hexdigest()
```

---

## Roles

Gemini uses a role-based system for private API endpoints so that you can separate privileges for your API keys.

| Endpoint       | URI                | Trader | Fund Manager | Auditor |
| -------------- | ------------------ | ------ | ------------ | ------- |
| Order Events   | `/v1/order/events` | Yes    | No           | Yes     |

---

## Responses

If successful, API requests will return an HTTP `101 Switching Protocols` code in the response headers:

```
HTTP/1.1 101 Switching Protocols
Connection: upgrade
Upgrade: websocket
Sec-WebSocket-Accept: wEV5o5orKGO27qATSTLczquY3EH=
```

Then the HTTP connection will be replaced by a WebSocket connection.

---

## Data Types

| Type          | Description |
| ------------- | ----------- |
| `string`      | A simple quoted string, following standard JSON rules. |
| `decimal`     | A decimal value, encoded in a JSON string. |
| `timestamp`   | The number of seconds since 1970-01-01 UTC. Use `timestampms` when available. |
| `timestampms` | The number of milliseconds since 1970-01-01 UTC. Transmitted as a JSON number. |
| `integer`     | A whole number, transmitted as a JSON number. |
| `boolean`     | A JSON boolean, the literal string `true` or `false`. |
| `array`       | A JSON array. |

### Timestamps

The timestamp data type describes a date and time as a whole number in Unix Time format.

:::info
Gemini strongly recommends using milliseconds instead of seconds for timestamps.
:::

| Timestamp format            | Example           | Supported request type |
| --------------------------- | ----------------- | ---------------------- |
| whole number (seconds)      | `1495127793`      | `GET`, `POST`          |
| string (seconds)            | `"1495127793"`    | `POST` only            |
| whole number (milliseconds) | `1495127793000`   | `GET`, `POST`          |
| string (milliseconds)       | `"1495127793000"` | `POST` only            |

In responses, `timestamp` denotes seconds and `timestampms` denotes milliseconds since 1970-01-01 UTC.

### Basis Points

Fees are calculated as a fraction of the notional value of each trade (price x amount) in basis points ("bps"), which represent 1/100th of a percent. For example, a fee of 25 bps means 0.25% of the denominated value.

---

## Sequence Numbers

Events and heartbeats contain a `socket_sequence` number:

1. WebSocket connection is established
2. Optional subscription acknowledgement
3. First event with `socket_sequence` set to `0`
4. Each subsequent message increases the sequence by one

If you see a gap in the sequence number, disconnect and reconnect.

:::info
Each time you reconnect, the sequence number resets to zero. Multiple WebSocket connections each have separate sequence numbers.
:::

---

## Rate Limits

To prevent abuse, Gemini imposes rate limits on incoming requests.

For public WebSocket APIs, we recommend that you do not exceed 1 request per symbol per minute.

---

## Error Codes

If a response is in error, the HTTP response code will reflect this, and a JSON body will be returned:

```json
{
    "result": "error",
    "reason": "BadNonce",
    "message": "Out-of-sequence nonce <1234> precedes previously used nonce <2345>"
}
```

### HTTP Status Codes

| HTTP Status | Meaning |
| ----------- | ------- |
| 200         | Request was successful |
| 30x         | API entry point has moved, see Location header |
| 400         | Market not open, or the request was malformed |
| 403         | The API key is missing the role necessary to access this endpoint |
| 404         | Unknown API entry point or Order not found |
| 406         | Insufficient Funds |
| 429         | Rate Limiting was applied |
| 500         | The server encountered an error |
| 502         | Technical issues are preventing the request from being satisfied |
| 503         | The exchange is down for maintenance |

### Error Reasons

| Reason | Meaning |
| ------ | ------- |
| ClientOrderIdTooLong | The Client Order ID must be under 100 characters |
| ConflictingOptions | New orders using a combination of order execution options are not supported |
| EndpointMismatch | The request was submitted to an endpoint different than the one in the payload |
| InsufficientFunds | The order was rejected because of insufficient funds |
| InvalidJson | The JSON provided is invalid |
| InvalidNonce | The nonce was not greater than the previously used nonce |
| InvalidOrderType | An unknown order type was provided |
| InvalidPrice | For new orders, the price was invalid |
| InvalidQuantity | A negative or otherwise invalid quantity was specified |
| InvalidSide | For new orders, an invalid side was specified |
| InvalidSignature | The signature did not match the expected signature |
| InvalidSymbol | An invalid symbol was specified |
| MarketNotOpen | The order was rejected because the market is not accepting new orders |
| MissingApikeyHeader | The `X-GEMINI-APIKEY` header was missing |
| MissingPayloadHeader | The `X-GEMINI-PAYLOAD` header was missing |
| MissingSignatureHeader | The `X-GEMINI-SIGNATURE` header was missing |
| MissingRole | The API key does not have the required role assigned |
| OrderNotFound | The order specified was not found |
| RateLimit | Requests were made too frequently |
| System | We are experiencing technical issues |

---

## Sandbox

Gemini's [sandbox site](https://exchange.sandbox.gemini.com/) is an instance of the Gemini Exchange that offers full exchange functionality using test funds.

| Resource | URL |
| -------- | --- |
| Website | https://exchange.sandbox.gemini.com |
| REST API | https://api.sandbox.gemini.com |
| WebSocket Feed | wss://api.sandbox.gemini.com |
| Documentation | https://docs.sandbox.gemini.com |

Go to the [sandbox site](https://exchange.sandbox.gemini.com) to register for a test account. Your account will automatically be credited with USD, BTC, ETH, BCH, LTC, OXT, LINK, BAT and DAI.

**Two Factor Authentication**: 2FA is enabled by default. To disable for automated testing, set a cookie or HTTP header named `GEMINI-SANDBOX-2FA` and enter `9999999` as the 2FA code.

---

## Client Order ID

Client order ID is a client-supplied order identifier that Gemini will echo back in all subsequent messages about that order. Gemini strongly recommends supplying `client_order_id` when placing orders.

Your client order IDs are only visible to Gemini and you. They should be unique per trading session and must match: `[:\-_\.#a-zA-Z0-9]{1,100}`.

| Characters | Description | ASCII Codes |
| :--------: | ----------- | ----------- |
| `A-Z`      | Uppercase letters | 65-90 |
| `a-z`      | Lowercase letters | 97-122 |
| `0-9`      | Digits | 48-57 |
| `# - . : _` | Special characters | 35, 45, 46, 58, 95 |

---

# Market Data v1

Market data is a public API that streams all the market data on a given symbol.

## WebSocket Request

`wss://api.gemini.com/v1/marketdata/:symbol`

## URL Parameters

| Parameter | Required | Default | Description |
| --------- | -------- | ------- | ----------- |
| `heartbeat` | No | false | Set to `true` to receive a heartbeat every 5 seconds |
| `top_of_book` | No | false | If `true`, receive top of book only (bids and offers) |
| `bids` | No | true | Include bids in `change` events |
| `offers` | No | true | Include asks in `change` events |
| `trades` | No | true | Include `trade` events |

The semantics of entry type filtering:
- To be excluded from `change` events, an entry type must be explicitly flagged `false`
- If no filtering parameters are included, all entry types will appear

:::info
`top_of_book` has no meaning and initial book events are empty when only `trades` is specified.
:::

## Response

Each frame contains a JSON message with the following format:

| Field | Type | Description |
| ----- | ---- | ----------- |
| `type` | string | `heartbeat` or `update` |
| `socket_sequence` | integer | Monotonic increasing sequence number |

Messages of type `update` also include:

| Field | Type | Description |
| ----- | ---- | ----------- |
| `eventId` | integer | Monotonically increasing sequence number for changes |
| `events` | array | Order book changes or trade indications |
| `timestamp` | timestamp | Timestamp in seconds (use `timestampms` instead) |
| `timestampms` | timestampms | Timestamp in milliseconds |

All elements of `events` share:

| Field | Type | Description |
| ----- | ---- | ----------- |
| `type` | string | Either `trade` or `change` |

### Change Event

| Field | Type | Description |
| ----- | ---- | ----------- |
| `price` | decimal | Price of this order book entry |
| `side` | string | `bid` or `ask` |
| `reason` | string | `place`, `trade`, `cancel`, or `initial` |
| `remaining` | decimal | Quantity remaining at this price level |
| `delta` | decimal | Quantity changed (may be negative) |

:::info
Every trade triggers a message with entries of both types `trade` and `change`.
:::

To keep an up-to-date order book, watch for `{"type": "change"}` events and update the price level at `price` with the amount at `remaining`.

### Trade Event

| Field | Type | Description |
| ----- | ---- | ----------- |
| `price` | decimal | Execution price |
| `amount` | decimal | Amount traded |
| `makerSide` | string | `bid` or `ask` |

## Examples

```python
import ssl
import websocket

def on_message(ws, message):
    print(message)

ws = websocket.WebSocketApp(
    "wss://api.gemini.com/v1/marketdata/BTCUSD",
    on_message=on_message)
ws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE})
```

### Initial Response (top of book)

```json
{
  "type": "update",
  "eventId": 5375461993,
  "socket_sequence": 0,
  "events": [
    {
      "type": "change",
      "reason": "initial",
      "price": "3641.61",
      "delta": "0.83372051",
      "remaining": "0.83372051",
      "side": "bid"
    },
    {
      "type": "change",
      "reason": "initial",
      "price": "3641.62",
      "delta": "4.072",
      "remaining": "4.072",
      "side": "ask"
    }
  ]
}
```

### When a trade occurs

```json
{
  "type": "update",
  "eventId": 5375547515,
  "timestamp": 1547760288,
  "timestampms": 1547760288001,
  "socket_sequence": 15,
  "events": [
    {
      "type": "trade",
      "tid": 5375547515,
      "price": "3632.54",
      "amount": "0.1362819142",
      "makerSide": "ask"
    }
  ]
}
```

### Heartbeat

```json
{
  "type": "heartbeat",
  "socket_sequence": 30
}
```

