17.1 Overview
The api: block on a resource customizes how the resource is exposed through the REST transport. It controls the URL path, which operations are enabled, and what custom endpoints are available.
A resource with no api: block uses all defaults: auto-generated path, all five CRUD operations enabled, no custom operations.
resources:
Post:
api:
path: /articles
disable_delete: true
customOperations:
- name: publish
method: POST
path: /:id/publish
permissions: [editor, admin]
17.2 Path Override
The path key overrides the auto-generated URL path for the resource.
api:
path: /items
Default Path Generation
When path is omitted, the path is derived from the resource name: PascalCase → kebab-case → pluralized.
| Resource Name | Default Path |
|---|---|
Post | /posts |
OrderItem | /order-items |
Category | /categories |
SiteSettings | /site-settings |
Person | /people |
Full URL
The full URL for a resource combines the domain prefix and the resource path:
/api/v1/{domain}/{resource-path}
For a domain named blog with a resource Post using the default path:
/api/v1/blog/posts
With path: /articles:
/api/v1/blog/articles
The domain prefix (/api/v1/{domain}) is configured at the domain level (§6.2) and is not affected by the resource’s path override.
17.3 Operation Disabling
Individual CRUD operations can be disabled. A disabled operation returns 404 as if the route does not exist.
api:
disable_list: true
disable_get: false
disable_create: true
disable_update: false
disable_delete: true
| Key | Default | Suppresses |
|---|---|---|
disable_list | false | GET / (list endpoint) |
disable_get | false | GET /:id (get endpoint) |
disable_create | false | POST / (create endpoint) |
disable_update | false | PATCH /:id and PUT /:id (both update methods) |
disable_delete | false | DELETE /:id (delete endpoint) |
An alternative array syntax is also accepted:
api:
disable: [list, create, delete]
Both forms produce identical behavior. The boolean form is preferred for clarity when disabling one or two operations. The array form is more compact when disabling several.
Disabled Operations and Permissions
Disabling an operation is stronger than setting its permission to [] (nobody). A disabled operation has no route — the URL returns 404. A permission of [] has a route that always returns 403. Disabling is appropriate when the operation is conceptually invalid for the resource (e.g., you cannot create a singleton). Permission restriction is appropriate when the operation exists but is limited to specific users.
17.4 Singleton Pattern
The singleton pattern exposes a resource that has exactly one record. It is implemented by disabling list, create, and delete:
resources:
SiteSettings:
api:
disable_list: true
disable_create: true
disable_delete: true
The single record is created by the seeder at boot time. API consumers access it via:
GET /api/v1/cms/site-settings/:id
PATCH /api/v1/cms/site-settings/:id
Singleton Behavior
| Operation | Available? | Description |
|---|---|---|
| List | ❌ | No list endpoint — there is only one record |
| Get | ✅ | Retrieve the single record by ID |
| Create | ❌ | Record created by seeder only |
| Update | ✅ | Modify the single record |
| Delete | ❌ | Cannot delete the singleton |
UI Pattern
A UI for a singleton resource skips the data table entirely and renders the edit form directly, pre-populated with the record’s current values. The record’s ID is obtained from the schema endpoint or hardcoded from the seed data.
17.5 Custom Operations
Custom operations define non-CRUD endpoints on a resource. They are declared in the customOperations array:
api:
customOperations:
- name: publish
method: POST
path: /:id/publish
permissions: [editor, admin]
fields:
status: published
publishedAt: "NOW()"
- name: duplicate
method: POST
path: /:id/duplicate
permissions: [owner, admin]
- name: bulk-activate
method: POST
path: /bulk-activate
permissions: [admin]
Declaration
| Key | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | yes | — | Operation identifier. Used in action names and route registration. |
method | string | no | "POST" | HTTP method: POST, PUT, PATCH, GET, DELETE. |
path | string | no | /:id/{name} | URL path relative to the resource base. |
permissions | string[] | no | [admin] | Role array for access control. |
fields | object | no | {} | Field values to auto-set when the operation executes. |
Generated Route
Each custom operation registers a route:
POST /api/v1/orders/orders/:id/publish → adl.orders.order.publish
POST /api/v1/orders/orders/:id/duplicate → adl.orders.order.duplicate
POST /api/v1/orders/orders/bulk-activate → adl.orders.order.bulk-activate
Fields Auto-Set
The fields object defines values that are applied to the record when the operation executes, similar to state machine sets:
fields:
status: published
publishedAt: "NOW()"
NOW() is replaced with the current UTC timestamp. Other supported expressions match state machine sets behavior (§12.5).
Request Body
Custom operation endpoints accept a JSON request body for additional field values (subject to field-level permissions). The fields auto-set values override any client-provided values for the same fields.
Implementation
Custom operations with fields are handled by the ADL engine’s built-in action handler — no Lua script is needed. For operations that require complex logic beyond field updates, the operation’s name serves as the hook identifier, and a Lua script can subscribe to the corresponding bus action:
adl.{domain}.{resource}.{operationName}
17.6 Operation Overrides
Standard CRUD operations can have their HTTP method or URL path overridden:
api:
operation_overrides:
update:
method: PUT
path: /:id/edit
delete:
method: POST
path: /:id/remove
Supported Operations
| Operation | Default Method | Default Path | Overridable |
|---|---|---|---|
list | GET | / | method, path |
get | GET | /:id | method, path |
create | POST | / | method, path |
update | PATCH + PUT | /:id | method, path |
delete | DELETE | /:id | method, path |
When method is overridden on update, both PATCH and PUT are replaced with the specified method. When path is overridden, the new path is relative to the resource base.
Use Cases
- Legacy API compatibility: Override paths and methods to match an existing API contract that the ADL domain is replacing.
- Convention alignment: Some teams prefer
PUTfor all updates instead ofPATCH. - URL structure: Override delete to
POST /:id/removefor systems that don’t supportDELETEmethod.
Operation overrides are rare. Most domains use the default methods and paths. Overrides exist for backward compatibility, not as a preferred pattern.
17.7 Nested Resource Paths
A resource MAY declare that it is accessed through a parent resource’s URL:
resources:
Comment:
api:
under: Post
path: /posts/:postId/comments
Behavior
When under is declared, the resource’s routes are nested under the parent’s path:
GET /api/v1/blog/posts/:postId/comments → list comments for post
GET /api/v1/blog/posts/:postId/comments/:id → get comment
POST /api/v1/blog/posts/:postId/comments → create comment (postId auto-filled)
PATCH /api/v1/blog/posts/:postId/comments/:id → update comment
DELETE /api/v1/blog/posts/:postId/comments/:id → delete comment
The postId path parameter is automatically used as the FK value on create — the client does not need to include it in the request body.
Direct Access
A resource with under MAY also expose direct routes at its own path. This allows both nested and direct access:
resources:
Comment:
api:
under: Post
directAccess: [get]
This generates:
GET /api/v1/blog/comments/:id → direct get by comment ID
GET /api/v1/blog/posts/:postId/comments → nested list under post
Requirements
- The resource MUST have a
belongsTorelationship to the parent resource specified inunder. - The FK field on the resource MUST match the parent’s ID path parameter.
- Nested routes do NOT affect the resource’s table structure — it is the same database table regardless of how it is accessed via URL.
17.8 Domain-Level API Configuration
The domain itself has an api: block that configures the URL prefix:
domain:
name: blog
version: "1.0.0"
api:
prefix: /api/v1/blog
aliases:
- /api/blog
- /blog
| Key | Type | Default | Description |
|---|---|---|---|
prefix | string | /api/v1/{domain.name} | Base URL prefix for all resource routes. |
aliases | string[] | [] | Alternative prefixes that also route to the domain’s resources. |
When prefix is omitted, the default is /api/v1/{name} where {name} is the domain’s name in lowercase.
Aliases register duplicate routes at alternative paths. This is useful for migration scenarios where clients use different URL conventions:
/api/v1/blog/posts → primary prefix
/api/blog/posts → alias
/blog/posts → alias
All three URLs resolve to the same action handler.
17.9 Schema Endpoint
The schema introspection endpoint returns the full API configuration:
{
"api": {
"path": "/articles",
"disable_list": false,
"disable_get": false,
"disable_create": false,
"disable_update": false,
"disable_delete": true,
"customOperations": [
{
"name": "publish",
"method": "POST",
"path": "/:id/publish",
"permissions": ["editor", "admin"]
}
],
"operation_overrides": {}
}
}
A UI generator uses this data to determine which CRUD buttons to render, which custom action buttons to add, and what the endpoint URLs are.
End of Chapter 17.
Next: Chapter 18 — Content & Media Types