Crypto Exchange API Woes

Crypto Exchange API Woes

This post is directed towards crypto exchanges. So when I say “you” I mean the exchanges:

1. Client Reference IDs

If there’s a connectivity issue or my engine crashes midway through sending multiple order creation requests, how do I, upon restarting, easily determine which requests were successful and which failed? Without a client reference ID, I’m forced to identify unrecognized exchange orders, then try to match them against my backlog of pending new orders. If a race condition occurs between my order creator and my trade execution poller/stream, I have to deal with edge cases of executions against orders that are still marked as “pending creation” in my engine. I also have to deal with these edge cases during crash recovery. Sometimes the trade executions I get from the exchange are at a better price than the order I’m creating (ie, if I put a buy order in at $10 I can get executions at $9) so I have to fuzzy match the order, and may misallocate it. These are all hacky workarounds because you don’t let me specify my own ID for the order. You can still have your own ID, but let me have mine too.

2. Error Handling

  • You don’t list what your error codes mean.
  • You do list error codes, but some are missing.
  • You sometimes, for no apparent reason, don’t return JSON, but HTML pages instead, like a 404 page if I go to a nonexistant endpoint (although still on /api/).
  • You have multiple ways of returning errors - as HTML, as a HTTP response code, in an error field, and in the response body.
  • You have intermittent errors that give no details/explanation as to what went wrong.
  • You do weird, unintuitive stuff like return an orderId of 0 if order creation failed.
  • You return errors that have nothing to do with the actual problem.
  • You return errors when none occured. For example, Liqui sometimes returns “external service unavailable” on order creation/cancellation requests, but actually creates/cancels the order.

3. Documentation

  • For enum-type parameters, you don’t specify all the possible values (for requests AND responses).
  • You don’t specify which parameters are mandatory and which are optional.
  • Your documentation is just straight wrong. Liqui was guilty of this for a while, saying they implemented all BTCe functionality when some of it was just plain missing. OKEX tells you to set the a header field with the key “contentType” when really they mean “Content-Type”. If you do the former, you get a “missing mandatory parameters” error (see #2, Error Handling).
  • You don’t specify the precision of the price and quantity of all your pairs, and have no endpoint for it either.
  • You state you have rate limits, but give no indication as to what they actually are.
  • The language used is ambiguous and there aren’t enough examples to clarify. The documentation was clearly written at the last minute and minimal proofreading was done. Have someone who has never used the API before go through the documentation, implement all the methods in their language of choice, then provide you feedback afterwards about how things could’ve been clearer.
  • The library you wrote as an example is either outdated or awfully written and missing key functionality.
  • You use your own terminology without any clarification as to what they mean. For example, OKEX has orders, trades, and deals.

4. Missing Endpoints & Information

Whilst most APIs get the public orderbook functionality right, they completely fail for getting a stream of executions against my own orders. It’s one of the most important things for a trade engine: the status of my orders. I need to know immediately when there is an execution, and if I’m trading against a dozen crosses, I shouldn’t have to have to have a dozen websocket connections open or make 12 REST requests for information that could easily be grouped together in one place. Sometimes the information I need isn’t available via the API at all. Bittrex and others don’t even offer a way to get a list of executions. Others do, but miss important information, such as whether I’m the maker or the taker so I can calculate my fees or the execution ID so I can safeguard against processing the same execution twice.

5. Authentication

Sometimes I feel like I have to do a raindance and stand on my head whilst I send a request for it to be properly authenticated. For example, some exchanges require me to take all of the requests parameters, put them in alphabetical order, turn them into a query string, add &secret_key= to the end, hash this via MD5 to get the sign value, add "&sign=" to the query string, then submit all of this via a form-url-encoded API request. All of this is even more fun with #3 above. For example, some exchanges mention that a nonce is required, but upon checking, the nonce isn't used at all. Others specify you have to set a header field, but list the wrong header field. Others require the signature to be all lowercase, but don't mention it anywhere. Others show the wrong error message on invalid authentication. All of this leads to making authentication implementation a miserable experience.


Getting this stuff right is incredibly important for companies integrating with you. Not catching an error from Bitstamp that was returned in a response body rather than an error field (like other errors) once cost my company $250,000 because we thought we were hedging our shorts when we weren’t. We’ve also wasted countless hours trying to work around incorrect/missing documentation on top of poor implementation. All of these issues are easily fixed and will help people with trade engines integrate with your exchange, generating you more money in fees, so please fix them, for our sake and for yours.

Contact Me

Is there something in this post that’s incorrect, unclear, or could be improved? Tweet or DM me on Twitter. If you liked this post, feel free to follow me :-).

Jonathan Sterling

Written by

In March 2017, I quit my job as a software engineer and began travelling around the world. This open-source blog exists to share my thoughts and let my friends and family know that I'm not dead.