Skip to content

Latest commit

 

History

History
561 lines (434 loc) · 23.4 KB

File metadata and controls

561 lines (434 loc) · 23.4 KB

API Guidelines

Request for comment

1. Introduction

Welcome to Enturs API guidelines, designed to ensure a consistent and robust approach to developing RESTful APIs. REST (Representational State Transfer) is an architectural style that uses standard HTTP methods to create scalable, lightweight, and maintainable services.

1.1 Purpose of the Entur API guidelines

  • To establish common guidelines and best practices for API design
  • To simplify integration between different systems and applications
  • To contribute to a secure, efficient and user-friendly implementation of APIs

1.2 Intended audience

This guide is for developers, architects, and technical designers who work on the design, implementation, and maintenance of APIs. By following these guidelines, we ensure APIs are easy to understand, consistent in structure, and simple to integrate with other systems.

1.3 Contributing

For details on how to contribute to these guidelines, see the contributing guide.

1.4 Requirement Levels

Throughout this document, these requirement levels are used:

  • MUST: This is an absolute requirement
  • SHOULD: There may be valid reasons to ignore this requirement, but implications must be understood and carefully weighed
  • MAY: This is optional

1.5 Linter Coverage

Throughout this document, rules are marked with the following indicators:

  • ✅ Automatically enforced by linter
  • ☑️ Partially checked by linter
  • 👀 Requires manual review

2. Core Principles

2.1 General Design Principles

  • 👀 Consistency - Make sure the API is easy to understand and predictable
  • ✅ HTTP Methods - API operations MUST use standard HTTP methods (GET, POST, PUT, PATCH, DELETE)
  • ✅ Data Format - API endpoints SHOULD support the JSON data format, unless there is a good reason not to
  • ✅ APIs MUST be documented using OpenAPI 3.x
  • ✅ Documentation - All functionality SHOULD be documented with examples and descriptions
  • ✅ Encryption: All communication MUST be over HTTPS
  • ✅ You SHOULD not use localhost (or 127.0.0.1) host names in info.servers.
  • info.title MUST be non-empty and MUST not contain the word 'api'.

2.2 Development Approach

  • 👀 We encourage to follow a Contract-First workflow:
    • Create the API specification before implementing the API
    • The specification is the primary reference for both development and documentation
    • Update the specification throughout development to reflect changes
  • 👀 Lint your API spec
  • 👀 Separate API specifications per target audience/visibility (public, partner, internal). An API spec SHOULD only contain endpoints for one target audience. This audience is used in the Developer Portal to organize APIs.
  • 👀 Differentiate APIs based on the target audience:
    • Internal APIs - May have extended functionality and less stringent requirements, but MUST still be documented and tested
    • External APIs - MUST be carefully documented with a focus on stability, security, and consistency

2.3 Authentication and Authorization

2.3.1 About security in OpenAPI specs

Use securitySchemes inside components to define security schemes. Then, refer to schemes using security either at the root level of the spec,to apply security to all operations, or under individual operations which are secured, which replaces the root config.

See:

2.3.2 Partner- and Internal endpoints

  • 👀 These endpoints are secured using JWT tokens. This MUST be documented using a jwt scheme.

Example:

{
  "security": [{ "jwt": [] }],
  "components": {
    "securitySchemes": {
      "jwt": { "type": "http", "scheme": "bearer", "bearerFormat": "JWT" }
    }
  }
}

More information on Authentication

2.3.3 Documenting permissions for Partner endpoints

  • ☑️ Endpoints that require permissions MUST be documented with the x-entur-permissions extension.

Example:

{
  ...,
  "paths": {
    "/items": {
      "get": {
        "operationId": "getItems",
        ...,
        "x-entur-permissions": {
          "value": "items:les"
        }
      },
      "post": {
        "operationId": "createItem",
        ...,
        "x-entur-permissions": {
          "value": "items:opprett"
        }
      }
    }
  }
}

where items:les means that you need the access les on the operation items.

You can also specify that an endpoint requires multiple permissions, for example:

{
  "x-entur-permissions": {
    "value": {
      "all": [
        "organisations:les",
        {
          "any": [
            "items:opprett",
            "items-global:opprett"
          ]
        }
      ]
    }
  }
}

This means that the endpoint requires organisations:les, as well as either items:opprett or items-global:opprett.

If you need to explain the required permissions in more detail, you can declare a description field at the root.

{
  "x-entur-permissions": {
    "description": "To call this endpoint you need access to read organisations, as well as creating items for your organisation.",
    "value": {
      "all": [
        "organisations:les",
        {
          "any": [
            "items:opprett",
            "items-global:opprett"
          ]
        }
      ]
    }
  }
}

