IIST Web API

Developer and administrator manual

API contract and endpoint guide

All core operations are registered in core/src/Api/Routes.php. Routing, request validation, example requests and OpenAPI use the same definitions. Module routes are generated from approved manifests. Administrators maintain the core; external developers implement public SDK interfaces.

Responses

Success:

{"ok":true,"data":{"greeting":"Hello, IIST!"},"meta":{"requestId":"...","apiVersion":"v1","sandbox":true}}

Failure:

{"ok":false,"error":{"code":"HTTP_422","message":"$.name is required."},"meta":{"requestId":"..."}}

Use HTTP status and requestId for diagnostics. 401 authentication; 403 role; 404 resource; 409 revision/state conflict; 413 size; 415 content type; 422 validation; 429 rate limit; 502 worker execution failure; 503 unavailable worker. JSON bodies must be objects. Requests max 6 MB; source bundles max 1 MB of source plus metadata; images max 4 MB. API rate limit 180 requests/minute per directly observed IP, login 10 attempts/15 minutes. If using a proxy, configure upstream rate limiting; the application does not trust client-supplied forwarded IP headers.

Authentication

POST /api/v1/auth/login with email/password; use Authorization: Bearer TOKEN. Tokens are random, stored as hashes, expiring and revocable. Default login token lifetime 24 hours, configurable. Additional server tokens may last 1–720 hours. Roles: admin, developer, consumer; migrated customer accounts have consumer-style access. Tokens carry their account's role, not independent per-token permission scopes. Keep long-lived tokens server-side. Password reset/change revokes all existing tokens.

HTML, blocks and modules

GET /components lists properties/defaults/child support. POST /components/{type}/node creates a validated object; POST /components/render returns html/css/js/assets. Standard safe tag aliases are /html/{tag}/node and /render. /ui/{feature}/node and /render support popup, contact, table, gallery, slideshow, ajax, websocket, features and built-in block names.

{"component":{"id":"contact-button","type":"html.button","version":"1.0.0","props":{"text":"Contact us","variant":"3d"},"children":[]}}

Element IDs must be unique per page. Customer attributes exclude event-handler attributes, raw style/srcdoc and unsafe URLs. Public documents cannot create raw script/style/embedded executable content. Registry metadata gives exact supported properties. Legacy compatibility wrappers remain private developer tools. No SVG/MathML content model is exposed.

CSS and JavaScript

/css/utilities returns common CSS. POST /css/render accepts rules with class, declarations, state, grid/flex layout, transition and responsive breakpoints, plus an optional complete theme. It performs syntactic checks, not full CSS semantic validation.

/js/capabilities describes common runtime helpers. POST /js/render generates safe DOM creation, event actions and timers. /js/ajax/config validates a browser request configuration; /js/websocket/config validates secure endpoint/retry settings. AJAX and WebSocket execute in the browser; these endpoints do not turn HTTP hosting into a WebSocket server or proxy arbitrary URLs. PHP tools/ws-demo.php remains a loopback development echo service.

Websites

/templates lists styles; /templates/{style} returns a complete definition; /templates/{style}/pages/{slug} returns a page. /documents/validate and /pages/render validate full documents. Create websites with /sites; save using expectedRevision; publish that exact draft. Edits never change the published revision until publication. History/restore creates a new draft, retaining history. Ownership is enforced even for admin calls to ordinary site APIs.

Public website/page endpoints return only published definitions. Public render returns complete HTML and enabled signed contact forms. Responses with contact tokens use no-store; tokens expire after one hour and are checked against the current published revision. An owner inbox stores messages; email delivery is not included.

/media accepts base64 PNG/JPEG/WebP images, max 4 MB with dimension/pixel limits, max 200 images/account. Uploaded images are public by unguessable media ID; do not upload confidential media. Gallery/table properties use structured arrays; tables search/filter/sort/paginate client-side.

Documentation

/docs uses bundled Swagger UI. /explorer displays schemas, errors and PHP/JavaScript usage snippets and loads private package OpenAPI for owners/admins. Schema-only core examples may be placeholders for IDs/secrets and need adjustment. Module examples are mandatory and validated. OpenAPI includes public endpoint metadata for published modules, never their source; administrative routes are visible but remain authenticated/role-protected.

Developer module guide and mandatory rules

Developers do not receive core source or server access. Obtain your account and namespace, the public ModuleSdk.php contract and client SDKs. Develop locally or in your own environment; upload source to the remote sandbox through the API/portal. No SSH or browser source editor is required.

