---
title: Authentication
---

Every request to the Light Horse API must include both an API key and a cryptographic signature. The signature is computed from the full request content — method, path, query string, headers, and body — so any tampering in transit will cause the signature check to fail and the request to be rejected.

## Required Headers

Every request must include the following HTTP headers:

`x-trade-apikey`
A unique ID issued when your API key pair is created. This identifies the key to use for cryptographic signature.

`x-trade-algorithm`
The algorithm used to generate the signature. Currently only `HMAC-SHA256` is supported.

`x-trade-nonce`
Any arbitrary unique value, for example an UUID. The server rejects any request that reuses a nonce it has already seen.
Example: `d3a6c7b1-8e4f-4a2d-9c3b-1f8e7d6c5b4a`

`x-trade-timestamp`
Unix timestamp (seconds) at the time of the request. Requests with a timestamp older than 5 minutes are rejected.
Example: `1765148421`, which corresponds to `2025-12-07 23:00:21`

`x-trade-signature`
The computed signature (see below).

## Computing the Signature

### Step 1 — Prepare the request body

- Serialize the body as json, if the request has no body, use empty JSON object string `{}`.
- Compute the MD5 hash of the resulting string.

### Step 2 — Build the string to sign

Concatenate the following fields in order, separated by `\n`:

```
METHOD\n
REQUEST_PATH\n
QUERY_STRING\n
x-trade-apikey:X-Trade-APIKey\n
x-trade-timestamp:X-Trade-Timestamp\n
x-trade-nonce:X-Trade-Nonce\n
MD5(body)
```

For example, a POST to `/request/url?param1=value1&param2=value2` with empty body produces:

```
POST\n
/request/url\n
param1=value1&param2=value2\n
x-trade-apikey:739c38fa-0135-494d-88e1-f51e0ecc579c\n
x-trade-timestamp:1705148421\n
x-trade-nonce:d3a6c7b1-8e4f-4a2d-9c3b-1f8e7d6c5b4a\n
99914b932bd37a50b983c5e7c90ae93b
```

### Step 3 — Sign and encode

Compute HMAC-SHA256 over the string to sign using your API secret, then Base64-encode the result:

```js
const bodyHash = crypto
  .createHash('md5')
  .update(JSON.stringify(sortedBody))
  .digest('hex');

const stringToSign = [
  method.toUpperCase(),
  requestPath,
  queryString,
  apiKey,
  timestamp,
  nonce,
  bodyHash,
].join('\n');

let signature = crypto
  .createHmac('sha256', apiSecret)
  .update(stringToSign)
  .digest('hex');

signature = Buffer.from(signature).toString('base64');
```

Set the resulting value as the `x-trade-signature` header.

## Complete Example

```js
import crypto from 'crypto';

function getSignedHeaders({ method, path, query, body, apiKey, apiSecret }) {
  const nonce = crypto.randomUUID();
  const timestamp = Math.floor(Date.now() / 1000).toString();

  const sortedBody = body || '{}';
  const bodyHash = crypto.createHash('md5').update(sortedBody).digest('hex');

  const stringToSign = [
    method.toUpperCase(),
    path,
    query ?? '',
    `x-trade-apikey:${apiKey}`,
    `x-trade-timestamp:${timestamp}`,
    `x-trade-nonce:${nonce}`,
    bodyHash
  ].join('\n');

  let signature = crypto.createHmac('sha256', apiSecret).update(stringToSign).digest('hex');
  signature = Buffer.from(signature).toString('base64');

  return {
    'x-trade-apikey': apiKey,
    'x-trade-algorithm': 'HMAC-SHA256',
    'x-trade-nonce': nonce,
    'x-trade-timestamp': timestamp,
    'x-trade-signature': signature,
  };
}
```
