NAV Navbar
.NET Core JSON Python SH

Introduction

Welcome to the Stablehouse's developer documentation.

{
"result": ...,
"isSuccessful": true,
"errorMessage": null,
}

This document explains the general guidelines about how to the Stablehouse API.

HTTP Status Codes

HTTP status codes are limited to 200, 400 (Bad Request), 401 (Unauthorized), 404 (Not Found) and 429 (Rate Limit Exceeded). HTTP 500 and above may be returned when the service is down for maintaince or when an unexpected error occurs.

HTTP 200

This indicates the specified action was successful.

HTTP 400

This indicates that the request failed because the parameter a user specified was invalid. This could be a datatype issue, such as specifying a non-numeric string in a anumeric field, or because of a higher level rejection, such as insufficent funds to execute a trade.

HTTP 429

This indicates the request failed because of exceeded rate limits. In that situation, the following headers will be set on the response:

Header Description
X-Rate-Limit-Limit The maximum amount of requests that can be issued in the time window.
X-Rate-Limit-Remaining The amount of requests that can be issued within the rate limit budget.
X-Rate-Limit-Reset A ISO 8601 compliant timestamp of when requests will be allowed.

Authentication

Generating an API Key

You must create an API key through Stablehouse's website. Upon creating a key you will be given the following items:

The Key and Secret will be randomly generated and provided to you by Stablehouse.
In addition to those keys, you can provide a Passphrase to further secure your API access.
Stablehouse stores the salted bcrypt hash of your passphrase for verification.

Authenticating

curl -X POST \
  https://api.stablehouse.io/api/funds/get-deposit-address \
  -H 'Accept: text/plain, application/json, text/json' \
  -H 'Content-Type: application/json' \
  -H 'SH-API-KEY: yDC2HdqvenXQdLQMaq6h62b27P41JqS0LRVT+iuL/CQ=' \
  -H 'SH-SIGNATURE: 84c3d5a0d516a5080b3f8ac8b14ba4d55006e73c4d62c05adce2366399454982' \
  -H 'SH-TIMESTAMP: 1550248260' \
  -H 'cache-control: no-cache' \
  -d '{"CurrencyCode":"TUSD"}'
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Mime;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;

namespace StablehouseExample
{
    class Program
    {
        static async Task Main(string[] args)
        {
            string key = "MY_KEY";
            string secret = "MY_SECRET";
            string passphrase = "MY_PASS";
            string baseUrl = "https://api.stablehouse.io/v1";

            var handler = new StablehouseApiAuthenticationHandler(key, secret, passphrase)
            {
                InnerHandler = new HttpClientHandler()
            };

            var client = new StablehouseClient(handler, true)
            {
                BaseAddress = new Uri(baseUrl)
            };

            var accountId = await client.WhoAmI(CancellationToken.None);
            Console.WriteLine($"My account ID: {accountId}");
        }
    }

    public class StablehouseClient : HttpClient
    {
        public StablehouseClient(DelegatingHandler handler, bool disposeHandler) : base(handler, disposeHandler)
        {
            DefaultRequestHeaders
                .Accept
                .Add(new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Json));
        }

        public async Task<string> WhoAmI(CancellationToken cancellationToken)
        {
            var httpResponseMessage = await PostAsync("/v1/system/whoami", null, cancellationToken);
            httpResponseMessage.EnsureSuccessStatusCode();
            var content = await httpResponseMessage.Content.ReadAsStringAsync();
            var response = JsonConvert.DeserializeObject<StablehouseResponse<string>>(content);
            return response.Result;
        }
    }

    public class StablehouseResponse<T>
    {
        public T Result { get; set; }
        public bool IsSuccessful { get; set; }
        public string ErrorMessage { get; set; }
    }

    public class StablehouseApiAuthenticationHandler : DelegatingHandler
    {
        private readonly string _apiKey;
        private readonly HMACSHA256 _hmacSha256;
        private readonly string _stableHousePassphrase;

        public StablehouseApiAuthenticationHandler(string apiKey, string apiSecret, string stableHousePassphrase)
        {
            _apiKey = apiKey;
            _hmacSha256 = new HMACSHA256(Encoding.UTF8.GetBytes(apiSecret));
            _stableHousePassphrase = stableHousePassphrase;
        }

        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            var unixTimeStamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();

            var method = request.Method.Method.ToUpperInvariant();
            var requestPath = request.RequestUri.PathAndQuery;
            var requestBody = request.Content is null ? string.Empty : await request.Content?.ReadAsStringAsync();
            var what = unixTimeStamp + method + requestPath + requestBody;

            byte[] signatureBytes;
            lock (_hmacSha256)
                signatureBytes = _hmacSha256.ComputeHash(Encoding.UTF8.GetBytes(what));

            var signature = ToHexString(signatureBytes);
            request.Headers.Add("SH-API-KEY", _apiKey);
            request.Headers.Add("SH-SIGNATURE", signature);
            request.Headers.Add("SH-TIMESTAMP", unixTimeStamp.ToString("G29"));

            if (!string.IsNullOrWhiteSpace(_stableHousePassphrase))
                request.Headers.Add("SH-PASSPHRASE", _stableHousePassphrase);

            return await base.SendAsync(request, cancellationToken);
        }

        private static string ToHexString(byte[] bytes)
        {
            var sb = new StringBuilder();
            foreach (var t in bytes)
                sb.Append(t.ToString("X2"));
            return sb.ToString();
        }

        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
            _hmacSha256?.Dispose();
        }
    }
}
# Requires python-requests. Install with pip:
#
#   pip install requests
import json, hmac, hashlib, time, requests, base64
from requests.auth import AuthBase