Package layout

An .iist.json bundle contains manifest and files. files maps src/ClassName.php to PHP source text and README.md to instructions. Flat source paths only: no archives, symlinks, arbitrary directories or traversal. At most 50 files, 100 KB/file, 1 MB total source; README is required. PHP handlers return an object implementing Iist\Module\Handler. Additional classes autoload by their short name from src; use one class per file, matching filenames, and unique short names inside a package.

<?php
use Iist\Module\{Endpoint,Request,Context};
class Greeting extends Endpoint {
    public function handle(Request $request, Context $context): mixed {
        return ['greeting' => 'Hello, '.$request->require('name').'!'];
    }
}
return new Greeting();

Request::get and require read validated input. Context::caller contains authenticated id/role for informational use; authorization is enforced by core/broker. Context::api->get calls declared catalogue APIs; request calls declared stateless generation APIs. Context::get/put/delete access module storage scoped to the caller. Never echo/debug-print to stdout: stdout is the worker protocol. Throw an exception on failure; worker errors are reported without core secrets.

Manifest rules

Module schemas support object, array, string, integer, number, boolean and null; properties, required, additionalProperties, items, enum, numeric/length/item limits, descriptions/examples/default and email format. This release intentionally rejects unsupported JSON Schema keywords, references and external schemas. Defaults describe examples; they are not automatically inserted into handler inputs. GET/DELETE use scalar query properties. POST/PUT/PATCH support nested JSON data. Response examples describe data inside the standard success envelope.

At least one fixture per operation must match its documented request/response example. Tests run actual handlers and validate output schemas. A passing test is evidence for those fixtures, not a substitute for source review or comprehensive testing.

Remote lifecycle

Upload -> draft -> test -> tested -> submit -> submitted -> admin approve -> approved -> publish -> published. Rejection produces rejected; changes require a new version. Disable prevents calls to the disabled version. Older published versions remain callable by exact version; active is an administrator-selected alias. Rollback chooses an existing published version. Approval and publication are separate.

Remote sandbox calls are private to the owner/admin. /developer/packages/{id}/openapi produces private sandbox docs. No submitted PHP is loaded by the core process. Without a worker, tests/sandbox/invocations return 503 rather than running code unsafely.

Module-to-core and module-to-module calls

Container networking is disabled. The SDK writes a broker request over stdin/stdout. The parent runner checks permission and calls the fixed core base URL using the original caller identity; caller tokens never enter the container.

$catalogue = $context->api->get('/api/v1/components');
$context->put('theme', ['colour' => 'blue']);
$value = $context->get('theme');
$result = $context->api->request(
    'POST', '/api/v1/modules/iist.hello/1.0.0/greet', ['name'=>'IIST']
);

Declare /api/v1/components in coreCalls and iist.hello:1.0.0 in dependencies when using those calls. Calls are bounded to 10/request and dependency depth to two nested hops. Module storage max 64 KB/value and 200 keys/account. Storage is persistent and isolated by user/module/key; external API credentials for the same account can access its storage. Treat it as shared account data, not a secret vault.

UI extensions

API handlers may return existing component trees; use those to build new blocks/modules without changing renderers. A genuinely new component type needs reviewed PHP/JavaScript renderer support and registry changes by core maintainers. Arbitrary uploaded UI JavaScript/CSS is not automatically installed by this API module release. API modules and UI component registration remain distinct contracts.

Public object classes and inheritance

The SDK includes Node, HtmlElement, Block, Module and 113 standard tag wrapper classes under Iist\Module\Html\Tags. They hold data and call the API; they contain no private core renderer implementation.

class BrandButton extends Iist\Module\Html\Tags\TagButton {
    public function __construct(string $id) {
        parent::__construct($id);
        $this->text('Contact us')->classes('iw-button')->attribute('type', 'button');
    }
}
$button = new BrandButton('brand-button');
return $button; // JsonSerializable: the endpoint returns component data.

Node supports prop/add/jsonSerialize/render; HtmlElement adds text/attribute/classes. Node::render calls POST /api/v1/components/render and requires that exact declaration in coreCalls. Blocks/modules use registered base types; for a new appearance compose existing nodes, CSS rules and supported behaviors. The core validates generated nodes when rendering. Inheriting an SDK wrapper does not automatically register a new renderer type.

PHP and JavaScript SDKs

