REST API Best Practices for Interoperability

A great API makes integrating with services easy and painless. While there are numerous articles on what goes into a great API at a high level, the devil is in the details as they say.
This article is a best practices article with focus on interoperability, and thus ease of use. It is based on my experience with many APIs, both APIs that I work on for RingCentral and APIs I used for many other services. I’ve created SDKs in Go, Ruby and PHP and have worked with our team who has built SDKs in other languages including C#, Java, JavaScript, PHP, Python, Ruby, and Swift. I’m also on the Technical Committee for the OpenAPI Generator open source SDK codegen tool and have had conversations on the OpenAPI Specification project.
Here’s the list of topics that I’ve found important to make APIs easier to use.
- API and OAuth Hostnames
- Authentication
- Date Formats
- File Uploads and Multipart Requests
- Polymorphism
- Custom Variables
- Publishing API Specs and Postman Collections
API and OAuth Hostnames
There are two common approaches to API and OAuth hostnames:
- a single hostname for all customers such as
https://api.service.com - separate hostnames per customer, with a custom sub-domain for each customer, such as
https://<customer>.service.com
While having a separate subdomain is nice from an end-user website perspective, it can be problematic for API tools. The OpenAPI 2.0 / Swagger 2.0 specification does not support multiple URLs and while OpenAPI 3.0 specification does, you cannot include all customer names in a specification. This makes it harder to use API tools.
An example API that uses the subdomain approach is the Aha! API and OAuth URLs. The API supports a single domain if you use the X-Aha-Account HTTP request header. You can see my SDK implementation here to adjust for this:
https://github.com/grokify/oauth2more/blob/master/aha/aha_client.go
Recommendation: Use single hostnames for all customers, not custom sub-domains for each customer.
Authentication
Authentication is one of the most important, complex, and some times hardest to use, aspects of an API, often resulting in lots of support and Stack Overflow questions.
OAuth, JWT, and OpenID Connect are common, industry standard auth APIs that have a lot of library support and even OAuth services like Auth0.
Sometimes an API requires more than standard security. Staying with standards here helps as well. For example, the Visa API uses TLS Client Authentication so that the client must be in possession of a X.509 key pair and certificate. Because it is standards-based, there are still libraries that can help. You can see my Go implementation here:
- Visa API Client: https://github.com/grokify/oauth2more/blob/master/visa/visa_client.go
- TLS Config Library: https://github.com/grokify/gotilla/blob/master/crypto/tlsutil/tlsutil.go
Recommendation: Use common, standards-based authentication like OAuth, JWT, and OpenID Connect.
Date Time Formats
There are many date and time formats, however, only one supported for each in the OpenAPI 3.0 specification, the IETF RFC-3339 date and date-time formats. Using these times will allow ecosystem tools like API Explorers and SDK auto-generators to properly process dates and times.
date:2006-01-02date-time:2006-01-02T15:04:05Z07:00
While RFC-3339 is common now, many other systems use different date formats, including database servers. If your system is such a system, it is better to have the API use RFC-3339 and then translate it in your app before using it internally.
In an interesting exchange, I requested the OpenAPI Initiative support different time formats by adding strftime or Golang time format strings, but some on the OAI have indicated that the specification is not meant to support all APIs and that non-conforming APIs “should be blamed and made difficult”.
One API I was using, the Insightly API, has the following time formats which cannot be supported by the OpenAPI spec:
- Query string:
M/d/yyyy h:mm:ss AM/PM-11/7/2015 8:07:05 AM - Object data:
yyyy-MM-dd HH:mm:ss-2015-04-10 21:15:00
The Insightly API spec is available here for reference:
https://github.com/grokify/api-specs/tree/master/insightly
Below is a list of time functions from Go’s time library and my timeutil library:
ANSIC = "Mon Jan _2 15:04:05 2006"
UnixDate = "Mon Jan _2 15:04:05 MST 2006"
RubyDate = "Mon Jan 02 15:04:05 -0700 2006"
RFC822 = "02 Jan 06 15:04 MST"
RFC822Z = "02 Jan 06 15:04 -0700"
RFC850 = "Monday, 02-Jan-06 15:04:05 MST"
RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST"
RFC1123Z = "Mon, 02 Jan 2006 15:04:05 -0700"
RFC3339 = "2006-01-02T15:04:05Z07:00"
RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
ISO8601 = "2006-01-02T15:04:05Z0700"
ISO8601TZHour = "2006-01-02T15:04:05Z07"
ISO8601MilliNoTZ = "2006-01-02T15:04:05.000"
ISO8601CompactZ = "20060102T150405Z0700"
ISO8601CompactNoTZ = "20060102T150405"
InsightlyApiQuery = "_1/_2/2006 _3:04:05 PM"
SQLTimestamp = "2006-01-02 15:04:05"Recommendation: Use IETF RFC-3339
dateanddate-timeformats in your APIs, even if you have to convert them to a different internal format.
File Uploads and Multipart Requests
There are two general formats for file uploads:
multipart/form-data: this format was originally designed for web browser forms to upload files.multipart/mixed: a generic format to support multiple content files, such as APIs and email.
There can be an inclination to support multipart/mixed because it can provide a nicer interface as you can group all the file metadata into a single JSON part, whereas multipart/form-data generally requires a MIME part every field, and even multiple parts for arrays.
The downside of multipart/mixed is that it is not supported natively by many HTTP libraries or SDK generation tools and requires custom implementations for SDKs.
At RingCentral, we started with multipart/mixed and then moved to support multipart/form-data to make SDK support easier. We have SDKs in 7 languages: C#, Java, JavaScript, PHP, Python, Ruby and Swift, so it is important to minimize support efforts.
Recommendation: use
multipart/form-dataovermultipart/mixedfor uploading files.
Polymorphism
Polymorphism is the ability for a property to be expressed as different types. Two common implementations for this include:
- A JSON object with a property that can either be a string or another object. Travis CI and Postman both use this.
- A message that has standard metadata at the top level and then a property that can be one of many different properties.
While this can be adopted easily with dynamic typed languages like JavaScript and Python, it is problematic for static type languages like Go and Java. For SDK code generators, it can make it impossible to autogenerate working code.
In the case of a string vs. object choice, my recommendation is to always implement the object when there are potentially more than 1 sub-property, even if sometimes only one property may be used.
For the multi-object payload, which I’ve seen in numerous webhook messages, my recommendation is to use separate properties for each payload type. This way, most of those properties can be non-existent or null when not used.
While the OpenAPI 3.0 Spec does support polymorphism with the allOf, anyOf and oneOf designations, implementation is not complete and not very mature right now. A lot of software doesn’t support it at all yet and what support there is may not be ideal.
Here’s an example with different payloads for the body property.
Example 1 — Event1
{
"eventType":"eventType1",
"body":{
"eventType1Property":"string"
}
}Example 1 — Event 2
{
"eventType":"eventType2",
"body":{
"eventType2Property":"string"
}
}My preferred solution for this type of message is to have separate property names for each type. This way you can have different schemas for each and they won’t collide.
{
"eventType":"eventType1",
"eventType1":{...}.
"eventType2":{...},
"eventType3":{...}
}An example of this is the StatusPage webhook payload where they use separate component and incident properties instead of a generic body property. Some example messages are here:
https://github.com/grokify/chathooks/tree/master/docs/handlers/statuspage
Recommendation: The short answer on polymorphism is “don’t” do it.
Custom Variables
Numerous APIs have custom variables but the implementation can make them harder to support. Salesforce and other APIs are known for their heavy use of custom variables.
The way to handle this may be to auto-generate custom OpenAPI / Swagger specifications for each customer. This may be possible with APIs that list custom variables with types but it may be custom for each API.
Requests
In request parameters, custom properties may very difficult to auto-generate request objects for static languages. It may not be possible to make a request using an auto-generated method and a raw request may be needed which isn’t as ideal.
For example if a request query parameter was custom_variables[my_custom_var_name], there may be no way to auto-generate this because the list of custom variable names may not be know for the OpenAPI / Swagger spec.
Responses
Custom response handling with casting to the right type may be needed.
Publishing API / Postman Specs and SDKs
After going through all the work to create a great API and a great spec, publishing the spec is often a foregone conclusion, however, that shouldn’t necessarily be the end, if you have the resources.
OpenAPI / Swagger Specs
Publishing an API specification is a great step in ensuring interoperability. When you do, please make sure it validates. I’ve used the following validators successfully:
I like the API Dev Tools UI better, but have found that the Mermade error responses can be more meaningful for debugging purposes.
I also collect API specifications for the APIs I’ve used and have run into enough issues that sometimes I have to modify the specs to validate and generate working SDKs. You can see these here:
Postman
Postman is a great API IDE and was a fellow 2018 API World API winner along with our RingCentral API. It can import an OpenAPI / Swagger spec but it does not support all the OpenAPI features and OpenAPI doesn’t support all Postman features.
Rather than leaving the conversion to others, it’s useful to publish your own Postman collection. For RingCentral, I’ve published a Postman Collection and my conversion tool:
- Postman Collection: https://github.com/grokify/ringcentral-postman
- Conversion Tool: https://github.com/grokify/swaggman
SDKs
SDKs are a great way to make your API more consumable by developers of specific programming languages.
When publishing SDKs, publishing on GitHub and popular package management services like NPM, Packagist, Ruby Gems, etc. will ensure your SDK gets noticed and used.
While some SDKs are made by hand, a number have also been auto-generated using tools like OpenAPI Generator and Swagger Codegen. Some developer programs just publish a spec and link to these tools which is a great service, but if you can build one or more SDKs, you’ll learn what it takes to actually turn a spec into a SDK and sometimes it’s not trivial.
You can also see some SDKs I’ve created here. Go through the GitHub issues list and you can see some of what I’ve run into.
Recommendation: Provide specs and SDKs if you can. Things that work in theory can be much different in practice, so it’s good for you to experience it yourself rather than making your users do it.
Summary
When you build APIs please keep in mind the needs for ease of use and interoperability for your APIs. Think of the above items carefully. When you use APIs, think of the difficulties you may encounter and document them as examples for others. Please post any comments you have here so we can keep track of other interoperability best practices!
Find me on:
- Twitter: https://twitter.com/grokify
- GitHub: https://github.com/grokify
Check out our APIs at:
- RingCentral APIs: https://developers.ringcentral.com/
- RingCentral Engage Digital APIs: https://engage-api-docs.readthedocs.io

