Details
The details endpoint, GET /{resource}, describes a resource for the current user: its fields, relations, scopes, actions, metrics, rules, text search, gates and authorizations. A frontend reads it once to build a list, a form or the dialog of an action.
The endpoint answers a thin base. Ask for the rest with the with query parameter: what is not asked for is not computed.
The base
GET /api/artworks
{"data": {
"fields": [
{"name": "id", "label": null, "type": "id", "nullable": false},
{"name": "title", "label": "Title", "type": "text", "nullable": false},
{"name": "medium", "label": null, "type": "enum", "nullable": false, "values": ["oil", "ink", "watercolor"]}
],
"limits": [10, 25, 50],
"meta": {}
}}
fieldshas one entry per field, in declared order:name,label(the field'slabel(), ornull),typeandnullable. AnEnumfield addsvalues, the backing values of the enum, andoffered, the values that writes accept, when itsoffered()leaves cases out:
{"name": "status", "label": null, "type": "enum", "nullable": false, "values": ["planned", "on_sale", "sold_out"], "offered": ["planned", "on_sale"]}
limitslists the page sizes thatsearch.limitaccepts.metaholds what the resource'sdetailsMeta()returns, see below.- The endpoint checks
viewAnyon the resource. Withoutauthorizations,actions.authorizedormetrics.authorizedinwith, it calls no other policy.
Asking for more with with
with is a comma-separated list of sections, such as actions, and parts of a section, such as actions.authorized:
GET /api/artworks?with=fields.capabilities,actions.authorized
A part adds its section with the base keys of that section. A repeated value counts once, and an empty with answers the base.
with | Adds |
|---|---|
fields.capabilities | select, sort, create and update on each field |
fields.filter | filter.operators on each field |
fields.rules | rules on each field: all, create and update |
fields.meta | meta on each field |
relations | each relation: resource, relation and name, plus types and morph on a MorphTo |
relations.constraints | constraints on each relation |
relations.operations | operations on each relation |
relations.pivot_fields | pivot_fields on each many-to-many relation |
actions | each action: name, uriKey, target, destructive, confirm and confirmation |
actions.fields | the input entries of each action |
actions.authorized | authorized on each action (policy calls) |
actions.meta | meta on each action |
metrics | each metric: name, uriKey, type and ranges |
metrics.authorized | authorized on each metric (policy calls) |
metrics.meta | meta on each metric |
instructions | each instruction: name, uriKey and text |
instructions.fields | the input entries of each instruction |
instructions.meta | meta on each instruction |
scopes | each scope: name |
scopes.fields | the input entries of each scope |
scopes.meta | meta on each scope |
rules | the rules of the resource |
text | the text search, null without one |
gates | the names that search.gates accepts |
confirmations | the writes that ask for a confirmation |
authorizations | the class-level abilities for the user (policy calls) |
Any other value is a 422 on with that lists the accepted values:
{
"message": "The with field accepts these values, separated by commas: fields.capabilities, fields.filter, fields.rules, fields.meta, relations, relations.constraints, relations.operations, relations.pivot_fields, actions, actions.fields, actions.authorized, actions.meta, metrics, metrics.authorized, metrics.meta, instructions, instructions.fields, instructions.meta, scopes, scopes.fields, scopes.meta, rules, text, gates, confirmations, authorizations.",
"errors": {"with": ["The with field accepts these values, separated by commas: fields.capabilities, fields.filter, fields.rules, fields.meta, relations, relations.constraints, relations.operations, relations.pivot_fields, actions, actions.fields, actions.authorized, actions.meta, metrics, metrics.authorized, metrics.meta, instructions, instructions.fields, instructions.meta, scopes, scopes.fields, scopes.meta, rules, text, gates, confirmations, authorizations."]}
}
The parsing is strict:
fieldsandlimitsare always in the base:with=fieldsis a422.- There is no third level (
actions.fields.rules) and no wildcard (*). - Values are case sensitive, take no space after a comma, and no empty value between two commas.
- A list,
with[]=gates, is a422:The with field must be a string.
One request per screen
Ask each screen for what it shows:
GET /api/orders?with=fields.capabilities,fields.filter,scopes.fields,actions.authorized
GET /api/orders?with=fields.capabilities,fields.rules,relations.operations,relations.constraints,authorizations
GET /api/orders?with=actions.fields,actions.authorized
The first one serves a list (sortable and filterable columns, scopes, bulk action buttons), the second a create or edit form, the third the dialog of an action.
Fields
GET /api/artworks?with=fields.capabilities,fields.filter,fields.rules,fields.meta
{"name": "title", "label": "Title", "type": "text", "nullable": false, "select": true, "sort": true, "filter": {"operators": ["=", "!=", "like", "not like", "ilike", "not ilike", "in", "not in"]}, "create": true, "update": true, "rules": {"all": ["string", "max:120"], "create": ["required"], "update": ["filled"]}, "meta": {}}
select: accepted insearch.selectsand returned.sort: accepted insearch.sorts.filter.operators: the operators thatsearch.filtersaccept on the field,[]when it is not filterable.createandupdate: writable on that operation.rules.allholds the type rule,nullableandrules().rules.createandrules.updateholdcreationRules()andupdateRules().- Rules are strings: a pipe string is split, a rule object that casts to a string gives that string, an enum rule gives
enum, another rule object gives its snake class name, and closures are left out.
See Fields.
Relations
GET /api/artworks?with=relations.constraints,relations.operations
{"resource": "App\\Rest\\Resources\\ExhibitionResource", "relation": "exhibitions", "constraints": {"required_on_creation": false, "prohibited_on_creation": false, "required_on_update": false, "prohibited_on_update": false}, "operations": ["create", "update", "attach", "detach", "sync", "toggle"], "name": "BelongsToMany"}
resourceis the class of the related resource. AMorphTowith several targets has noresourcekey.operationslists the operations that the entries of the relation accept inmutate,deletelast on adeletable()relation,[]onHasOneThroughandHasManyThrough. Read the operations from it, not fromname.- A
MorphToaddstypes, each morph type mapped to its resource class, andmorph, itstype_columnandkey_column. relations.pivot_fieldsaddspivot_fieldsto each many-to-many relation, with every key of a field entry.
Detect pivot_fields, types and morph by their presence, not by name. See Relations.
Actions and metrics
GET /api/artworks?with=actions.fields,actions.authorized,metrics
{"data": {
"actions": [{"name": "Retire Artworks", "uriKey": "retire-artworks", "target": "search",
"fields": [{"name": "notify", "label": null, "type": "boolean", "nullable": false, "default": false, "rules": ["boolean"], "meta": {}}],
"destructive": false, "confirm": null, "confirmation": false, "authorized": true}],
"metrics": [{"name": "Artworks By Medium", "uriKey": "artworks-by-medium", "type": "partition", "ranges": []}],
"fields": [...],
"limits": [10, 25, 50],
"meta": {}
}}
targetissearch,resourcesornone, see Actions.destructiveandconfirmare hints for clients.confirmationistruewhen the action declares a server-checked confirmation.- An input entry holds
name,label,type,nullable,default,rulesandmeta, plusvaluesfor anEnum, andofferedwhen itsoffered()leaves cases out. A frontend can render it as a form. authorizedistruewhen the user passes every ability that the route of the action or the metric checks before it reads the request. A refused one stays in the list withfalse, and its route answers403.
Instructions and scopes
GET /api/artworks?with=instructions,scopes
{"instructions": [{"name": "By Medium", "uriKey": "by-medium", "text": true}], "scopes": [{"name": "pricedUnder"}]}
An instruction's text says whether it can be sent next to a search.text. instructions.fields and scopes.fields add their input entries. See Scopes and instructions.
Rules, text and gates
GET /api/artworks?with=rules,text,gates
{
"rules": {"all": {}, "create": {"price": ["required", "min:0"]}, "update": {}},
"text": {"fields": ["title"], "modes": ["words", "phrase"], "relevance": false, "trashed": false, "max_length": 255, "engine": null},
"gates": ["view", "create", "update", "delete"]
}
rulesholdsrules(),createRules()andupdateRules()of the resource, keyed by attribute.textdescribes the text search. OnScoutEngine,enginedescribes the filters and sorts of the index, see Search engines.gateslists the abilities thatsearch.gatesaccepts, then the gates that the resource declares.restoreandforceDeletecome afterdeletewhen the model usesSoftDeletes. It is[]when the gates are off. See Gates.
Confirmations
GET /api/documents?with=confirmations
{"confirmations": {"mutate": false, "destroy": true, "restore": false, "forceDestroy": false}}
confirmations says, for each write endpoint, whether the resource declares its confirmation hook, such as destroyConfirmation(). restore and forceDestroy appear when the model uses SoftDeletes. A true endpoint can answer 428: see Confirmation.
Authorizations
GET /api/artworks?with=authorizations
{"authorizations": {"viewAny": {"allowed": true, "message": null}, "create": {"allowed": false, "message": "Only curators add artworks."}}}
authorizations answers each class-level ability that the routes of the resource check: viewAny, create, the abilities that abilities() returns for each endpoint (restore and forceDestroy only when the model uses SoftDeletes), then those of the actions and the metrics. Row abilities are not here: list them in the gates of a search. A resource whose isAuthorizingEnabled() returns false answers "allowed": true to every ability.
The answers go through the decisions of the request, and an ability that authorizationCache() declares is read from the cache.
Meta
detailsMeta() adds keys under meta. It runs on each details request, for its user:
public function detailsMeta(): array
{
return [
'label' => 'Artworks',
'default_sort' => ['field' => 'title', 'direction' => 'desc'],
'curator' => $this->request()->user()?->name,
];
}
{"meta": {"label": "Artworks", "default_sort": {"field": "title", "direction": "desc"}, "curator": "Ada"}}
Behaviour
- The details list what the declarations of the resource return for the current request, so a list that depends on
$this->request()->user()changes with the user. actions,instructionsandrelationsare lists. Maps are objects, also when empty:"meta": {}.- The
describing()event runs afterviewAny, anddescribed()receives the details as the request asked for them. See Events and hooks. - A declaration error throws when the request reads that declaration, for example
InvalidTextSearchExceptionwithwith=text. - Clients ignore the keys that they do not know: later releases can add keys.
- The OpenAPI document gives each resource a
{Resource}Detailsschema, where the base keys are required and the sections and parts optional, and documents thewithparameter. See OpenAPI.