Chapter 37 · Volume V — Reference

OpenAPI Generation

37.1 Overview

ADL automatically generates an OpenAPI 3.1.0 specification from each domain model. The specification documents all generated CRUD endpoints, custom operations, state machine transitions, query parameters, request/response schemas, and authentication requirements.

The OpenAPI spec is available at:

GET /api/v1/domains/{domain}/openapi.json

It serves as both API documentation and as a contract for client code generation tools (OpenAPI Generator, Swagger Codegen, openapi-typescript).


37.2 Specification Structure

{
  "openapi": "3.1.0",
  "info": {
    "title": "Blog API",
    "version": "1.0.0",
    "description": "Generated from ADL domain: blog",
    "x-adl-version": "0.3.1"
  },
  "servers": [
    {
      "url": "https://api.example.com/api/v1/blog",
      "description": "Production"
    }
  ],
  "security": [
    { "bearerAuth": [] }
  ],
  "paths": { ... },
  "components": {
    "schemas": { ... },
    "securitySchemes": { ... },
    "parameters": { ... },
    "responses": { ... }
  },
  "tags": [ ... ]
}

37.3 Path Generation

Standard CRUD Paths

For each resource, ADL generates up to 11 standard paths (unless disabled):

GET    /posts                 # list
GET    /posts/{id}            # get
POST   /posts                 # create
PATCH  /posts/{id}            # update (partial)
PUT    /posts/{id}            # replace (full)
DELETE /posts/{id}            # delete
GET    /posts/count           # count
GET    /posts/distinct/{field} # distinct values
POST   /posts/bulk            # bulk create
PATCH  /posts/bulk            # bulk update
DELETE /posts/bulk            # bulk delete

State Machine Transitions

For resources with state machines (§12), transition endpoints are generated:

POST /posts/{id}/transitions/publish
POST /posts/{id}/transitions/archive

Each transition operation includes:

  • Required field parameters in request body
  • Guard condition documentation
  • Available from/to states

Custom Operations

Custom operations (§17) appear as additional paths:

# ADL definition
api:
  custom:
    sendReminder:
      method: POST
      path: /:id/send-reminder

Generated OpenAPI:

{
  "paths": {
    "/posts/{id}/send-reminder": {
      "post": {
        "summary": "Send reminder",
        "operationId": "sendReminderPost",
        "tags": ["Post"],
        "parameters": [ ... ],
        "responses": { ... }
      }
    }
  }
}

M2M Relationship Paths

For many-to-many relationships (§08), link/unlink endpoints are generated:

GET    /posts/{id}/tags              # list linked tags
POST   /posts/{id}/tags              # link tag
DELETE /posts/{id}/tags/{tagId}      # unlink tag

37.4 Component Schemas

Resource Schemas

Each resource generates three schemas:

SchemaPurposeFields
{Resource}Read responseAll fields including computed, auto-generated
{Resource}InputCreate/update requestWritable fields only (excludes computed, auto-generated)
{Resource}ListList responseArray wrapper with pagination meta

Example:

{
  "components": {
    "schemas": {
      "Post": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "title": { "type": "string" },
          "slug": { "type": "string" },
          "content": { "type": "string" },
          "status": {
            "type": "string",
            "enum": ["draft", "published", "archived"],
            "x-enum-labels": {
              "draft": "Draft",
              "published": "Published",
              "archived": "Archived"
            },
            "x-enum-colors": {
              "draft": "#94a3b8",
              "published": "#10b981",
              "archived": "#6b7280"
            }
          },
          "publishedAt": { "type": "string", "format": "date-time", "nullable": true },
          "createdAt": { "type": "string", "format": "date-time" },
          "updatedAt": { "type": "string", "format": "date-time" }
        },
        "required": ["id", "title", "slug", "content", "status"]
      },
      "PostInput": {
        "type": "object",
        "properties": {
          "title": { "type": "string", "maxLength": 200 },
          "content": { "type": "string" },
          "status": { "type": "string", "enum": ["draft", "published"] }
        },
        "required": ["title", "content"]
      },
      "PostList": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/Post" }
          },
          "count": { "type": "integer" },
          "meta": { "$ref": "#/components/schemas/PaginationMeta" }
        }
      }
    }
  }
}

Field Type Mapping

ADL TypeOpenAPI TypeFormatAdditional
stringstringmaxLength, minLength, pattern
textstring
integerintegerminimum, maximum
decimalnumberdouble
moneynumberdouble
booleanboolean
datestringdate
datetimestringdate-time
uuidstringuuid
emailstringemail
urlstringuri
phonestringpattern
jsonobject
enumstringenum array
mediastringuuidReference to media record
svgstring
blocksarrayBlock editor content
componentobjectNested object schema
repeaterarrayArray of objects

Enum Extensions

Rich enums (§9) include extension fields for UI rendering:

{
  "type": "string",
  "enum": ["draft", "published", "archived"],
  "x-enum-labels": {
    "draft": "Draft",
    "published": "Published",
    "archived": "Archived"
  },
  "x-enum-colors": {
    "draft": "#94a3b8",
    "published": "#10b981",
    "archived": "#6b7280"
  },
  "x-enum-descriptions": {
    "draft": "Not yet published",
    "published": "Live on the site",
    "archived": "No longer visible"
  }
}

37.5 Query Parameters

All list endpoints accept standardized query parameters documented as reusable components:

