REST API Guidelines
This design documents provides guidelines for specifying API documentation in Go Code using go-swagger, OpenAPI 3.0, and OpenAPI Generator. This document standardizes Ory's V1 API contract.
Context and Reference
Ory has an established API and SDK generation system consisting of four parts:
- Extraction of code comments from Go Code using Go Swagger (example);
- Conversion of Swagger 2.0 to OpenAPI Spec 3.0 and applying JsonPatch documents to improve the OpenAPI 3.0 file (example);
- Generation of code in the repository itself (example);
- Pushing the code to ory/sdk (example) where it is published to Maven, Packagist, npm, and other package repositories.
As projects grew organically, there is a lack of consistency across the API definitions. This document defines rules and regulations which will bring consistency to our APIs.
Goals and Non-Goals
Goals:
- Provide consistent naming patterns for API methods.
- Solve coarse-grained versioning.
- Use the existing tool-chain.
Non-goals:
- Fine-grained versioning will be discussed in future work.
- Fixing limitations of OpenAPI Generator:
- Has often problems with discriminators;
- Some templates have code generation bugs;
- Most of the times all code is dumped into one directory making naming conflicts likely;
- Fixing limitations of Go Swagger:
- Only generates Swagger 2.0 (OpenAPI 2.0);
- Has wonky enum generation;
- Discriminators do not work;
- Has problems when multiple definitions for models / parameters / resources exist;
- We need to explicitly include packages we want to use for spec generation, otherwise random definitions end up in the spec;
- gRPC API design is left to future work.
The Design for OpenAPI
This section discusses how Ory uses OpenAPI 3.0.
Routes
Routes are the functions of an RPC infrastructure and are annotated using
swagger:route
:
// swagger:route [method] [path pattern] [?tag1 tag2 tag3] [operation id]
Non-normative example of a fully-defined route:
// swagger:route GET /admin/identities identities listIdentities
//
// # List Identities
//
// Lists all identities. Does not support search at the moment.
//
// Learn how identities work in [Ory Kratos' User And Identity Model Documentation](https://www.ory.sh/docs/next/kratos/concepts/identity-user-model).
//
// Produces:
// - application/json
//
// Schemes: http, https
//
// Security:
// oryAccessToken:
//
// Responses:
// 200: identityList
// default: jsonError
Naming
All route operator names should be named using the following schema:
get{ResourceName}
(e.g.getIdentity
): Retrieves a resource usingGET
.set{ResourceName}
(e.g.setIdentity
): Replaces a resource usingPUT
.patch{ResourceName}
(e.g.patchIdentity
): Patches a resource usingPATCH
.delete{ResourceName}
(e.g.deleteIdentity
): Deletes a resource usingDELETE
.list{ResourceName}s
(e.g.listIdentities
): Retrieves a list of resources with pagination and optionally filtering usingGET
.
If the schema above does not fit your use case, please discuss it with a code owner.
Parameters
Parameters of requests or responses should always be written in snake_case
.
This includes:
- Query (sometimes referred to as "search") parameters (e.g.
?query_param=1&query_param2=test
) - Parameters in a request or response body (form values or JSON)
- Header names
// Update Registration Flow with Password Method
//
// swagger:model updateRegistrationFlowWithPasswordMethod
type UpdateRegistrationFlowWithPasswordMethod struct {
// The CSRF Token
CSRFToken string `json:"csrf_token"`
// ... other fields
}
Tags
Tags should denote a functional domain:
- identity
- oauth2
- project
- relationships
Security
Unless specified otherwise, administrative actions require an Ory Access Token to be included:
// swagger:route GET /admin/identities identities listIdentities
//
// ...
//
// Security:
// oryAccessToken:
//
// ...
func listIdentities(w http.ResponseWriter, r *http.Request) {}
Public routes that don't need authorization can omit this parameter.
Request
Routes which have request parameters should have the name of the operation. If the request has a body either use an existing
model, or create a struct named like the parameter and postfixed with RequestBody
.