2.4 Entur Metadata

All OpenAPI specifications published to Enturs developer portal must declare a block x-entur-metadata in the info section of the specification.

Field name Type Description
id string REQUIRED. Unique id for this specification. Read more.
audience string REQUIRED. Who this specification is targeted to. Must be one of "open", "partner", "internal"
owner string REQUIRED. The Entur team responsible for this specification. Read more.
parentId string Id of the parent specification, used when merging. Read more.

Example:

{
  "info": {
    "x-entur-metadata": {
      "id": "items",
      "owner": "team-api",
      "audience": "partner"
    }
  }
}

2.4.1 Identifying a specification

  • ✅ OpenAPI specifications SHOULD use the id property in the x-entur-metadata extension in the info section.

Choosing an id The id is used to uniquely identify a specification - each id results in an entry in the Developer Portal API catalogue, and an entry in the linting results. Because of this, the id should not change over time. The current api title (in kebab-case) could be a good id - but, of course, you should not update the id if the title changes in the future. The id should not have a -id suffix (or prefix).

  • ✅ If the id is present, it MUST be in lower kebab-case and contain only dashes, digits and letters (a-z).

2.4.2 Specification owner

  • ✅ OpenAPI specifications SHOULD use the owner property in the x-entur-metadata extension in the info section.

The owner field declares which team is responsible for maintaining a specification. It should follow the same format as the owner field in the platform orchestrator.

2.4.3 Merging specifications

Sometimes it is useful to develop some API endpoints separately, but still document them externally as a single specification. To achieve this, you can use the x-entur-metadata field parentId. This field may contain a reference the x-entur-metadata.id field in another published specification.

Nesting is not supported. parentId must refer to a specification that does not itself declare a parentId.

If there are problems with the merging, for example conflicts between the schemas, none of the specifications will be exposed.

Limitations

There are some limitations when merging specifications:

  • servers.url in all specs must be a subpath of the declared servers.url in the "parent specification". The merged specification will use the servers definition from the parent specification, and the subpath of the other specifications will be added to the operation paths. If servers are not compatible, none of the specifications will be published.
  • Root level security must be identical in all specs to be merged.
  • If there are multiple non-equal schemas with the same name, they will be namespaced with the x-entur-metadata.id field. So if multiple specs declare a schema Item that differ from each other, there will be one schema Item_alpha and one schema Item_beta.
Example For example, say you have three microservices, `alpha`, `beta` and `gamma`:

Microservice alpha publishes the specification:

{
  "info": {
    "x-entur-metadata": {
      "id": "alpha"
    }
  },
  ...
}

Microservice beta publishes the specification:

{
  "info": {
    "x-entur-metadata": {
      "id": "beta",
      "parentId": "alpha"
    }
  },
  ...
}

Microservice gamma publishes the specification:

{
  "info": {
    "x-entur-metadata": {
      "id": "gamma",
      "parentId": "alpha"
    }
  },
  ...
}

Here, only 1 specification will be shown on the Developer Portal, which is a combination of alpha, beta and gamma. Since alpha is the parent specification, the combined specification will use alpha as the base, so fields like info.title will be picked from there.

3. Naming & Structure Conventions

3.1 Resource Naming

  • 👀 You SHOULD use plural Nouns - For example: /customers, /benefits and /offers
    • Exception: Singleton resources that represent a unique entity MAY use singular form, such as /user/profile, /me
  • 👀 Hierarchical Structure - For related resources you SHOULD use hierarchical URL structure. For example: /orders/{orderId}/fees
  • 👀 No Actions in URL - Actions (such as create, delete) SHOULD be handled via the HTTP method, not in the URL itself
  • ✅ URL in kebab-case - URL for APIs MUST be in kebab-case. For example /realtime-deviations/v1/subscription
  • ✅ Field Names in camelCase - For request and response body field names and query parameters, camelCase MUST be used
  • 👀 Consistent Terminology - Similar concepts across different APIs SHOULD use consistent naming. For example, don't mix /users and /people or /proposals and /offers when referring to the same concept across different APIs.
  • ✅ Server URL MUST be in lowercase
  • ✅ Paths SHOULD NOT contain "api" in the name

3.2 Versioning

3.2.1 API Versioning

  • 👀 URL Based Versioning - You MUST include the version number in the URL
  • 👀 Best Practices on Version Info - You SHOULD place the version number immediately after the domain, and before the resource path itself
    • Example: https://api.entur.io/sales/v1/orders?distributionChannelId=1

3.2.2 API Spec Versioning