# Create custom authentication for Stablehouse
class StablehouseExchangeAuth(AuthBase):
    def __init__(self, api_key, secret_key, pass_phrase):
        self.api_key = api_key
        self.secret_key = secret_key
        self.pass_phrase = pass_phrase

    def __call__(self, request):
        timestamp = str(int(time.time()))
        queryString = ''
        finalBody = request.body or ''
        what = timestamp + request.method + request.path_url + queryString + finalBody
        signature = hmac.new(bytes(self.secret_key, 'UTF-8'), str(what).encode('utf-8'), hashlib.sha256)
        signature_hexed = signature.hexdigest().upper()

        request.headers.update({
            'SH-API-KEY': self.api_key,
            'SH-SIGNATURE': signature_hexed,
            'SH-TIMESTAMP': timestamp,
            'SH-PASSPHRASE': self.pass_phrase,
        })
        return request

def printResults(r):
    print('\r\n\r\n')
    print('================================================================================')
    print(' DISPLAYING RESULTS')
    print('================================================================================')
    print('\r\n')
    print('response status code = ' + str(r.status_code) + '\r\n')
    print('response raw text = ' + r.text + '\r\n')
    json_data = json.loads(r.text)
    if json_data['isSuccessful']:
        json_result_field = json_data['result']
        print('json_result_field = ' + str(json_result_field)  + '\r\n')
    else:
        json_error_message_field = json_data['errorMessage']
        print('json_error_message_field = ' + str(json_error_message_field)  + '\r\n')
    print('--------------------------------------------------------------------------------')

# add your api Key here
API_KEY = ''

# add your api Secret here
API_SECRET = ''

# add your api Passphrase here
API_PASSPHRASE = ''

api_url = 'https://api.stablehouse.io'
auth = StablehouseExchangeAuth(API_KEY, API_SECRET, API_PASSPHRASE)

# Get funds
r = requests.post(api_url + '/api/funds/get-funds', auth=auth)
printResults(r)
#{
#  "result": {
#    "funds": [
#      {
#        "currencyCode": "string",
#        "currencyName": "string",
#        "currencyDecimals": 0,
#        "amount": "string",
#        "lockedAmount": "string",
#        "canMarketMake": true
#      }
#    ]
#  },
#  "isSuccessful": true,
#  "errorMessage": "string"
#}

To be authenticated, all requests must contain the following headers:

Header Description
SH-API-KEY The api key as a string
SH-SIGNATURE The hex representation of the signature
SH-TIMESTAMP A timestamp for your request (UTC UNIX timestamp in seconds)

If the API Key was created using a Passphrase, the request must contain the following additional header:

Header Description
SH-PASSPHRASE The passphrase that you provided when creating the API Key

To generate the SH-SIGNATURE header:

  1. Create a SHA256 HMAC against your UTF8 encoded secret.

  2. Build a string that contains timestamp + method + requestPath + body. The timestamp should be Unix Epoch seconds (UTC). The method is the HTTP method and should be upper case. The requestPath is the part of the URL after the domain. requestPath should include the leading forward-slash, and should include the query string if one exists (i.e. the full path). The body is the request body string or omitted if there is no request body.

  3. Compute the hash for this string using the SHA256 HMAC you created on step 1.

  4. HEX-encode the output. This string is case insensitive. The result is the SH-SIGNATURE header.

