Once an entity is defined, its records are served by your app's endpoint. All six routes share one generic execution path, so auth, plan limits, encryption, and indexing behave identically for every entity.

http
POST   /app/{orgcode}/create/{entity}        insert (auto keys, formula, encryption, indexes)
GET    /app/{orgcode}/item/{entity}/{id}      read one (projected); unknown id -> 404
PUT    /app/{orgcode}/update/{entity}/{id}    update fields; recompute formula; re-validate
DELETE /app/{orgcode}/delete/{entity}/{id}    delete record (+ unique sentinels)
GET    /app/{orgcode}/list/{entity}           list (see Listing & querying)
GET    /app/{orgcode}/count/{entity}          { count }

Request lifecycle

Every CRUD request flows through the same steps:

1App gateway authorizer resolves your tenant ACL and the workspace scope.
2The runtime loads the entity's api-config record for the operation.
3{token} placeholders are resolved against path, body, query, and JWT claims.
4The DDB query or mutation is built from the key templates.
5Projection and enrichment run — rename, cast, fetch S3 fields, resolve lookups, strip encrypted fields.
6The response is shaped per the selected view.

What create does

On create, the runtime checks plan limits (a 429 with an upgrade URL if exceeded), generates auto keys (guid, autoincrement), resolves the record keys, stamps index attributes, writes audit fields, encrypts encrypted fields, runs any before-events, writes the record, and best-effort indexes it for search.

Before-events can block a write

If a synchronous before-event hook fails, the create returns 424 and the record is not written. See Events & automations.

Views & projections

A Views map on each read/create config decides what crosses the wire. ?view=<name> overrides the default per request.

  • Read projection (get/list) — choose fields, rename with as, cast types, hide with visible:false, fetch s3 fields, or enrich lookup (foreign-key) fields.
  • Create/update response — returns only the fields named in the response view; if none is configured it returns just id. The raw item is never echoed.
  • Encrypted fields are stripped from every response regardless of the view.

Only listed fields are returned

Once a view has a projection, only the fields it lists appear in the response — a field you don't list never shows up, whatever its type. A listed field comes back under its as name (product_ref with as: product_id is returned as product_id, never product_ref). Get and list follow the same rules.

Showing a name instead of an id (lookup)

A record stores the other record's id (say product_ref). To return the product's name as well, add a separate new field to the get/list projection with type: "lookup". Keep the id as its own plain entry if you also want the id back.

json
{
  "id": { "type": "string", "visible": true },
  "product_ref": { "type": "string", "as": "product_id", "visible": true },
  "product_name": {
    "type": "lookup",
    "lookupOn": "product_ref",
    "sourceapi": "get-product",
    "passLookupAs": "id",
    "fieldNameFromLookupApi": "name",
    "visible": true
  }
}

The response then contains product_id (the stored id) and product_name (read from the product's get API). lookupOn is the stored id field, sourceapi is the other entity's get config, passLookupAs is the parameter that get API expects for the id, and fieldNameFromLookupApi is the field to return.

Give the lookup its own name

Don't define the lookup under the id field's own name (a lookup entry called product_ref whose lookupOn is also product_ref). Use a new name such as product_name.

Add a named view later

Generated entities ship with default (all non-encrypted fields) and compact (id + name). Add views like summary afterwards with PUT /api/{api} and a merged Views map — no record changes needed.