Skip to content

Requirements & API: Design

This chapter explores the structure and API design specifics of HTTP API and its definition with the OpenAPI specification. Then, we distinguish a traditional code-first, design-first, and fresh-new AI approach to API development.

The Structure

The Contract is an essential part of the API design, which relies on a protocol and architecture style.

For instance, HTTP-based API design differs from its SOAP counterpart. Even within a single protocol, the design of REST(ful) and GraphQL is different. They are based on HTTP, but how they are designed is driven by corresponding architecture styles.


Note: As a Product person (product manager, product owner, business analyst), you don't necessarily need to dive into the protocol and know the specifics. The previous chapters (Definition and Analysis) would be sufficient for you.

But if you work on an API-driven product and your organization uses an API design-first approach, that will be helpful to know more.

Your organization is likely taking one or two API architecture approaches. You need to know the rules, but again, the specifics will come as soon as you gain experience working with them.


HTTP example

Let's see how a simple API endpoint to return Customer details looks with raw HTTP:

GET /api/customer/{customerId} HTTP/1.1
Host: example.com
Authorization: Bearer <access_token>
Content-Type: application/json
  • GET is an HTTP method to retrieve the information.
  • /api/customer/{customerId} is part of the URL (aka path) required to call an endpoint
    • {customerId} is a placeholder for an object identifier (aka path parameter)
  • Host, Authorization, and Content-Type are headers that deliver request metadata about the destination, access, and content type for a server to consume.

As a response, the server returns a JSON response body with metadata (HTTP status code):

200 OK
{
  "customerId": "12345",
  "internalId": "913ec1e3-4952-31a6-a24d-9ff71794ae40",
  "firstName": "John",
  "lastName": "Doe",
  "email": "johndoe@example.com",
  "phoneNumber": "+1-555-123-4567",
  "address": {
    "addressLine": "123 Main St",
    "city": "Anytown",
    "state": "CA",
    "postalCode": "90210",
    "country": "US"
  },
  "accountStatus": "Active",
  "createdDate": "2023-02-15T12:34:56Z"
}

If you want to learn more about HTTP, then I suggest reading:


From a structural perspective, we now have:

  • request data
  • request metadata
  • response data
  • response metadata

However, we lack overall context about that API endpoint and the use cases it covers. We also don't know the exact meaning of its data attributes.

If the demographic data we get in the response body is basic and quite evident, we can only assume what internalId implies and how it differs from customerId. Also, we don't know the specifics of accountStatus. What statuses apply to a Customer, and what do they mean?

OpenAPI example

HTTP can't answer such questions, but the OpenAPI specification (OAS) does. It provides additional information layers over HTTP, comprehensively defining how API works.

Let's see our example done with OAS:

openapi: 3.1.0
info:
  title: Legacy CRM API
  description: API to retrieve data from Legacy CRM (READ ONLY)
  version: 1.0.0
servers:
  - url: https://example.com/api
