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:
| Schema | Purpose | Fields |
|---|---|---|
{Resource} | Read response | All fields including computed, auto-generated |
{Resource}Input | Create/update request | Writable fields only (excludes computed, auto-generated) |
{Resource}List | List response | Array 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 Type | OpenAPI Type | Format | Additional |
|---|---|---|---|
string | string | — | maxLength, minLength, pattern |
text | string | — | |
integer | integer | — | minimum, maximum |
decimal | number | double | |
money | number | double | |
boolean | boolean | — | |
date | string | date | |
datetime | string | date-time | |
uuid | string | uuid | |
email | string | email | |
url | string | uri | |
phone | string | — | pattern |
json | object | — | |
enum | string | — | enum array |
media | string | uuid | Reference to media record |
svg | string | — | |
blocks | array | — | Block editor content |
component | object | — | Nested object schema |
repeater | array | — | Array 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:
| Extension | Location | Purpose |
|---|---|---|
x-adl-version | info | ADL specification version |
x-enum-labels | Schema | Human-readable enum labels |
x-enum-colors | Schema | Enum badge colors |
x-enum-descriptions | Schema | Enum value descriptions |
x-displayName | Tag | Pluralized display name |
x-guard | Operation | State machine guard condition |
x-required-fields | Operation | Transition required fields |
x-from-states | Operation | Valid source states for transition |
x-to-state | Operation | Target 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