Skip to content

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:

pascal
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.

VerbURLOperationResponse
GET/api/v4/{V}List{ "data": [ … ], "total": N }
GET/api/v4/{V}/{id}Read onethe record object, or 404
POST/api/v4/{V}Create201 + the created record (with keys/derived values)
PUT/api/v4/{V}/{id}Full update200 + the updated record
PATCH/api/v4/{V}/{id}Partial update200 + the updated record (only the body's fields are applied)
DELETE/api/v4/{V}/{id}Delete204

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

bash
# 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   # → 204

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>:

yaml
# MainMenu.yaml
View: Build AutoList
  Model: KITTO_USER_ROLES
bash
curl "…/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 gets 403.
  • Model/IsReadOnly: True makes every write verb return 405 Method Not Allowed.
  • Per-operation Model flagsModel/PreventAdding (→ 405 on POST), Model/PreventEditing (→ 405 on PUT/PATCH), Model/PreventDeleting (→ 405 on DELETE) — let you forbid a single operation while allowing the others.
  • Field-level CanInsert / CanUpdate (and IsReadOnly) are honored: a non-writable field in the body is ignored.
  • The view Controller's PreventAdding / PreventEditing / PreventDeleting are 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:

json
{ "error": "human-readable message", "code": "symbolic_code", "field": "optional" }
StatusWhen
400Malformed body, or unknown database on /token (unknown_database)
403ACL denied
404View or record not found
405Model is read-only
422Business-rule / validation violation
500Unexpected internal error

Authentication

REST clients authenticate with a Bearer token (requires Auth: JWT):

  1. Get a tokenPOST /api/v4/token (anonymous endpoint). The body may be JSON or application/x-www-form-urlencoded (the latter is what the Swagger UI form sends, and the OAuth2-standard token encoding):

    bash
    curl -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>"}

    database is optional (only for multi-database apps with Auth/DatabaseChoices); when given it must be a configured name (a Databases/<Name> entry), otherwise the call returns 400{"code":"unknown_database"}. Bad credentials return 401 {"code":"invalid_login"}.

  2. Call the API — send the token in the Authorization header (no cookie needed):

    bash
    curl "…/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 Bearer scheme because KittoX installs an Indy OnParseAuthentication passthrough — without it Indy rejects any non-Basic scheme 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).

bash
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:

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:

  1. Open POST /token (listed first, no lock icon — it is public) → Try it out. Because the request body is offered as application/x-www-form-urlencoded, Swagger shows separate username / password / database fields (not a raw JSON editor).
  2. Execute, copy access_token from the response.
  3. 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 /docs page 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 (returning 405), and request filtering / the ISAPI handler mapping may restrict the allowed verbs. Remove WebDAV for the app and allow PUT/DELETE/PATCH on 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:

yaml
Server:
  CORS:
    AllowedOrigins: https://app.example.com, https://admin.example.com   # or '*'
    AllowCredentials: True   # emit Access-Control-Allow-Credentials

When the request's Origin matches the list (or AllowedOrigins: '*'), KittoX:

  • answers the preflight OPTIONS /api/v4/… with 204 + Access-Control-Allow-Origin (the echoed origin), Access-Control-Allow-Methods, Access-Control-Allow-Headers (the requested headers, defaulting to Authorization, Content-Type) and Access-Control-Max-Age;
  • adds Access-Control-Allow-Origin (plus Vary: Origin, and Access-Control-Allow-Credentials when 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.pasTKXDataService (core, no JSON): the shared CRUD service (LoadList / LoadRecord / CreateRecord / UpdateRecord / DeleteRecord) that applies the rule sequence and raises EKXDataError (carrying the HTTP status).
  • Kitto.Web.Rest.pas (opt-in) — TKXApiHandlerBase (the /api/v4/{ViewName} handler, virtual so an app can subclass + RegisterOverride a single verb), TKXApiAuthHandler (the token endpoint), IKXApiSerializer + TKXSystemJSONSerializer (System.JSON), and TKXApiErrorFilter (the JSON error envelope).
  • Kitto.Web.Rest.OpenAPI.pas (opt-in, pulled in by Kitto.Web.Rest) — TKXOpenAPIBuilder and the GET /api/v4/openapi.json handler; 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.

Released under Apache License, Version 2.0.