REST API (JSON)
A KittoX application can expose its data views as a REST/JSON web service, in parallel to the HTMLx GUI. The REST tree lives under /api/v4/{ViewName} with real HTTP verbs (GET/POST/PUT/PATCH/DELETE) and reuses the same model, the same business rules (TKRules) and the same ACL as the interactive UI — a record created over REST behaves exactly like one entered in a form (computed fields, reference captions, validation).
The API version (
v4) follows the KittoX major version.
Base path is configurable
/api/v4 is the default. To change the whole REST tree's prefix, set Server/RestBasePath in Config.yaml (e.g. RestBasePath: /rest moves every endpoint under /rest/…, including /rest/token, /rest/docs and /rest/openapi.json). No recompile — just restart. Throughout this page /api/v4 stands for whatever RestBasePath resolves to.
Enabling it (opt-in)
The REST support is opt-in and adds no JSON-framework dependency to a browser-only app. Add the unit to your app's UseKitto.pas, exactly as you opt into Kitto.Web.Enterprise:
uses
Kitto.Html.All,
Kitto.Web.Enterprise,
Kitto.Web.Rest, // <-- enables /api/v4/...
...An app that does not reference Kitto.Web.Rest exposes no /api routes and links no REST code.
Because the router and the TKWebEngine are transport-agnostic, the API is available in every deployment mode once the unit is linked — Standalone, Windows Service, Console, ISAPI and Apache all funnel requests into the same engine (see the caveat on write verbs under IIS/Apache below).
Endpoints
Base path: /api/v4/{ViewName} (the view must be a data view). Content-Type: application/json.
| Verb | URL | Operation | Response |
|---|---|---|---|
GET | /api/v4/{V} | List | { "data": [ … ], "total": N } |
GET | /api/v4/{V}/{id} | Read one | the record object, or 404 |
POST | /api/v4/{V} | Create | 201 + the created record (with keys/derived values) |
PUT | /api/v4/{V}/{id} | Full update | 200 + the updated record |
PATCH | /api/v4/{V}/{id} | Partial update | 200 + the updated record (only the body's fields are applied) |
DELETE | /api/v4/{V}/{id} | Delete | 204 |
List query parameters (same mechanism as the GUI list): ?start= / ?limit= (paging), ?sort= / ?dir= (field name + asc/desc), and ?f_<n>= for the view's configured filters (the client supplies only the value, mapped through the view's ExpressionTemplate — never raw SQL).
Record key ({id}): a single-field PK is the value itself; a composite PK is field=val&field2=val2 (URL-encoded), consistent with the GUI's key handling.
Examples
# List (paged, filtered, sorted)
curl "…/api/v4/Hairs?f_0=Bl&sort=Hair_Color&dir=desc" -b cookies.txt
# → {"data":[{"Hair_Id":"1","Hair_Color":"Blond"},{"Hair_Id":"3","Hair_Color":"Black"}],"total":2}
# Create
curl -X POST "…/api/v4/Hairs" -H "Content-Type: application/json" \
--data '{"Hair_Color":"Chestnut"}' -b cookies.txt
# → 201 {"Hair_Id":"77110F72…","Hair_Color":"Chestnut"} (PK default %COMPACT_GUID% applied)
# Partial update / delete
curl -X PATCH "…/api/v4/Hairs/Hair_Id=77110F72…" -H "Content-Type: application/json" --data '{"Hair_Color":"Auburn"}' -b cookies.txt
curl -X DELETE "…/api/v4/Hairs/Hair_Id=77110F72…" -b cookies.txt # → 204Menu "autobuild" views
A menu can publish a data view built at runtime from a model, with no .yaml view file — a View: Build AutoList node with a Model: child. These autobuild views are exposed over REST too, but only the ones the menu actually references (not every model). The endpoint name is <Builder>_<Model>:
# MainMenu.yaml
View: Build AutoList
Model: KITTO_USER_ROLEScurl "…/api/v4/AutoList_KITTO_USER_ROLES?limit=3" -b cookies.txt
# → {"data":[{"USER_NAME":"admin","ROLE_NAME":"admin"}, …],"total":6}The <Builder>_ prefix keeps builders distinct on the same model (a future Build AutoForm on the same model would be AutoForm_KITTO_USER_ROLES). Requesting a model that the menu does not publish returns 404 — the REST surface is exactly what the menu exposes. Autobuild views honor the same ACL, model permissions and rules as any other view, and are listed in the OpenAPI document alongside the file-based views.
What the API allows (Model + Rules, not View)
Authorization and writability for the REST API are decided at the Model and Rules level, not by the view's Controller flags:
- ACL is enforced per operation (
View/Add/Modify/Delete) — a denied user gets403. Model/IsReadOnly: Truemakes every write verb return405 Method Not Allowed.- Per-operation Model flags —
Model/PreventAdding(→405onPOST),Model/PreventEditing(→405onPUT/PATCH),Model/PreventDeleting(→405onDELETE) — let you forbid a single operation while allowing the others. - Field-level
CanInsert/CanUpdate(andIsReadOnly) are honored: a non-writable field in the body is ignored. - The view Controller's
PreventAdding/PreventEditing/PreventDeletingare NOT honored by REST — they are GUI-only (they hide the buttons; when absent, the GUI falls back to the model's flag). To forbid an operation over REST, set the flag on the Model (or use a rule), not on the view.
Business rules run in the shared service layer on the "apply" step, identically to the GUI: field AfterFieldChange rules (computed fields), record BeforeAddOrUpdate rules (e.g. master totals) and the Model.SaveRecord validation. A rule that rejects the record returns 422.
Errors and status codes
Errors on /api are returned as a JSON envelope, with a real HTTP status:
{ "error": "human-readable message", "code": "symbolic_code", "field": "optional" }| Status | When |
|---|---|
400 | Malformed body, or unknown database on /token (unknown_database) |
403 | ACL denied |
404 | View or record not found |
405 | Model is read-only |
422 | Business-rule / validation violation |
500 | Unexpected internal error |
Authentication
REST clients authenticate with a Bearer token (requires Auth: JWT):
Get a token —
POST /api/v4/token(anonymous endpoint). The body may be JSON orapplication/x-www-form-urlencoded(the latter is what the Swagger UI form sends, and the OAuth2-standard token encoding):bashcurl -X POST "…/api/v4/token" -H "Content-Type: application/json" \ --data '{"username":"admin","password":"secret","database":"FireDAC_MSSQL"}' # → 200 {"token_type":"Bearer","access_token":"<jwt>"}databaseis optional (only for multi-database apps withAuth/DatabaseChoices); when given it must be a configured name (aDatabases/<Name>entry), otherwise the call returns400{"code":"unknown_database"}. Bad credentials return401{"code":"invalid_login"}.Call the API — send the token in the
Authorizationheader (no cookie needed):bashcurl "…/api/v4/Customers" -H "Authorization: Bearer <jwt>"
A missing/invalid token on a protected endpoint returns 401 {"code":"unauthorized"} (JSON, not the HTML page the SPA gets). The browser SPA continues to use the kx_token cookie; the two schemes coexist (the server reads the Authorization: Bearer header first, then the cookie).
The token is self-sufficient: it carries the user, ACL, database and language claims, and the server re-hydrates the request context from them on every call. A valid token therefore keeps working across a server restart or on a different cluster node — it never triggers a "session lost" error, because the token (not a server-side session) is the source of truth. Repeated calls with the same token reuse a single server-side session (keyed by the token's sid claim), so a REST client does not leak a session per request. The server-side TKWebSession remains only as a per-request cache of non-serializable state, re-populated from the claims when absent.
Indy note: the standalone/service/console hosts accept the
Bearerscheme because KittoX installs an IndyOnParseAuthenticationpassthrough — without it Indy rejects any non-Basicscheme with "Unsupported authorization scheme". No configuration needed.
OpenAPI description
The API describes itself: GET /api/v4/openapi.json returns an OpenAPI 3.0 document generated at runtime from the application's metadata — one path group per data view (with only the CRUD operations the model actually allows) and one component schema per view built from its fields (type, maxLength, enum from AllowedValues, readOnly for keys/computed fields, nullable, description).
curl "…/api/v4/openapi.json"Point any OpenAPI tool at it — Postman or openapi-generator to scaffold typed clients. The document is built with the RTL System.JSON by walking the TKViewField metadata — no delphi-neon or typed-DTO layer is needed, because the API's shape is defined by the YAML metadata, not by Delphi types. The endpoint is anonymous (it describes the contract, not the data); operations a model forbids via IsReadOnly / Prevent* are omitted from the spec.
Interactive docs — Swagger UI
Try it live
The REST API of the online demos is public — open the Swagger UI in your browser right now:
- HelloKitto: https://scm.ethea.it/hellokittox/api/v4/docs
- TasKitto: https://scm.ethea.it/taskittox/api/v4/docs
Use the demo credentials from the live demos page for POST /token.
A ready-to-use Swagger UI page is served at GET /api/v4/docs — open it in a browser to explore and try every endpoint. To authenticate:
- Open
POST /token(listed first, no lock icon — it is public) → Try it out. Because the request body is offered asapplication/x-www-form-urlencoded, Swagger shows separate username / password / database fields (not a raw JSON editor). - Execute, copy
access_tokenfrom the response. - Click Authorize (top right), paste the token → every protected endpoint now sends the Bearer.
The Swagger UI assets are vendored in the framework's Home/Resources (served from /res), so the page works offline with no CDN dependency. Each operation carries a stable operationId (list_<View>, get_<View>, postToken, …) that OpenAPI client generators use to name methods.
Tip: after the API metadata changes (new view, new model field), the spec (
openapi.json) is cached by the browser — do a hard refresh (Ctrl/Cmd+Shift+R) on the/docspage to reload it.
Deploying under IIS / Apache — allow the write verbs
The KittoX framework serves the REST API identically on every transport. However, IIS and Apache may block the write verbs before the request reaches the app:
- IIS: the WebDAV module intercepts
PUT/DELETE(returning405), and request filtering / the ISAPI handler mapping may restrict the allowed verbs. Remove WebDAV for the app and allowPUT/DELETE/PATCHon the handler mapping. - Apache: ensure the
<Location>/module configuration and any security modules permit the write verbs.
This is web-server configuration, not a KittoX code change. The Standalone / Service / Console hosts (Indy) accept all verbs out of the box.
CORS (cross-origin browser clients)
By default the API sends no CORS headers, so a browser page served from a different origin cannot call it (server-to-server clients and the same-origin SPA are unaffected). To allow specific origins, configure them under Server/CORS in Config.yaml:
Server:
CORS:
AllowedOrigins: https://app.example.com, https://admin.example.com # or '*'
AllowCredentials: True # emit Access-Control-Allow-CredentialsWhen the request's Origin matches the list (or AllowedOrigins: '*'), KittoX:
- answers the preflight
OPTIONS /api/v4/…with204+Access-Control-Allow-Origin(the echoed origin),Access-Control-Allow-Methods,Access-Control-Allow-Headers(the requested headers, defaulting toAuthorization, Content-Type) andAccess-Control-Max-Age; - adds
Access-Control-Allow-Origin(plusVary: Origin, andAccess-Control-Allow-Credentialswhen configured) to the actual response.
Only /api/v4 is affected; an origin not in the list receives no CORS headers (the browser blocks the response). Works on every deployment mode.
Under the hood
Kitto.Web.Data.Service.pas—TKXDataService(core, no JSON): the shared CRUD service (LoadList/LoadRecord/CreateRecord/UpdateRecord/DeleteRecord) that applies the rule sequence and raisesEKXDataError(carrying the HTTP status).Kitto.Web.Rest.pas(opt-in) —TKXApiHandlerBase(the/api/v4/{ViewName}handler,virtualso an app can subclass +RegisterOverridea single verb),TKXApiAuthHandler(the token endpoint),IKXApiSerializer+TKXSystemJSONSerializer(System.JSON), andTKXApiErrorFilter(the JSON error envelope).Kitto.Web.Rest.OpenAPI.pas(opt-in, pulled in byKitto.Web.Rest) —TKXOpenAPIBuilderand theGET /api/v4/openapi.jsonhandler; builds the OpenAPI 3.0 spec from the metadata with System.JSON.
delphi-neon is not used anywhere in the REST stack; it would only be introduced (in a separate Kitto.Web.Rest.Neon unit) if the API ever exposed typed Delphi DTO endpoints.
See also Attribute-Based Routing and Business Rules.
