Skip to content

Overview

LN Markets API (2.0.0)

LN Markets opens a REST and a Websocket API to integrate with your program or trading bot.

This API reference provides information on available endpoints and how to interact with it.

You can find a javascript package here ready to use

Signature

LNM-ACCESS-SIGNATURE header is generated by creating a sha256 HMAC using the secret key on the prehash string timestamp + method + path + params (where + represents string concatenation) digested in Base 64.

  • method : MUST be UPPER CASE.

  • path : is the request path of the URL, e.g.: /v2/user

  • timestamp : value is the same as the LNM-ACCESS-TIMESTAMP header.

    It MUST be number of millisecond since Unix Epoch in UTC and it should be within 30 seconds of our API time.

  • params : is the request body as a JSON string (with no space, no line return)

    or the request query as URL search params

    and if there is no data it should be an empty string

Here is some example to create the signature

Javascript

const { createHmac } = require('crypto')
const { URLSearchParams } = require('url')

let params = ''

if (method.match(/^(GET|DELETE)$/)) {
  params = new URLSearchParams(data).toString()
} else {
  params = JSON.stringify(data)
}

const signature = createHmac('sha256', secret).update(`${timestamp}${method}${path}${params}`).digest('base64')

Python

import hmac
import base64
import json
import urllib

params = ''

if ((method == 'GET') | (method == 'DELETE')):
    params = urllib.parse.urlencode(data)

elif ((method == 'POST') | (method == 'PUT')):
    params = json.dumps(data, separators=(',', ':'))

signature = base64.b64encode(hmac.new(secret, timestamp + method + path + params, hashlib.sha256).digest())

Bash

signature=$(echo -n "$timestamp$method$path$params" | openssl dgst -sha256 -hmac $SECRET -binary | base64 )

REST API

The API endpoint for mainnet is https://api.lnmarkets.com/v2

If you want to try our API with testnet bitcoin use https://api.testnet.lnmarkets.com/v2

REST Authentication

API key authentication requires each request to be signed (enhanced security measure). You can create and activate new API keys on your profile here. Your API keys should be assigned to access only accounts and permission scopes that are necessary for your app to function.

Making a request

All REST requests must contain the following headers:

HeaderDescription
LNM-ACCESS-KEYAPI key as a string.
LNM-ACCESS-SIGNATUREMessage signature.
LNM-ACCESS-PASSPHRASEAPI key passphrase.
LNM-ACCESS-TIMESTAMPTimestamp for your request.

Request POST and PUT must have content type Content-Type: application/json and be valid JSON and in the body. Request queries with GET and DELETE must not have a body and the header Content-Type: application/json must be omitted.

curl https://api.lnmarkets.com/v2/futures \
  --header "LNM-ACCESS-KEY: <your api key>" \
  --header "LNM-ACCESS-PASSPHRASE: <your api key passphrase>" \
  --header "LNM-ACCESS-SIGNATURE: <the user generated message signature in base64>" \
  --header "LNM-ACCESS-TIMESTAMP: <a timestamp for your request in milliseconds>" \
  --header "Content-Type: application/json" \
  --request POST \
  --data '{"type":"l","side":"b","price":40000,"quantity":1,"leverage":10}'

REST Examples

Here is an example on how to do a request to LN Markets :

const https = require('https')
const { createHmac } = require('crypto')
const { URLSearchParams } = require('url')

const requestAPI = (options) => {
  return new Promise((resolve, reject) => {
    const req = https.request(options, (res) => {
      res.setEncoding('utf8')

      let data = ''

      res.on('data', (chunk) => {
        data += chunk
      })

      res.on('error', (error) => {
        reject(error)
      })

      res.on('end', () => {
        if (this.debug) {
          return resolve({ req, res })
        }

        try {
          const body = JSON.parse(data)

          if (res.statusCode === 200) {
            resolve(body)
          } else {
            console.error(body)
            reject(new Error(res.statusCode, body))
          }
        } catch (error) {
          error.data = data
          reject(error)
        }
      })
    })

    req.on('error', (error) => {
      reject(error)
    })

    if (options.method.match(/^(PUT|POST)$/) && options.params) {
      req.write(JSON.stringify(options.params))
    }

    req.end()
  })
}

