Skip to content

_describe manifest

GET /_describe returns a compact JSON manifest of every loaded schema — fields, relations, aggregations, file fields, ACL slots, soft-delete / audit / search flags, REST endpoints, GraphQL queries and mutations, MCP tools.

An agent landing on a fresh dAvePi can plan against the API in one round-trip, before writing a line of integration code. No swagger.json bloat, no “we need three queries to figure out what’s here.”

Swagger 2.0 is what the framework also serves at /api-docs/swagger.json, but it has gaps for agent-first use:

Swagger 2.0_describe
SizeLarge — full path matrix per resource.Compact — feature flags + per-schema metadata.
FormatSwagger 2.0.Custom, but documented and stable.
RelationsUntyped — just an accountId: string field.First-class — relations: { contacts: { hasMany, fk } }.
State machinesUntyped — just an enum.First-class — stateMachines: { status: { initial, states, transitions } }.
AggregationsEach is a path with hand-typed params.First-class — aggregations: [{ name, params }].
ACL slotsAbsent.Present — acl: { list: ['admin'] }.
Idempotency supportAbsent.Per-route flag.
MCP toolsAbsent.Per-schema list.

For an agent that needs to plan, _describe is the right map.

{
"version": "v1",
"features": {
"softDelete": true,
"audit": true,
"search": true,
"files": true,
"relations": true,
"aggregations": true,
"idempotency": true,
"stateMachines": true,
"webhooks": true
},
"schemas": [
{
"path": "account",
"collection": "account",
"softDelete": true,
"audit": true,
"fields": [
{ "name": "userId", "type": "String", "required": true, "stamped": true },
{ "name": "name", "type": "String", "required": true, "searchable": true },
{ "name": "isActive", "type": "Boolean", "computed": true }
],
"relations": {
"contacts": { "kind": "hasMany", "target": "contact", "fk": "accountId" }
},
"aggregations": [
{
"name": "countByRegion",
"params": [{ "name": "since", "type": "date" }]
}
],
"stateMachines": {},
"fileFields": [],
"acl": { "list": ["admin"] },
"endpoints": {
"rest": [
{ "method": "POST", "path": "/api/v1/account", "idempotent": true },
{ "method": "GET", "path": "/api/v1/account" },
{ "method": "GET", "path": "/api/v1/account/:id" },
{ "method": "PUT", "path": "/api/v1/account/:id" },
{ "method": "DELETE", "path": "/api/v1/account/:id" },
{ "method": "POST", "path": "/api/v1/account/:id/restore" }
],
"graphql": {
"queries": ["accountById", "accountMany", "accountSearch", "accountCountByRegion"],
"mutations": ["accountCreateOne", "accountUpdateById", "accountRemoveById", "accountRestore"]
},
"mcp": [
"list_account", "get_account", "create_account", "update_account",
"delete_account", "restore_account", "search_account",
"list_account_contacts", "aggregate_account_countByRegion"
]
}
}
],
"auth": {
"endpoints": {
"register": "/register",
"login": "/login"
},
"tokenType": "Bearer",
"tokenTtl": "2h"
}
}

_describe is public by default — it carries shape, not data. Knowing that an account resource exists doesn’t compromise any particular tenant’s data; an agent still needs a valid JWT to make calls.

If you want it gated, wrap with auth(true) in app.js:

app.use('/_describe', auth(true), describeRouter);

The _describe shape is part of dAvePi’s stable contract — the framework versions it via version and treats breaking changes the same as a major-version bump. Field flags can be added without breaking consumers, but the existing keys’ meanings won’t change.

Use caseWhy _describe
Agent landing on a fresh projectOne round-trip to plan against.
Admin SPA bootstrapThe shipped Refine admin reads _describe at startup.
TypeScript client generationdavepi gen-client walks the schema map directly, but a remote variant could read _describe if you’d rather pull live.
Custom dashboards”Render a form for resource X” needs schema metadata; this is the source.
Documentation toolingGenerated docs / SDKs / wrappers can pull from one endpoint.