JSON transfers data, not executable PHP objects or inheritance. Developers extend public SDK classes locally and submit server handlers through module packages. Private core classes cannot be inherited directly outside the core.

PHP

Copy sdk/php/IistClient.php into your application. PHP cURL is required; keep your token outside web-accessible source.

require 'IistClient.php';
$client = new Iist\Sdk\Client('https://webapi.iist.lk', getenv('IIST_API_TOKEN'));
$button = $client->component('html.button', 'contact-button', [
    'text'=>'Contact us', 'variant'=>'3d'
]);
$button->prop('text', 'Send inquiry');
$button->print();

RemoteComponent supports prop/add/jsonSerialize/render/html/print. render returns html/css/js/assets. RemotePage renders a complete document. API calls return data, not the success envelope; ApiException exposes status and requestId.

$document = $client->request('GET', '/api/v1/templates/agency');
$client->page($document, 'home')->print();

Prefer one complete page call to many per-element requests. Directly compose returned JSON when building documents. The SDK uses the server renderer and wraps the results locally; it does not duplicate all private PHP renderers. Public publishedPage needs no token. Unpublished sample/page rendering disables contact submission.

JavaScript

Serve sdk/js/iist-client.js with your developer application and import it as an ES module. Configure private API CORS for your origin. Use a signed-in user token kept in memory, never embed a shared admin/server token in frontend code.

import {Client, element} from './iist-client.js';
const client = new Client('https://webapi.iist.lk');
const published = await client.publishedPage('my-company', 'home');
client.mountPage(document.querySelector('iframe'), published);
const paragraph = element('p', 'welcome', {text:'Hello'});
document.body.append(paragraph);

Client.mount renders a component via the API and installs trusted returned HTML/CSS/JS into the page. Use only your trusted IIST core response; never use it to execute arbitrary JSON from another service. IDs must be unique across mounted components. It returns element and destroy(), which removes module listeners/timers. Your Content Security Policy must explicitly accommodate the trusted returned scripts/styles; stricter environments should use iframe rendering or develop a compatible precompiled renderer.

mountPage uses a sandboxed iframe. The initial published HTML is API-rendered, with JavaScript enhancement for widgets. Public forms use their signed token and the public contacts endpoint. CSS/image/script URLs point to the API base for bundled assets; relative URLs supplied in your own component data remain your responsibility.

SDK API methods

Client: request, login, component, page, publishedPage (PHP); request, login, component, page, publishedPage, mountPage, mount (JavaScript). Component: prop, add, toJSON/jsonSerialize, render; PHP also html/print. Request authentication and schema failures expose HTTP status. No automatic retries for writes; handle conflicts explicitly and reload expected revision before saving.

Administrator workflow

Create accounts from the portal or /admin/users; assign developer/consumer/admin roles. Developers receive devID.* namespaces. Share credentials through your own approved channel. Admin password reset revokes existing API tokens.

Review an uploaded package's exact files, SHA-256, endpoint definitions, dependencies, examples, test report and requested catalogue calls. Read code for incorrect permissions, unbounded work, accidental output, unsafe generated content and misleading docs. Tests use a worker but do not prove all behavior.

Write a review note when approving or rejecting. Publish separately after approval. Publication registers versioned endpoints and regenerates their documentation. Disable immediately to stop an affected version; active routing is cleared if it pointed to that version. Disabling a dependency causes calls relying on it to fail; inspect dependents before changing published availability.

Rollback changes a module's active version to an already published version. Exact version URLs remain pinned. Package history is immutable and audit entries record upload/test/submit/review/publish/disable/rollback. There is no destructive module deletion endpoint in this release.

The portal shows your own websites, source review, tests/sandbox, account creation and catalogue. Site history/inbox are owner operations. For large source reviews use the package JSON endpoint and your normal code review tooling; this portal is not a complete IDE.

API tokens inherit account role and permissions. A successful admin call does not grant uploaded module code administrative API access: broker routes are restricted. Keep worker credentials separate from account tokens. Review operational logs and backups. Third-party source should be accepted only after real isolation and failure-path tests on your deployed worker.

Deployment and first server test

Choose your hosting layout

Recommended: PHP core behind HTTPS on webapi.iist.lk; private worker on a separate Linux VPS. For an initial VPS test they may share a host, with the worker listening on loopback and module containers receiving no core mounts or network.

