One of the benefits of basing an API on schemas is the ability to use code generators. Generated code can have some disadvantages like generating hard to understand code and difficulties in customizing a request beyond what the generated code allows. Personally, in many cases, I find manually written code to more elegant. However, for schema-based code, code generation is a tool that can quickly and consistently build good solutions.

Generated code should be consistent across all API requests and responses. Therefore, once a request is made and a response is processed, the usage knowledge should apply to all requests and responses. If there is a bug in the generated code, a fix can be consistently applied to all of the applicable code. Furthermore, if there is a need for clients in different programming languages, code generation can ensure that all languages are able to get access to the new APIs with minimal effort. In effect, a code generator can be a codification of how clients should interact with an API.

Code generation applies to more than code for APIs, but it is especially suited to API client SDKs. Client SDKs are tedious to maintain, and eventually maintaining multiple programming language bindings will be required (if not immediately, eventually). Schemas can help maintain and verify API behavior but generating code can ensure a consistency from the clients as well. When a new API needs to be exposed, instead of an engineer having to recall how to write the SDK code (and keep the code consistent with the rest of the SDK), the code generator can be given the schema and output the code.

In API SDKs, there is usually a base protocol client such as a HTTP client. The client can be coded manually and expose unique features per platform. The generated code is usually the requests, responses, and domain specific objects.

The generated code can also be generated in a way to allow custom protocol clients to use the code. Therefore, the code can still be of some value where someone wants to quickly interact with the API but wants to customize the protocol behavior. Imagine an app that has specific requirements for timeouts or an app that needs to use a specific pool of HTTP clients for outbound requests. If the generated code is not coupled with the protocol client, it can still possibly be used to serialize/deserialize the request/response.

While I’ve not had a strong preference towards code generation in the past, code generation can be a great tool, especially when API SDKs are needed.

While a successful 2xx HTTP response is what most clients should look for, additional metadata is usually included in the HTTP response. For instance, a correlation ID can be provided.

A correlation ID is useful in identifying a request. It is commonly used where the initial API endpoint (usually a load balancer or a request router) creates an ID, and then the ID is propagated throughout the service’s systems (e.g. when calling downstream internal services). Logs and metrics can use the correlation ID as part of their context, so it is easy to lookup related logs/metrics using the correlation ID. In effect, the correlation ID can be used to trace a request’s execution path across service boundaries. The correlation ID may be propagated to the initial client as part of the HTTP response.

If you are operating a client to a third party service, it is generally benefical to store correlation IDs for a period of time. When there are issues with processing a request, giving the correlation ID can save time for you and the support team.

Another tip is that you may want to keep correlation IDs for both failed requests and successful ones. There may be times when “successful” requests need to be investigated.

Correlation IDs are also commonly called request IDs, trace IDs, and debug IDs. While there are possible subtle distinctions, in the end, they are useful in isolating relevant data when communicating to a support or development team.

As a client to any API, you may be concentrating on a specific API endpoint and the documented request body and parameters. An important consideration though is looking for headers and other parameters that may be documented as being accepted across the entire API.

For instance, one very important header (which is not standardized) is an idempotency header. While RESTful APIs have conventions for dealing with idempotency, the practical reality is that many APIs today rely on an idempotency header, especially APIs involving payments or other forms of transactions. Each API also deals with idempotency slightly differently, but the general theory is that a request with the same idempotency header value should only be processed once. If the request was already processed, usually a cached response is returned. Some APIs require that the request body must be the same, so the specific details vary between APIs. See the PayPal and Stripe documentation for examples.

It is easy to miss the general API details in the documentation. However, in order to have a successful API integration, it is critical to use parameters like the idempotency header when available.