Example:

You want to query POST https://api.stablehouse.io/v1/deposits/get-address with the following body {"CurrencyCode":"TUSD"}.

Your API Key is yDC2HdqvenXQdLQMaq6h62b27P41JqS0LRVT+iuL/CQ= and your Secret is ZO7jwHpr2a3eVUAASs6xNC7j/NpANUhVvjJbwANGsjM=.

You start by computing the current unix timestamp (this should be the unix epoch seconds UTC). We will suppose it's 1550248260.

  1. You then continue by generating the string to sign:
    It's 1550248260POST/v1/deposits/get-address{"CurrencyCode":"TUSD"}

  2. You sign this string using your Secret. You get the following signature:
    84c3d5a0d516a5080b3f8ac8b14ba4d55006e73c4d62c05adce2366399454982

  3. Finally, you add those 3 headers to your request.

Inventory

Listing funds

{
    "timestamp": 1231006505000,
    "result":
    {
        "funds":
        [
            {
                "currencyCode": "BTC",
                "currencyName": "Bitcoin",
                "totalAmount": "0.00000000",
                "lockedAmount": "0.00000000",
                "pendingFundingAmount": "0.00000000",
                "allowReceive": true,
                "fundingRebateBps": "15",
                "receivingRebateBps": "0",
                "underlyingCurrencyName": "Bitcoin",
                "underlyingCurrencyCode": "BTC",
                "loanBps": "100"
            },
            .
            .
            .
            {
                "currencyCode": "LUSDT",
                "currencyName": "Liquid USDT",
                "totalAmount": "0.00",
                "lockedAmount": "0.00",
                "pendingFundingAmount": "0.00",
                "allowReceive": true,
                "fundingRebateBps": "15",
                "receivingRebateBps": "0",
                "underlyingCurrencyName": "United States Dollar",
                "underlyingCurrencyCode": "USD",
                "loanBps": "100"
            }
        ],
        "ious":
        [
            {
                "underlyingCurrencyCode": "BTC",
                "underlyingCurrencyName": "Bitcoin",
                "amount": "0.00000000"
            },
            {
                "underlyingCurrencyCode": "EUR",
                "underlyingCurrencyName": "Euro",
                "amount": "0.00"
            },
            {
                "underlyingCurrencyCode": "JPY",
                "underlyingCurrencyName": "Japanese Yen",
                "amount": "0"
            },
            {
                "underlyingCurrencyCode": "USD",
                "underlyingCurrencyName": "United States Dollar",
                "amount": "0.00"
            }
        ]
    },
    "isSuccessful": true,
    "errorMessage": null
}

You inventory is composed of two types of balances: funds and IOUs (loans). A fund is representing a currency (stablecoin or non-stable cryptocurrency) that can be exchanged or sent to an external wallet. IOUs are a debt that Stablehouse owes to your account.

Each currency has an associated underlying currency that the currency is understood to be fungible with. For example, a Tether Omni (USDT) has an underlying currency of USD, and a Liquid Bitcoin (LBTC) has an underlying currency of Bitcoin (BTC). Bitcoin exists as both a currency (BTC) and an underlying currency (BTC). Funds are denominated in currencies, and IOUs are denominated in underlying currencies.

Here is an example response from the /v1/funds/get-funds endpoint.

The funds property contains an array of objects representing currencies that you have balances for at Stablehouse.

Key Description
currencyCode The currency code. It should be used when communicating with the API
currencyName The currency name is the full name of the currency
totalAmount You total balance for that currency inside your account. Inside the pool or not
lockedAmount The amount out of your total balance that is reserved for trading. This amount is not available for market making and won't be exchanged
pendingFundingAmount The amount out of your pooled balance that is about to be used for funding. If the trade succeeds you might receive another currency or an IOU
allowReceive True if you accept to receive that currency as part of a trade funding
fundingRebateBps The rebate in bips that should be applied when you are funding that currency during a funding transaction (See fees)
receivingRebateBps The rebate in bips that should be applied when you are receiving that currency during a funding transaction (See fees)
underlyingCurrencyName The underlying currency (USD, EUR, BTC...) the currency is pegged to
underlyingCurrencyCode The code name for the underlying currency
loanBps The annualized rate used for loans created during a funding transaction (See fees)

An IOU is a representation of a debt Stablehouse has toward your account. It is denominated in the underlying currency of the currency Stablehouse borrowed from you.