Shared hosting: upload the core and configure core/public as the subdomain's document root. Core endpoints, rendering, Studio, authentication, module submission/review and documentation work. Module tests/execution require a separate Docker worker connected privately through HTTPS. The API returns 503 when no worker is configured; it never executes uploaded PHP in the core process.

Requirements: PHP 8.3+, PDO, pdo_sqlite, cURL and sessions. Python 3.10+ and Docker Engine on the worker host. Apache mod_rewrite or the supplied Nginx configuration. No Composer/npm installation is needed on the core. The worker Docker image requires an initial PHP image download; pin a verified image digest for your deployment.

A. Install the core

  1. Extract the archive to a private application directory such as /opt/iist-web-api. Keep sdk, worker, tests and core source outside every public web root on the hosting account, including any parent domain. Keep the uploaded ZIP private too. The supplied Apache deny rule is a secondary safeguard; Nginx ignores .htaccess.
  2. Create the webapi.iist.lk subdomain and point its document root to /opt/iist-web-api/core/public.
  3. Copy core/config.example.php to core/config.php. Set base_url to https://webapi.iist.lk; set db_path to a private writable path such as /var/lib/iist-web/iist.sqlite. Set sandbox=true during testing.
  4. Set cors_origins to exact permitted developer origins, including scheme and port. Public read endpoints and signed contact submissions allow cross-origin access without credentials. Private endpoints require explicit origins and bearer authentication. CORS does not authorize a caller.
  5. Give the PHP service write access to the private data directory only; source/assets should not be writable by uploaded modules. Ensure the private database, contact.key, WAL/SHM and media files cannot be downloaded through the web server.
  6. From the core directory run:
php bin/api-setup.php your-admin-email@example.com admin

Enter a password of at least 12 characters through stdin. No default account or production token is bundled. If the email already exists, use it after php bin/api-migrate.php; setup never silently replaces passwords.

  1. For an existing 0.2 installation, stop writes, back up the entire private data directory using SQLite-aware backup, preserve config.php/extensions.php, replace source/assets and run php bin/api-migrate.php. Migration is additive. Keep the original source and matching data backup for rollback. The external API intentionally uses only the built-in registry; local extensions.php remains a trusted Studio extension mechanism, not a remote module installation mechanism.
  2. Enable HTTPS. Apache uses core/public/.htaccess. Nginx example is in deployment/nginx.conf; adjust paths and PHP-FPM socket. PHP must receive the Authorization header.
  3. Open /api/v1/health, /docs and /. Sign in; create a developer account; create/publish a sample site and test its contact inbox.

For local development:

cd core
php bin/api-setup.php admin@example.com admin
PHP_CLI_SERVER_WORKERS=4 php -S 127.0.0.1:8090 -t public public/router.php

Set base_url to http://127.0.0.1:8090 for this installation. Multiple PHP workers are needed when module broker calls return to the same core server. The built-in PHP server is not a production server.

B. Install the isolated worker

On the Linux worker host, copy only worker/ (it contains the public module SDK). Core source/database/secrets must not be mounted into module containers.

cd /opt/iist-worker
# Review and pin the FROM image digest in Dockerfile before production.
docker build -t iist-module-runtime:1 .
python3 -c 'import secrets; print(secrets.token_hex(32))'

Store the generated 64-character hex secret in a private worker environment file and in core/config.php as worker_token. Keep it out of the module SDK and developer credentials.

IIST_WORKER_TOKEN=YOUR_RANDOM_64_HEX_SECRET
IIST_CORE_BASE=https://webapi.iist.lk
IIST_WORKER_BIND=127.0.0.1
IIST_WORKER_PORT=8096
IIST_WORKER_IMAGE=iist-module-runtime:1

The worker token is also used to authenticate dependency-depth context, so both ends must share the same secret. The worker service is trusted and must be able to invoke Docker. Use a dedicated worker host where practical; Docker access is powerful. Modules themselves never receive the Docker socket or the worker token.

Install deployment/iist-worker.service with the correct paths/user, then start it. For a foreground test, export those environment values and run python3 server.py. It refuses to start without the secret or runtime image.

On one host set core worker_url to http://127.0.0.1:8096. On separate hosts put the worker behind private-network HTTPS or a private tunnel; restrict access to the core host and set worker_url accordingly. Never expose an unauthenticated worker. TLS on the public core is not sufficient to protect a plaintext cross-host worker connection.

