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.
The base url for all requests is
https://api.stablehouse.io/v1
.The complete API Reference is accessible through our Swagger UI here. The raw Swagger JSON document is available here.
While it's possible to use the API with plain HTTP calls, we recommend that you use a Swagger code generation tool like https://github.com/RSuter/NSwag to generate your API Client.
All the API responses are wrapped into a common JSON object.
Most endpoints use HTTP POST with a request body. The request body must always be JSON, and the response body will always be JSON. URL-based parameters, including path parameter and query string parameters, are not used in 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:
- ApiKey
- Secret
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:
Create a SHA256 HMAC against your UTF8 encoded secret.
Build a string that contains
timestamp + method + requestPath + body
. Thetimestamp
should be Unix Epoch seconds (UTC). Themethod
is the HTTP method and should be upper case. TherequestPath
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). Thebody
is the request body string or omitted if there is no request body.Compute the hash for this string using the SHA256 HMAC you created on step 1.
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
.
You then continue by generating the string to sign:
It's1550248260POST/v1/deposits/get-address{"CurrencyCode":"TUSD"}
You sign this string using your Secret. You get the following signature:
84c3d5a0d516a5080b3f8ac8b14ba4d55006e73c4d62c05adce2366399454982
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:
Service fee
Market maker fee
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:
Funding Rebate
Receiving Rebate
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.