{
  "components": {
    "parameters": {
      "PageParam": {
        "name": "page",
        "in": "query",
        "description": "Page number (1-indexed)",
        "schema": { "type": "integer", "minimum": 1, "default": 1 }
      },
      "LimitParam": {
        "name": "limit",
        "in": "query",
        "description": "Items per page",
        "schema": { "type": "integer", "minimum": 1, "maximum": 1000, "default": 50 }
      },
      "SortParam": {
        "name": "sort",
        "in": "query",
        "description": "Sort fields (comma-separated, prefix with - for desc)",
        "schema": { "type": "string" },
        "example": "-createdAt,title"
      },
      "SearchParam": {
        "name": "search",
        "in": "query",
        "description": "Full-text search term",
        "schema": { "type": "string" }
      },
      "IncludeParam": {
        "name": "include",
        "in": "query",
        "description": "Related resources to include (comma-separated)",
        "schema": { "type": "string" },
        "example": "author,tags"
      },
      "ScopeParam": {
        "name": "scope",
        "in": "query",
        "description": "Named scope to apply",
        "schema": { "type": "string", "enum": ["active", "deleted", "all"] }
      },
      "FieldsParam": {
        "name": "fields",
        "in": "query",
        "description": "Fields to return (comma-separated)",
        "schema": { "type": "string" }
      }
    }
  }
}

Resource-specific filter parameters are generated per field:

{
  "parameters": [
    {
      "name": "filter[status]",
      "in": "query",
      "description": "Filter by status (exact match)",
      "schema": { "type": "string", "enum": ["draft", "published", "archived"] }
    },
    {
      "name": "filter[publishedAt][gte]",
      "in": "query",
      "description": "Filter by publishedAt (greater than or equal)",
      "schema": { "type": "string", "format": "date-time" }
    }
  ]
}

37.6 Response Schemas

Standard Envelope

All responses use a standard envelope:

{
  "components": {
    "schemas": {
      "Envelope": {
        "type": "object",
        "properties": {
          "status": { "type": "string", "enum": ["ok", "error"] },
          "code": { "type": "integer" },
          "data": { "type": "object" },
          "error_code": { "type": "string" },
          "error_message": { "type": "string" }
        },
        "required": ["status", "code"]
      }
    }
  }
}

Pagination Meta

{
  "PaginationMeta": {
    "type": "object",
    "properties": {
      "total": { "type": "integer", "description": "Total matching records" },
      "page": { "type": "integer", "description": "Current page (1-indexed)" },
      "per_page": { "type": "integer", "description": "Items per page" },
      "total_pages": { "type": "integer", "description": "Total pages" },
      "has_next": { "type": "boolean", "description": "More pages available" },
      "has_prev": { "type": "boolean", "description": "Previous pages available" }
    }
  }
}

Error Responses

{
  "components": {
    "responses": {
      "ValidationError": {
        "description": "Validation failed",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "status": { "const": "error" },
                "code": { "const": 422 },
                "error_code": { "const": "VALIDATION_ERROR" },
                "error_message": { "type": "string" },
                "data": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "object",
                    "properties": {
                      "code": { "type": "string" },
                      "message": { "type": "string" }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

37.7 Security Schemes

{
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "JWT access token obtained from /api/v1/auth/login"
      }
    }
  },
  "security": [
    { "bearerAuth": [] }
  ]
}

All endpoints require authentication unless explicitly marked as public.


37.8 Tags and Organization

Resources are grouped by tags for documentation organization:

{
  "tags": [
    {
      "name": "Post",
      "description": "Blog post management",
      "x-displayName": "Posts"
    },
    {
      "name": "Tag",
      "description": "Post tag management",
      "x-displayName": "Tags"
    },
    {
      "name": "Comment",
      "description": "Post comment management",
      "x-displayName": "Comments"
    }
  ]
}

37.9 Extension Fields

ADL uses OpenAPI extension fields (prefixed with x-) to convey additional metadata:

ExtensionLocationPurpose
x-adl-versioninfoADL specification version
x-enum-labelsSchemaHuman-readable enum labels
x-enum-colorsSchemaEnum badge colors
x-enum-descriptionsSchemaEnum value descriptions
x-displayNameTagPluralized display name
x-guardOperationState machine guard condition
x-required-fieldsOperationTransition required fields
x-from-statesOperationValid source states for transition
x-to-stateOperationTarget state for transition

37.10 Usage Examples

Client Code Generation

# Generate TypeScript client
npx openapi-typescript \
  http://localhost:8087/api/v1/domains/blog/openapi.json \
  --output ./src/api/blog.types.ts

# Generate Python client
openapi-generator-cli generate \
  -i http://localhost:8087/api/v1/domains/blog/openapi.json \
  -g python \
  -o ./clients/python

API Documentation

The spec can be rendered with Swagger UI, Redoc, or Stoplight Elements:

<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist/swagger-ui.css" />
</head>
<body>
  <div id="swagger-ui"></div>
  <script src="https://unpkg.com/swagger-ui-dist/swagger-ui-bundle.js"></script>
  <script>
    SwaggerUIBundle({
      url: 'http://localhost:8087/api/v1/domains/blog/openapi.json',
      dom_id: '#swagger-ui'
    });
  </script>
</body>
</html>

End of Chapter 37.

Next: Chapter 38 — Admin Endpoints