API CLI
List, describe, and call Signals v1 API endpoints in-process with php artisan signals:api — auth, flags, plugin tools, scripting, and LLM instructions.
Overview
php artisan signals:api is an Artisan surface for discovering and invoking the same /api/v1 catalogue the MCP server uses. Calls run in-process through OperationInvoker (full HTTP kernel, Sanctum abilities, metering, idempotency) — not via an outbound HTTP client.
Command class: App\Console\Commands\Api\SignalsApiCommand.
Enable
- Open Admin → Users & Security → MCP & CLI.
- Enable API CLI (
api_cli.enabled). - For
call, the acting user (token owner or--user) must holdcli.access. - Mint a PAT on API Tokens when using
--token, scoped to the abilities you need.
list and describe require only that the CLI is enabled. call additionally requires --token or --user and cli.access.
Authentication
| Flag | Behaviour |
|---|---|
--token= |
Plain-text Sanctum personal access token. Abilities on that token apply to the API call. Token owner must hold cli.access. |
--user= |
User id or email. Mints a short-lived wildcard ephemeral token for the call. User must hold cli.access. |
Provide either --token or --user for call, not both. Owners pass the permission check via Gate::before.
Command reference
php artisan signals:api
{action : list, describe, or call}
{endpoint? : Route name (e.g. api.v1.catalogue_items.index) or plugin tool name}
{--resource= : Filter list by catalogue resource}
{--method= : Filter list by HTTP method}
{--search= : Filter list by substring (route, resource, summary, uri)}
{--plugins : List plugin MCP/CLI tools instead of catalogue endpoints}
{--format= : Output format (list: table|json; describe: text|json; call: json|table)}
{--param=* : Path parameter as key=value (repeatable)}
{--query=* : Query parameter as key=value (repeatable, incl. q[...])}
{--data= : JSON body string, or @path/to/file.json}
{--idempotency-key= : Idempotency-Key header value}
{--token= : Plain-text Sanctum personal access token}
{--user= : Acting user id or email (wildcard ephemeral token)}
list
List catalogue endpoints (or plugin tools with --plugins).
| Flag | Notes |
|---|---|
--resource= |
Exact catalogue resource segment (case-insensitive), e.g. catalogue_items |
--method= |
HTTP method filter, e.g. GET |
--search= |
Substring across route name, resource, summary, URI |
--plugins |
List plugin tools exposed on the cli surface instead of API routes |
--format= |
table (default) or json |
Examples:
php artisan signals:api list
php artisan signals:api list --resource=catalogue_items --method=GET
php artisan signals:api list --search=rental --format=json
php artisan signals:api list --plugins
php artisan signals:api list --plugins --format=json
describe
Requires {endpoint} route name. Formats: text (default) or json.
php artisan signals:api describe api.v1.catalogue_items.index
php artisan signals:api describe api.v1.rentals.store --format=json
Text output includes method/URI, summary, ability, parameters, request body schema, response statuses, and SchemaRegistry filterable/sortable/searchable fields when available.
call
Invoke a catalogue endpoint or a plugin tool.
Catalogue endpoint
php artisan signals:api call api.v1.catalogue_items.index \
--token="$SIGNALS_PAT" \
--query="per_page=5" \
--query="q[name_cont]=widget"
php artisan signals:api call api.v1.catalogue_items.show \
[email protected] \
--param="product=42"
php artisan signals:api call api.v1.catalogue_items.store \
--token="$SIGNALS_PAT" \
--data='{"name":"Widget","catalogue_item_type":"rental"}' \
--idempotency-key="$(uuidgen)"
php artisan signals:api call api.v1.catalogue_items.store \
--token="$SIGNALS_PAT" \
--data=@./payload.json \
--idempotency-key=job-123
| Flag | Notes |
|---|---|
--param=* |
Path params as key=value (repeatable) |
--query=* |
Query params as key=value (repeatable; supports q[...]) |
--data= |
JSON object string, or @path/to/file.json |
--idempotency-key= |
Sent as Idempotency-Key |
--format= |
json (default) or table (flat collections only; otherwise falls back to JSON with a warning) |
Exit code: 0 on HTTP 2xx, 1 on failure (unknown endpoint, auth denial, non-2xx, invalid flags).
Plugin tool
Namespaced name: plugin:{package}:{tool} (package may contain /).
php artisan signals:api list --plugins
php artisan signals:api call plugin:signals/slots-demo:signals.slotsdemo.echo \
--user=1 \
--data='{"message":"hello"}'
Plugin call uses --data as the tool argument object (not HTTP body semantics). The acting user must pass cli.access and the tool's declared permission. Tools must expose the cli surface.
Output formats
| Action | Default | Allowed |
|---|---|---|
list |
table |
table, json |
describe |
text |
text, json |
call |
json |
json, table |
Invalid --format prints an error and exits non-zero.
Scripting examples
# JSON pipeline: first product id
php artisan signals:api call api.v1.catalogue_items.index \
--token="$SIGNALS_PAT" --query="per_page=1" --format=json \
| jq '.catalogue_items[0].id'
# Discover then describe
php artisan signals:api list --search=accounts --format=json \
| jq -r '.[].route_name' \
| head -1 \
| xargs -I{} php artisan signals:api describe {} --format=json
# Fail a script on API errors
set -e
php artisan signals:api call api.v1.catalogue_items.show \
--token="$SIGNALS_PAT" --param="product=999999" \
|| echo "call failed with exit $?"
Aggregation (reporting reads)
GET /api/v1/aggregate/{model} — group-by + measure over a SchemaModelCatalog model, without paging the whole collection client-side.
# Revenue by month (money measures are summed in minor units)
php artisan signals:api call api.v1.aggregate.show \
--token="$SIGNALS_PAT" \
--param="model=rentals" \
--query="group_by=starts_at" --query="bucket=month" \
--query="measure=charge_total" --query="fn=sum"
# Outstanding by account, filtered to issued invoices
php artisan signals:api call api.v1.aggregate.show \
--token="$SIGNALS_PAT" \
--param="model=invoices" \
--query="group_by=account_id" --query="measure=amount_outstanding" --query="fn=sum" \
--query="q[status_eq]=issued"
Bulk writes
POST {resource}/bulk accepts a {resource} array of row payloads (same shape as the single-row warehouse endpoint) and returns per-row results — HTTP 201 when every row succeeds, 207 (Multi-Status) when some rows fail so the caller can retry only the failed indices.
php artisan signals:api call api.v1.activities.bulk \
--token="$SIGNALS_PAT" \
--data='{"activities":[{"title":"Call A"},{"title":"Call B"}]}'
php artisan signals:api call api.v1.accounts.bulk \
--token="$SIGNALS_PAT" \
--data=@./accounts-batch.json \
--idempotency-key="$(uuidgen)"
Dry-run (preview before mutating)
Rental transition endpoints (convert_to_quote, convert_to_order, change_status) accept dry_run=true (query or body) to run the guard pipeline and report {allowed, reason, code} without applying the transition — useful for an agent that wants to check feasibility before spending a mutating call.
php artisan signals:api call api.v1.rentals.change_status \
--token="$SIGNALS_PAT" \
--param="rental=42" \
--data='{"status_id":1,"dry_run":true}'
Cursor pagination
List endpoints accept cursor=1 (start) in place of page for large sweeps — the response meta carries next_cursor/prev_cursor instead of total/page, avoiding the cost of an offset count on big tables.
php artisan signals:api call api.v1.activities.index \
--token="$SIGNALS_PAT" \
--query="cursor=1" --query="per_page=100" --query="sort=id"
| Exit code | Meaning |
|---|---|
0 |
Success (list/describe completed, or call returned 2xx) |
1 |
Settings disabled, unknown action/endpoint, auth/permission denial, invalid options, or non-2xx call |
LLM instructions
- Confirm
api_cli.enabledand that the acting principal hascli.accessbefore mutating. - Prefer
list --search=/--resource=to discover route names, thendescribebeforecall. - Use curated MCP tools when connected over MCP; use this CLI for shell/automation and CI.
- On writes, pass
--idempotency-keywhen the API orapi.require_idempotency_keyrequires it. - Scope
--tokenabilities to the job; use--useronly in trusted local/ops contexts (wildcard ephemeral token). - Parse
--format=jsonwithjq; treat exit code1as failure. - Plugin tools:
list --plugins, thencall plugin:{package}:{tool}with--dataargs. - Reach for
api.v1.aggregate.showbefore paging a whole collection to sum a measure client-side (revenue by month, outstanding by account, etc.). - Prefer
{resource}/bulkover N sequential single-rowcalls when creating more than a handful of rows; check each row'sstatusin theresultsarray — a207response means partial success, not failure. - Pass
dry_run=trueon rental transition endpoints (change_status,convert_to_quote,convert_to_order) to check{allowed, reason, code}before spending a mutating call. - Use
--query="cursor=1"(then followmeta.next_cursor) instead ofpage=when sweeping a large collection.