API spec versioning is done automatically when publishing the spec. The version is set based on the CalVer versioning convention, and uses the format YYYY.MM.MICRO. When the spec is published, the current year and month is used, and MICRO is set to "00". If a version already exists for the given year and month, "01" is used (and so on). If the published spec is equal to the current spec, no new version is created. Because versioning is done automatically, the value in info.version is ignored, but must be set in order for linting to pass, therefore a placeholder value like 1.0.0 may be used.

3.3 Backward Compatibility

  • 👀 You MUST not remove or modify existing fields or endpoints
  • 👀 You MUST introduce new versions for changes that break previous contracts
  • 👀 You MUST clearly document which features are deprecated and provide guidance for migration

3.4 Tags Naming

👀 Tags should be used as a logical grouping that reflects functional domains, use cases or data types, not internal architecture.

  • Bad example: internal, partner, production, route-service-v2, misc, other
  • Good example, when using tags for functional domains: Journey Planning, Realtime Departures
  • Good example, when using tags for use-cases : Search, Booking
  • Good example, when using tags for data types : Routes, Departures

4. Communication Standards

4.1 HTTP Status Codes

  • ✅ Request body is only allowed for PUT, POST and PATCH
Code Description GET POST PUT PATCH DELETE
200 OK
201 Created
202 Accepted
204 No Content
302 Found
303 See Other
400 Bad Request
401 Unauthorized
403 Forbidden
404 Not Found
409 Conflict
500 Internal Server Error
503 Service Unavailable

4.2 Error Handling

When an error occurs, the IETF standard for Problem Details for HTTP APIs (RFC 9457) SHOULD be followed, with few additional guidelines:

  • ✅ Error responses MUST either use media type application/problem+json, OR application/problem+xml (if Accept header is application/xml)
  • ✅ The fields title and status MUST be included
  • 👀 The detail field SHOULD be included when it provides additional useful information
  • 👀 The type field MAY be included. If included, it MUST be an absolute URI
  • 👀 The instance MAY be included. If included, it MUST be an absolute URI

Example:

HTTP/1.1 403 Forbidden Content-Type: application/problem+json Content-Language: en

{
  "type": "https://example.com/request-endpoint",
  "title": "Access forbidden",
  "status": 403,
  "detail": "You do not have permission to access this resource.",
  "instance": "https://example.com/something/something",
  "balance": 30,                        //Custom field
  "recommended-action": "Something."    //Custom field
}

5. Data Formatting Standards

5.1 Language & Spelling

  • 👀 You MAY allow clients to specify a preferred language via the Accept-Language header or a query parameter.

  • 👀 You MAY return the Content-Language header to inform clients of language used in response.

  • ☑️ Accept-Language and Content-Language values MUST be valid IETF BCP 47 language tags. BCP 47 uses ISO 639-1 codes when available, and ISO 639-3 codes if no two-letter code exist. E.g. "en", not "eng". Also, as specified by BCP 47, clients may send multiple tags with quality values (e.g. nb,en;q=0.9).

  • ☑️ Macrolanguage tags (e.g., "no") MUST NOT be used - instead use the specific language variant (e.g., "nb" or "nn").

  • 👀 British English spelling as defined in the Oxford English Dictionary SHOULD be used for consistency

Example:

GET /api/v1/info Accept-Language: nb

GET /api/v1/info?lang=nb

5.2 Date & Time

  • 👀 You SHOULD use the ISO 8601 standard for all date and timestamps

Example:

"lastUpdated": "2025-02-13T15:30:00Z"

5.3 Currency Representation

  • 👀 Prices should be specified as floating-point numbers, represented as strings
  • 👀 Request: Max 18 digits total; max 5 decimals (follows ISO 20022)
  • 👀 Response: Always serialized with the standard number of decimals. For NOK, this means 2, since øre is the smallest unit.

Example:

{
    "amount": "99.00"
    "currency": "NOK"
}

5.4 Character Encoding

  • 👀 You MUST encode all text in UTF-8
  • 👀 Set the Content-Type header to for example application/json; charset=utf-8
    • Tip: test the api with international characters (e.g. æ, ø, å)

5.5 HTTP Headers

  • ☑️ HTTP headers MUST use Hyphenated-Pascal-Case format (e.g., Content-Type, Accept-Language)
  • 👀 Custom HTTP headers SHOULD use the prefix Entur-, with some exceptions.

5.5.1 Known Custom HTTP Headers

ET-Client-Name
  • ✅ All endpoints SHOULD allow consumers to identify themselves using the request header ET-Client-Name. The header is added automatically to all endpoints in a specification before publication to the developer portal.
Entur-POS

