Back to Articles

New Luno API SDKs

Neil Garb
3 minute read

From code to client

Many customers interact with Luno using our API, which gives anyone access to market data. Developers use the Luno API to build applications to automatically buy, sell, send, receive, deposit and withdraw supported local and digital currencies.

If this still makes sense to you, read on.

The API documentation lists SDKs for popular programming languages. Only the Go SDK, though, is officially supported.

Maintaining SDKs in multiple languages is difficult and costly. Each supported language requires duplicated work in another language, leading to bugs and constant maintenance.

To automate this process, we’ve annotated all our API handlers in our internal Go code with Swagger. The swagger tool can read annotations and generate an OpenAPI spec. The generated spec is a JSON file describing each annotated endpoint, its input parameters and its output.

  // swagger:parameters getOrderBook  type getOrderbookRequest struct {      // in: query      // required: true      Pair string `json:"pair"`  }    // swagger:model  type getOrderBookResponse struct {      Bids []Order `json:"bids"`      Asks []Order `json:"asks"`  }    // swagger:route GET /api/1/orderbook orders getOrderBook  //  // Get order book  //  // Returns a list of bids and asks in the order book. Ask orders are sorted  // by price ascending. Bid orders are sorted by price descending. Note that  // multiple orders at the same price are not necessarily conflated.  //  // Responses:  // 200: body:getOrderBookResponse description:OK  func GetOrderBook(w http.ResponseWriter, r *http.Request) {      // ...  }  

An example API endpoint with swagger annotations

For example, the code sample above shows an API endpoint with Swagger annotations. These indicate the API has an endpoint getOrderBook, requiring a query-string parameter called pair. It also designates that the endpoint’s response, if the HTTP response code is 200, will be a JSON message with a list of bid and ask orders.

To generate an OpenAPI JSON spec for the above code, you would install the swagger tool (go get -u github.com/go-swagger/go-swagger) and run swagger generate spec.

  "/api/1/orderbook": {      "get": {          "description": "Returns a list of bids and asks in the order book. Ask orders are sorted by\nprice ascending. Bid orders are sorted by price descending. Note that\nmultiple orders at the same price are not necessarily conflated.",          "tags": [              "market"          ],          "summary": "Get order book",          "operationId": "getOrderBook",          "parameters": [              {                  "type": "string",                  "format": "pair",                  "example": "XBTZAR",                  "description": "Currency pair",                  "name": "pair",                  "in": "query",                  "required": true              }          ],          "responses": {              "200": {                  "description": "OK",                  "schema": {                      "$ref": "#/definitions/getOrderBookResponse"                  }              }          }      }  }  

Excerpt from Luno’s OpenAPI spec

The generated JSON spec provides a language-agnostic description of the API. Using this, we’re able to auto-generate API clients in various languages. The result is a set of up-to-date, documented API call wrappers, each with a type-safe request and response objects.

  type GetOrderBookRequest struct {      // Currency pair      Pair string `json:"pair" url:"pair"`  }    type GetOrderBookResponse struct {      Asks []OrderBookEntry `json:"asks"`      Bids []OrderBookEntry `json:"bids"`      Timestamp int64 `json:"timestamp"`  }    // GetOrderBook makes a call to GET /api/1/orderbook.  //  // Returns a list of bids and asks in the order book. Ask orders are sorted by  // price ascending. Bid orders are sorted by price descending. Note that  // multiple orders at the same price are not necessarily conflated.  func (cl *Client) GetOrderBook(ctx context.Context, req *GetOrderBookRequest) (*GetOrderBookResponse, error) {      var res GetOrderBookResponse      err := cl.do(ctx, "GET", "/api/1/orderbook", req, &res, false)      if err != nil {          return nil, err      }      return &res, nil  }  

Auto-generated code for the getOrderBook endpoint

The functionality to generate clients is available using swagger generate client. But we decided to write our own set of tools to do this. We wanted the code to be as lightweight as possible, and to be able to tweak the treatment of data structures. The core client generation code essentially traverses the JSON spec and invokes Go templates to output the individual functions.

  // {{.Name}} makes a call to {{.Method}} {{.Path}}.  //  {{range .Description -}}  // {{.}}  {{end -}}  func (cl *Client) {{.Name}}(ctx context.Context, req *{{.Request.Name}}) (*{{(index .Responses 0).Name}}, error) {      // …  }  

An excerpt from the template for the Go client

The ability to generate client SDKs automatically means that we can publish updates whenever changes to the API are deployed. This allows us to release improvements to the API faster while keeping our SDKs up to date.

As a start, we’ve made the generated Go SDK available. Alpha releases for PHP and Python will follow shortly.

Did you find this interesting? Help us take this further, join our engineering squad.

Avatar Neil Garb
Author

Neil Garb

Neil holds a BSc degree in IT from the University of Cape Town. He has worked as a software developer in several sectors, including publishing, travel, marketing, social media and e-commerce at one of Africa's largest online retailers.

It’s never too late to get started.
Buy, store and learn about Bitcoin and Ethereum now.

Desktop Icon Apple App Store Logo Google Play Store Logo