const main = async () => {
  const key = 'API_KEY'
  const secret = 'API_SECRET'
  const passphrase = 'API_PASSPHRASE'
  const timestamp = Date.now()

  const params = { type: 'm', side: 'b', quantity: 4242 }

  const method = `POST`
  const path = `/v2/futures`

  let data = ''

  if (method.match(/^(GET|DELETE)$/)) {
    data = new URLSearchParams(params).toString()
  } else {
    data = JSON.stringify(params)
  }

  const payload = `${timestamp}${method}${path}${data}`

  const signature = createHmac('sha256', secret)
    .update(payload)
    .digest('base64')

  const headers = {
    'Content-Type': 'application/json',
    'LNM-ACCESS-KEY': key,
    'LNM-ACCESS-PASSPHRASE': passphrase,
    'LNM-ACCESS-TIMESTAMP': timestamp,
    'LNM-ACCESS-SIGNATURE': signature,
  }

  const options = {
    port: 443,
    hostname: 'api.lnmarkets.com',
    method,
    path,
    headers,
  }

  if (method.match(/^(GET|DELETE)$/) && params) {
    options.path += `?${new URLSearchParams(params).toString()}`
  }

  const position = await requestAPI(options)
  console.log(position)
}

main()

If you want a full implementation example you can take a look at our npm package @ln-markets/api.

Websockets API

The websocket endpoint for mainnet is wss://api.lnmarkets.com

If you want to try with testnet bitcoin use wss://api.testnet.lnmarkets.com

This API follows the JSON-RPC specification.

Request sent to the API should be a valid JSON like:

{
  "jsonrpc": "2.0",
  "method": "debug/echo",
  "id": "faffssdfsdf432", // Random id
  "params": {
    "hello": "world"
  }
}

And response would look like:

{
  "jsonrpc": "2.0",
  "id": "faffssdfsdf432", // Same id
  "result": {
    "hello": "world"
  }
}

You need to listen for the id provided in the request to get the response back!

Websocket Authentication

To create an authenticated websocket you need to send a payload once, this payload should be like:

{
  "jsonrpc": "2.0",
  "method": "auth/api-key",
  "params": {
    "timestamp": 1636389122390, // The current timestamp
    "signature": "SAFiGx46GGqztiHu31Mfm89VT3Cp0kqhap4DEs6Pv/U=", // HMAC SHA256 (method + timestamp concatenation) Base 64
    "passphrase": "fd026g0d4i52", // Your passphrase
    "key": "DKJLy/OlXQqQgbT0bE18HJgzQOJnuaTW43OQD8EEHuM=" // Your api key
  }
}

Websockets Examples

Here is an example on how to do a request to LN Markets :

const Websocket = require('ws')

const ws = new Websocket('wss://api.lnmarkets.com')

const key = 'API_KEY'
const secret = 'API_SECRET'
const passphrase = 'API_PASSPHRASE'

const timestamp = Date.now()
const method = `auth/api-key`
const payload = `${timestamp}${method}`

const signature = createHmac('sha256', secret)
  .update(payload)
  .digest('base64')

const request = {
  jsonrpc: '2.0',
  method,
  params: {
    timestamp,
    signature,
    passphrase,
    key,
  },
}

ws.on('message', console.log)
ws.send(JSON.stringify(request))

If you want a full implementation example you can take a look at our npm package @ln-markets/api

Heartbeats

If you are concerned about your connection silently dropping, we recommend implementing the following flow:

After receiving each message, set a timer duration of 5 seconds. If any message is received before that timer fires, restart the timer. When the timer fires (no messages received in 5 seconds), send a raw ping frame. Expect a raw pong frame in response. If this is not received within 5 seconds, throw an error or reconnect.

Subscription

You can subscribe to differents events using the subscribe method. If you wish to unsubscribe, call the unsubscribe and the event you want to be unsubscribed from. Here is the full list of available subscriptions.

[
  "futures/market/bid-offer",
  "futures/market/index",
  "options/data/forwards",
  "options/data/volcurve",
  "options/data/ordermap"
]

Limits

We established some limitation across th API to ensure our services integrity.

Futures

There is a maximum of 50 open positions per account.

Options

There is a maximum of 50 open trades per account.

Rate

Requests to our REST API are rate limited to 1 request per second, endpoints which do not require authentication are limited to 30 requests per minute.

Here are Headers related to rate limitation:

HeaderDescription
Retry-AfterWill tell you how many seconds you need to wait to call this endpoint if your limit is down to 0.
X-Ratelimit-RemainingThis is how many request do you have left before blocking.
X-Ratelimit-ResetThis is the timestamp in ms when limitation will be reset.

Requests

If you throw too much error such as 4XX or 5XX, your IP could be banned for a certain period of time.

Errors

CodeMeaning
400Bad request. Your request is invalid.
401Unauthorized. Your API key is wrong or you don’t have access to the requested resource.
403Forbidden. Your API key has the wrong scope.
404Not found.
405Method Not Allowed. You tried to access a resource with an invalid method.
418I’m a teapot.
429Too many requests. Your connection is being rate limited.
500Internal server error. Something went wrong, please try again or contact us.
503Service unavailable. We’re temporarily offline for maintenance. Please try again later.
  • OpenAPI version: 3.0.2