Key Description
underlyingCurrencyName The underlying currency (USD, EUR, BTC...) name
underlyingCurrencyCode The underlying currency code
amount The total amount of IOU for that underlying currency

See fees for more information about the fee system and the purpose of IOUs.

Note that decimals are not the same for every currency. Each currency's decimal depends on the underlying currency it is associated with. Here is the list of decimals:

Underlying currency Decimals
Bitcoin 8
United States Dollar 2
Euro 2
Japanese Yen 0

Currencies

public async Task<CurrencyModel[]> ListCurrencies(CancellationToken cancellationToken)
{
    var httpResponseMessage = await GetAsync("/v1/currencies", cancellationToken);

    if (!httpResponseMessage.IsSuccessStatusCode)
        throw new Exception(httpResponseMessage.ReasonPhrase);

    var content = await httpResponseMessage.Content.ReadAsStringAsync();

    var response = JsonConvert.DeserializeObject<StablehouseResponse<CurrencyModel[]>>(
        content, _jsonSerializerSettings);

    if (!response.IsSuccessful)
        throw new Exception(response.ErrorMessage);

    return response.Result;
}

public class CurrencyModel
{
    public string Name { get; set; }
    public int Decimals { get; set; }
    public string Code { get; set; }
    public string Blockchain { get; set; }
    public string UnderlyingCurrencyCode { get; set; }
    public string UnderlyingCurrencyName { get; set; }
    public string[] TradableCurrencies { get; set; }
    public bool WithdrawalsAllowed { get; set; }
    public bool TradingAllowed { get; set; }
}
{
    "timestamp": 1231006505000,
    "result":
    [
        .
        .
        {
            "name": "Tether Erc20",
            "decimals": 2,
            "code": "USDTE",
            "blockchain": "ETH",
            "underlyingCurrencyCode": "USD",
            "underlyingCurrencyName": "United States Dollar",
            "tradableCurrencies":
            [
                "SUSD",
                "PAX",
                "TUSD",
                "USDS",
                "USDT",
                "SAI",
                "USDC",
                "GUSD",
                "LUSDT",
                "BTC",
                "EURS",
                "LBTC"
            ],
            "withdrawalsAllowed": true,
            "tradingAllowed": true
        }
        .
        .
    ],
    "isSuccessful": true,
    "errorMessage": null
}

The best way to find more information about the currencies listed on Stablehouse is to call the endpoint GET /v1/currencies.

Key Description
blockchain The underlying blockchain the currency is moving on. Can be used to determine what kind of address your should request withdrawals to
decimals The number of decimal supported for that currency
tradableCurrencies A list of currency from/to which you can trade that currency
withdrawalsAllowed Whether withdrawals are permitted on that currency
tradingAllowed Whether trading is enabled on that currency

Trading

Funding and Fees

public async Task<StablehouseResponse> SetFundingRebate(SetFundingRebateRequest request, CancellationToken cancellationToken)
{
    var requestContent = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, JsonContentType);
    var httpResponseMessage = await PostAsync("/v1/funds/set-funding-rebate", requestContent, cancellationToken);

    if (!httpResponseMessage.IsSuccessStatusCode)
        throw new Exception(httpResponseMessage.ReasonPhrase);

    var content = await httpResponseMessage.Content.ReadAsStringAsync();

    var response = JsonConvert.DeserializeObject<StablehouseResponse>(
        content, _jsonSerializerSettings);

    if (!response.IsSuccessful)
        throw new Exception(response.ErrorMessage);

    return response;
}

public async Task<StablehouseResponse> SetReceivingRebate(SetReceivingRebateRequest request, CancellationToken cancellationToken)
{
    var requestContent = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, JsonContentType);
    var httpResponseMessage = await PostAsync("/v1/funds/set-receiving-rebate", requestContent, cancellationToken);

    if (!httpResponseMessage.IsSuccessStatusCode)
        throw new Exception(httpResponseMessage.ReasonPhrase);

    var content = await httpResponseMessage.Content.ReadAsStringAsync();

    var response = JsonConvert.DeserializeObject<StablehouseResponse>(
        content, _jsonSerializerSettings);

    if (!response.IsSuccessful)
        throw new Exception(response.ErrorMessage);

    return response;
}

public class StablehouseResponse
{
    public bool IsSuccessful { get; set; }
    public string ErrorMessage { get; set; }
}

public class SetFundingRebateRequest
{
    public string CurrencyCode { get; set; }

    public int FundingRebateBps { get; set; }
}

