Documenting your API (Moonlight)
What Moonlight docs are
Pionia’s HTTP API is not one OpenAPI path per REST resource. Clients POST to a versioned URL (e.g. /api/v1/) with a JSON body:
{
"service": "auth",
"action": "list_auth",
"page": 1
}Moonlight documentation describes every { service, action } pair your app exposes: parameters, auth, examples, and the response envelope. The framework scans your service classes and produces:
| Output | Command | Typical path |
|---|---|---|
| OpenAPI 3.1 spec | php pionia api:docs | docs/api/openapi.json |
| Markdown index | same | docs/api/index.md |
| Scalar HTML UI | php pionia api:docs --ui | docs/api/index.html |
| Live browser UI | runtime | /docs (when enabled) |
| JSON catalog | php pionia api:catalog | stdout or /api/v1/__catalog |
Moonlight docs describe your application API, not Pionia framework internals (those use phpDocumentor in the core repo).
Quick start
- Add
@moonlight-*tags to a service class and its action methods (see below). - Generate committed docs:
php pionia api:docs --ui- In development, open interactive docs:
php pionia serve
open http://127.0.0.1:8003/docs- In CI, fail on drift:
php pionia api:docs --check
# or: composer document:api:check # in PioniaCore monorepoDocument a service (class level)
Put Moonlight tags on the service class docblock. They apply to every action unless overridden on the method.
<?php
namespace Application\Services;
use Pionia\Http\Services\Service;
/**
* Authentication — demo CRUD for auth records.
*
* @moonlight-service auth
* @moonlight-version v1
* @moonlight-auth partial
*/
class AuthService extends Service
{
// actions …
}| Tag | Required | Purpose |
|---|---|---|
@moonlight-service | Recommended | Service alias in requests ("service": "auth"). Defaults to the switch registry key if omitted. |
@moonlight-version | Optional | API version label in docs (usually v1). |
@moonlight-auth | Optional | Default auth for the service: none, optional, partial, or required. |
@moonlight-table | Optional | Primary table for generic CRUD actions. |
The service alias must match how the service is registered on your switch (registerServices()), e.g. auth, category, mail.
Document an action (method level)
Each API action is a *Action method on the service. Document it in the method docblock:
use Pionia\Collections\Arrayable;
use Pionia\Http\Response\ApiResponse;
/**
* List auth records with optional filters.
*
* @moonlight-action list_auth
* @moonlight-summary Returns paginated auth rows
* @moonlight-auth none
* @moonlight-perm list_auth
* @moonlight-param int page Page number, default 1
* @moonlight-param int limit Page size, default 20
* @moonlight-return object items array of records, total int count
* @moonlight-example {"service":"auth","action":"list_auth","page":1,"limit":20}
*/
protected function listAuthAction(Arrayable $data): ApiResponse
{
// …
}Tag reference (actions)
| Tag | Required | Purpose |
|---|---|---|
@moonlight-action | Recommended | The action string clients send in JSON. |
@moonlight-summary | Recommended | One-line description in OpenAPI and /docs. |
@moonlight-auth | Optional | none, optional, or required for this action. |
@moonlight-perm | Optional | Permission slug (repeat tag for multiple). |
@moonlight-param | Optional | type name Description — documents a request body field. |
@moonlight-return | Optional | Shape of returnData in the response envelope. |
@moonlight-example | Optional | Full example JSON payload (include service and action). |
@moonlight-deprecated | Optional | Mark action deprecated in generated docs. |
Parameter syntax: @moonlight-param type name Description
Examples:
* @moonlight-param string email Recipient address
* @moonlight-param int id Row id
* @moonlight-param object data Row payload (name, optional id)PHP 8 attributes (alternative)
Instead of (or in addition to) PHPDoc, you can use the MoonlightAction attribute:
use Pionia\Documentation\Attributes\MoonlightAction;
#[MoonlightAction(
name: 'list_auth',
summary: 'Returns paginated auth rows',
auth: 'none',
permissions: ['list_auth'],
)]
protected function listAuthAction(Arrayable $data): ApiResponse
{
// …
}PHPDoc tags and attributes merge; attributes win when both define the same field.
What gets inferred without tags
The doc collector still picks up actions when tags are missing:
| Item | Inference rule |
|---|---|
| Action methods | Public/protected methods ending in Action, plus generic service macros. |
| Action name | Snake_case of the method name without the Action suffix (listAuthAction → list_auth). |
| Auth | Merged from $serviceRequiresAuth, $actionsRequiringAuth, $actionPermissions on the service. |
Inference is enough for an internal catalog, but you should add @moonlight-summary and @moonlight-example before sharing docs with frontend teams or external consumers.
Response envelope
Every action returns the same JSON shape (see Actions):
{
"returnCode": 0,
"returnMessage": "OK",
"returnData": {},
"extraData": null
}returnCode: 0 means success. Document the payload inside returnData with @moonlight-return.
Generate docs (api:docs)
php pionia api:docs
php pionia api:docs --ui
php pionia api:docs --format=openapi,markdown --output=docs/api
php pionia api:docs --check| Option | Purpose |
|---|---|
--format=openapi,markdown,html | Output formats (comma-separated). |
--output=docs/api | Destination directory (default: docs/api/ under app root). |
--ui | Also write index.html (Scalar UI bundle). |
--check | Exit with error if generated files differ from disk (CI). |
Aliases: make:api-docs, docs:api
Print catalog JSON
php pionia api:catalog
php pionia api:catalog --json # pretty-printedSame source as api:docs, useful for scripts and debugging.
Runtime docs (/docs)
When DEBUG=true or docs are explicitly enabled, the app serves live documentation from registered services at boot (not only committed files).
| URL | Content |
|---|---|
/docs | Scalar interactive UI |
/docs/openapi.json | OpenAPI spec |
/api/v1/__catalog | JSON action catalog (debug gate) |
Enable in staging/production
environment/settings.ini:
[docs]
ENABLED = trueOr .env:
DOCS_ENABLED=trueOptional token (recommended when DEBUG=false):
[docs]
ENABLED = true
TOKEN = your-secretDOCS_TOKEN=your-secretAccess with ?token=your-secret or header X-Docs-Token: your-secret.
See also Developer stats — /stats uses a separate STATS_TOKEN.
OpenAPI shape
Moonlight OpenAPI uses one POST path per API version (e.g. /api/v1/) with a oneOf schema per {service}.{action} — not separate REST paths per endpoint. This matches how clients actually call your API.
Commit docs/api/openapi.json if your team consumes it in CI or client generators; regenerate after changing @moonlight-* tags.
Full example service
<?php
namespace Application\Services;
use Pionia\Collections\Arrayable;
use Pionia\Http\Response\ApiResponse;
use Pionia\Http\Services\Service;
/**
* Company categories.
*
* @moonlight-service category
* @moonlight-version v1
* @moonlight-table company
*/
class CategoryService extends Service
{
/**
* @moonlight-action list
* @moonlight-summary List all companies
* @moonlight-example {"service":"category","action":"list"}
*/
protected function listAction(Arrayable $data): ApiResponse
{
return response(0, 'OK', ['items' => []]);
}
/**
* @moonlight-action update
* @moonlight-summary Update a company by id
* @moonlight-param int id Row id
* @moonlight-param string name New name
* @moonlight-example {"service":"category","action":"update","id":1,"name":"Acme"}
*/
protected function updateAction(Arrayable $data): ApiResponse
{
return response(0, 'Updated', ['id' => $data->get('id')]);
}
}Register category on your switch, run php pionia api:docs --ui, then open /docs to verify.
Checklist for new actions
- Service has
@moonlight-service(or relies on registry alias). - Each public API method has
@moonlight-action(or follows*Actionnaming). -
@moonlight-summarydescribes what the action does in one line. -
@moonlight-exampleshows a copy-paste JSON body withserviceandaction. - Params documented with
@moonlight-paramwhen non-obvious. -
@moonlight-authset when auth differs from service default. - Run
php pionia api:docs --checkin CI after changing tags.
Related guides
- Services overview — register services on switches
- Actions — write action methods and envelopes
- Security — auth traits and
@moonlight-auth - CLI commands — full
api:docs/api:catalogreference - Moonlight architecture — switches, services, and the request model