Skip to content

Object endpoints

All object operations live on a single controller, ObjectController, under:

/api/v1/genie/object

Every endpoint is [Authorize]. An object is addressed by its name (the view name/slug). The former separate /table and /form controllers, and the legacy GET /metadata/{name} and POST /object/values routes, no longer exist — everything is unified here.

Method Route Returns Purpose Gate
GET /{name}/metadata MetadataResult SQL-free schema (the ObjectView with SQL stripped). The UI decides table vs form vs view from this. Static & cacheable. auth only
POST /{name}/table TableResult A page of grid rows + pagination + per-user permission facts. View
POST /{name}/form FormResult A record’s field values for an editable form (first row of the form SQL). View / Update
POST /{name}/view FormResult A record’s field values for a read-only view (view mode forced). View

POST /{name}/table accepts a TableRequest body (filters, search text, sort, page/pageSize). POST /{name}/form and /{name}/view accept a FormRequest body (the record’s primary-key / parent parameters). An omitted body is treated as an empty request.

The TableResult/FormResult permission facts (Permissions) carry the standard verb booleans (Create/Update/Delete/Export/Import) plus a Capabilities list naming the caller’s business capabilities beyond them (e.g. ["Approve"]). Rows returned by /table and /form//view, and exports, are already row-filtered by the caller’s List filter, and each row may carry Permit__<Capability> cells answering per-row conditions.

Method Route Returns Purpose Gate
POST /submit ObjectActionResult Create or update a record (SubmitRequest — requires FormName). Create / Update
POST /delete-row ObjectActionResult Delete a row (DeleteRowRequest). Delete
POST /execute-row-action ObjectActionResult Run a declared row action’s SQL (ExecuteRowActionRequest). per action

Mutation results can carry dispatch envelopes (e.g. refreshTable, showAlert) that tell the UI what to do next — see Actions & row actions.

When an entity declares Concurrency="Enabled", the record’s RowVersion stamp is loaded with the form and sent back in SubmitRequest.Parameters (and, for the atomic authored-SQL path, DeleteRowRequest.RowVersion). If the stored stamp has moved on since the record was loaded, the mutation is rejected with HTTP 409 Conflict (envelope { "success": false, "error": "This record was changed by someone else…" }) instead of silently overwriting the other change. RowVersion is a reserved parameter — it passes the mass-assignment sanitizer and is never written as data.

FormResult (the /{name}/form and /{name}/view payload) carries FormLoadTime — the UTC instant (ISO round-trip) at which the record was loaded, stamped server-side. Like RowVersion it is a reserved parameter the client sends back in SubmitRequest.Parameters, and the submit binds it as @FormLoadTime for the authored SQL (falling back to “now” when a caller supplies none). It is never written as data. Being client-round-tripped it can be replayed — treat it as an audit/heuristic value, not a security control. See Parameters the submit blocks always receive.

A submit (or /import-data) that fails field validation — required, RegexValidation, a type bound, or an author-declared <Validate> rule — is rejected with HTTP 400 whose envelope carries fieldErrors (field name → message) alongside error, so the client can highlight and focus the offending field. Rules declared Type="Sql" run server-side only, which is exactly why the response has to name the field. See Validation.

Every mutation above (plus /import-data and /upload) honours an optional Idempotency-Key request header. Send a unique key with a write and the engine remembers the first response (scoped per user + company, for Genie:Idempotency:Ttl, default 24 h); a repeat of the same key replays that response instead of re-running the mutation — so a network retry or double-submit can’t create duplicate rows. A duplicate that arrives while the first is still in flight gets a 409. Configure via Genie:Idempotency; omit the header to opt out per request.

Method Route Returns Purpose Gate
POST /export-data ObjectActionResult Generate an export file under wwwroot/Exports (ExportTableRequest). View / Export
GET /export-file/{fileName} file stream Download a previously-generated export as an authenticated attachment. Bare file name only; traversal rejected. auth
POST /import-data ObjectActionResult Bulk import (ImportTableRequest, multipart form). Body cap from Genie:Uploads:MaxImportBytes (default 1 GiB). Import
GET /{name}/import-template?format=xlsx|csv ObjectActionResult Generate a header-only import template; download via export-file. Import

Import/export column mapping and custom handlers are authored in the view — see Bulk import & handlers.

Method Route Returns Purpose
POST /field-dataset List<Dictionary<string,string>> Options for a dependent Select field (FieldDataSetRequest — requires FormName + FieldName).
POST /sequence-number string Preview/reserve a formatted sequence number (SequenceNumberRequest). See Sequences.
POST /upload List<FileUploadResult> Upload temporary attachment files (multipart: objectName, fieldName, viewId). Size limit 100 MB.
DELETE /upload/{uploadKey} message Remove a temporary uploaded file before submit.
GET /label/{objectName} string The view’s display label.
  • GET/POST /api/v1/genie/object-explorer/... — the object explorer surface.
  • /api/v1/genie/rpc/... — stored-procedure / RPC calls (ProcedureCallController).

See Other endpoints for the non-object controllers.