The Api layer
Modules declare JSON API endpoints through a declarative
ApiConfig, one registration per module built into a registry as the module
loads. The site calls Api::Route once, which serves every registered
endpoint under /api.
How endpoints are registered
An API module declares its endpoints in an Endpoints function and calls
it at load time from its index file. That single call registers the module's endpoints
into the ApiConfig registry, so by the time the site runs, every included
API module is already in the registry:
// at the bottom of QueueApi/index.php - registers at load
QueueApi::Endpoints();
Declaring a module's endpoints
Inside Endpoints, a module opens a version block with
ApiConfig::Module and declares one endpoint per call with
ApiConfig::Get or ApiConfig::Post (also Put and
Delete). Within an endpoint you declare its inputs with
ApiInputConfig, any guard with ApiConfig::Guard, and the
handler with ApiConfig::Handler:
static function Endpoints(): void {
ApiConfig::Module('Queue', 'v1', function() {
ApiConfig::Get('list', function() {
ApiConfig::Guard('QueueApi::Authenticate');
ApiConfig::Handler('QueueApi::List');
});
ApiConfig::Post('process', function() {
ApiInputConfig::String('queue');
ApiConfig::Guard('QueueApi::Authenticate');
ApiConfig::Handler('QueueApi::Process');
ApiConfig::Error('QueueRateLimitError', 429, 'Queue rate limit exceeded');
});
});
}
An endpoint declared under module Queue, version v1, path
process is served at /api/Queue/v1/process.
Serving the registry
The site calls Api::Route once in its render pass. It walks the whole
registry and wires every registered endpoint under /api. Because each
module registered itself at load, the site never lists the endpoints:
Api::Route();
Responses and errors
Responses have no envelope. On success the response body is exactly what the handler returns, JSON-encoded at the top level. There are no wrapper fields.
On error the HTTP status is the machine signal and the body is a short plain-text
message. A handler can raise one directly with
throw new ApiErrorException($status, $message). A module can also map a
domain exception type to a status and message with ApiConfig::Error, as in
the example above, so that raising that exception anywhere in the handler produces the
mapped response.
Handlers are a place exceptions are caught
An Api handler is one of the few top-level places the framework catches exceptions,
which shapes how a handler is written. The handler codes only the happy path: it reads
its inputs, does the work, and returns the JSON it wants. When something is missing or
invalid it simply throws, and the Api layer catches the exception and maps its class to
an HTTP status and message centrally through ApiConfig::Error. There is no
catch block in the handler itself.
This is why handler code carries few or no null checks and little defensive branching. Rather than checking for a missing record and returning an error shape by hand, the handler lets the lookup throw and trusts the central mapping to turn that exception into the right response. The result reads as a straight line of intent, and every endpoint reports errors the same way.
Next: Members and accounts.