Used to declare which "point-of-sale" a request is coming from.

Entur-Distribution-Channel

Used to declare which distribution channel (sales channel) a request is coming from.

X-Correlation-Id

A "de-facto" standard for correlating a request throughout a microservice architecture. At Entur, this header is handled by our logging library cloud-logging. It is automatically added to all endpoints in a specification before publication to the developer portal.

6. Advanced Design Patterns

6.1 Filtering, Sorting & Pagination

  • 👀 You MAY allow filtering, sorting, and pagination to retrieve specific data
  • 👀 If you implement pagination, you MUST use either query parameters "page" (zero based page to get) and "size" (number of items per page), OR query parameters offset (zero based) and limit (number of items)
  • 👀 If you implement sorting, you SHOULD use query parameter "sort". Sorting can be done on multiple levels, and sort order (desc / asc) is also specified, like so: sort=<field1>,<asc|desc>&sort=<field2>,<asc|desc>
  • TODO: Requirements for response format for pagination and sorting

The requirements above are based on the Spring way of doing things: https://docs.spring.io/spring-data/rest/reference/paging-and-sorting.html

Example:

GET /api/v1/bus-stops?city=Oslo&sort=name,asc&sort=something,desc&page=0&size=20

6.2 Partial Responses

  • 👀 You MAY let clients choose which fields to include to reduce data transfer

Example:

GET /api/v1/bus-stops?fields=id,name,location

6.3 Batch Operations

  • 👀 When multiple operations need to be handled in a single call, the API SHOULD support batch operations

Example:

POST /api/v1/batch
{
 "operations": [
   { "method": "GET", "path": "/v1/resource/1" },
   { "method": "DELETE", "path": "/v1/resource/2" }
 ]
}

6.4 Caching & Resource Expiration

  • 👀 You MAY use HTTP headers such as Cache-Control, ETag, and Last-Modified

Example:

HTTP/1.1 200 OK
Cache-Control: max-age=3600
ETag: "abc123"
Last-Modified: Fri, 13 Feb 2025 15:30:00 UTC

The client can then cache the result for 3600 seconds, and after that it can issue a conditional GET using the ETag and Last-Modified headers

Example:

GET /your-resource HTTP/1.1  
If-None-Match: "abc123"  
If-Modified-Since: Fri, 13 Feb 2025 15:30:00 UTC

If both If-None-Match and If-Modified-Since are sent, the server MUST ignore If-Modified-Since (RFC7232 §3.3). If the resource has not changed, the server can respond with 304 Not Modified.

Example:

HTTP/1.1 304 Not Modified

6.5 Import & Export Formats

  • 👀 You SHOULD support JSON, unless you have a good reason not to.
  • ✅ Use the HTTP Accept header to specify desired response format

Example:

GET /api/v1/data
Accept: application/json

6.6 Validation

  • 👀 You SHOULD validate all incoming data and return detailed error messages with appropriate HTTP status codes
  • 👀 Error messages SHOULD be specific enough to guide the client toward fixing the issue
  • 👀 Validation errors SHOULD return 400 Bad Request status code

Example:

HTTP/1.1 400 Bad Request Content-Type: application/problem+json Content-Language: en

{
 "type": "https://api.entur.io/v1/orders",
 "title": "Invalid input",
 "status": 400,
 "detail": "Field must be valid email.",
 "errors": [
   {
     "field": "email",
     "message": "Field must be valid email.",
     "code": "INVALID_FORMAT"
   }
 ]
}

6.7 HATEOAS

  • 👀 You MAY include links to related resources in responses to enable easy API navigation

Example:

{
  "id": 123,
  "name": "Sentralstasjonen",
  "links": [
    { "rel": "self", "href": "/api/v1/bus-stops/123" },
    { "rel": "schedules", "href": "/api/v1/bus-stops/123/schedules" }
  ]
}

7. Alternative Approaches

7.1 When to Consider Non-RESTful Designs

There may be situations where a pure REST architecture is not the best solution. In such cases, consider alternative design patterns. The choice should be justified based on performance requirements, complexity, and consistency with other systems, as well as overall guidelines in the architect's intent.

Alternative Design Patterns:

  • GraphQL: For flexible queries where the client can specify exactly what data is needed
  • Event-driven architecture: For asynchronous or event-based scenarios
  • WebSocket: For real-time bidirectional communication
  • gRPC: For high-performance, strongly-typed services

FAQ

Must existing APIs conform the guidelines?

  • Non-breaking changes (like adding example values) SHOULD be updated to be compliant with the guidelines.
  • Breaking changes MAY be added in a new version of the API.
  • New APIs MUST follow the guidelines.