LN Markets API (1.0.0)

Download OpenAPI specification:Download

Introduction

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

REST API

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

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

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:

Header Description
LNM-ACCESS-KEY API key as a string.
LNM-ACCESS-SIGNATURE Message signature.
LNM-ACCESS-PASSPHRASE API key passphrase.
LNM-ACCESS-TIMESTAMP Timestamp for your request.

All request bodies should have content type application/json and be valid JSON.

curl https://api.lnmarkets.com/v1/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}'

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.: /v1/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 )

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 = `/v1/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!

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
  }
}

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:

Header Description
Retry-After Will tell you how many seconds you need to wait to call this endpoint if your limit is down to 0.
X-Ratelimit-Remaining This is how many request do you have left before blocking.
X-Ratelimit-Reset This 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

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

Authentication

apiKeyAuth

Api key auth is using LNM-ACCESS-KEY LNM-ACCESS-PASSPHRASE LNM-ACCESS-TIMESTAMP LNM-ACCESS-SIGN headers (Only one header is shown below because our docs engine does not allow to put more there) See (here)[https://docs.lnmarkets.com/api/v1/#section/REST-API/Authentication]

Security Scheme Type API Key
Header parameter name: LNM-ACCESS-KEY

Login

Here you can find the different way to auth yourself on LN Markets, mostly use for browser.

Futures

Interactions with the futures market.

Add margin

Add margin to a running position.

Authorizations:
apiKeyAuth (
  • positions_modify
)
Request Body schema: application/json

Payload with data relative to the process.

amount
required
integer <int64> >= 1

Amount of margin to add (in sats)

pid
required
string^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]...

Position ID from which to retrieve PL

Responses

Request samples

Content type
application/json
{
  • "amount": 1000,
  • "pid": "99c470e1-2e03-4486-a37f-1255e08178b1"
}

Response samples

Content type
application/json
{
  • "pid": "249dc818-f8a5-4713-a3a3-8fe85f2e8969",
  • "id": 666,
  • "type": "m",
  • "takeprofit_wi": "running",
  • "takeprofit": 13337,
  • "stoploss_wi": "running",
  • "stoploss": 1337,
  • "side": "s",
  • "quantity": 42,
  • "price": 8888,
  • "pl": -13640,
  • "market_wi": "filled",
  • "market_filled_ts": "020-09-15T10:50:40.332Z",
  • "margin_wi": "running",
  • "margin": 424242,
  • "liquidation": 1000,
  • "leverage": 50,
  • "creation_ts": "020-09-15T10:50:40.332Z"
}

Cancel-all

Cancel all open positions

Authorizations:
apiKeyAuth (
  • positions_close
)

Responses

Response samples

Content type
application/json
{
  • "data": [
    ]
}

Close-all

Close every user position. The PL will be calculated against the current bid or offer depending on the side of the position.

Authorizations:
apiKeyAuth (
  • positions_close
)

Responses

Response samples

Content type
application/json
{
  • "data": [
    ]
}

Cancel

Cancel the position linked to the given pid.Only works on positions that are not currently filled.

Authorizations:
apiKeyAuth (
  • positions_close
)
Request Body schema: application/json

Payload containing the position id that will be cancelled.

pid
required
string^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}...

Position ID

Responses

Request samples

Content type
application/json
{
  • "pid": "b87eef8a-52ab-2fea-1adc-c41fba870b0f"
}

Response samples

Content type
application/json
{
  • "pid": "b87eef8a-52ab-2fea-1adc-c41fba870b0f",
  • "canceled": true,
  • "closed_ts": "020-11-15T10:50:40.332Z"
}

Carry fees history

Retrieves carry fees for user

Authorizations:
apiKeyAuth (
  • positions_get_closed
)
query Parameters
from
integer <int64>
Example: from=1605162757621

Starting timestamp (in ms).

to
integer <int64>
Example: to=1605162857621

Ending timestamp (in ms).

limit
integer <int64> [ 1 .. 100 ]
Default: 100
Example: limit=100

Number of rows

Responses

Response samples

Content type
application/json
{
  • "data": [
    ]
}

Cash-in

Retrieves part of one running position's PL.

Authorizations:
apiKeyAuth (
  • positions_modify
)
Request Body schema: application/json

Payload with data relative to the process.

amount
required
integer <int64> >= 1

Amount of PL to cash-in (in sats)

pid
required
string^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]...

Position ID from which to retrieve PL

Responses

Request samples

Content type
application/json
{
  • "amount": 1000,
  • "pid": "99c470e1-2e03-4486-a37f-1255e08178b1"
}

Response samples

Content type
application/json
{
  • "pid": "249dc818-f8a5-4713-a3a3-8fe85f2e8969",
  • "id": 666,
  • "type": "m",
  • "takeprofit_wi": "running",
  • "takeprofit": 13337,
  • "stoploss_wi": "running",
  • "stoploss": 1337,
  • "side": "s",
  • "quantity": 42,
  • "price": 8888,
  • "pl": -13640,
  • "market_wi": "filled",
  • "market_filled_ts": "020-09-15T10:50:40.332Z",
  • "margin_wi": "running",
  • "margin": 424242,
  • "liquidation": 1000,
  • "leverage": 50,
  • "creation_ts": "020-09-15T10:50:40.332Z"
}

Close

Close the user position, the PL will be calculated against the current bid or offer depending on the side of the position

Authorizations:
apiKeyAuth (
  • positions_close
)
query Parameters
pid
required
string^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}...
Example: pid=a2ca6172-1078-463d-ae3f-8733f36a9b0e

ID of the position to close.

Responses

Response samples

Content type
application/json
{
  • "pid": "a2ca6172-1078-463d-ae3f-8733f36a9b0e",
  • "exit_price": 2000,
  • "closed_ts": "020-10-15T10:50:40.332Z",
  • "closed": true,
  • "pl": 1337
}

Positions

Fetch users positions

Authorizations:
apiKeyAuth (
  • positions_get_open
  • positions_get_closed
)
query Parameters
type
required
string
Default: "running"
Enum: "open" "running" "closed"
Example: type=open

Choose wich kind of positions you need to fetch

from
integer <int64>
Example: from=1605162757621

Starting timestamp (in ms) (only used for closed positions)

to
integer <int64>
Example: to=1605162857621

Ending timestamp (in ms) (only used for closed positions)

limit
integer <int64> [ 1 .. 100 ]
Default: 100
Example: limit=100

Number of rows (only used for closed positions)

Responses

Response samples

Content type
application/json
{
  • "positions": [