public class SetReceivingRebateRequest
{
    public string CurrencyCode { get; set; }
    public int ReceivingRebateBps { get; set; }
}

There are two type of fees on Stablehouse:

The service fee is a fixed fee collected by Stablehouse, while the market maker fee is dynamically set by liquidity providers and is paid to them. When a trade is created Stablehouse pulls the available liquidity from market makers and computes fundings by using the best offers.

The market maker fee is split in two:

The Funding Rebate is used to price in the value of holding a given currency. It is a rebate the liquidity provider takes to give up ownership of that currency. The Receiving Rebate is used to price in any premium/discount the received currency might have on other exchanges.

On Stablehouse, a pair is considered FX when the two underlying currencies are different, for example when swapping USDC to BTC. In that case, instead of giving up ownership of a given currency, the market maker will lend the amount to Stablehouse. A loan will be created corresponding to the amount provided + the funding rebate.

Loans

public async Task<StablehouseResponse> SetLendingRate(SetLendingRateRequest request, CancellationToken cancellationToken)
{
    var requestContent = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, JsonContentType);
    var httpResponseMessage = await PostAsync("/v1/funds/set-lending-rate", requestContent, cancellationToken);

    if (!httpResponseMessage.IsSuccessStatusCode)
        throw new Exception(httpResponseMessage.ReasonPhrase);

    var content = await httpResponseMessage.Content.ReadAsStringAsync();

    var response = JsonConvert.DeserializeObject<StablehouseResponse>(
        content, _jsonSerializerSettings);

    if (!response.IsSuccessful)
        throw new Exception(response.ErrorMessage);

    return response;
}

public class StablehouseResponse
{
    public bool IsSuccessful { get; set; }
    public string ErrorMessage { get; set; }
}

public class SetLendingRateRequest
{
    public string UnderlyingCurrencyCode { get; set; }
    public int Rate { get; set; }
}

In the case where a currency is lent to Stablehouse the maker is earning interest on the amount left to be reimbursed.

Funding matrix


public async Task<GetFundingMatrixResponse> GetFundingMatrix(CancellationToken cancellationToken)
{
    var httpResponseMessage = await GetAsync("/v1/funds/get-matrix", cancellationToken);

    if (!httpResponseMessage.IsSuccessStatusCode)
        throw new Exception(httpResponseMessage.ReasonPhrase);

    var content = await httpResponseMessage.Content.ReadAsStringAsync();

    var response = JsonConvert.DeserializeObject<StablehouseResponse<GetFundingMatrixResponse>>(
        content, _jsonSerializerSettings);

    if (!response.IsSuccessful)
        throw new Exception(response.ErrorMessage);

    return response.Result;
}

public class GetFundingMatrixResponse
{
    public FundingMatrix FundingMatrix { get; set; }
}

public class FundingMatrix
{
    public FundingRate[] FundingRates { get; set; }
}

public class FundingRate
{
    public string CurrencyCode1;

    public string CurrencyCode2;

    [Required]
    public string Rate;

    [Required]
    public string Liquidity;
}
{
    "timestamp": 1231006505000,
    "result":
    {
        "fundingMatrix":
        {
            "fundingRates":
            [
                .
                .
                {
                    "currencyCode1": "USDTE",
                    "currencyCode2": "BTC",
                    "rate": "10056",
                    "liquidity": "281.22276187"
                },
                .
                .
                {
                    "currencyCode1": "LUSDT",
                    "currencyCode2": "EURS",
                    "rate": "1.0779",
                    "liquidity": "6540.69"
                },
                .
                .
                {
                    "currencyCode1": "BTC",
                    "currencyCode2": "USDC",
                    "rate": "9987.95",
                    "liquidity": "94009.37"
                },
                .
                .
            ]
        }
    },
    "isSuccessful": true,
    "errorMessage": null
}

This API allows the retrieval of a matrix showing the rate and available liquidity for each currency pair. The rates include both the service fee and the market maker fee. The market maker fee is the cost for taking all available liquidity. The rate for a trade will always be equal to or better than the rate provided in the funding matrix as the trade will always use less or equal to all available liquidity.

In this example, the exchange rate from USDTE to BTC is 10056. It means you will need to exchange 10056 USDTE to buy 1 BTC. The total available liquidity between the two is 281.22276187 BTC.

If instead you would like to sell bitcoins for a currency whose underlying is the US Dollar, like USDC, you must find the appropriate result. In that example, you will only be able to exchange your bitcoin for 9987.95 USDC.