paths:
  /customer/{customerId}:
    get:
      summary: Retrieve Customer details
      description: Retrieves basic information about a customer by their customer ID
      parameters:
        - name: customerId
          in: path
          required: true
          description: The external identifier assigned to the customer
          schema:
            type: string
      responses:
        '200':
          description: Customer details retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  customerId:
                    type: string
                    description: The external identifier assigned to the customer
                    examples: 
                      - "12345"
                  internalId:
                    type: string
                    format: uuid
                    description: The internal identifier autogenerated by the system
                    examples: 
                      - "913ec1e3-4952-31a6-a24d-9ff71794ae40"
                  firstName:
                    type: string
                    description: The customer's first name
                    examples: 
                      - "John"
                  lastName:
                    type: string
                    description: The customer's last name
                    examples: 
                      - "Doe"
                  email:
                    type: string
                    format: email
                    description: The customer's email address
                    examples: 
                      - "johndoe@example.com"
                  phoneNumber:
                    type: string
                    description: The customer's phone number in E.164 format
                    examples: 
                      - "+1-555-123-4567"
                  address:
                    type: object
                    description: The customer's address details
                    properties:
                      addressLine:
                        type: string
                        description: The first line of the customer's address
                        examples: 
                          - "123 Main St"
                      city:
                        type: string
                        description: The city where the customer resides
                        examples: 
                          - "Anytown"
                      state:
                        type: string
                        description: The state or region of the customer's address
                        examples: 
                          - "CA"
                      postalCode:
                        type: string
                        description: The postal or ZIP code of the customer's address
                        examples: 
                          - "90210"
                      country:
                        type: string
                        description: The ISO 3166-1 alpha-2 country code of the customer's address
                        examples: 
                          - "US"
                  accountStatus:
                    type: string
                    description: The current status of the customer's account
                    enum:
                      - "Active"
                      - "Inactive"
                      - "Suspended"
                      - "Closed"
                    examples: 
                      - "Active"
                  createdDate:
                    type: string
                    format: date-time
                    description: The date and time when the customer account was created, in ISO 8601 format
                    examples: 
                      - "2023-02-15T12:34:56Z"
        '401':
          description: Unauthorized - invalid or missing access token
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    examples: 
                      - "Unauthorized access"
        '404':
          description: Customer not found
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    examples: 
                      - "Customer not found"
      security:
        - bearerAuth: []
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

It looks vast for one API endpoint, right? I will not cover all OAS components in that definition. Instead, if you are unfamiliar with that format, you can copy-paste it to the Swagger Editor (its newer edition with 3.1 support) and look into the visualization.


If you want to learn more about OpenAPI, I suggest:


Let's quickly check what we've got here compared with the raw HTTP:

  • We know some context about that API definition, as it comes from a Legacy CRM system now used for read-only operations. So we can utilize that API to get historical customer data.
  • We know more about the data structure. We can differentiate customerId from internalId, see the format specifics of phoneNumber and country, and understand what accountStatus implies.
  • We know which errors to expect from a Server in particular cases to handle them on a Client side.

Layers of the Design structure

We still have the request/response data and metadata we saw in the HTTP example now extended with documentation and context details:

This is how that design structure can be visualized by adding documentation and context to the request/response data and metadata.

The described OAS example could be done in many ways, from another path to different naming of response body attributes. I am not even talking about moving the response body to the Component object. In the production, that schema might look very different.

There are two major problems in software engineering: naming and caching. Naming greatly concerns API design. It is easy to deal with a dozen APIs and keep the structure consistent and understandable. But when there are hundreds or even thousands of APIs organization-wide, there must be an explicit set of rules for naming and managing things. That is an entire discipline called API Governance.

So, when you proceed with API definition, you better have guidelines provided by Software architects that represent the protocols, architecture approaches, and background with practices applied in the organization.

Development approaches

There are a few ways to develop API. Those ways impact how we proceed with the design.

In the code-first approach, a business analyst works with stakeholders to write down API-specific or general functional and quality requirements in a traditional format (e.g., user story). A developer usually combines the design phase with implementation, describing an API endpoint in source code.

Of course, that is oversimplified. After the implementation, an OpenAPI file can be generated based on the source code annotations for documentation purposes.

The API design-first approach means a business analyst works with stakeholders to create a formal machine-readable API definition (e.g., OpenAPI). Someone from the technical side may assist and guide the process. Code-generation tools can be used to take OpenAPI as input and produce source code. A developer is likely to be involved in finalizing the development.

In the end, we can compare API definitions prior to the implementation and the one doc-generated to check for differences.

I previously wrote about the API design-first approach, so check it out for more information.

AI also introduced a new perspective. It can create an OpenAPI definition from human input or generate code directly. However, you might still require some skills to adjust the final result.

It is realistic to be able to integrate with API using a chatbot or design and expose a new API with it. That does not come without some trade-offs, though. Whether the AI approach will take the others, we will see. We may discuss that topic in detail on different occasions.