Containers run as a non-root user with no network, read-only root/module files, bounded memory/CPU/processes and a 15-second execution deadline. Module calls to core APIs use a controlled parent-process broker. Modules do not receive caller tokens; the broker retains them.

C. Prove module publication

  1. Sign in as a developer and download the starter bundle from the portal. Its namespace is changed to devYOUR_ID.hello.
  2. Upload it; inspect its immutable source and SHA-256.
  3. Click test; every source file is linted and each fixture executes through Docker. Call hello.greet in the sandbox with {"name":"IIST"}.
  4. Submit the tested package.
  5. Sign in as admin; inspect source, schemas, usage examples and test report; write a review note; approve; publish.
  6. Call the versioned endpoint using another authorized account. It appears automatically in /docs and /openapi.json.
  7. Disable the version and verify calls are rejected. Re-publish a reviewed disabled version or select an older published version through the rollback endpoint.

Set sandbox=false only after server acceptance tests, backup/recovery testing and deployment review. “Try it out” uses the current installation; it does not silently redirect production operations to another server.

Backup and rollback

Back up the full private data directory, application version, config and worker image identification. Use SQLite backup API or stop writes before copying database/WAL/SHM. Package source and immutable version hashes are stored in the database. Restoring only old source does not undo new writes. Module active-version rollback changes routing, not stored data; module migrations are not supported and must not be assumed reversible.

Testing and acceptance

Never run mutation tests against production. Tests create accounts, websites, messages and module packages.

cd core
php tests/extended.php
python3 ../tests/worker-tests.py
python3 ../tests/worker-protocol.py

Core tests cover all standard/legacy classes and built-in components; worker tests validate command restrictions, source path controls and broker denial rules without needing Docker.

With a disposable installed API running:

export IIST_TEST_BASE=http://127.0.0.1:8090
export IIST_TEST_EMAIL=admin@example.test
# Set IIST_TEST_PASSWORD privately to your disposable test password.
python3 tests/api-integration.py

Default integration tests expect a core with no worker configured and check its 503 behavior. For a real configured worker set IIST_TEST_MODULES=1; the tests additionally test/sandbox/submit/approve/publish/invoke/disable a developer package. IIST_TEST_ALL_COMPONENTS=1 broadens HTTP rendering coverage and may wait for rate limits; do not disable production limits to run tests.

Server acceptance: repeat API integration with the actual Docker worker; inspect isolation flags and source mounts; test memory/time/response limits and bad packages; confirm no core files or host services are accessible; test namespace ownership, account isolation, version routing, dependency broker access, private storage and disable behavior. Then test published site contact forms, Swagger examples and PHP/JS clients from real permitted origins.

Delivered verification is in VERIFICATION.md. Live Docker execution cannot be inferred from mocked protocol tests; run the real-worker acceptance test on your host.

worker-protocol.py runs trusted examples against real PHP with Docker process calls mocked. It verifies message exchange and SDK behavior, not container isolation. Set IIST_TEST_PHP if PHP is not on PATH.

Release boundaries and production work

This is a deployable server-testing release, not a claim that every future platform feature is finished.

Implemented: 68 core API operations, automatic OpenAPI/Swagger, account tokens and roles, built-in object rendering, templates, websites/revisions/publication, images, contact inbox, module ownership and immutable packages, isolated worker design/execution implementation, schema/fixture testing, review/publication/disable/active rollback, public client/module SDKs and caller-owned module storage.

Not implemented: MySQL backend, email delivery, password-recovery email/OTP, billing/subscriptions, third-party arbitrary UI renderer installation, dependency data migrations, scheduled/background module jobs, general internet access from modules, general-purpose WebSocket service hosting, shared section editing, drag-and-drop API-native redesign of the original Studio, or AI integration. Existing Studio remains available on the same database with its original session-based endpoints.

Swagger and custom documentation are generated from registered contracts; some core response schemas use an open object where output depends on component/document type. Concrete component schemas are included under components/schemas. Core document validation adds recursive component constraints that the broad page schema alone does not enforce.

The app does not promise perfect isolation solely because Docker is used. Harden the worker host and image, verify container limits, keep the Docker socket private, test exhaustion/failure cases and obtain appropriate security review before production use. The worker implementation has no unsafe in-process execution fallback.

SQLite handles initial tests and modest installations. Operational migration to MySQL, production load targets, complete accessibility review and independent security testing remain separate work. Website generation does not remove browser compatibility/accessibility responsibilities from developers.