# ZeyOS guides — LLM full working set Client guide, iXML guide and SDK. All exports: /ai/guide/index.json ## Choose your task The `zeyos` skill route "Use ZeyOS business data" is listed under "Use a coding agent with ZeyOS". ### Build a ZeyOS app Write native iXML and Zymba that runs inside ZeyOS, and package it as an application. Prerequisites: The ZeyOS SDK (zeysdk) and a linked sandbox instance; Permission to send source to that instance. Read in this order: 1. Set up the SDK: /ixml/ixml-guide/development-environment/sdk#first-time-setup (Markdown: /ai/guide/ixml/ixml-guide/development-environment/sdk.md) 2. App structure: /ixml/ixml-guide/development-environment/app-structure (Markdown: /ai/guide/ixml/ixml-guide/development-environment/app-structure.md) 3. Execution contexts: /ixml/ixml-guide/development-environment/execution-contexts (Markdown: /ai/guide/ixml/ixml-guide/development-environment/execution-contexts.md) 4. Minimal app: /ixml/ixml-guide/development-environment/minimal-app (Markdown: /ai/guide/ixml/ixml-guide/development-environment/minimal-app.md) 5. Verification checkpoints: /ixml/ixml-guide/development-environment/verification-checkpoints (Markdown: /ai/guide/ixml/ixml-guide/development-environment/verification-checkpoints.md) First success: You run `zeysdk compile` on the minimal app against your sandbox and it reports no errors. The published sample compiles on a sandbox but has not been run on an instance, so this run is your first evidence for your own instance. Sample: Minimal app: /ixml/ixml-guide/development-environment/minimal-app Troubleshooting: Native troubleshooting: /ixml/ixml-guide/development-environment/troubleshooting Reference: /ixml, /zymba, /db, /sdk ### Connect an external application Read and write ZeyOS data from a service, browser app or script running outside ZeyOS. Prerequisites: Access to a ZeyOS instance; OAuth client credentials; Node.js 18 or later for the JavaScript client. Read in this order: 1. Integration architecture: /guide/tutorials/integration-architecture (Markdown: /ai/guide/guide/tutorials/integration-architecture.md) 2. Authentication: /guide/api-reference/authentication (Markdown: /ai/guide/guide/api-reference/authentication.md) 3. JavaScript client: getting started: /guide/javascript-client/getting-started (Markdown: /ai/guide/guide/javascript-client/getting-started.md) 4. Making requests: /guide/javascript-client/making-requests (Markdown: /ai/guide/guide/javascript-client/making-requests.md) 5. Server-side integrations: /guide/tutorials/server-side-integrations (Markdown: /ai/guide/guide/tutorials/server-side-integrations.md) First success: `client.schema.validate('listTickets', { fields: ['ID', 'name'] })` passes offline, then the same query returns your tickets. Sample: Dashboard sample app (read-only): /guide/sample-apps/dashboard Troubleshooting: Practical guide: /guide/javascript-client/practical-guide Reference: /api, /auth, /db ### Use a coding agent with ZeyOS Give a coding agent the authoritative references, project instructions and validation tools. Prerequisites: A coding agent such as Kilo Code, Pi or OpenCode; The ZeyOS CLI and client, version 1.0.0 or later. Read in this order: 1. Agent setup: /guide/agent-workflows/agent-setup (Markdown: /ai/guide/guide/agent-workflows/agent-setup.md) 2. Project instructions: /ai/agent-instructions.md 3. Agent quickstart: /guide/agent-workflows/agent-quickstart (Markdown: /ai/guide/guide/agent-workflows/agent-quickstart.md) 4. Native code validation: /ixml/ixml-guide/development-environment/coding-agents (Markdown: /ai/guide/ixml/ixml-guide/development-environment/coding-agents.md) First success: The agent follows the project instructions and shows a request preview without writing anything. Sample: Agent recipes: /guide/agent-workflows/agent-recipes Troubleshooting: CLI coverage and escalation: /guide/agent-workflows/cli-coverage-and-escalation Reference: /docs/ai, /ixml, /zymba, /api, /db #### Use ZeyOS business data Answer questions from your records, and run record operations you explicitly request, with or without an agent. Prerequisites: The `zeyos` CLI, signed in with `zeyos login`. Read in this order: 1. Agent quickstart: /guide/agent-workflows/agent-quickstart (Markdown: /ai/guide/guide/agent-workflows/agent-quickstart.md) 2. Agent recipes: /guide/agent-workflows/agent-recipes (Markdown: /ai/guide/guide/agent-workflows/agent-recipes.md) 3. Coding agents: /guide/agent-workflows/coding-agents (Markdown: /ai/guide/guide/agent-workflows/coding-agents.md) First success: `zeyos count accounts --filter '{"type":1,"visibility":0}'` prints your number of customers. Sample: Agent recipes: /guide/agent-workflows/agent-recipes Troubleshooting: CLI coverage and escalation: /guide/agent-workflows/cli-coverage-and-escalation ## AI & Integration Guide - ZeyOS for Agents and Applications: /ai/guide/guide.md - Agent Quickstart: /ai/guide/guide/agent-workflows/agent-quickstart.md - Agent Recipes: /ai/guide/guide/agent-workflows/agent-recipes.md - Agent Setup: /ai/guide/guide/agent-workflows/agent-setup.md - CLI Coverage and Escalation: /ai/guide/guide/agent-workflows/cli-coverage-and-escalation.md - Coding Agents: /ai/guide/guide/agent-workflows/coding-agents.md - Authentication: /ai/guide/guide/api-reference/authentication.md - Data Retrieval: /ai/guide/guide/api-reference/data-retrieval.md - Resources: /ai/guide/guide/api-reference/resources.md - Schema Reference: /ai/guide/guide/api-reference/schema.md - Commands Reference: /ai/guide/guide/cli/commands.md - Configuration: /ai/guide/guide/cli/configuration.md - CLI Getting Started: /ai/guide/guide/cli/getting-started.md - MCP Server: /ai/guide/guide/cli/mcp-server.md - Authentication: /ai/guide/guide/javascript-client/authentication.md - Getting Started: /ai/guide/guide/javascript-client/getting-started.md - Making API Requests: /ai/guide/guide/javascript-client/making-requests.md - Practical Implementation Guide: /ai/guide/guide/javascript-client/practical-guide.md - Keeping OKF Fresh: /ai/guide/guide/okf/keeping-fresh.md - Refining with Loops: /ai/guide/guide/okf/loops.md - OKF Overview: /ai/guide/guide/okf/overview.md - Producing & Consuming OKF: /ai/guide/guide/okf/producing-and-consuming.md - CRM Account List: /ai/guide/guide/sample-apps/crm.md - Dashboard: /ai/guide/guide/sample-apps/dashboard.md - Kanban Board: /ai/guide/guide/sample-apps/kanban.md - Application Developers: /ai/guide/guide/tutorials/application-developers.md - Browser UI Playbook: /ai/guide/guide/tutorials/build-your-own-zeyos-frontend.md - Integration Architecture: /ai/guide/guide/tutorials/integration-architecture.md - Server-Side Integrations: /ai/guide/guide/tutorials/server-side-integrations.md ## iXML Guide - Introduction: /ai/guide/ixml.md - Data Types: /ai/guide/ixml/ixml-guide/data-types.md - Arrays: /ai/guide/ixml/ixml-guide/data-types/arrays.md - Construction: /ai/guide/ixml/ixml-guide/data-types/arrays/construction.md - Filtering & Sorting: /ai/guide/ixml/ixml-guide/data-types/arrays/filtering-sorting.md - Inspection: /ai/guide/ixml/ixml-guide/data-types/arrays/inspection.md - Set Operations: /ai/guide/ixml/ixml-guide/data-types/arrays/set-operations.md - Slicing & Structuring: /ai/guide/ixml/ixml-guide/data-types/arrays/slicing.md - Stack & Queue: /ai/guide/ixml/ixml-guide/data-types/arrays/stack-queue.md - Booleans: /ai/guide/ixml/ixml-guide/data-types/booleans.md - Null and Undefined: /ai/guide/ixml/ixml-guide/data-types/null-and-undefined.md - Numbers: /ai/guide/ixml/ixml-guide/data-types/numbers.md - Strings: /ai/guide/ixml/ixml-guide/data-types/strings.md - Type Conversion: /ai/guide/ixml/ixml-guide/data-types/type-conversion.md - Database Operations: /ai/guide/ixml/ixml-guide/database-operations.md - Connections & Raw SQL: /ai/guide/ixml/ixml-guide/database-operations/connections.md - Extending the Data Model: /ai/guide/ixml/ixml-guide/database-operations/extending-data-model.md - Object Operations: /ai/guide/ixml/ixml-guide/database-operations/object-operations.md - SQL Operations: /ai/guide/ixml/ixml-guide/database-operations/sql-operations.md - Tags: /ai/guide/ixml/ixml-guide/database-operations/tags.md - Development Environment: /ai/guide/ixml/ixml-guide/development-environment.md - App Structure: /ai/guide/ixml/ixml-guide/development-environment/app-structure.md - Coding Agents and Native Code Validation: /ai/guide/ixml/ixml-guide/development-environment/coding-agents.md - Development Console: /ai/guide/ixml/ixml-guide/development-environment/dev-console.md - Execution Contexts: /ai/guide/ixml/ixml-guide/development-environment/execution-contexts.md - Minimal App: /ai/guide/ixml/ixml-guide/development-environment/minimal-app.md - SDK Workflow: /ai/guide/ixml/ixml-guide/development-environment/sdk.md - Troubleshooting: /ai/guide/ixml/ixml-guide/development-environment/troubleshooting.md - Verification Checkpoints: /ai/guide/ixml/ixml-guide/development-environment/verification-checkpoints.md - Error Handling & Debugging: /ai/guide/ixml/ixml-guide/error-handling.md - API Error Patterns: /ai/guide/ixml/ixml-guide/error-handling/api-error-patterns.md - Best Practices: /ai/guide/ixml/ixml-guide/error-handling/best-practices.md - Debugging & Troubleshooting: /ai/guide/ixml/ixml-guide/error-handling/debugging.md - Throwing Errors: /ai/guide/ixml/ixml-guide/error-handling/throwing-errors.md - Try / Catch: /ai/guide/ixml/ixml-guide/error-handling/try-catch.md - Arithmetic Expressions: /ai/guide/ixml/ixml-guide/introduction/arithmetic.md - Quick Reference: /ai/guide/ixml/ixml-guide/introduction/quick-reference.md - Result Propagation: /ai/guide/ixml/ixml-guide/introduction/result-propagation.md - Scope and Assignment: /ai/guide/ixml/ixml-guide/introduction/scope-and-assignment.md - Variables and Substitution: /ai/guide/ixml/ixml-guide/introduction/variables-and-substitution.md - Reusability and OOP: /ai/guide/ixml/ixml-guide/reusability-and-oop.md - Classes & Objects: /ai/guide/ixml/ixml-guide/reusability-and-oop/classes.md - Client Bindings: /ai/guide/ixml/ixml-guide/reusability-and-oop/clients.md - Functions & Includes: /ai/guide/ixml/ixml-guide/reusability-and-oop/functions.md - Macros: /ai/guide/ixml/ixml-guide/reusability-and-oop/macros.md - OOP Patterns: /ai/guide/ixml/ixml-guide/reusability-and-oop/patterns.md - Standard Library: /ai/guide/ixml/ixml-guide/standard-library.md - Encoding and Decoding: /ai/guide/ixml/ixml-guide/standard-library/encoding.md - Excel and ZIP: /ai/guide/ixml/ixml-guide/standard-library/excel-zip.md - File & Storage Operations: /ai/guide/ixml/ixml-guide/standard-library/filesystem.md - REST and HTTP Operations: /ai/guide/ixml/ixml-guide/standard-library/http-rest.md - Mathematics and Date/Time: /ai/guide/ixml/ixml-guide/standard-library/math-datetime.md - PDF Generation: /ai/guide/ixml/ixml-guide/standard-library/pdf.md - Syntax Essentials: /ai/guide/ixml/ixml-guide/syntax-essentials.md - Branching: /ai/guide/ixml/ixml-guide/syntax-essentials/branching.md - Conditionals: /ai/guide/ixml/ixml-guide/syntax-essentials/conditionals.md - Loops: /ai/guide/ixml/ixml-guide/syntax-essentials/loops.md - Output & Serialization: /ai/guide/ixml/ixml-guide/syntax-essentials/output.md - String Operations: /ai/guide/ixml/ixml-guide/syntax-essentials/strings.md - Type Introspection: /ai/guide/ixml/ixml-guide/syntax-essentials/type-introspection.md - Variables: /ai/guide/ixml/ixml-guide/syntax-essentials/variables.md - User Interface & Forms: /ai/guide/ixml/ixml-guide/user-interface.md - Exports: /ai/guide/ixml/ixml-guide/user-interface/exports.md - Forms: /ai/guide/ixml/ixml-guide/user-interface/forms.md - Weblets: /ai/guide/ixml/ixml-guide/user-interface/weblets.md ## SDK - ZeyOS SDK Documentation: /ai/guide/sdk.md - Application Activity Management: /ai/guide/sdk/activity-management.md - Activate Application: /ai/guide/sdk/activity-management/zeysdk-activate.md - Deactivate Application: /ai/guide/sdk/activity-management/zeysdk-deactivate.md - Pre-delete Application: /ai/guide/sdk/activity-management/zeysdk-predelete.md - Application Management: /ai/guide/sdk/application-management.md - Clone Application: /ai/guide/sdk/application-management/zeysdk-clone.md - Configure Application: /ai/guide/sdk/application-management/zeysdk-configure.md - Create Application: /ai/guide/sdk/application-management/zeysdk-create.md - Delete Application: /ai/guide/sdk/application-management/zeysdk-delete.md - Host Commands: /ai/guide/sdk/application-management/zeysdk-host.md - Migrate Application: /ai/guide/sdk/application-management/zeysdk-migrate.md - Rollout Application: /ai/guide/sdk/application-management/zeysdk-rollout.md - Asset Management: /ai/guide/sdk/asset-management.md - Add Asset: /ai/guide/sdk/asset-management/zeysdk-add.md - Remove Asset: /ai/guide/sdk/asset-management/zeysdk-remove.md - Rename Asset: /ai/guide/sdk/asset-management/zeysdk-rename.md - Update Asset: /ai/guide/sdk/asset-management/zeysdk-update.md - Authentication: /ai/guide/sdk/authentication.md - Bundle Management: /ai/guide/sdk/bundle-management.md - Create Bundle: /ai/guide/sdk/bundle-management/zeysdk-bundle-create.md - Install Bundle: /ai/guide/sdk/bundle-management/zeysdk-bundle-install.md - Configuration Files: /ai/guide/sdk/configuration-files.md - Debugging and Compilation: /ai/guide/sdk/debugging.md - ZeyOS Entities: /ai/guide/sdk/entities.md - Platform Management: /ai/guide/sdk/platform-management.md - Link Instance: /ai/guide/sdk/platform-management/zeysdk-link.md - Unlink Instance: /ai/guide/sdk/platform-management/zeysdk-unlink.md - Use Instance: /ai/guide/sdk/platform-management/zeysdk-use.md - Show Current Instance: /ai/guide/sdk/platform-management/zeysdk-whoami.md - Snapshot Management: /ai/guide/sdk/snapshot-management.md - Create Snapshot: /ai/guide/sdk/snapshot-management/zeysdk-snapshot-create.md - Delete Snapshot: /ai/guide/sdk/snapshot-management/zeysdk-snapshot-delete.md - List Snapshots: /ai/guide/sdk/snapshot-management/zeysdk-snapshot-list.md - Restore Snapshot: /ai/guide/sdk/snapshot-management/zeysdk-snapshot-restore.md - Synchronization: /ai/guide/sdk/synchronization.md - Compare Files (zeysdk compare): /ai/guide/sdk/synchronization/zeysdk-compare.md - Pull Files: /ai/guide/sdk/synchronization/zeysdk-pull.md - Push Files: /ai/guide/sdk/synchronization/zeysdk-push.md - Recover Sync State: /ai/guide/sdk/synchronization/zeysdk-recover.md - Sync Files (zeysdk sync): /ai/guide/sdk/synchronization/zeysdk-sync.md - Watch Files: /ai/guide/sdk/synchronization/zeysdk-watch.md - ZeyOS Views Reference: /ai/guide/sdk/views.md --- Canonical: https://www.zeyos.com/guide # ZeyOS for Agents and Applications ZeyOS is a business platform that unifies CRM, project management, ticketing, invoicing, and related operational data behind a single API surface. For external integrations, you should think of ZeyOS as the central provider for business data and business logic that feeds coding agents, connected applications, internal tools, and automation services. Start from what you want to do. The first two paths are also routes a coding agent classifies tasks into. The third covers working through an agent, and contains the third route, **Use ZeyOS business data**. ## Build a ZeyOS app For native apps that run inside ZeyOS: manifests, services, resources, weblets, hooks and settings, written in iXML or Zymba and managed with `zeysdk`. **Prerequisites:** the ZeyOS SDK, a linked sandbox instance, and permission to send source to it. **Recommended reading:** 1. [App structure](https://www.zeyos.com/ixml/ixml-guide/development-environment/app-structure) 2. [Execution contexts](https://www.zeyos.com/ixml/ixml-guide/development-environment/execution-contexts) 3. [Agent Setup](https://www.zeyos.com/guide/agent-workflows/agent-setup), if a coding agent is helping 4. [Verification checkpoints](https://www.zeyos.com/ixml/ixml-guide/development-environment/verification-checkpoints) **First success:** the [minimal app](https://www.zeyos.com/ixml/ixml-guide/development-environment/minimal-app) compiles with `zeysdk compile` and behaves as expected in its real context. **When stuck:** [native troubleshooting](https://www.zeyos.com/ixml/ixml-guide/development-environment/troubleshooting). ## Connect an external application For software outside ZeyOS that reads or writes it through the CLI, MCP, `@zeyos/client` or REST. **Prerequisites:** a ZeyOS instance, OAuth client credentials, and Node.js 18 or later for the JavaScript client. **Recommended reading:** 1. [Integration Architecture](https://www.zeyos.com/guide/tutorials/integration-architecture) 2. [JavaScript client: Getting Started](https://www.zeyos.com/guide/javascript-client/getting-started) 3. [Making Requests](https://www.zeyos.com/guide/javascript-client/making-requests) 4. [CLI Coverage and Escalation](https://www.zeyos.com/guide/agent-workflows/cli-coverage-and-escalation) **First success:** `client.schema.validate('listTickets', { fields: ['ID', 'name'] })` passes offline, then the same query returns your tickets. **Sample:** the read-only [Dashboard](https://www.zeyos.com/guide/sample-apps/dashboard). Move on to the [Kanban board](https://www.zeyos.com/guide/sample-apps/kanban) when you need writes. **When stuck:** the [Practical Guide](https://www.zeyos.com/guide/javascript-client/practical-guide). ## Use a coding agent with ZeyOS For working through Claude Code, Codex, OpenCode, Pi or another coding agent. The `zeyos` router skill classifies each task as one of the two paths above or as **Use ZeyOS business data** below, and loading a skill authorizes nothing. **Prerequisites:** a coding agent and the `zeyos` CLI. **Recommended reading:** 1. [Coding Agents](https://www.zeyos.com/guide/agent-workflows/coding-agents) 2. [Agent Setup](https://www.zeyos.com/guide/agent-workflows/agent-setup) **First success:** `zeyos skills install zeyos`, then `zeyos skills verify` reports every file verified. **When stuck:** run `zeyos doctor agent`. ### Use ZeyOS business data For questions answered from your records, and record operations you explicitly request. You can also do this without an agent. **Prerequisites:** the `zeyos` CLI, signed in to your instance with `zeyos login`. **Recommended reading:** 1. [Agent Quickstart](https://www.zeyos.com/guide/agent-workflows/agent-quickstart) 2. [Agent Recipes](https://www.zeyos.com/guide/agent-workflows/agent-recipes) 3. [Coding Agents](https://www.zeyos.com/guide/agent-workflows/coding-agents), for the skills that define metrics such as revenue and workload **First success:** `zeyos count accounts --filter '{"type":1,"visibility":0}'` prints your number of customers. **Sample:** the worked examples in [Agent Recipes](https://www.zeyos.com/guide/agent-workflows/agent-recipes). **When stuck:** run `zeyos doctor agent`, then see [CLI Coverage and Escalation](https://www.zeyos.com/guide/agent-workflows/cli-coverage-and-escalation). :::info Scope This documentation set primarily covers **external integrations**: the CLI, JavaScript client, REST/OpenAPI, and reusable application patterns. The [coding-agent setup](https://www.zeyos.com/guide/agent-workflows/agent-setup) also connects this workflow to native iXML/Zymba development, with [reusable instructions](https://www.zeyos.com/ai/agent-instructions.md) and links to the language references. ::: ## How Are You Integrating? | Interface | Best for | Start here | |-----------|----------|------------| | **CLI** (`zeyos`) | Coding agents, shell automation, operational CRUD on curated resources | [Coding Agents](https://www.zeyos.com/guide/agent-workflows/coding-agents) | | **JavaScript Client** (`@zeyos/client`) | Browser apps, Node services, scheduled jobs, full generated API coverage | [Application Developers](https://www.zeyos.com/guide/tutorials/application-developers) | | **REST/OpenAPI** | Non-JavaScript runtimes, SDK generation, custom HTTP clients | [API Reference](https://www.zeyos.com/guide/api-reference/resources) | ### Coverage Boundary - The **CLI** is the default interface for coding agents, but it intentionally exposes a curated registry of common resources. Use `zeyos resources` to see the supported set. - The **JavaScript client** covers the broader generated API surface and is the recommended escalation path when the CLI registry is not enough. - The **raw API reference** remains the lowest-level source of truth for endpoints, query language, and schema details. ## API Base URL Every ZeyOS instance exposes its REST API at a predictable base URL: ``` https://cloud.zeyos.com/{INSTANCE}/api/v1/ ``` Replace `{INSTANCE}` with your ZeyOS instance identifier (e.g. `demo`, `acme-corp`). ## Authentication All API requests require authentication. ZeyOS supports two authentication methods: - **OAuth 2.0 Bearer Tokens** -- The recommended approach for server-side integrations, scheduled jobs, CLI tools, and browser apps that receive tokens from a trusted flow. Obtain tokens through the ZeyOS OAuth2 endpoint at `https://cloud.zeyos.com/{INSTANCE}/oauth2/v1/`. - **Session Cookies** -- For browser-based applications where the user is already logged into ZeyOS. The browser sends the `ZEYOSID` session cookie automatically. Include a bearer token in the `Authorization` header for all API requests: ``` Authorization: Bearer YOUR_ACCESS_TOKEN ``` See the [Authentication](https://www.zeyos.com/guide/api-reference/authentication) guide for full details on the OAuth 2.0 Authorization Code flow, token refresh, token revocation, and session-based authentication. ## Return Values and Error Handling The ZeyOS REST API returns JSON data along with an HTTP status code indicating the outcome of a request. - **HTTP 200** or **201** indicates a successful response. The result is a **JSON object**. - **HTTP 400** or greater indicates an error. The response is a **text message** describing the problem. We recommend treating any HTTP status code greater than or equal to 400 as an error. ## HTTP Methods The API uses the following HTTP methods, which differ from typical REST conventions in some cases: | Operation | HTTP Method | Description | |-----------|-------------|-------------| | **List** | `POST` | Retrieve a filtered list of records (query parameters are sent in the request body) | | **Get** | `GET` | Retrieve a single record by ID | | **Create** | `PUT` | Create a new record | | **Update** | `PATCH` | Partially update an existing record | | **Delete** | `DELETE` | Delete a record by ID | | **Exists** | `HEAD` | Check if a record exists (returns no body) | :::info **List** operations use `POST` rather than `GET` because the query language (filters, field selection, sorting) can produce payloads that exceed URL length limits. Sending the query in the request body ensures complex queries work reliably. ::: ## Integration Conventions Across the CLI, JavaScript client, and sample applications, the same operational rules apply: - Prefer `filters` in JavaScript client code for compatibility across scalar and foreign-key fields. - Use `visibility: 0` on resources that **have** the column — `accounts`, `contacts`, `tickets`, `tasks`, `projects`, `items`, `documents`, `notes`, `opportunities`, `appointments`, `campaigns`, `mailinglists`, `storages`, `contracts`, `ledgers` — unless you want archived (`1`) or deleted (`2`) records. **Most other resources have no `visibility` column** — `transactions` (and every billing/procurement entity), `payments`, `messages`, `actionsteps`, `addresses`, `users`, `prices`, `dunning` — and filtering on it there returns an opaque HTTP 400. Check with `zeyos describe ` when unsure. - For updates, pass changed fields alongside the `ID` directly (`{ ID, status }`) or wrap them in an explicit `body` object (`{ ID, body: { status } }`) — both work; the explicit `body` is only needed to disambiguate a payload field that collides with a reserved control key. - Treat `extdata` and `expand` as separate features: - `extdata` includes custom fields - `expand` inlines JSON or binary columns - Treat count-enabled list responses defensively. Different endpoints or client layers may return either a count wrapper or a list wrapper with count metadata. ## Data Retrieval The API provides a flexible query language for retrieving data from any resource endpoint. List requests accept a JSON body with the following capabilities: - **Field selection** -- Request only the columns you need, including fields from related records via dot notation (e.g. `contact.city`, `assigneduser.name`). - **Filters** -- Restrict results using equality checks, comparison operators (`<`, `>`, `IN`, etc.), string matching (`~`, `~~`, etc.), and composite logic (`AND`, `OR`, `NOT`). - **Full-text search** -- Search across a resource's indexed text fields with the `query` parameter. - **Sorting** -- Order results by one or more fields, ascending (`+`) or descending (`-`). - **Pagination** -- Control result size with `limit` and `offset`. Use `count: true` to retrieve the total number of matching records. - **Extended data** -- Include custom/extended data fields by passing `extdata: 1` as a request parameter. - **Expand** -- Use the `expand` parameter to include JSON or binary column data that is omitted from list responses by default (e.g. `binfile` on messages, `items` on transactions). See the [Data Retrieval](https://www.zeyos.com/guide/api-reference/data-retrieval) guide for the complete query language reference with examples. ## Developer Tools This project includes tools to help you work with the ZeyOS REST API: | Tool | Description | Docs | |------|-------------|------| | **JavaScript Client** (`@zeyos/client`) | A dependency-light, auto-generated client library with generated methods for every API operation, OAuth 2.0 helpers, and automatic token refresh. Works in browsers and Node.js 18+. | [Getting Started](https://www.zeyos.com/guide/javascript-client/getting-started) | | **CLI** | A command-line interface for scripting and automation against your ZeyOS instance. Supports filtering, sorting, pagination, and multiple output formats. | [Getting Started](https://www.zeyos.com/guide/cli/getting-started) | | **Agent Workflows** | CLI-first guidance for coding agents, JSON-first recipes, and escalation rules when the CLI registry is not enough. | [Coding Agents](https://www.zeyos.com/guide/agent-workflows/coding-agents) | | **Application Developers** | Architecture, browser UI patterns, and server-side integration guidance for connected applications. | [Application Developers](https://www.zeyos.com/guide/tutorials/application-developers) | | **Practical Guide** | A field-tested collection of implementation patterns, gotchas, and best practices learned from building real applications against the ZeyOS API. | [Practical Guide](https://www.zeyos.com/guide/javascript-client/practical-guide) | | **Schema Reference** | Field names, types, enum values, and GIN-indexed fields for the 20 most commonly used ZeyOS resources. | [Schema Reference](https://www.zeyos.com/guide/api-reference/schema) | ## Sample Applications Three ready-to-run sample applications demonstrate different integration patterns: | Sample | What it demonstrates | Docs | |--------|---------------------|------| | **Kanban Board** | Drag-and-drop ticket management, optimistic UI updates, status changes via PATCH, task tables, project/account filtering | [Kanban](https://www.zeyos.com/guide/sample-apps/kanban) | | **CRM Account List** | Dot-notation joins, field aliasing, full-text search, sortable columns, pagination, modal editing | [CRM](https://www.zeyos.com/guide/sample-apps/crm) | | **Dashboard** | KPI cards with `count: true`, parallel API queries, status distribution charts, overdue calculations, read-only patterns | [Dashboard](https://www.zeyos.com/guide/sample-apps/dashboard) | ## Prerequisites - **A ZeyOS instance** -- You need access to a ZeyOS cloud instance. Your instance URL follows the pattern `https://cloud.zeyos.com/{INSTANCE}/`. - **Node.js 18+** -- Required for the JavaScript client. - **Node.js 18.3+** -- Required for the CLI package. ## Quick Example List the 10 most recently modified open tickets using `curl`: ```bash curl -X POST "https://cloud.zeyos.com/demo/api/v1/tickets" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "fields": ["ID", "name", "status", "priority", "duedate"], "filter": {"visibility": 0}, "sort": ["-lastmodified"], "limit": 10 }' ``` The same request using the JavaScript client: ```js import { createZeyosClient, MemoryTokenStore } from '@zeyos/client'; const client = createZeyosClient({ platform: 'https://cloud.zeyos.com/demo/', auth: { mode: 'oauth', oauth: { tokenStore: new MemoryTokenStore({ accessToken: 'YOUR_ACCESS_TOKEN', }), }, }, }); const tickets = await client.api.listTickets({ fields: ['ID', 'name', 'status', 'priority', 'duedate'], filters: { visibility: 0 }, sort: ['-lastmodified'], limit: 10, }); console.log(tickets); ``` For long-lived OAuth sessions in trusted code, add `clientId`, `clientSecret`, and a refresh-capable token store. For browser-only apps, prefer session mode or a backend token broker instead of shipping client credentials. :::tip You can also use **session authentication** when running inside a browser where the user is already logged into ZeyOS. Set `auth.mode` to `'session'` and the client will use browser cookies automatically. See the [Authentication](https://www.zeyos.com/guide/api-reference/authentication) guide for details. ::: ## Next Steps - **[Coding Agents](https://www.zeyos.com/guide/agent-workflows/coding-agents)** -- CLI-first workflows, JSON output patterns, and escalation guidance - **[Application Developers](https://www.zeyos.com/guide/tutorials/application-developers)** -- browser and server-side integration paths - **[Getting Started](https://www.zeyos.com/guide/javascript-client/getting-started)** -- Install the JavaScript client and make your first API call - **[Making Requests](https://www.zeyos.com/guide/javascript-client/making-requests)** -- CRUD operations, filtering, sorting, pagination, and error handling - **[Practical Guide](https://www.zeyos.com/guide/javascript-client/practical-guide)** -- Patterns and gotchas from real-world implementations - **[Schema Reference](https://www.zeyos.com/guide/api-reference/schema)** -- Field definitions and enum values for all major resources - **[Browser UI Playbook](https://www.zeyos.com/guide/tutorials/build-your-own-zeyos-frontend)** -- Step-by-step browser integration guide --- Canonical: https://www.zeyos.com/guide/agent-workflows/agent-quickstart # Agent Quickstart This guide gets a coding agent from zero to authenticated CRUD access with the ZeyOS CLI. ## Install or Run the CLI From this repository: ```bash npm install node cli/bin/zeyos.mjs --help ``` For a global `zeyos` command during development: ```bash npm link cli/ zeyos --help ``` ## Load the ZeyOS Skills Before doing real work, install the bundled skill packs so the agent picks up ZeyOS's query conventions (entity model, `filters` usage, safe writes): ```bash zeyos skills list # see what's available zeyos skills install zeyos # interactive: pick an agent (claude, codex, opencode, droid, pi…), then local/global zeyos skills install zeyos --target claude --global -y # or non-interactively with flags zeyos skills verify --target claude --global # check the installed files ``` This is the recommended entry point for an agent: the `zeyos` router classifies the task, and the domain skills encode how to resolve names to IDs, which resource to query first, and how to escalate from the CLI to `@zeyos/client`. ## Authenticate Interactive login: ```bash zeyos login ``` Pre-fill login parameters: ```bash zeyos login \ --base-url https://cloud.zeyos.com/demo \ --client-id myapp \ --secret "$ZEYOS_CLIENT_SECRET" ``` This still starts the OAuth authorization-code flow and requires a browser redirect or pasted code. For fully unattended agents, inject `ZEYOS_BASE_URL`, `ZEYOS_TOKEN`, and optionally `ZEYOS_REFRESH_TOKEN`, `ZEYOS_CLIENT_ID`, and `ZEYOS_CLIENT_SECRET` through the environment. The CLI stores credentials in `.zeyos/auth.json` in the current project tree, or in `~/.config/zeyos/credentials.json` when `--global` is used. ## Verify the Session Check the current user: ```bash zeyos whoami --json ``` `whoami` hides access tokens by default. Use `zeyos whoami --show-token --json` only when the token must be handed to another local tool. Discover which curated resources the CLI can operate on directly: ```bash zeyos resources ``` Inspect a resource's fields, types and enum values before querying (works offline, no login needed): ```bash zeyos describe tickets ``` ## Read Data List tickets in machine-readable form: ```bash zeyos list tickets --json ``` List only the fields an agent needs: ```bash zeyos list tickets \ --fields ID,ticketnum,name,status,priority,lastmodified \ --filter '{"visibility":0}' \ --sort -lastmodified \ --limit 20 \ --json ``` Fetch one record: ```bash zeyos get ticket 42 --all --json ``` Count matching records: ```bash zeyos count tickets --filter '{"visibility":0,"status":4}' --json zeyos count customfields --json zeyos count actionsteps --filter '{"status":0}' --json ``` Summarize actionstep effort/time-entry evidence: ```bash zeyos list actionsteps \ --fields ID,name,status,date,duedate,effort,ticket,task,account \ --filter '{"status":3}' \ --limit 100 \ --json ``` Inspect ticket-linked mail without sending anything: ```bash zeyos list messages \ --fields ID,date,mailbox,subject,sender_email,to_email,ticket,reference,messageid \ --filter '{"ticket":42}' \ --sort +date \ --json ``` ## Write Data Create with JSON-first input: ```bash zeyos create ticket \ --data '{"name":"Follow up with customer","status":0,"priority":2,"visibility":0}' \ --json ``` Update with JSON-first input: ```bash zeyos update ticket 42 \ --data '{"status":4,"priority":3}' \ --json ``` Delete interactively: ```bash zeyos delete ticket 42 ``` Delete non-interactively only when the automation has already decided the record must be removed: ```bash zeyos delete ticket 42 --force ``` ## Safe Defaults for Agents - Use `--json` unless a human is actively reading the output. - Use `visibility: 0` on resources that **have** the column — `accounts`, `contacts`, `tickets`, `tasks`, `projects`, `items`, `documents`, `notes`, `opportunities`, `appointments`, `campaigns`, `mailinglists`, `storages`, `contracts`, `ledgers` — unless you want archived (`1`) or deleted (`2`) records. **Most other resources have no `visibility` column** — `transactions` (and every billing/procurement entity), `payments`, `messages`, `actionsteps`, `addresses`, `users`, `prices`, `dunning` — and filtering on it there returns an opaque HTTP 400. Check with `zeyos describe ` when unsure. - Prefer `--data ''` over many separate flags in automation. - Run `zeyos resources` before assuming a resource is CLI-supported. - Use `actionsteps.effort` for time-entry totals; do not infer booked time from task assignment. - Draft e-mail text in the response unless the user explicitly asks for a real ZeyOS draft record; never send mail from an agent workflow. - Escalate to [`@zeyos/client`](https://www.zeyos.com/guide/agent-workflows/cli-coverage-and-escalation) when the CLI resource registry is not enough. --- Canonical: https://www.zeyos.com/guide/agent-workflows/agent-recipes # Agent Recipes These workflows are designed for coding agents and shell automation. Every example is safe to convert into CI jobs, local scripts, or ad hoc operational tooling. ## Work a Ticket Queue List active tickets with only the fields you need: ```bash zeyos list tickets \ --fields ID,ticketnum,name,status,priority,assigneduser,lastmodified \ --filter '{"visibility":0,"status":4}' \ --sort -lastmodified \ --limit 50 \ --json ``` Count the backlog before acting: ```bash zeyos count tickets --filter '{"visibility":0,"status":0}' --json ``` Move a ticket to a new status: ```bash zeyos update ticket 42 --data '{"status":7}' --json ``` ## Create a Follow-up Ticket ```bash zeyos create ticket \ --data '{"name":"Escalate billing issue","status":0,"priority":3,"account":15,"visibility":0}' \ --json ``` Use `zeyos get ticket --json` immediately after creation if the workflow needs server-confirmed fields or related data. ## Query Accounts for an Agent-Facing CRM View Use field aliases and joins when you need a compact response: ```bash zeyos list accounts \ --fields '{"Id":"ID","Name":"lastname","City":"contact.city","Agent":"assigneduser.name"}' \ --filter '{"visibility":0}' \ --sort +lastname \ --limit 25 \ --json ``` ## Pull Tasks for a Project or Ticket ```bash zeyos list tasks \ --fields ID,tasknum,name,status,priority,duedate \ --filter '{"visibility":0,"project":123}' \ --sort +duedate \ --json ``` ```bash zeyos list tasks \ --fields ID,tasknum,name,status,priority,duedate \ --filter '{"visibility":0,"ticket":42}' \ --json ``` ## Paginate Deterministically ```bash zeyos list tickets \ --filter '{"visibility":0}' \ --sort -lastmodified \ --limit 100 \ --offset 0 \ --json ``` Advance by incrementing `--offset` yourself. For long-running jobs, always keep the sort explicit so the ordering stays stable. ## Pipe to `jq` Extract IDs: ```bash zeyos list tickets --fields ID,name --filter '{"visibility":0}' --json | jq '.[].ID' ``` Extract the current access token for another tool: ```bash zeyos whoami --show-token --json | jq -r '.accessToken' ``` Treat printed access tokens as secrets. Avoid writing them to logs, shared shell history, or agent transcripts. ## Destructive Guardrails - `zeyos delete` prompts by default. Keep that behavior in human-in-the-loop sessions. - Use `--force` only in automation that already has a clear selection rule. - Prefer `count` or a dry-run `list` before a bulk action. - If a workflow needs unsupported resources, raw request control, or custom retry logic, move it to [`@zeyos/client`](https://www.zeyos.com/guide/agent-workflows/cli-coverage-and-escalation). --- Canonical: https://www.zeyos.com/guide/agent-workflows/agent-setup # Agent Setup Kilo Code, Pi, OpenCode, and other coding agents need the same ZeyOS project context: authoritative references, project instructions, validation tools, and explicit permission to execute or change anything remotely. Configure these once for the project, then adapt how your chosen harness loads them. A capable model can still invent an iXML attribute or database column if it works from memory. Each task belongs to one route, and the `zeyos` router skill classifies it first: | Route | Use it for | Interfaces | | --- | --- | --- | | **Build a ZeyOS app** | Native manifests, services, resources, weblets, hooks and settings | iXML/Zymba, `zeysdk` | | **Connect an external application** | Software outside ZeyOS that reads or writes it | CLI, MCP, `@zeyos/client`, REST | | **Use ZeyOS business data** | Questions and explicitly requested record operations | The 15 domain skills, through the CLI or MCP | An explanation, code edit, review or validation-only task must never trigger a live query, compile, push or run. Loading a skill is not authorization to execute anything. The tools serve different purposes: | Work | Tool | Contract to check | | --- | --- | --- | | Native iXML/Zymba compilation checks | ZeyOS SDK `zeysdk compile` / `zeysdk lint` | Linked instance compiler; supplied source is not executed | | Native runtime behavior | ZeyOS SDK and the platform runtime, when execution is requested | Application entry point, inputs, outputs, and side effects | | Business data queries and bounded updates | `zeyos` CLI or its MCP server | CLI resource registry, database metadata, and API contract | | Connected JavaScript applications | `@zeyos/client` | Generated operations and runtime schema | The client validator and MCP server do not compile iXML or Zymba. Use the SDK's `compile` or `lint` for native compilation checks without executing the supplied source. These are remote checks, not offline linters. `zeysdk run` executes code and is not a substitute for validation. ## Set up the project first 1. Open the application repository as the agent's working directory. Identify the native application root or external integration entry point. 2. Download the [ZeyOS project instructions as Markdown](https://www.zeyos.com/ai/agent-instructions.md). Merge them into an existing `AGENTS.md`, or keep the file as `instructions/zeyos.md` and load it using your harness's supported mechanism. Do not overwrite existing project rules. 3. Add a short project map: reference directory, application root, relevant examples, installed tool versions, validation commands, and target profile. Leave unknown values explicitly unset; keep credentials out of the map. 4. Configure a provider and model in the harness. Keep personal authentication in its private credential store and project instructions in version control. 5. Enable the file access and local validation tools needed for the task. Treat remote execution, database writes, synchronization, and deployment as separate capabilities. A request to validate code does not authorize running it. 6. Start a fresh session and ask the agent to identify the loaded instructions, exact reference files, and available non-executing checks before editing. Prefer a reviewed local copy of the instructions over fetching a mutable remote URL at every session start. Update that copy deliberately when the ZeyOS guidance changes. Keep shared rules in one place and project paths in the project; avoid duplicate or contradictory instruction sources. ### Install and verify the skills Install the router for your harness, then check the result: ```bash zeyos skills install zeyos --target opencode --local zeyos skills verify --target opencode --local ``` Use `--target claude`, `codex`, `droid`, `pi` or `agents` for the other harnesses, or `--dir ` for any directory. `install zeyos` also installs `zeyos-dev`, the 15 domain skills, and `shared/`, which holds the shared references, the OKF bundle and a copy of the development contract. The harness only advertises the skills; their content loads on demand. The tree needs no source checkout, and every link in it resolves. To upgrade, install again after updating the CLI. `--preview` shows the plan and writes nothing. Files you changed are kept unless you pass `--force`. See [Commands → skills](https://www.zeyos.com/guide/cli/commands#skills). ### Connect your coding harness These are adapters for the same project setup, not separate ZeyOS workflows. Configuration details were checked against the official documentation on **30 October 2026**; check the documentation for your installed version before copying a configuration. | Harness | Project instructions | Harness configuration | | --- | --- | --- | | Kilo Code | Root `AGENTS.md`, or `instructions` paths in `kilo.jsonc` | Configure provider/model and tool permissions in Kilo's settings or the existing project config. Start a new task after changing instructions. | | Pi | Root `AGENTS.md`; Pi also loads applicable parent-directory context files | Project settings live in `.pi/settings.json`. Choose the model with `/model`; use `/reload` after changing instructions or configuration. | | OpenCode | Root `AGENTS.md`, or `instructions` paths in `opencode.json` / `opencode.jsonc` | Configure provider/model and permissions in the existing project config. Start from the application directory. For V2, see the [adapter](#opencode-v2-adapter). | Versions matter for OpenCode: the unversioned `/docs/` pages linked here describe V1, and V2 has its own [agents](https://opencode.ai/v2/docs/agents/), [skills](https://opencode.ai/v2/docs/skills/) and [commands](https://opencode.ai/v2/docs/commands/) pages. Sources: [Kilo project instructions](https://kilo.ai/docs/customize/agents-md), [Kilo settings](https://kilo.ai/docs/getting-started/settings), [Pi configuration](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/configuration.md), [Pi usage](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/usage.md), [OpenCode rules](https://opencode.ai/docs/rules/), and [OpenCode configuration](https://opencode.ai/docs/config/). For **Kilo or OpenCode**, merge this into the appropriate config if you keep the downloaded instructions separate: ```json { "instructions": ["instructions/zeyos.md"] } ``` For **Pi**, merge the instructions into the project's `AGENTS.md` and confirm that the startup header lists it. Do not replace Pi's `SYSTEM.md` merely to add ZeyOS project rules: that file replaces the default system prompt. Pi does not ask before every tool call; use the available tool controls and a sandbox appropriate to the task. Prompt instructions are not access controls. See [Pi configuration](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/configuration.md) and [Pi usage](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/usage.md). The CLI is enough to begin. Add MCP only if it improves your harness's tool discovery; no retrieval framework or vector database is required for the initial setup. ### OpenCode V2 adapter This adapter adds a `zeyos-expert` agent plus `/zeyos` and `/zeyos-dev` commands. It references skills by name, so it works from any install location that OpenCode discovers. Install the skills as above, then merge this into your project's `opencode.json`: ```json { "agents": { "zeyos-expert": { "description": "Works on ZeyOS tasks after classifying them with the zeyos skill.", "mode": "primary", "system": "You are a ZeyOS engineer. Load the zeyos skill first and follow its routing. Loading a skill authorizes nothing." } }, "commands": { "zeyos": { "description": "Route a ZeyOS task and continue with only the relevant context.", "agent": "zeyos-expert", "subagent": false, "template": "Load the zeyos skill, classify the task and continue with only the relevant context. Task: $ARGUMENTS" } } } ``` This excerpt shows the shape. The [complete fragment](https://github.com/zeyos/client/tree/main/agents/adapters/opencode-v2) also defines `/zeyos-dev` and the permission rules that make OpenCode ask before remote compiles, SDK runs, pushes and CLI record writes. The complete fragment was checked against OpenCode **2.2.5** on 5 November 2026, in an isolated home directory: it validates against the published [config schema](https://opencode.ai/config.json), and OpenCode loaded the agent, discovered all 17 skills and registered both commands. No model session was run, so this does not establish how a given model follows the skills. ## Check the ZeyOS tools and versions The join and custom-field validation below requires client/CLI **1.0.0 or later**. Check `zeyos --version` and the package version used by your application; upgrade client and CLI together. The [1.0.0 release](https://github.com/zeyos/client/releases/tag/v1.0.0) contains these checks. Native iXML/Zymba tooling comes from the separate `zeysdk` installation. Inspect `zeysdk compile --help` and `zeysdk lint --help`; use the [SDK debugging reference](https://www.zeyos.com/sdk/debugging/) for the supported commands and their effects. ## Retrieve authoritative Zymba entries Start with the [symbol index](https://www.zeyos.com/ai/zymba/symbols.json) and [namespace index](https://www.zeyos.com/ai/zymba/namespaces.json). For example, find `Array.joinValues` with `kind: "method"`, then inspect its exact `definition` and source JSON pointer. Keep static and instance methods distinct. Fetch a single [Array namespace](https://www.zeyos.com/ai/zymba/namespace.Array.json), or use the [full reference working set](https://www.zeyos.com/zymba/llms-full.txt) when needed. Each entry retains the source URL, hash, and pointer into the [official JSON mirror](https://www.zeyos.com/__doc/zymbaref.json). Preserve duplicate declarations and investigate differences rather than choosing one silently. Read argument order, types, defaults, returns and annotations from the raw definition; the generated signature is a display aid. An omitted metadata property is not proof that an argument is optional or required. These indexes support reference review, not compilation or runtime certification, and do not promote the deferred Zymba guide drafts. ## Choose a model by testing ZeyOS tasks For complex ZeyOS work, prioritize correctness over the lowest token price. Start with a provider's capable reasoning/coding model, use its supported reasoning controls, and hold the model fixed while evaluating it. The harness and model are separate choices: changing from Kilo to Pi or OpenCode does not remove the need for the same references and checks. Select the exact model through the provider's picker and verify its availability in your harness. A model recommendation is a starting point, not a ZeyOS benchmark result. See [Kilo model selection](https://kilo.ai/docs/code-with-ai/agents/model-selection), [Pi model selection](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/models.md), and [OpenCode models](https://opencode.ai/docs/models/). Evaluate candidates with the same references, tools, prompts, and revision. Run each task more than once. In a client checkout, the [evaluation suite](https://github.com/zeyos/client/blob/main/test/agent-protocol/README.md) turns this kind of acceptance task into scored scenarios: `node test/agent-protocol/harness/run.mjs --layer n`. | Acceptance task | Required behavior | | --- | --- | | Repair `` | Find the documented comparison attributes; preserve XML escaping | | Return rows from `db:select` | Distinguish `var` from `var_result` and use the intended result type | | Join Zymba array values | Find `@Array.joinValues` and its signature; avoid `@String.joinValues` | | Prepare an account creation | Reject `name`, detect missing `currency`, and preview without creating | | Query transaction records | Reject an invented `visibility` field; resolve the correct resource | | Query a custom field or unknown join | Retrieve its definition or report the gap instead of guessing | | Test a hook or service | Identify its invocation context; do not equate an inline run with a full integration test | | Recover from a write timeout | Reconcile the outcome before considering a retry | Require zero invented identifiers in the accepted patch, detection of every deliberately planted error, and no unauthorized side effects. Compare first-pass correctness, repair attempts, total cost, and elapsed time. Only move routine work to a cheaper model after it meets the same criteria. Large context windows and general coding leaderboards do not establish ZeyOS competence. ## Supply focused reference context In a checkout of the developer guide, use `ref/ixmldoc.xml`, `ref/zymbaref.json`, `ref/zymbadoc.md`, `ref/dbref.json`, `ref/simpledb.sql`, `ref/views.txt`, and the relevant `ref/openapi/` and `ref/sdk/` entries. `LEARN.md` records dated successes and failures; `docs/zymba/` remains draft material. Existing application code supplies conventions, not authority for undocumented behavior. Without a local checkout, start with [AI Resources](https://www.zeyos.com/docs/ai) and [llms.txt](https://www.zeyos.com/llms.txt). Use the namespace, table, and operation indexes to retrieve only the entries needed. The [artifact manifest](https://www.zeyos.com/ai/manifest.json) provides hashes for caching. A hash identifies a snapshot; it does not establish compatibility with your instance. Keep a small evidence list for each change: symbol or field, reference location, snapshot/version, and relevant runtime result. Re-read the exact entries after context compaction or a schema update. If a source is inaccessible, surface the gap before implementing dependent behavior. ## Validate native code without executing it Choose the check by what it establishes and where it runs: | Check | Executes the submitted application code? | What it establishes | | --- | --- | --- | | Local reference and source review | No | Documented commands, arguments, schema names, and application input contracts | | A local XML parser, for a compatible complete XML document | No | XML well-formedness only; not the iXML language contract | | `zeysdk compile` | No | Whether selected iXML/Zymba source compiles on the linked ZeyOS instance | | `zeysdk lint` | No | Compilation results for the eligible local project scripts it selects | | `zeysdk run` or a real form/hook/service invocation | Yes | Runtime behavior; use only when execution is part of the requested test | ### Review structure and contracts locally Formatting must preserve text and result propagation. For new iXML snippets use two-space indentation, quoted attributes, and compact text-only elements; preserve established conventions in existing files. ```xml iXML Hello $name ``` Check each iXML command's attributes, defaults, child elements, content type, and result bindings against the reference. For calls and includes, inspect the called code and its input contract. An attribute without a documented default is not automatically mandatory. A generic XML parser can reject native fragments, prefixed commands or entities that the ZeyOS parser accepts; do not add invented namespace declarations to make a generic parser pass. For Zymba, review the receiver, method, argument order, defaults, return value, and mutation behavior against the authoritative definitions. PHP or JavaScript syntax validation does not establish Zymba correctness. Neither a reference index nor an agent's review is a full language validator. ### Ask the ZeyOS compiler for diagnostics In an already linked application directory, after confirming the intended compiler target and permission to send the source there: ```bash # Check one selected asset without executing its source. zeysdk compile services/example.ixml # Check inline iXML or Zymba without executing it. zeysdk compile --ixml 'iXMLHello $name' zeysdk compile --zy 'return 1;' # Check eligible modified project scripts, or all eligible local scripts. zeysdk lint zeysdk lint --all ``` Replace the example path with the asset you intend to check. `compile` and `lint` submit source to the linked instance's authenticated `PUT sdk/v1/compile` endpoint. They do not execute that source and do not run a sync/push, but they require network access, existing authentication, and application linkage. Treat source transmission separately from a local-only check. `lint --all` still compiles remotely; it is not an offline mode. [SDK compile and lint documentation](https://www.zeyos.com/sdk/debugging/). Inspect the diagnostics and the selected file count. `lint` selects eligible manifest-backed scripts; it is not a recursive validation of every file in the repository. A successful exit with no eligible scripts is not evidence that changed code was checked. Use `compile` on a specific asset when you need explicit coverage. A compilation pass does not prove database identifiers, required business inputs, permissions, or behavior in a form/hook/HTTP context. Review those contracts separately. If the task is only to validate code, stop with the diagnostics and remaining coverage gaps. Do not run it merely to get stronger evidence. When execution is explicitly needed, perform a separate, bounded sandbox test through the correct entry point; `zeysdk run` belongs to that stage. See the [native formatting and validation workflow](https://www.zeyos.com/ixml/ixml-guide/development-environment/coding-agents), [iXML reference](https://www.zeyos.com/ixml/), and [Zymba reference](https://www.zeyos.com/zymba/). ## Prevent invented tables and fields Resolve names before generating a request. For a join, trace every relationship to its target table and check the terminal field. Do this for selections, filters, sorting, grouping, and updates. Keep SQL tables/views separate from API resources, CLI aliases, and custom fields. The database reference confirms that accounts have `lastname` and `firstname`, not `name`; `accounts.contact` references `contacts.ID`; `accounts.currency` has no database default; and `transactions` has no `visibility` field. A CLI resource such as a billing-invoice alias is not proof of a SQL table with the same name. With the existing CLI, start locally: ```bash zeyos resources --json zeyos describe accounts --json zeyos list accounts --fields ID,lastname --limit 5 --dry-run --json zeyos create account --lastname "Agent sandbox example" --currency EUR --dry-run --json ``` The final command previews a create request; it does not create an account. The current CLI still requires configured authentication to construct these request previews, although the preview does not dispatch the request. Use the credential-free JavaScript schema check below if no profile is configured. Do not remove `--dry-run` until the target and write are authorized. Keep validation enabled. The human CLI flag is `--filter`, while JavaScript and MCP request payloads use `filters`. For JavaScript, inspect a request without sending it: ```js import { createZeyosClient } from '@zeyos/client'; const client = createZeyosClient({ platform: 'https://cloud.zeyos.com/demo/', auth: { mode: 'none' }, validate: true, }); console.log(client.schema.validate('createAccount', { lastname: 'Agent sandbox example', })); // valid: false; missing currency. This does not make an API request. console.log(client.schema.validate('listTransactions', { fields: ['ID'], filters: { visibility: 0 }, limit: 5, })); // valid: false; transactions has no visibility field. ``` For real requests, use the established authenticated client configuration and retain `validate: true`. The unauthenticated example above is for local schema checks only. :::warning Validation coverage Client/CLI 1.0.0 and later check every documented foreign-key hop and terminal field in selections, filters, and sorting. It validates individual custom keys against an explicit `customFields` contract and checks supplied JSON types/enums. Missing custom metadata is an error. Upgrade client and CLI together; older releases accepted these paths unchecked. It is still not a complete type checker or OpenAPI validator. Required parameters beyond the curated account-currency rule, permissions, record existence, join cardinality, and business rules need separate checks. Relationships absent from the bundled FK metadata are rejected, even if a descriptive column name looks plausible. `describe` reads bundled schema plus your supplied snapshot, not live tenant introspection. ::: For native database code, apply the same name checks to the database reference and use the documented iXML or Zymba query APIs. The JavaScript validator cannot validate arbitrary SQL. Use documented structured conditions or preparation for values, and allowlist dynamic identifiers. Check cardinality, nullability, and totals as well as syntax: a valid join can still double a sum. ### Configure custom fields once per instance Use the [snapshot format and setup](https://www.zeyos.com/guide/cli/configuration#custom-field-validation) for CLI/MCP, or the [JavaScript contract](https://www.zeyos.com/guide/javascript-client/making-requests#custom-field-contracts). Set `ZEYOS_CUSTOM_FIELDS_FILE` to its absolute path in the agent environment. Check `customFieldSource.platform` in `describe` output; actual dispatch rejects snapshots from a different instance. Discover active extdata definitions using `customfields.context` for the owning resource, `source: 0`, and `activity: 0`; use `reference` as the exact key. Page through all results and verify permissions and completeness before declaring an allowlist. `customfields.entity` describes the target of an entity-valued field, not its owner. Display types are not JSON types: add type and enum constraints only from reviewed application contracts. Refresh after configuration changes. Keep snapshots private and credential-free. Do not repair `custom_fields_unavailable` or `unknown_custom_field` by disabling validation. Retrieve and review the missing definitions. An absent resource map means unknown; an explicit empty map means known empty. Related custom fields use the destination resource's map, such as `contact.extdata.preferred` on accounts using `contacts` definitions. ## Optional MCP connection The existing [ZeyOS MCP server](https://www.zeyos.com/guide/cli/mcp-server) exposes resource discovery, schema descriptions, and record queries. Install the matching client/CLI version first and ensure the harness can find `zeyos-mcp`. Use your actual configured profile name; `sandbox` below is an example, not evidence of a safe target. Resolve conflicting inherited credential variables and keep authentication in the existing private configuration. **Kilo and OpenCode** use this structure in their respective project configuration (`kilo.jsonc` or `opencode.json` / `opencode.jsonc`): ```json { "mcp": { "zeyos": { "type": "local", "command": ["zeyos-mcp"], "environment": { "ZEYOS_PROFILE": "sandbox", "ZEYOS_MCP_ALLOW_WRITES": "0" }, "enabled": true } } } ``` OpenCode V2 nests servers under `servers`, for example `{ "mcp": { "servers": { "zeyos": { "type": "local", "command": ["zeyos-mcp"], "environment": { "ZEYOS_MCP_ALLOW_WRITES": "0" } } } } }`. Sources: [Kilo MCP](https://kilo.ai/docs/automate/mcp/using-in-kilo-code) and [OpenCode MCP](https://opencode.ai/docs/mcp-servers/) (V1). **Pi's current MCP support** uses `.pi/mcp.json` for a trusted project. Its keys differ from the Kilo/OpenCode format: ```json { "mcpServers": { "zeyos": { "command": "zeyos-mcp", "args": [], "env": { "ZEYOS_PROFILE": "sandbox", "ZEYOS_MCP_ALLOW_WRITES": "0" } } } } ``` Run `pi mcp list` to inspect connections, or use `/mcp` in a session. Use `/reload` after changing the file. Older Pi versions and extensions may use a different integration; use the CLI directly if your installed version does not support this format. See [Pi MCP configuration](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/mcp.md). Start with `list_resource_types` and `describe_resource`; these work without authenticated API access. MCP write tools require `ZEYOS_MCP_ALLOW_WRITES=1`; there is no delete tool. This switch does not restrict a separate shell or HTTP tool, so configure those permissions and credential scope as well. The [agent skills](#install-and-verify-the-skills) add routing and task-specific workflows. Project instructions and the CLI are sufficient for the initial setup; a skill installer target for every harness is not required. ## Make automation repeatable Use a progression with measurable acceptance gates: 1. **Local assistance:** reference lookup, small patches, schema preflight, and review. Record the model and reference versions. 2. **Sandbox execution:** exercise normal, empty, malformed, and missing-input cases. Include real HTTP/hook/form context where relevant. Record expected and actual results without private data. 3. **Bounded operational jobs:** define the instance, resources, allowed fields, record cap, time budget, retry policy, and stop conditions. Begin with a preview; verify each authorized write by reading its result. 4. **Repeatable unattended operation:** prevent overlapping runs, reconcile uncertain outcomes, and retain redacted audit evidence. Use a documented idempotency mechanism where available; otherwise design explicit deduplication and test it. Do not invent an idempotency header or retry creates blindly. The join validator and explicit custom-field snapshots now cover API identifier checks. The authoritative Zymba indexes support lookup, and the SDK supports non-executing compilation checks. Broader required-parameter/schema validation and a regression set built from recurring failures remain separate work. A checker should distinguish **error**, **verified within its coverage**, and **requires runtime evidence**; it must never turn missing metadata into a silent pass. Keep rules short and load domain details on demand. Promote only reproducible fixes into shared instructions. Keep the guide's pinned `LEARN.md` unchanged during normal project work; write new project findings to the designated application log. ## First task to give your agent ```text Read our ZeyOS project instructions and identify the reference and example files for this task. Inspect the target entry point and list its required inputs. Resolve every database field and relationship before writing code. Make the smallest useful patch and validate without executing it. Report local checks separately from SDK compilation. Only send source for compilation after confirming the linked target and permission; otherwise prepare the command. Do not use zeysdk run, invoke a service, sync, or write business data for this task. Report any behavior that still needs a separately authorized runtime test. ``` --- Canonical: https://www.zeyos.com/guide/agent-workflows/cli-coverage-and-escalation # CLI Coverage and Escalation The CLI is the default interface for coding agents, but it intentionally covers a curated registry instead of the full API surface. ## What the CLI Covers Directly The command `zeyos resources` is the source of truth for CLI-supported resource types. At the time of writing, the curated registry includes: | Resource | Operations | |----------|------------| | `account`, `actionstep`, `appointment`, `campaign`, `contact`, `document`, `event`, `file`, `invitation`, `item`, `message`, `note`, `opportunity`, `payment`, `project`, `storage`, `task`, `ticket`, `transaction` | `list`, `get`, `create`, `update`, `delete` | | `customfield` / `customfields` | `list`, `get` | | `group`, `user` | `list`, `get` | Plural names and common aliases such as `tickets`, `actionsteps`, `time-entries`, `docs`, `invoice`, and `crm` are resolved by the CLI, but the underlying coverage boundary is still the registry above. ## What the CLI Does Not Try to Cover Switch away from the CLI when you need: - Resources that are present in the generated API client but missing from `zeyos resources` - Low-level request control beyond the registry-backed commands - Browser session behavior or embedded app flows - Full access to generated operations such as `listApplications`, `listServices`, `listResources`, `listPermissions`, or `listWeblets` - Custom auth overrides, raw responses, or path-and-method requests ## Escalation Path ### 1. Stay on the CLI if the resource exists in the registry This remains the best option for shell automation, quick operational tooling, and JSON-first CRUD workflows. ### 2. Move to `@zeyos/client` for the full generated API surface ```js import { createZeyosClient, MemoryTokenStore } from '@zeyos/client'; const client = createZeyosClient({ platform: 'https://cloud.zeyos.com/demo/', auth: { mode: 'oauth', oauth: { tokenStore: new MemoryTokenStore({ accessToken: process.env.ZEYOS_TOKEN }), }, }, }); const services = await client.api.listServices({ limit: 20 }); ``` Use the JavaScript client whenever the CLI registry is insufficient or the workflow needs request-level control. ### 3. Drop to raw REST/OpenAPI when your runtime is not JavaScript Use the API reference and OpenAPI files when you are implementing integrations in another language or building a custom SDK. ## Rule of Thumb - If `zeyos resources` shows the resource and the built-in command shape is enough, stay with the CLI. - If the resource exists in the API but not in the CLI registry, move to `@zeyos/client`. - If you are not using JavaScript at all, use the REST/OpenAPI reference directly. --- Canonical: https://www.zeyos.com/guide/agent-workflows/coding-agents # Coding Agents This path is for coding agents, automation scripts, and operational tools working with ZeyOS. Every task starts by choosing one of three routes: | Route | Use it for | Interfaces | | --- | --- | --- | | **Build a ZeyOS app** | Native manifests, services, resources, weblets, hooks and settings | iXML/Zymba, `zeysdk` | | **Connect an external application** | Software outside ZeyOS that reads or writes it | CLI, MCP, `@zeyos/client`, REST | | **Use ZeyOS business data** | Questions and explicitly requested record operations | The 15 domain skills, through the CLI or MCP | Install the `zeyos` router skill, and the agent classifies tasks itself: ```bash zeyos skills install zeyos --target claude --global zeyos skills verify --target claude --global ``` An explanation, code edit, review or validation-only task must never trigger a live query, compile, push or run, and loading a skill authorizes nothing. For harness configuration, including OpenCode V2, see [Agent Setup](https://www.zeyos.com/guide/agent-workflows/agent-setup). Native work also uses the [copyable project instructions](https://www.zeyos.com/ai/agent-instructions.md); the separate ZeyOS SDK provides compilation checks without executing the source, and runtime tests are a separate step. ## Start Here For **Connect an external application** and **Use ZeyOS business data**, start with the CLI: | Need | Recommended interface | Why | |------|-----------------------|-----| | Fast CRUD against common business resources | [`zeyos`](https://www.zeyos.com/guide/cli/getting-started) | Human-readable help, JSON output, built-in auth flow, safe delete confirmation | | Reliable shell automation and pipelines | [`zeyos --json`](https://www.zeyos.com/guide/agent-workflows/agent-quickstart) | Stable machine-readable output, easy to combine with `jq` and scripts | | Access beyond the CLI's curated resource registry | [`@zeyos/client`](https://www.zeyos.com/guide/javascript-client/getting-started) | Covers the full generated API surface | | Non-JavaScript or custom HTTP clients | [REST/OpenAPI](https://www.zeyos.com/guide/api-reference/resources) | Raw endpoint and schema reference | ## Recommended Route 1. Follow the [Agent Quickstart](https://www.zeyos.com/guide/agent-workflows/agent-quickstart) to authenticate and make your first read/write calls. 2. Use [Agent Recipes](https://www.zeyos.com/guide/agent-workflows/agent-recipes) for common ticket, account, task, and project workflows. 3. Check [CLI Coverage and Escalation](https://www.zeyos.com/guide/agent-workflows/cli-coverage-and-escalation) whenever you need a resource or request shape the CLI does not expose directly. ## Working Rules - Prefer `--json` for agent-driven flows. - Prefer `filters`-style JSON payloads in CLI examples so the transition to `@zeyos/client` stays consistent. - Use `visibility: 0` on resources that **have** the column — `accounts`, `contacts`, `tickets`, `tasks`, `projects`, `items`, `documents`, `notes`, `opportunities`, `appointments`, `campaigns`, `mailinglists`, `storages`, `contracts`, `ledgers` — unless you want archived (`1`) or deleted (`2`) records. **Most other resources have no `visibility` column** — `transactions` (and every billing/procurement entity), `payments`, `messages`, `actionsteps`, `addresses`, `users`, `prices`, `dunning` — and filtering on it there returns an opaque HTTP 400. Check with `zeyos describe ` when unsure. - Treat `delete` as destructive. The CLI prompts by default; use `--force` only in deliberate automation. - Switch to the JavaScript client when the task needs unsupported resources, browser/session behavior, or low-level request control. --- Canonical: https://www.zeyos.com/guide/api-reference/authentication # Authentication The ZeyOS API supports multiple authentication methods to accommodate different use cases -- from server-side integrations to browser-based applications. This guide covers OAuth2 token-based authentication, session cookie authentication, and the helpers provided by the `@zeyos/client` library. ## Authentication Overview ZeyOS exposes an OAuth2-compatible authorization server at: ``` https://cloud.zeyos.com/{INSTANCE}/oauth2/v1/ ``` This endpoint supports the standard OAuth 2.0 Authorization Code flow, token refresh, token revocation, and token introspection. For browser applications where the user is already logged into ZeyOS, session cookie authentication is also available. ## OAuth 2.0 Authorization Code Flow The Authorization Code flow is the recommended approach for server-side applications and other trusted integrations that need to act on behalf of a user. ### Step 1: Build the Authorization URL Redirect the user to the ZeyOS authorization endpoint to request access. The JavaScript example below assumes a trusted environment that can safely hold OAuth client credentials: ```js import { createZeyosClient } from '@zeyos/client'; const client = createZeyosClient({ platform: 'https://cloud.zeyos.com/demo', auth: { oauth: { clientId: 'YOUR_CLIENT_ID', clientSecret: 'YOUR_CLIENT_SECRET', }, }, }); const authUrl = client.oauth2.buildAuthorizationUrl({ redirectUri: 'https://yourapp.example.com/callback', state: 'random-csrf-token', }); // Redirect the user to authUrl ``` The authorization URL follows this format: ``` https://cloud.zeyos.com/{INSTANCE}/oauth2/v1/authorize ?client_id=YOUR_CLIENT_ID &redirect_uri=https://yourapp.example.com/callback &response_type=code &state=random-csrf-token ``` ### Step 2: User Authorizes The user logs in (if not already authenticated) and approves access to your application. ZeyOS redirects the user back to your `redirect_uri` with an authorization code: ``` https://yourapp.example.com/callback?code=AUTHORIZATION_CODE&state=random-csrf-token ``` ### Step 3: Exchange the Code for Tokens Exchange the authorization code for an access token and refresh token: **Using the JavaScript client:** ```js const callback = client.oauth2.parseAuthorizationCallback(callbackUrl); if (callback.isError) { console.error('Authorization failed:', callback.errorDescription); } else { const tokenSet = await client.oauth2.exchangeAuthorizationCode({ code: callback.code, redirectUri: 'https://yourapp.example.com/callback', }); console.log('Access token:', tokenSet.accessToken); console.log('Refresh token:', tokenSet.refreshToken); } ``` **Using curl:** ```bash curl -X POST "https://cloud.zeyos.com/demo/oauth2/v1/token" \ -u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=authorization_code" \ -d "code=AUTHORIZATION_CODE" \ -d "redirect_uri=https://yourapp.example.com/callback" ``` **Response:** ```json { "access_token": "eyJhbGciOiJSUzI1NiIs...", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "dGhpcyBpcyBhIHJlZnJlc2g..." } ``` ### Step 4: Use the Bearer Token Include the access token in the `Authorization` header for all API requests: ```bash curl -X POST "https://cloud.zeyos.com/demo/api/v1/tickets" \ -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \ -H "Content-Type: application/json" \ -d '{"limit": 10}' ``` With the JavaScript client, tokens are managed automatically once stored: ```js const tickets = await client.api.listTickets({ limit: 10 }); ``` ## Token Refresh Access tokens expire after a limited time (indicated by `expires_in` in the token response). Use the refresh token to obtain a new access token without requiring user interaction. **Using the JavaScript client:** ```js const newTokenSet = await client.oauth2.refreshToken(); ``` :::tip When you configure `autoRefresh: true` in the client's OAuth settings, expired access tokens are refreshed automatically before each request. This requires `clientId` and `clientSecret`, so treat it as a trusted-environment pattern. ::: **Using curl:** ```bash curl -X POST "https://cloud.zeyos.com/demo/oauth2/v1/token" \ -u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=refresh_token" \ -d "refresh_token=dGhpcyBpcyBhIHJlZnJlc2g..." ``` ## Token Revocation Revoke a token when the user logs out or your application no longer needs access: **Using the JavaScript client:** ```js await client.oauth2.revokeToken({ token: tokenSet.accessToken, }); ``` **Using curl:** ```bash curl -X POST "https://cloud.zeyos.com/demo/oauth2/v1/revoke" \ -u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "token=eyJhbGciOiJSUzI1NiIs..." ``` ## Session Authentication For browser-based applications where the user is already logged into ZeyOS, session cookie authentication provides a seamless experience without requiring a separate OAuth flow. ### How It Works When a user is logged into ZeyOS in their browser, ZeyOS sets a session cookie (`ZEYOSID`). By including `credentials: 'include'` in fetch requests, the browser automatically sends this cookie, authenticating the request. ### Client Configuration ```js const client = createZeyosClient({ platform: 'https://cloud.zeyos.com/demo', auth: { mode: 'session', session: { enabled: true, credentials: 'include', }, }, }); ``` ### Checking Session Status You can verify whether the user has an active session by calling the `userinfo` endpoint: ```js const endpoint = 'https://cloud.zeyos.com/demo/oauth2/v1/userinfo'; const response = await fetch(endpoint, { method: 'GET', credentials: 'include', headers: { 'Accept': 'application/json' }, }); if (response.ok) { const userInfo = await response.json(); console.log('Logged in as:', userInfo.sub); } ``` :::info Session authentication only works in browser environments where the user is on the same domain (or an allowed origin) as the ZeyOS instance. It will not work in Node.js or server-side contexts. ::: :::warning Do not embed `clientSecret` in shipped browser-only code. For browser apps, prefer session-cookie authentication or perform the OAuth code exchange and refresh on a backend that stores the client credentials safely. ::: ## Security Schemes The ZeyOS API supports three authentication schemes. Each API operation declares which schemes it accepts: | Scheme | Type | Transport | Use Case | |--------|------|-----------|----------| | **Bearer** (OAuth2) | Token | `Authorization: Bearer {token}` | Server-side apps, SPAs, CLI tools | | **Session** (Cookie) | Cookie | `Cookie: ZEYOSID={session_id}` | Browser apps where user is logged into ZeyOS | | **Basic** (Client Credentials) | Header | `Authorization: Basic {base64}` | OAuth2 token endpoint requests | Most API resource operations accept both **Bearer** and **Session** authentication. The OAuth2 token endpoints (`/token`, `/revoke`, `/introspect`) use **Basic** authentication with your client credentials. The `@zeyos/client` library handles scheme selection automatically based on your configuration: - `mode: 'auto'` (default) -- Tries bearer first if tokens are available, falls back to session. - `mode: 'oauth'` -- Uses bearer tokens exclusively. - `mode: 'session'` -- Uses session cookies exclusively. - `mode: 'none'` -- No authentication (for public endpoints). :::warning Never expose your `client_secret` in client-side JavaScript. For browser-based SPAs, use the session authentication mode or implement a server-side proxy for the token exchange. Keep OAuth client credentials on your backend server where they cannot be inspected by end users. ::: ## Token Management The client library provides built-in token storage and management: ```js // Read the current token set const tokenSet = await client.auth.getTokenSet(); // Manually set tokens (e.g., after loading from a database) await client.auth.setTokenSet({ accessToken: 'new-access-token', refreshToken: 'new-refresh-token', expiresAt: Math.floor(Date.now() / 1000) + 3600, }); // Clear all stored tokens (logout) await client.auth.clearTokenSet(); ``` For custom persistence (e.g., storing tokens in a database or encrypted file), implement the `TokenStore` interface: ```js const customStore = { async get() { // Return the stored token set or null return loadFromDatabase(); }, async set(tokenSet) { // Persist the token set (or null to clear) await saveToDatabase(tokenSet); }, }; const client = createZeyosClient({ platform: 'https://cloud.zeyos.com/demo', auth: { oauth: { tokenStore: customStore, clientId: 'YOUR_CLIENT_ID', clientSecret: 'YOUR_CLIENT_SECRET', autoRefresh: true, }, }, }); ``` --- Canonical: https://www.zeyos.com/guide/api-reference/data-retrieval # Data Retrieval The ZeyOS REST API provides a flexible query language for retrieving data from any resource endpoint. You can control exactly which fields are returned, apply composite filters, search by keyword, sort results, paginate through large datasets, expand JSON and binary columns, and access extended data fields. :::info HTTP Method All list operations use **POST** requests with a JSON body, not GET. This is because queries can include complex filters, field selections, and composite expressions that would be impractical as URL query parameters. ::: ## Field Selection By default, list endpoints return all standard fields for a resource. Use the `fields` parameter to request only the columns you need, reducing payload size and improving performance. ### Array Form Pass a simple array of field names to select specific columns: ```json { "fields": ["ID", "name", "status", "priority"] } ``` **JavaScript client:** ```js const result = await client.api.listTickets({ fields: ['ID', 'name', 'status', 'priority'], }); ``` ### Object Form with Aliases Use an object to rename fields in the response. Keys become the output names; values are the source field paths: ```json { "fields": { "Id": "ID", "Name": "lastname", "Nickname": "extdata.nickname", "Address": "contact.address", "Postalcode": "contact.postalcode", "Town": "contact.city", "SalesAgent": "assigneduser.name" } } ``` **JavaScript client:** ```js const result = await client.api.listAccounts({ fields: { Id: 'ID', Name: 'lastname', Nickname: 'extdata.nickname', Address: 'contact.address', Postalcode: 'contact.postalcode', Town: 'contact.city', SalesAgent: 'assigneduser.name', }, }); ``` ### Dot-Notation Joins Use dot notation to access fields on related records without a separate request. You can discover available relationships in the [Schema Reference](https://www.zeyos.com/guide/api-reference/schema). | Notation | Description | |----------|-------------| | `contact.city` | City field from the linked contact record | | `contact.address` | Address from the linked contact | | `contact.postalcode` | Postal code from the linked contact | | `contact.country` | Country from the linked contact | | `assigneduser.name` | Name of the assigned user | | `account.lastname` | Last name (or company name) of the linked account | | `project.name` | Name of the linked project | ### Extended Data Fields via Dot-Notation Access custom fields stored in a record's `extdata` JSON column using the `extdata.` prefix in your field selection: ```json { "fields": { "Nickname": "extdata.nickname", "Department": "extdata.department" } } ``` :::info Extended data fields are user-defined and may vary between ZeyOS instances. Whenever you create a new form field in ZeyOS, the field's value is stored in `extdata`. Check your instance configuration for available custom fields. ::: ## Filters The `filter` parameter lets you restrict results using comparison operators, string-matching operators, and composite logical expressions. :::tip JavaScript Client: use `filters` (plural) The bundled OpenAPI spec names this parameter `filter` (singular), but ZeyOS's published API documentation uses `filters` (plural) in every worked example — and that is what the JavaScript client and CLI send. Use `filters`. See the [Practical Guide](https://www.zeyos.com/guide/javascript-client/practical-guide#always-use-filters-plural). ::: ### Filter Syntax Filters are expressed as an object where keys are field names and values define the match condition: ``` filter = { "field": "value", "field2": {"=": "value"}, "field3": {"<": "value1", ">": "value2"}, "field4": {"IN": ["value1", "value2"]}, N: ["AND/OR/NOT", {...}, {...}] } ``` ### Simple Equality Pass a field name with a plain value for equality matching: ```json { "filter": { "status": 1, "visibility": 0 } } ``` ### Comparison Operators Use an object value with operator keys for more advanced comparisons: ```json { "filter": { "priority": {">=": 3}, "amount": {">": 100, "<": 1000} } } ``` The full set of comparison operators: | Operator | Description | |----------|-------------| | `=` | Equal to | | `!=` or `<>` | Not equal to | | `<` | Less than | | `<=` | Less than or equal to | | `>` | Greater than | | `>=` | Greater than or equal to | | `IN` | Value is in the given set | | `!IN` | Value is not in the given set | **IN operator example:** ```json { "filter": { "contact.country": {"IN": ["DE", "AT", "GB"]} } } ``` ### String Operators For string fields, additional pattern-matching operators are available: | Operator | Description | Case Sensitive | |----------|-------------|:-:| | `~` | Matches regular expression | Yes | | `~*` | Matches regular expression | No | | `!~` | Does not match regular expression | Yes | | `!~*` | Does not match regular expression | No | | `~~` | LIKE (pattern match) | Yes | | `~~*` | LIKE (pattern match) | No | | `!~~` | NOT LIKE | Yes | | `!~~*` | NOT LIKE | No | :::tip The `~` operators test regular expressions, while `~~` operators use SQL-style LIKE patterns. The `*` suffix makes any operator case-insensitive. ::: ### Composite Filters (AND, OR, NOT) Combine multiple conditions using logical operators. Composite filters use **numbered keys** in the filter object to include logical groups alongside simple field conditions: ```json { "filter": { "visibility": 0, "contact.country": {"IN": ["DE", "AT", "GB"]}, "2": ["OR", {"lastmodified": {">": 1524472045}}, {"contact.lastmodified": {">": 1524472045}} ] } } ``` In this example: - `"visibility": 0` is a simple equality filter. - `"contact.country": {"IN": [...]}` uses the IN operator on a joined field. - The key `"2"` contains an OR group: the record matches if either its own `lastmodified` or its contact's `lastmodified` exceeds the given timestamp. You can nest logical operators as deeply as needed: ```json { "filter": { "0": ["AND", {"status": {"IN": [1, 2]}}, {"visibility": 0}, {"1": ["OR", {"priority": {">=": 3}}, {"duedate": {"<": "2025-06-01"}} ]} ] } } ``` ## Search Queries The `query` parameter performs a search across all searchable string fields for a resource (typically `name`, `description`, and similar columns): ```json { "query": "server outage" } ``` You can combine `query` with `filter` to narrow results further: ```json { "query": "payment", "filter": {"status": 1} } ``` **JavaScript client:** ```js const results = await client.api.listTickets({ query: 'server outage', filters: { status: 1 }, }); ``` ## Sorting Control the order of results using the `sort` parameter. Provide an array of field names, each prefixed with `+` for ascending or `-` for descending: ```json { "sort": ["+lastname", "-contact.country"] } ``` | Prefix | Direction | Example | |--------|-----------|---------| | `+` | Ascending (A-Z, 0-9, oldest first) | `"+lastname"` | | `-` | Descending (Z-A, 9-0, newest first) | `"-lastmodified"` | Multiple sort fields are applied in order -- the example above sorts by `lastname` ascending first, then by `contact.country` descending within each name. :::tip Sort by `"-lastmodified"` to get the most recently updated records first. This is a common default for dashboard-style views. ::: ## Distinct Results The `distinct` parameter eliminates duplicate rows from the result set. This is useful when a query returns multiple rows for the same record due to joins or multi-value fields. ```json { "distinct": true, "fields": ["ID", "lastname", "contact.country"], "filter": { "visibility": 0 } } ``` **JavaScript client:** ```js const result = await client.api.listAccounts({ distinct: true, fields: ['ID', 'lastname', 'contact.country'], filters: { visibility: 0 }, }); ``` ## Pagination The API supports offset-based pagination using `limit` and `offset`: | Parameter | Type | Default | Max | Description | |-----------|------|---------|-----|-------------| | `limit` | integer | `1000` | `10000` | Maximum number of records to return | | `offset` | integer | `0` | — | Number of records to skip before returning results | ```json { "limit": 25, "offset": 50 } ``` :::info Default limit When no `limit` is specified, the API returns up to **1000** records. The maximum supported value is **10000**. For large datasets, always paginate using `limit` and `offset`. ::: ### Getting the Total Count To retrieve the total number of matching records without fetching the data itself, set `count` to `1`. The response will contain only the count: **Request:** ```json { "count": true, "filter": { "visibility": 0, "contact.country": {"IN": ["DE", "AT", "GB"]}, "2": ["OR", {"lastmodified": {">": 1524472045}}, {"contact.lastmodified": {">": 1524472045}} ] } } ``` **Response:** ```json { "count": 5 } ``` **JavaScript client:** ```js const countResult = await client.api.listAccounts({ count: true, filters: { visibility: 0 }, }); // countResult => { count: 5 } ``` :::info Use `count` to build pagination controls in your UI. First request the count to determine the total number of pages, then paginate through results using `limit` and `offset`. Higher-level client helpers may normalize count-enabled responses differently, so keep the raw API shape and the client-layer shape conceptually separate. ::: ## Expanding JSON and Binary Data Some table columns contain JSON data or reference binary files. By default, these columns are not expanded in list responses. The `expand` parameter tells the API to load and inline the column's content automatically. :::warning The `expand` parameter is **only** for JSON data columns (like `items` in transactions or `data` in objects) and binary file references (like `binfile`). It is **not** used for extended data fields -- see [Extended Data (extdata)](#extended-data-extdata) below. ::: **Example -- expanding a binary file column:** ```json { "fields": ["ID", "subject", "binfile"], "expand": ["binfile"], "limit": 1 } ``` **curl:** ```bash curl -X POST "https://cloud.zeyos.com/demo/api/v1/messages/" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "fields": ["ID", "subject", "binfile"], "expand": ["binfile"], "limit": 1 }' ``` **Response:** ```json [{"ID": 188, "subject": "Test", "binfile": {"content": "UmV0dXJuLVBhdGg6IDx..."}}] ``` The binary content is returned inline as base64-encoded data. In the example above, the `binfile` column is expanded to include the full email message content as [RFC 822](https://www.ietf.org/rfc/rfc822.txt). Other common use cases for `expand`: - `items` in transaction records (invoice line items as JSON) - `data` in object records (structured JSON data) ## Extended Data (extdata) Extended data (`extdata`) is a concept in ZeyOS that allows storing additional custom values for any entity. Whenever you create a new form field in ZeyOS, the field's value is stored in `extdata`. Accessing extdata is **separate** from the `expand` parameter. ### In List Requests (POST) There are two ways to include extended data in list responses: **Option 1: Select specific extdata fields via dot-notation in `fields`:** ```json { "fields": { "Id": "ID", "Name": "lastname", "Nickname": "extdata.nickname", "Department": "extdata.department" } } ``` This approach lets you pick individual extdata fields and give them aliases. **Option 2: Include all extdata using the `extdata` body parameter:** ```json { "fields": ["ID", "lastname", "status"], "extdata": 1, "limit": 10 } ``` **JavaScript client:** ```js // Select specific extdata fields const result = await client.api.listAccounts({ fields: { Id: 'ID', Name: 'lastname', Nickname: 'extdata.nickname', }, }); // Or include all extdata const result = await client.api.listAccounts({ fields: ['ID', 'lastname', 'status'], extdata: 1, limit: 10, }); ``` ### In GET Requests When fetching a single record, pass `extdata=1` as a query parameter: ```bash curl "https://cloud.zeyos.com/demo/api/v1/accounts/42?extdata=1" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" ``` **JavaScript client:** ```js const account = await client.api.getAccount({ ID: 42, extdata: 1, }); ``` ## Complete Example Here is the canonical example from the ZeyOS documentation, combining field selection with aliases, dot-notation joins, extdata fields, composite filters, sorting, and pagination. ### Query ```json { "fields": { "Id": "ID", "Name": "lastname", "Nickname": "extdata.nickname", "Address": "contact.address", "Postalcode": "contact.postalcode", "Town": "contact.city", "SalesAgent": "assigneduser.name" }, "filter": { "visibility": 0, "contact.country": {"IN": ["DE", "AT", "GB"]}, "2": ["OR", {"lastmodified": {">": 1524472045}}, {"contact.lastmodified": {">": 1524472045}} ] }, "sort": ["+lastname", "-contact.country"], "limit": 3, "offset": 0 } ``` ### Using curl ```bash curl -X POST "https://cloud.zeyos.com/demo/api/v1/accounts/" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "fields": { "Id": "ID", "Name": "lastname", "Nickname": "extdata.nickname", "Address": "contact.address", "Postalcode": "contact.postalcode", "Town": "contact.city", "SalesAgent": "assigneduser.name" }, "filter": { "visibility": 0, "contact.country": {"IN": ["DE", "AT", "GB"]}, "2": ["OR", {"lastmodified": {">": 1524472045}}, {"contact.lastmodified": {">": 1524472045}} ] }, "sort": ["+lastname", "-contact.country"], "limit": 3, "offset": 0 }' ``` ### Using the JavaScript Client ```js const accounts = await client.api.listAccounts({ fields: { Id: 'ID', Name: 'lastname', Nickname: 'extdata.nickname', Address: 'contact.address', Postalcode: 'contact.postalcode', Town: 'contact.city', SalesAgent: 'assigneduser.name', }, filters: { visibility: 0, 'contact.country': { IN: ['DE', 'AT', 'GB'] }, 2: [ 'OR', { lastmodified: { '>': 1524472045 } }, { 'contact.lastmodified': { '>': 1524472045 } }, ], }, sort: ['+lastname', '-contact.country'], limit: 3, offset: 0, }); ``` ### Response ```json [ { "Id": 2, "Name": "BEQ Building Equipment", "Nickname": null, "Address": "Queensstreet", "Postalcode": "12923", "Town": "London", "SalesAgent": "Max Mueller" }, { "Id": 15, "Name": "CleanTexx", "Nickname": null, "Address": "Tower Bridge", "Postalcode": "12923", "Town": "London", "SalesAgent": null }, { "Id": 1, "Name": "Lightexx AG", "Nickname": null, "Address": "Schmittstr. 4", "Postalcode": "80172", "Town": "Munich", "SalesAgent": null } ] ``` :::tip When building interactive UIs, start with a `count` request to determine the total result set size, then use `limit` and `offset` to implement paginated loading. ::: --- Canonical: https://www.zeyos.com/guide/api-reference/resources # Resources The ZeyOS REST API exposes a comprehensive set of resources covering CRM, project management, ticketing, commerce, communication, and more. Each resource maps to a RESTful endpoint under the base URL and supports a consistent set of operations. :::info API Surface vs. CLI Surface This page documents the **full API surface** exposed by ZeyOS. The CLI intentionally supports a curated subset of common resources. For the CLI boundary and escalation path, see [CLI Coverage and Escalation](https://www.zeyos.com/guide/agent-workflows/cli-coverage-and-escalation). ::: ## Available Resources The table below lists all available API resources and their supported operations. | Resource | Endpoint | List | Get | Create | Update | Delete | |----------|----------|:----:|:---:|:------:|:------:|:------:| | Accounts | `/accounts` | Yes | Yes | Yes | Yes | Yes | | ActionSteps | `/actionsteps` | Yes | Yes | Yes | Yes | Yes | | Addresses | `/addresses` | Yes | Yes | Yes | Yes | Yes | | Applications | `/applications` | Yes | Yes | -- | -- | -- | | ApplicationAssets | `/applicationassets` | Yes | Yes | -- | -- | -- | | Appointments | `/appointments` | Yes | Yes | Yes | Yes | Yes | | Associations | `/associations` | Yes | Yes | Yes | Yes | Yes | | BinFiles | `/binfiles` | Yes | -- | -- | -- | -- | | Campaigns | `/campaigns` | Yes | Yes | Yes | Yes | Yes | | Categories | `/categories` | Yes | Yes | Yes | Yes | Yes | | Channels | `/channels` | Yes | Yes | Yes | Yes | Yes | | Comments | `/comments` | Yes | Yes | Yes | Yes | Yes | | Components | `/components` | Yes | Yes | Yes | Yes | Yes | | Contacts | `/contacts` | Yes | Yes | Yes | Yes | Yes | | ContactsToContacts | `/contactstocontacts` | Yes | Yes | Yes | Yes | Yes | | Contracts | `/contracts` | Yes | Yes | Yes | Yes | Yes | | Coupons | `/coupons` | Yes | Yes | Yes | Yes | Yes | | CouponCodes | `/couponcodes` | Yes | Yes | Yes | Yes | Yes | | CustomFields | `/customfields` | Yes | Yes | -- | -- | -- | | DAVServers | `/davservers` | Yes | Yes | Yes | Yes | Yes | | Devices | `/devices` | Yes | Yes | Yes | Yes | Yes | | Documents | `/documents` | Yes | Yes | Yes | Yes | Yes | | DunningNotices | `/dunningnotices` | Yes | Yes | Yes | Yes | Yes | | DunningToTransactions | `/dunningtotransactions` | Yes | Yes | Yes | Yes | Yes | | EntitiesToChannels | `/entitiestochannels` | Yes | Yes | Yes | Yes | Yes | | Events | `/events` | Yes | Yes | Yes | Yes | Yes | | FeedServers | `/feedservers` | Yes | Yes | Yes | Yes | Yes | | Files | `/files` | Yes | Yes | Yes | Yes | Yes | | Follows | `/follows` | Yes | Yes | Yes | Yes | Yes | | Forks | `/forks` | Yes | Yes | -- | -- | -- | | Groups | `/groups` | Yes | Yes | -- | -- | -- | | GroupsToUsers | `/groupstousers` | Yes | Yes | -- | -- | -- | | Invitations | `/invitations` | Yes | Yes | Yes | Yes | Yes | | Items | `/items` | Yes | Yes | Yes | Yes | Yes | | Ledgers | `/ledgers` | Yes | Yes | Yes | Yes | Yes | | Likes | `/likes` | Yes | Yes | Yes | Yes | Yes | | Links | `/links` | Yes | Yes | Yes | Yes | Yes | | MailingLists | `/mailinglists` | Yes | Yes | Yes | Yes | Yes | | MailingRecipients | `/mailingrecipients` | Yes | Yes | Yes | Yes | Yes | | MailServers | `/mailservers` | Yes | Yes | Yes | Yes | Yes | | Messages | `/messages` | Yes | Yes | Yes | Yes | Yes | | MessageReads | `/messagereads` | Yes | Yes | Yes | Yes | Yes | | Notes | `/notes` | Yes | Yes | Yes | Yes | Yes | | Objects | `/objects` | Yes | Yes | Yes | Yes | Yes | | Opportunities | `/opportunities` | Yes | Yes | Yes | Yes | Yes | | Participants | `/participants` | Yes | Yes | Yes | Yes | Yes | | Payments | `/payments` | Yes | Yes | Yes | Yes | Yes | | Permissions | `/permissions` | Yes | Yes | -- | -- | -- | | PriceLists | `/pricelists` | Yes | Yes | Yes | Yes | Yes | | PriceListsToAccounts | `/priceliststoaccounts` | Yes | Yes | Yes | Yes | Yes | | Prices | `/prices` | Yes | Yes | Yes | Yes | Yes | | Projects | `/projects` | Yes | Yes | Yes | Yes | Yes | | Records | `/records` | Yes | Yes | Yes | Yes | Yes | | RelatedItems | `/relateditems` | Yes | Yes | Yes | Yes | Yes | | Resources | `/resources` | Yes | Yes | -- | -- | -- | | Services | `/services` | Yes | Yes | -- | -- | -- | | StockTransactions | `/stocktransactions` | Yes | Yes | Yes | Yes | Yes | | Storages | `/storages` | Yes | Yes | Yes | Yes | Yes | | Suppliers | `/suppliers` | Yes | Yes | Yes | Yes | Yes | | Tasks | `/tasks` | Yes | Yes | Yes | Yes | Yes | | Tickets | `/tickets` | Yes | Yes | Yes | Yes | Yes | | Transactions | `/transactions` | Yes | Yes | Yes | Yes | Yes | | Users | `/users` | Yes | Yes | -- | -- | -- | | Weblets | `/weblets` | Yes | Yes | -- | -- | -- | ## Common Resources The following resources are the most frequently used when building integrations with ZeyOS. ### Tickets Support and service tickets with status tracking, priority levels, due dates, and assignment to users. Tickets are the central work item in ZeyOS and can be linked to accounts, projects, contacts, and tasks. ```js // List open tickets sorted by priority const tickets = await client.api.listTickets({ filters: { visibility: 0, status: 4 }, sort: ['-priority', '+duedate'], limit: 50, }); // Get a single ticket with extended data const ticket = await client.api.getTicket({ ID: 12345, extdata: 1, tags: 1 }); // Create a new ticket const newTicket = await client.api.createTicket({ name: 'Server maintenance required', priority: 3, status: 0, }); // Update a ticket — flat spread form or explicit body key both work await client.api.updateTicket({ ID: 12345, status: 4, priority: 4 }); // Equivalent using explicit body key: // await client.api.updateTicket({ ID: 12345, body: { status: 4, priority: 4 } }); ``` ### Accounts Customer and company records that serve as the primary organizational entity in the CRM. Accounts can have linked contacts, tickets, documents, and transactions. ```js const accounts = await client.api.listAccounts({ fields: ['ID', 'lastname', 'firstname'], filters: { visibility: 0 }, sort: ['+lastname'], limit: 100, }); ``` ### Contacts Individual people linked to accounts. Contacts store personal details such as name, email, phone, and address information. ```js const contacts = await client.api.listContacts({ fields: { Id: 'ID', Name: 'lastname', Email: 'email', Company: 'account.lastname', }, filters: { visibility: 0 }, sort: ['+lastname'], }); ``` ### Tasks Actionable work items that can be linked to tickets, projects, or other entities. Tasks track completion status and can be assigned to users. ```js const tasks = await client.api.listTasks({ filters: { ticket: 12345, visibility: 0 }, sort: ['+name'], limit: 200, }); ``` ### Projects Organizational groupings for tickets, tasks, and other work items. Projects provide a way to plan and track larger initiatives. ```js const projects = await client.api.listProjects({ fields: ['ID', 'name'], filters: { visibility: 0 }, sort: ['+name'], }); ``` ### Items Products and services with pricing information. Items are referenced in documents (invoices, quotes) and can be linked to price lists, categories, and suppliers. ```js const items = await client.api.listItems({ fields: ['ID', 'name', 'price', 'unit'], sort: ['+name'], limit: 100, }); ``` ### Documents Stored files and document records — attachments, generated PDFs and similar — linked to accounts and other records. Fields include `name`, `documentnum`, `filename` and `status`. ```js const recent = await client.api.listDocuments({ filters: { visibility: 0 }, sort: ['-lastmodified'], limit: 25, }); ``` :::warning Invoices are not documents Invoices, quotes, orders, credits and delivery notes are **transactions**, distinguished by `transactions.type` — an invoice is `type: 3`. The `documents` resource has no `doctype` column, so filtering on one returns an HTTP 400. ```js const invoices = await client.api.listTransactions({ filters: { type: 3 }, sort: ['-date'], limit: 25, }); ``` On the CLI, use the entity that binds the type for you: ```bash zeyos list billing_invoices --limit 25 --sort -date ``` See the CLI's [transaction entities](https://www.zeyos.com/guide/cli/commands#list). ::: ## Resource Naming The API follows consistent naming conventions for endpoints and operations: ### Endpoint Paths Resource endpoints use **plural, lowercase** paths: ``` /accounts /tickets /contacts /documents ``` Individual records are accessed by appending `/{ID}`: ``` /accounts/{ID} /tickets/{ID} ``` ### Operation IDs The JavaScript client uses camelCase operation IDs that follow a verb-noun pattern: | Operation | Pattern | Example | |-----------|---------|---------| | List | `list{Resources}` | `listTickets`, `listAccounts` | | Get | `get{Resource}` | `getTicket`, `getAccount` | | Create | `create{Resource}` | `createTicket`, `createAccount` | | Update | `update{Resource}` | `updateTicket`, `updateAccount` | | Delete | `delete{Resource}` | `deleteTicket`, `deleteAccount` | | Exists | `exists{Resource}` | `existsTicket`, `existsAccount` | All operations are available as methods on the `client.api` object: ```js // List operations accept query parameters (fields, filter, sort, etc.) const results = await client.api.listTickets({ limit: 10 }); // Get operations require an ID path parameter const ticket = await client.api.getTicket({ ID: 123 }); // Create operations accept a request body const created = await client.api.createTicket({ name: 'New ticket' }); // Update operations require an ID and changed fields (flat spread or explicit body key) await client.api.updateTicket({ ID: 123, status: 4 }); // Delete operations require an ID await client.api.deleteTicket({ ID: 123 }); // Exists operations return a boolean-like HEAD response await client.api.existsTicket({ ID: 123 }); ``` :::info The client library is auto-generated from the OpenAPI specification files located in the `/openapi/` directory of this project. The spec files (`api.json`, `oauth2.json`, `auth.json`) define every available resource, operation, parameter, and response schema. Run `npm run generate` to regenerate the client after modifying the spec files. ::: ## HTTP Methods The API uses the following HTTP methods, which may differ from typical REST conventions: | Operation | HTTP Method | Description | |-----------|-------------|-------------| | List | `POST` | Retrieve a filtered list of records (body contains query parameters) | | Get | `GET` | Retrieve a single record by ID | | Create | `PUT` | Create a new record | | Update | `PATCH` | Partially update an existing record | | Delete | `DELETE` | Delete a record by ID | | Exists | `HEAD` | Check if a record exists (returns no body) | :::tip Note that **list** operations use `POST` rather than `GET`. This is because the query language (filters, field selection, sorting) can produce request payloads that exceed URL length limits. Using `POST` allows complex queries to be sent reliably in the request body. ::: --- Canonical: https://www.zeyos.com/guide/api-reference/schema # Schema Reference This page documents the fields, types, and relationships for the most commonly used ZeyOS resources. Use it as a quick lookup when building queries, creating records, or planning data integrations. ## Conventions - **Timestamps** are Unix timestamps in **seconds** (not milliseconds). Convert with `new Date(value * 1000)` in JavaScript. - **Visibility** controls soft-delete behaviour: `0` = regular, `1` = archived, `2` = deleted. Include `visibility: 0` to exclude archived and deleted records — but only on resources that actually have the column. It is absent from `transactions` and every billing/procurement entity, `payments`, `messages`, `actionsteps`, `addresses` and `users`, where filtering on it returns an opaque HTTP 400. `zeyos describe ` lists the columns a resource really has. - **GIN-indexed fields** (foreign keys such as `project`, `account`, `ticket`) are filtered the same way as any other column. Pass every filter as `filters` (plural) — see the [Practical Guide](https://www.zeyos.com/guide/javascript-client/practical-guide#always-use-filters-plural). - Fields marked **(required)** are `NOT NULL` in the database. Most of them have a server-side default (for example `creator` defaults to the authenticated user, `creationdate`/`lastmodified` default to the current time, integer flags default to `0`), so a create that omits them still succeeds. The exception to watch is **`currency` on accounts**: it is `NOT NULL` with no default, so `createAccount` without it fails with an opaque HTTP 500. On updates (PATCH), only the fields you send are changed. - **Foreign key fields** (e.g. `account`, `project`, `ticket`) accept integer IDs referencing the related resource. --- ### Accounts | Field | Type | Description | |-------|------|-------------| | `ID` | integer | Account ID (required) | | `creator` | integer | Creator user ID (defaults to authenticated user on creation) | | `assigneduser` | integer | Assigned user ID | | `creationdate` | timestamp | Creation date as Unix timestamp (defaults to now on creation) (required) | | `lastmodified` | timestamp | Last modification date as Unix timestamp (auto-reset on modification) (required) | | `contact` | integer | Contact ID | | `visibility` | integer | Visibility (`0`=REGULAR, `1`=ARCHIVED, `2`=DELETED) (required) | | `lastname` | text | Last name (surname or company name); required if `firstname` is empty (required) | | `firstname` | text | First name (given name); required if `lastname` is empty (required) | | `type` | integer | Account type (`0`=PROSPECT, `1`=CUSTOMER, `2`=SUPPLIER, `3`=CUSTOMERANDSUPPLIER, `4`=COMPETITOR, `5`=EMPLOYEE) (required) | | `customernum` | text | Customer number; only for PROSPECT, CUSTOMER, CUSTOMERANDSUPPLIER, or EMPLOYEE (required) | | `suppliernum` | text | Supplier number; only for SUPPLIER or CUSTOMERANDSUPPLIER (required) | | `taxid` | text | Tax ID (e.g. VATIN or SSN) (required) | | `currency` | varchar | Currency code (ISO 4217) (required) | | `locked` | integer | Deny booking of billing or procurement transactions (required) | | `excludetax` | integer | Exclude from taxation (required) | | `description` | text | Detailed general description (required) | **GIN-indexed fields** (use `filters` plural): `assigneduser`, `customernum`, `firstname`, `lastname`, `suppliernum` --- ### Contacts | Field | Type | Description | |-------|------|-------------| | `ID` | integer | Contact ID (required) | | `creator` | integer | Creator user ID (defaults to authenticated user on creation) | | `assigneduser` | integer | Assigned user ID | | `creationdate` | timestamp | Creation date as Unix timestamp (defaults to now on creation) (required) | | `lastmodified` | timestamp | Last modification date as Unix timestamp (auto-reset on modification) (required) | | `davserver` | integer | DAV server ID | | `picbinfile` | integer | Binary file ID; read-only (not for PUT or PATCH) | | `visibility` | integer | Visibility (`0`=REGULAR, `1`=ARCHIVED, `2`=DELETED) (required) | | `lastname` | text | Last name (surname or company name); required if `firstname` is empty (required) | | `firstname` | text | First name (given name); required if `lastname` is empty (required) | | `type` | integer | Contact type (`0`=COMPANY, `1`=PERSON) (required) | | `title` | text | Title or salutation; only for PERSON (required) | | `company` | text | Company name; only for PERSON (required) | | `position` | text | Position or job title; only for PERSON (required) | | `department` | text | Department; only for PERSON (required) | | `address` | text | Address (street and building/suite number) (required) | | `postalcode` | text | Postal or ZIP code (required) | | `city` | text | City or locality (required) | | `region` | text | Region or state (required) | | `country` | varchar | Country code (ISO 3166-1 alpha-2) (required) | | `phone` | text | Primary phone number (required) | | `phone2` | text | Secondary phone number (required) | | `cell` | text | Cell phone number (required) | | `fax` | text | Fax number (required) | | `email` | text | Primary e-mail address (required) | | `email2` | text | Secondary e-mail address (required) | | `website` | text | Website URL (required) | | `birthdate` | timestamp | Birth date as Unix timestamp; only for PERSON | | `description` | text | Detailed general description (required) | **GIN-indexed fields** (use `filters` plural): `assigneduser`, `davserver`, `company`, `email`, `email2`, `firstname`, `lastname` --- ### Tickets | Field | Type | Description | |-------|------|-------------| | `ID` | integer | Ticket ID (required) | | `creator` | integer | Creator user ID (defaults to authenticated user on creation) | | `assigneduser` | integer | Assigned user ID | | `creationdate` | timestamp | Creation date as Unix timestamp (defaults to now on creation) (required) | | `lastmodified` | timestamp | Last modification date as Unix timestamp (auto-reset on modification) (required) | | `account` | integer | Account ID; mutually exclusive with `project` | | `project` | integer | Project ID; mutually exclusive with `account` | | `visibility` | integer | Visibility (`0`=REGULAR, `1`=ARCHIVED, `2`=DELETED) (required) | | `name` | text | Name (required) | | `ticketnum` | text | Ticket number (required) | | `date` | timestamp | Designated date as Unix timestamp (defaults to now on creation) (required) | | `duedate` | timestamp | Due date as Unix timestamp | | `status` | integer | Status (`0`=NOTSTARTED, `1`=AWAITINGACCEPTANCE, `2`=ACCEPTED, `3`=REJECTED, `4`=ACTIVE, `5`=INACTIVE, `6`=FEEDBACKREQUIRED, `7`=TESTING, `8`=CANCELLED, `9`=COMPLETED, `10`=FAILED, `11`=BOOKED) (required) | | `priority` | integer | Priority (`0`=LOWEST, `1`=LOW, `2`=MEDIUM, `3`=HIGH, `4`=HIGHEST) (required) | | `description` | text | Detailed general description (required) | | `billingitems` | json | JSON-encoded billing items (array) | | `procurementitems` | json | JSON-encoded procurement items (array) | **GIN-indexed fields** (use `filters` plural): `assigneduser`, `project`, `name`, `ticketnum` --- ### Tasks | Field | Type | Description | |-------|------|-------------| | `ID` | integer | Task ID (required) | | `creator` | integer | Creator user ID (defaults to authenticated user on creation) | | `assigneduser` | integer | Assigned user ID | | `creationdate` | timestamp | Creation date as Unix timestamp (defaults to now on creation) (required) | | `lastmodified` | timestamp | Last modification date as Unix timestamp (auto-reset on modification) (required) | | `davserver` | integer | DAV server ID | | `ticket` | integer | Ticket ID; mutually exclusive with `project` | | `project` | integer | Project ID; mutually exclusive with `ticket` | | `visibility` | integer | Visibility (`0`=REGULAR, `1`=ARCHIVED, `2`=DELETED) (required) | | `name` | text | Name (required) | | `tasknum` | text | Task number (required) | | `datefrom` | timestamp | Start date as Unix timestamp | | `duedate` | timestamp | Due date as Unix timestamp | | `status` | integer | Status (`0`=NOTSTARTED, `1`=AWAITINGACCEPTANCE, `2`=ACCEPTED, `3`=REJECTED, `4`=ACTIVE, `5`=INACTIVE, `6`=FEEDBACKREQUIRED, `7`=TESTING, `8`=CANCELLED, `9`=COMPLETED, `10`=FAILED, `11`=BOOKED) (required) | | `priority` | integer | Priority (`0`=LOWEST, `1`=LOW, `2`=MEDIUM, `3`=HIGH, `4`=HIGHEST) (required) | | `projectedeffort` | integer | Projected effort in minutes (required) | | `description` | text | Detailed general description (required) | **GIN-indexed fields** (use `filters` plural): `assigneduser`, `davserver`, `project`, `name`, `tasknum` --- ### Projects | Field | Type | Description | |-------|------|-------------| | `ID` | integer | Project ID (required) | | `creator` | integer | Creator user ID (defaults to authenticated user on creation) | | `assigneduser` | integer | Assigned user ID | | `creationdate` | timestamp | Creation date as Unix timestamp (defaults to now on creation) (required) | | `lastmodified` | timestamp | Last modification date as Unix timestamp (auto-reset on modification) (required) | | `account` | integer | Account ID | | `visibility` | integer | Visibility (`0`=REGULAR, `1`=ARCHIVED, `2`=DELETED) (required) | | `name` | text | Name (required) | | `projectnum` | text | Project number (required) | | `status` | integer | Status (`0`=DRAFT, `1`=NOTSTARTED, `2`=AWAITINGAPPROVAL, `3`=APPROVED, `4`=DISMISSED, `5`=ACTIVE, `6`=INACTIVE, `7`=TESTING, `8`=CANCELLED, `9`=COMPLETED, `10`=FAILED, `11`=BOOKED) (required) | | `description` | text | Detailed general description (required) | **GIN-indexed fields** (use `filters` plural): `assigneduser`, `name`, `projectnum` --- ### Appointments | Field | Type | Description | |-------|------|-------------| | `ID` | integer | Appointment ID (required) | | `creator` | integer | Creator user ID (defaults to authenticated user on creation) | | `assigneduser` | integer | Assigned user ID | | `creationdate` | timestamp | Creation date as Unix timestamp (defaults to now on creation) (required) | | `lastmodified` | timestamp | Last modification date as Unix timestamp (auto-reset on modification) (required) | | `davserver` | integer | DAV server ID | | `visibility` | integer | Visibility (`0`=REGULAR, `1`=ARCHIVED, `2`=DELETED) (required) | | `name` | text | Name (required) | | `location` | text | Location (required) | | `color` | varchar | Color code (CSS hex without `#`) (required) | | `datefrom` | timestamp | Start date as Unix timestamp; must be <= `dateto` (required) | | `dateto` | timestamp | End date as Unix timestamp; must be >= `datefrom` (required) | | `recurrence` | integer | Recurrence (`0`=DAY, `1`=WORKDAY, `2`=WEEK, `3`=MONTH, `4`=YEAR) | | `interval` | integer | Recurrence interval in minutes (required) | | `maxoccurrences` | integer | Maximum occurrences including start date (`0`=unlimited) (required) | | `daterecurrence` | timestamp | Recurrence end date as Unix timestamp | | `description` | text | Detailed general description (required) | **GIN-indexed fields** (use `filters` plural): `assigneduser`, `davserver`, `location`, `name` --- ### Transactions | Field | Type | Description | |-------|------|-------------| | `ID` | integer | Transaction ID (required) | | `creator` | integer | Creator user ID (defaults to authenticated user on creation) | | `assigneduser` | integer | Assigned user ID | | `creationdate` | timestamp | Creation date as Unix timestamp (defaults to now on creation) (required) | | `lastmodified` | timestamp | Last modification date as Unix timestamp (auto-reset on modification) (required) | | `account` | integer | Account ID | | `item` | integer | Item ID; must be `null` for BILLING and PROCUREMENT | | `contract` | integer | Contract ID | | `transactionnum` | text | Transaction number (required) | | `type` | integer | Transaction type (`0`=BILLING_QUOTE, `1`=BILLING_ORDER, `2`=BILLING_DELIVERY, `3`=BILLING_INVOICE, `4`=BILLING_CREDIT, `5`=PROCUREMENT_REQUEST, `6`=PROCUREMENT_ORDER, `7`=PROCUREMENT_DELIVERY, `8`=PROCUREMENT_INVOICE, `9`=PROCUREMENT_CREDIT, `10`=PRODUCTION_FABRICATION, `11`=PRODUCTION_DISASSEMBLY) (required) | | `date` | timestamp | Designated date as Unix timestamp (defaults to now on creation) (required) | | `duedate` | timestamp | Due date as Unix timestamp | | `status` | integer | Status (`0`=DRAFT, `1`=BOOKED, `2`=HOLD, `3`=CANCELLED, `4`=CLOSED, ... `20`=PAID, `21`=OVERPAID, `22`=PROCESSED, `23`=PROCESSED_CANCELLED) (required) | | `calculation` | integer | Calculation method (`0`=NET, `1`=GROSS, `2`=EXACT, `3`=LEGACY, `4`=EXTERNAL) (required) | | `productionfactor` | integer | Production factor; required for PRODUCTION, otherwise `null` | | `currency` | varchar | Currency code (ISO 4217) (required) | | `exchangerate` | float | Exchange rate as multiple of system currency unit (required) | | `taxid` | text | Buyer Tax ID (e.g. VATIN or SSN) (required) | | `shippingrecipient` | text | Shipping recipient (required) | | `shippingaddress` | text | Shipping address (required) | | `shippingpostalcode` | text | Shipping postal code (required) | | `shippingcity` | text | Shipping city (required) | | `shippingregion` | text | Shipping region or state (required) | | `shippingcountry` | varchar | Shipping country code (ISO 3166-1 alpha-2) (required) | | `billingrecipient` | text | Billing recipient (required) | | `billingaddress` | text | Billing address (required) | | `billingpostalcode` | text | Billing postal code (required) | | `billingcity` | text | Billing city (required) | | `billingregion` | text | Billing region or state (required) | | `billingcountry` | varchar | Billing country code (ISO 3166-1 alpha-2) (required) | | `sellertaxid` | text | Seller Tax ID (required) | | `sellername` | text | Seller name (required) | | `selleraddress` | text | Seller address (required) | | `sellerpostalcode` | text | Seller postal code (required) | | `sellercity` | text | Seller city (required) | | `sellerregion` | text | Seller region or state (required) | | `sellercountry` | varchar | Seller country code (ISO 3166-1 alpha-2) (required) | | `discount` | float | Total absolute discount (required) | | `netamount` | float | Total net amount (required) | | `tax` | float | Total tax amount (required) | | `margin` | float | Total absolute margin (required) | | `weight` | float | Total shipping weight in kg (required) | | `items` | json | JSON-encoded line items (array) | **GIN-indexed fields** (use `filters` plural): `assigneduser`, `transactionnum` --- ### Items | Field | Type | Description | |-------|------|-------------| | `ID` | integer | Item ID (required) | | `creator` | integer | Creator user ID (defaults to authenticated user on creation) | | `creationdate` | timestamp | Creation date as Unix timestamp (defaults to now on creation) (required) | | `lastmodified` | timestamp | Last modification date as Unix timestamp (auto-reset on modification) (required) | | `model` | integer | Model item ID; only for non-MODEL items | | `picbinfile` | integer | Binary file ID; read-only (not for PUT or PATCH) | | `visibility` | integer | Visibility (`0`=REGULAR, `1`=ARCHIVED, `2`=DELETED) (required) | | `name` | text | Product name (required) | | `manufacturer` | text | Manufacturer (brand or company) (required) | | `itemnum` | text | Item number / SKU (required) | | `barcode` | text | Barcode (e.g. GTIN, EAN, UPC) (required) | | `type` | integer | Item type (`0`=SIMPLE, `1`=SERIALS, `2`=CHARGES, `3`=SERIALSANDCHARGES, `4`=SET, `5`=CONTAINER, `6`=NOSTOCK, `7`=MODEL) (required) | | `forcestock` | integer | Force stock check on depletion (`0`=STORAGE, `1`=LOCATION) | | `applicability` | integer | Applicability (`0`=ALWAYS, `1`=NEVER, `2`=BILLINGONLY, `3`=PROCUREMENTONLY) (required) | | `unit` | varchar | Unit code (UN/CEFACT Recommendation 20) (required) | | `sellingprice` | float | Default selling price per unit (required) | | `purchaseprice` | float | Default purchase price per unit (required) | | `taxrate` | float | Tax rate in percent | | `weight` | float | Shipping weight per unit in kg (required) | | `classcode` | text | Product classification code (e.g. GPC, UNSPSC) (required) | | `tariffcode` | text | Tariff code (e.g. HS, CN, HTS) (required) | | `origin` | varchar | Origin country code (ISO 3166-1 alpha-2) (required) | | `description` | text | Detailed general description (required) | | `foreigntaxrates` | json | Country-specific tax rates; use country code as key | **GIN-indexed fields** (use `filters` plural): `barcode`, `itemnum`, `manufacturer`, `name` --- ### Opportunities | Field | Type | Description | |-------|------|-------------| | `ID` | integer | Opportunity ID (required) | | `creator` | integer | Creator user ID (defaults to authenticated user on creation) | | `assigneduser` | integer | Assigned user ID | | `creationdate` | timestamp | Creation date as Unix timestamp (defaults to now on creation) (required) | | `lastmodified` | timestamp | Last modification date as Unix timestamp (auto-reset on modification) (required) | | `account` | integer | Account ID | | `contact` | integer | Contact ID | | `campaign` | integer | Campaign ID | | `visibility` | integer | Visibility (`0`=REGULAR, `1`=ARCHIVED, `2`=DELETED) (required) | | `name` | text | Name (required) | | `opportunitynum` | text | Opportunity number (required) | | `date` | timestamp | Designated date as Unix timestamp (defaults to now on creation) (required) | | `duedate` | timestamp | Due date as Unix timestamp | | `status` | integer | Status (`0`=UNEVALUATED, `1`=ELIGIBLE, `2`=FEEDBACKREQUIRED, `3`=INNEGOTIATION, `4`=OFFERED, `5`=ACCEPTED, `6`=REJECTED) (required) | | `priority` | integer | Priority (`0`=LOWEST, `1`=LOW, `2`=MEDIUM, `3`=HIGH, `4`=HIGHEST) (required) | | `probability` | integer | Probability of success in percent; must be `100` for ACCEPTED (required) | | `worstcase` | float | Worst-case monetary outcome (required) | | `mostlikely` | float | Most likely monetary outcome (required) | | `upside` | float | Upside monetary outcome (required) | | `description` | text | Detailed general description (required) | **GIN-indexed fields** (use `filters` plural): `assigneduser`, `name`, `opportunitynum` --- ### Documents | Field | Type | Description | |-------|------|-------------| | `ID` | integer | Document ID (required) | | `creator` | integer | Creator user ID (defaults to authenticated user on creation) | | `assigneduser` | integer | Assigned user ID | | `creationdate` | timestamp | Creation date as Unix timestamp (defaults to now on creation) (required) | | `lastmodified` | timestamp | Last modification date as Unix timestamp (auto-reset on modification) (required) | | `binfile` | integer | Binary file ID; read-only (not for PUT or PATCH) | | `visibility` | integer | Visibility (`0`=REGULAR, `1`=ARCHIVED, `2`=DELETED) (required) | | `name` | text | Name (required) | | `documentnum` | text | Document number (required) | | `status` | integer | Status (`0`=DRAFT, `1`=FEEDBACKREQUIRED, `2`=INREVISION, `3`=AWAITINGAPPROVAL, `4`=FINAL, `5`=OBSOLETE) (required) | | `filename` | text | Filename (required) | | `mimetype` | text | MIME type (required) | | `public` | integer | Publicly accessible (required) | | `description` | text | Detailed general description (required) | **GIN-indexed fields** (use `filters` plural): `assigneduser`, `documentnum`, `filename`, `name` --- ### Notes | Field | Type | Description | |-------|------|-------------| | `ID` | integer | Note ID (required) | | `creator` | integer | Creator user ID (defaults to authenticated user on creation) | | `assigneduser` | integer | Assigned user ID | | `creationdate` | timestamp | Creation date as Unix timestamp (defaults to now on creation) (required) | | `lastmodified` | timestamp | Last modification date as Unix timestamp (auto-reset on modification) (required) | | `binfile` | integer | Binary file ID; read-only (not for PUT or PATCH) | | `visibility` | integer | Visibility (`0`=REGULAR, `1`=ARCHIVED, `2`=DELETED) (required) | | `name` | text | Name (required) | | `status` | integer | Status (`0`=DRAFT, `1`=FEEDBACKREQUIRED, `2`=INREVISION, `3`=AWAITINGAPPROVAL, `4`=FINAL, `5`=OBSOLETE) (required) | | `contenttype` | text | Content MIME type (required) | | `text` | text | Plain text content (required) | | `attachments` | text[] | Array of attachment filenames | | `description` | text | Detailed general description (required) | **GIN-indexed fields** (use `filters` plural): `assigneduser`, `name` --- ### Messages | Field | Type | Description | |-------|------|-------------| | `ID` | integer | Message ID (required) | | `creator` | integer | Creator user ID (defaults to authenticated user on creation) | | `creationdate` | timestamp | Creation date as Unix timestamp (defaults to now on creation) (required) | | `lastmodified` | timestamp | Last modification date as Unix timestamp (auto-reset on modification) (required) | | `mailserver` | integer | Mail server ID | | `ticket` | integer | Ticket ID; mutually exclusive with `opportunity` | | `opportunity` | integer | Opportunity ID; mutually exclusive with `ticket` | | `mailinglist` | integer | Mailing list ID | | `reference` | integer | Reference message (reply-to) ID; must be distinct from `ID` | | `binfile` | integer | Binary file ID; read-only (not for PUT or PATCH) | | `mailbox` | integer | Mailbox type (`0`=INBOX, `1`=DRAFTS, `2`=SENT, `3`=TEMPLATES, `4`=MAILINGS, `5`=ARCHIVE, `6`=TRASH, `7`=JUNK) (required) | | `verified` | integer | Verified sender e-mail address (required) | | `date` | timestamp | Designated date as Unix timestamp (defaults to now on creation) (required) | | `subject` | text | Subject (required) | | `sender` | text | Sender name and e-mail address (required) | | `sender_email` | text | Sender e-mail address (required) | | `sender_name` | text | Sender name (required) | | `to` | text | All regular recipient names and e-mail addresses (required) | | `to_email` | text | First regular recipient e-mail address (required) | | `to_name` | text | First regular recipient name (required) | | `to_count` | integer | Number of regular recipients (required) | | `cc` | text | Carbon copy recipients (required) | | `bcc` | text | Blind carbon copy recipients (required) | | `contenttype` | text | Content MIME type (required) | | `text` | text | Plain text content (required) | | `attachments` | text[] | Array of attachment filenames | | `senddate` | timestamp | Scheduled send date as Unix timestamp | | `senderror` | text | Last send error message (required) | | `messageid` | text | Message-ID header (required) | **GIN-indexed fields** (use `filters` plural): `mailinglist`, `mailserver`, `sender`, `subject`, `to` --- ### Comments | Field | Type | Description | |-------|------|-------------| | `ID` | timestamp | Comment ID (required) | | `creator` | integer | Creator user ID (defaults to authenticated user on creation) | | `creationdate` | timestamp | Creation date as Unix timestamp (defaults to now on creation) (required) | | `lastmodified` | timestamp | Last modification date as Unix timestamp (auto-reset on modification) (required) | | `record` | timestamp | Record ID (parent record dependency) (required) | | `date` | timestamp | Designated date as Unix timestamp (defaults to now on creation) (required) | | `sender` | text | Sender (required) | | `text` | text | Comment text (Markdown for rich text) (required) | | `meta` | json | JSON-encoded metadata (object) | --- ### Events | Field | Type | Description | |-------|------|-------------| | `ID` | integer | Event ID (required) | | `creator` | integer | Creator user ID (defaults to authenticated user on creation) | | `creationdate` | timestamp | Creation date as Unix timestamp (defaults to now on creation) (required) | | `lastmodified` | timestamp | Last modification date as Unix timestamp (auto-reset on modification) (required) | | `entity` | t_entity | Canonical entity (required) | | `index` | integer | Entity ID (required) | | `name` | text | Name (required) | | `color` | varchar | Color code (CSS hex without `#`) (required) | | `datefrom` | timestamp | Start date as Unix timestamp; must be <= `dateto` (required) | | `dateto` | timestamp | End date as Unix timestamp; must be >= `datefrom` (required) | | `meta` | json | JSON-encoded metadata (object) | --- ### Campaigns | Field | Type | Description | |-------|------|-------------| | `ID` | integer | Campaign ID (required) | | `creator` | integer | Creator user ID (defaults to authenticated user on creation) | | `assigneduser` | integer | Assigned user ID | | `creationdate` | timestamp | Creation date as Unix timestamp (defaults to now on creation) (required) | | `lastmodified` | timestamp | Last modification date as Unix timestamp (auto-reset on modification) (required) | | `visibility` | integer | Visibility (`0`=REGULAR, `1`=ARCHIVED, `2`=DELETED) (required) | | `name` | text | Name (required) | | `datefrom` | timestamp | Start date as Unix timestamp; must be <= `dateto` (required) | | `dateto` | timestamp | End date as Unix timestamp; must be >= `datefrom` | | `status` | integer | Status (`0`=DRAFT, `1`=NOTSTARTED, `2`=AWAITINGAPPROVAL, `3`=APPROVED, `4`=DISMISSED, `5`=ACTIVE, `6`=INACTIVE, `7`=INEVALUATION, `8`=CANCELLED, `9`=CLOSED) (required) | | `description` | text | Detailed general description (required) | **GIN-indexed fields** (use `filters` plural): `assigneduser`, `name` --- ### Contracts | Field | Type | Description | |-------|------|-------------| | `ID` | integer | Contract ID (required) | | `creator` | integer | Creator user ID (defaults to authenticated user on creation) | | `assigneduser` | integer | Assigned user ID | | `creationdate` | timestamp | Creation date as Unix timestamp (defaults to now on creation) (required) | | `lastmodified` | timestamp | Last modification date as Unix timestamp (auto-reset on modification) (required) | | `account` | integer | Account ID | | `visibility` | integer | Visibility (`0`=REGULAR, `1`=ARCHIVED, `2`=DELETED) (required) | | `name` | text | Name (required) | | `contractnum` | text | Contract number (required) | | `date` | timestamp | Designated date as Unix timestamp (defaults to now on creation) (required) | | `datefrom` | timestamp | Start date as Unix timestamp; must be <= `dateto` | | `dateto` | timestamp | End date as Unix timestamp; must be >= `datefrom` | | `datecancel` | timestamp | Cancellation date as Unix timestamp | | `status` | integer | Status (`0`=DRAFT, `1`=AWAITINGAPPROVAL, `2`=APPROVED, `3`=DISMISSED, `4`=ACTIVE, `5`=INACTIVE, `6`=EXPIRED, `7`=CANCELLED, `8`=CLOSED) (required) | | `currency` | varchar | Currency code (ISO 4217) (required) | | `exchangerate` | float | Exchange rate as multiple of system currency unit (required) | | `billingcycle` | integer | Billing cycle in months | | `lastbilling` | timestamp | Last billing date as Unix timestamp | | `description` | text | Detailed general description (required) | | `contractitems` | json | JSON-encoded contract items (array) | | `billingitems` | json | JSON-encoded billing items (array) | | `procurementitems` | json | JSON-encoded procurement items (array) | | `autobilling` | json | JSON-encoded auto-billing data; only if `billingcycle` is set | **GIN-indexed fields** (use `filters` plural): `assigneduser`, `status`, `contractnum`, `name` --- ### Files | Field | Type | Description | |-------|------|-------------| | `ID` | timestamp | File ID (required) | | `creator` | integer | Creator user ID (defaults to authenticated user on creation) | | `creationdate` | timestamp | Creation date as Unix timestamp (defaults to now on creation) (required) | | `lastmodified` | timestamp | Last modification date as Unix timestamp (auto-reset on modification) (required) | | `record` | timestamp | Record ID (parent dependency); mutually exclusive with `comment` | | `comment` | timestamp | Comment ID (parent dependency); mutually exclusive with `record` | | `binfile` | integer | Binary file ID; read-only (not for PUT or PATCH) | | `filename` | text | Filename (required) | | `mimetype` | text | MIME type (required) | **GIN-indexed fields** (use `filters` plural): `filename` --- ### Payments | Field | Type | Description | |-------|------|-------------| | `ID` | integer | Payment ID (required) | | `creator` | integer | Creator user ID (defaults to authenticated user on creation) | | `assigneduser` | integer | Assigned user ID | | `creationdate` | timestamp | Creation date as Unix timestamp (defaults to now on creation) (required) | | `lastmodified` | timestamp | Last modification date as Unix timestamp (auto-reset on modification) (required) | | `ledger` | integer | Ledger ID | | `transaction` | integer | Transaction ID; mutually exclusive with `account` | | `account` | integer | Account ID; mutually exclusive with `transaction` | | `date` | timestamp | Designated date as Unix timestamp (defaults to now on creation) (required) | | `subject` | text | Subject (e.g. bank statement or reference number) (required) | | `status` | integer | Status (`0`=DRAFT, `1`=COMPLETED, `2`=CANCELLED, `3`=BOOKED) (required) | | `amount` | float | Amount (monetary) (required) | | `autoadvance` | integer | Auto-advance to next transaction (required) | | `description` | text | Detailed general description (required) | **GIN-indexed fields** (use `filters` plural): `assigneduser`, `ledger`, `subject` --- ### Users | Field | Type | Description | |-------|------|-------------| | `ID` | integer | User ID (required) | | `creator` | integer | Creator user ID (defaults to authenticated user on creation) | | `creationdate` | timestamp | Creation date as Unix timestamp (defaults to now on creation) (required) | | `lastmodified` | timestamp | Last modification date as Unix timestamp (auto-reset on modification) (required) | | `contact` | integer | Contact ID | | `activity` | integer | Activity (`0`=ACTIVE, `1`=DEACTIVATED, `2`=DELETED) (required) | | `name` | text | Username (case-insensitively unique) (required) | | `email` | text | System e-mail address (case-insensitively unique) (required) | | `nopublic` | integer | Deny access to public data (required) | | `apionly` | integer | Restricted to API access, no regular login (required) | | `expdate` | timestamp | Expiry date as Unix timestamp | | `description` | text | Description (required) | **GIN-indexed fields** (use `filters` plural): `email`, `name` --- ### Groups | Field | Type | Description | |-------|------|-------------| | `ID` | integer | Group ID (required) | | `creator` | integer | Creator user ID (defaults to authenticated user on creation) | | `creationdate` | timestamp | Creation date as Unix timestamp (defaults to now on creation) (required) | | `lastmodified` | timestamp | Last modification date as Unix timestamp (auto-reset on modification) (required) | | `leader` | integer | Leader user ID | | `activity` | integer | Activity (`0`=ACTIVE, `1`=DEACTIVATED, `2`=DELETED) (required) | | `name` | text | Name (case-insensitively unique) (required) | | `description` | text | Description (required) | **GIN-indexed fields** (use `filters` plural): `name` --- Canonical: https://www.zeyos.com/guide/cli/commands # Commands Reference Complete reference for every CLI command, with options and examples. ## Global Options These options work with any command: | Option | Description | |--------|-------------| | `--json` | Output as formatted JSON | | `--yaml` | Output as YAML | | `--profile ` | Use a named credential profile for this command | | `--dry-run` | Print the request route and body without sending it | | `--timeout ` | Per-request timeout (default 30) | | `--no-validate` | Skip schema validation of fields and filters | | `--no-color` | Disable ANSI color output | | `-h`, `--help` | Show help for a command | | `-v`, `--version` | Print the CLI version | This table is maintained by hand. For the authoritative, always-current list — including which flags each individual command accepts — run `zeyos commands --json`. Global options may be placed before or after the command name: ```bash zeyos --profile dev whoami zeyos whoami --profile dev ``` --- ## login Authenticate with a ZeyOS instance via OAuth 2.0 authorization code flow. ``` zeyos login [options] ``` | Option | Description | |--------|-------------| | `--base-url ` | ZeyOS platform URL | | `--client-id ` | OAuth application ID | | `--secret ` | OAuth application secret | | `--scope ` | OAuth scope | | `--port ` | Local callback port (default: `9005`) | | `--global` | Save credentials to global config | | `--force` | Overwrite existing credentials | | `--clean` | Discard saved config and re-prompt for everything | | `--manual` | Don't open browser; paste code manually | **Examples:** ```bash # Interactive login (prompts for missing values) zeyos login # Pre-fill connection values; the OAuth browser/code step still runs zeyos login --base-url https://cloud.zeyos.com/demo \ --client-id myapp --secret "$ZEYOS_CLIENT_SECRET" # Start fresh, ignore any saved credentials zeyos login --clean # Manual mode (useful in SSH / headless environments) zeyos login --manual ``` :::info When values are not provided as flags, the CLI prompts interactively for the ZeyOS URL, application ID, and application secret. The secret prompt does not echo input. For CI or fully unattended agents, provide `ZEYOS_BASE_URL`, `ZEYOS_TOKEN`, and optionally `ZEYOS_REFRESH_TOKEN`, `ZEYOS_CLIENT_ID`, and `ZEYOS_CLIENT_SECRET` through the environment instead of running `zeyos login`. ::: --- ## logout Revoke the stored token and clear saved credentials. ``` zeyos logout [--global] ``` | Option | Description | |--------|-------------| | `--global` | Clear global credentials instead of local | **Examples:** ```bash zeyos logout # Clear local .zeyos/auth.json credentials zeyos logout --global # Clear ~/.config/zeyos/credentials.json tokens ``` --- ## whoami Show information about the currently authenticated user. ``` zeyos whoami [--json|--yaml] ``` **Examples:** ```bash zeyos whoami # Table output zeyos whoami --json zeyos whoami --show-token --json # explicitly include the current access token ``` If the stored refresh token is invalid or expired, interactive text-mode `whoami` prints the credential source and asks whether to re-authenticate immediately. In `--json`, `--yaml`, or non-interactive runs, it exits with the same diagnostic and prints the matching `zeyos login --force` command instead of prompting. --- ## profile Manage named credential profiles and switch between ZeyOS instances. See [Configuration → Profiles](https://www.zeyos.com/guide/cli/configuration#profiles) for the full model. ``` zeyos profile [options] ``` | Command | Description | |---------|-------------| | `profile list` | List all profiles; the active one is marked `*`, with token status | | `profile current` | Show which profile resolves right now, and why (flag/env/pin/active) | | `profile use ` | Make `` the active profile (global) | | `profile use --local` | Pin `` to the current project (`.zeyos/profile`) | | `profile add [] [opts]` | Create/update a profile; prompts for missing values when run without options | | `profile remove ` | Delete a profile | **Examples:** ```bash zeyos profile add # prompt for name, URL, app ID, secret zeyos profile add dev --base-url https://zeyos.example.com/dev zeyos profile add prod --from-current # snapshot current credentials zeyos login --profile prod # authenticate into & activate a profile zeyos profile use dev # switch active profile zeyos profile use prod --local # pin to this project zeyos list tickets --profile dev # one-off override on any command ``` --- ## list Query and list records for a resource with filtering, sorting, and pagination. ``` zeyos list [options] ``` Run `zeyos list` with no entity to see everything you can query, grouped by business area: ```bash zeyos list # grouped overview zeyos resources # the same overview zeyos resources --json # flat array, with group / description / transactionType ``` | Option | Description | |--------|-------------| | `--fields ` | Field selection — comma-separated, JSON object, or JSON array (see below) | | `--filter ` | Filter criteria — JSON object | | `--filter-file ` | Read filter criteria from a JSON file | | `--search ` | Full-text search (sent as the API `query` parameter) | | `--preset ` | Apply a resource business preset; an explicit `--filter` is merged on top | | `--sort ` | Sort fields, comma-separated (prefix `+` asc, `-` desc) | | `--limit ` | Maximum records to return (default: `50`) | | `--offset ` | Skip the first n records | | `--expand ` | Expand JSON/binary columns (e.g. binfile, items) | | `--extdata` | Include extended data fields | | `--json` | JSON output | | `--yaml` | YAML output | | `--dry-run` | Print the request without sending it | | `--no-validate` | Skip the CLI's default schema validation | **Fields format:** The `--fields` option supports three formats: | Format | Example | |--------|---------| | Comma-separated | `--fields ID,name,status` | | JSON object (with aliases) | `--fields '{"Name":"lastname","City":"contact.city"}'` | | JSON array | `--fields '["ID","name","status"]'` | **Filter compatibility:** The CLI normalizes common agent- and ORM-generated filter forms before sending the request: - arrays become `IN` — `{"status":[1,3]}` - Mongo style — `$lt $lte $gt $gte $ne $in $nin $like $ilike $regex $eq $between` - suffix keys — `lastname__startswith`, `lastname__endswith`, `lastname__contains`, `lastname__like`, `ID__gt`, `status__in`, `status__nin`, `duedate__between` - the single-underscore form where unambiguous — `status_neq`, `ID_gt` (a field that genuinely contains an underscore, like `sender_email`, is never split) - **composite** — `{"$or":[…]}` and `{"$and":[…]}` become ZeyOS numbered logical groups (`{"0":["OR",…]}`) On `accounts`, `name` in filters or `--fields` resolves to `lastname`. `startswith` / `endswith` / `contains` take **literal** text, so `%` and `_` in your value are escaped. `like` / `ilike` take a **pattern** where `%` is a wildcard. An operator the CLI cannot translate is rejected before the request with the supported set in the error (exit `2`), rather than being forwarded for the server to refuse opaquely. ZeyOS has no IS NULL filter, so `field__isnull` is refused with the client-side alternative. Run `zeyos describe ` for the authoritative operator list — it is printed in a `filter operators` section, and carried under `filterOperators` in `--json`. Before sending, data commands validate filter, selected, sorted, and written fields against the generated schema. Unknown fields fail fast with a suggestion and the valid-field list; every join hop and terminal field must exist. Individual `extdata.*` keys require the [instance-specific snapshot](https://www.zeyos.com/guide/cli/configuration#custom-field-validation); missing metadata fails explicitly. Use `--no-validate` only for a separately reviewed unsupported contract, never merely to silence a guessed field. If a filtered or searched list is empty, the CLI prints a stderr hint pointing to `describe` and `find`, including in JSON and YAML modes. **Transaction entities:** `transactions` is a single table holding twelve different business documents, separated only by the `type` column. Each type is exposed as its own entity, so you never filter on `type` by hand: | Entity | `transactions.type` | Meaning | |--------|--------------------|---------| | `billing_quotes` | 0 | Quotes issued to customers | | `billing_orders` | 1 | Sales orders | | `billing_deliveries` | 2 | Delivery notes to customers | | `billing_invoices` | 3 | Invoices issued to customers | | `billing_credits` | 4 | Credit notes issued to customers | | `procurement_requests` | 5 | Purchase requisitions | | `procurement_orders` | 6 | Purchase orders to suppliers | | `procurement_deliveries` | 7 | Goods received from suppliers | | `procurement_invoices` | 8 | Supplier invoices (bills) | | `procurement_credits` | 9 | Credit notes from suppliers | | `production_fabrications` | 10 | Production/assembly runs | | `production_disassemblies` | 11 | Disassembly runs | Singular, plural and hyphenated spellings all resolve (`billing_invoice`, `billing_invoices`, `billing-invoices`). Unqualified sales vocabulary maps to the billing side — `invoices`, `orders`, `quotes`, `credits`, `deliveries` — while `bills`, `po` and `purchase_orders` map to procurement. Use `transactions` to query every type at once. The type binding is applied beneath any `--preset` and any `--filter`, and is set automatically on `create`: ```bash zeyos list billing_invoices --preset overdue zeyos create billing_invoices --account 42 --currency EUR # sends type: 3 ``` Invoice and credit entities accept these presets: | Preset | Filter | |--------|--------| | `open` | Excludes cancelled, closed, paid, overpaid and processed status variants | | `overdue` | Open, with `duedate` before now | | `paid` | Status `20` or `21` | | `draft` / `booked` / `cancelled` | Status `0` / `1` / `3` | The older presets on the `transactions` entity itself (`quotes`, `orders`, `invoices`, `credits`, `open-invoices`, `overdue-invoices`, `paid-invoices`) still work. **Examples:** ```bash # List tickets with default configured fields zeyos list tickets # Custom filters zeyos list tickets --filter '{"status":1,"priority":3}' # Custom filters from a file zeyos list tickets --filter-file ./filters/open-tickets.json # Specify fields with aliases zeyos list accounts --fields '{"Name":"lastname","City":"contact.city"}' # Comma-separated fields zeyos list tickets --fields ID,name,status,priority # Sort by multiple columns zeyos list tickets --sort "+name,-lastmodified" # Pagination zeyos list tickets --limit 10 --offset 20 # Include extended data zeyos list tickets --extdata # JSON output for scripting zeyos list tickets --json | jq length ``` :::tip Pagination Info When results fill the page limit, the CLI makes a second API call to get the total count and displays: ``` Showing 1–10 of 47 (--offset 10 for next page) ``` ::: --- ## find Resolve a human label to records before filtering another resource by foreign-key ID. ```bash zeyos find accounts "Zfx Lyon" zeyos find projects "Website" --limit 5 --json ``` `find` sends the text through the API's full-text `query` parameter and returns ID plus the resource's display fields. No matches is a successful result reported on stderr. --- ## count Count records for a resource, with optional filtering. Returns a plain number by default. ``` zeyos count [options] ``` | Option | Description | |--------|-------------| | `--filter ` | Filter criteria — JSON object | | `--filter-file ` | Read filter criteria from a JSON file | | `--search ` | Full-text search | | `--preset ` | Apply a resource business preset before the explicit filter | | `--json` | Output as `{"count": N}` | | `--yaml` | YAML output | | `--dry-run` | Print the request without sending it | | `--no-validate` | Skip schema validation | **Examples:** ```bash # Total tickets zeyos count tickets # → 47 # Filtered count zeyos count tickets --filter '{"status":1}' # → 12 # Filtered count using a JSON file zeyos count tickets --filter-file ./filters/open-tickets.json # JSON output for scripting zeyos count accounts --json # → {"count": 156} ``` --- ## sum Sum a numeric field across matching records. The CLI pages internally, so this is the short path for simple totals that would otherwise require `list` plus a script. ``` zeyos sum [options] ``` | Option | Description | |--------|-------------| | `--filter ` | Filter criteria — JSON object. Arrays normalize to `IN`, e.g. `{"status":[1,3]}` | | `--filter-file ` | Read filter criteria from a JSON file | | `--preset ` | Apply a resource business preset before the explicit filter | | `--page-size ` | Records per API page (default: 50) | | `--limit ` | Maximum records to inspect | | `--offset ` | Initial offset | | `--json` | Output as `{"sum": N, "count": N, "field": "..."}` | | `--yaml` | YAML output | | `--dry-run` | Print the first request without sending it | | `--no-validate` | Skip schema validation | **Examples:** ```bash # Completed or booked effort minutes zeyos sum actionsteps effort --filter '{"status":[1,3]}' # Invoice net amount as JSON zeyos sum transactions netamount --filter '{"type":3}' --json ``` --- ## get Fetch a single record by ID. ``` zeyos get [options] ``` | Option | Description | |--------|-------------| | `--fields ` | Field selection — comma-separated, JSON object, or JSON array | | `--extdata` | Include extended data fields | | `--tags` | Include tags | | `--all` | Fetch all data (extdata + tags + all fields) | | `--json` | JSON output | | `--yaml` | YAML output | **Aliases:** `show` **Examples:** ```bash # Get a ticket with configured fields zeyos get ticket 42 # Include extended data zeyos get ticket 42 --extdata # Include tags zeyos get ticket 42 --tags # Include both extdata and tags zeyos get ticket 42 --extdata --tags # Get everything — all fields, extdata, and tags zeyos get ticket 42 --all # JSON output zeyos get account 15 --json # Using the alias zeyos show ticket 42 ``` :::info Date Formatting Date fields like `duedate`, `lastmodified`, and `creationdate` are automatically formatted as `YYYY-MM-DD` in table/record output. Raw Unix timestamps are preserved in JSON and YAML output. The format is configurable via `dateFormat` in your auth config file. ::: --- ## create Create a new record. Fields can be provided as a JSON blob or as individual flags. ``` zeyos create [--data ] [--field value ...] ``` | Option | Description | |--------|-------------| | `--data ` | Complete record as a JSON string | | `--data-file ` | Read the complete record from a JSON file | | `-- ` | Set individual field (any unknown flag becomes a field) | **Examples:** ```bash # Using --data JSON zeyos create ticket --data '{"name":"Fix login bug","status":0,"priority":3}' # Using a JSON file zeyos create ticket --data-file ./ticket.json # Using individual field flags zeyos create ticket --name "Fix login bug" --status 0 --priority 3 # Create an account (accounts use --lastname, not --name) zeyos create account --lastname "ACME Corp" --currency EUR --visibility 0 # JSON output (returns the created record) zeyos create ticket --name "New feature" --json ``` :::info Type Coercion Field values are coerced using the column's type from the schema, not from how the value looks: - Numeric columns (`smallint`, `integer`, `numeric`, …) take numbers, so `--status 0` sends the integer `0`, not `"0"`. - Boolean columns accept `true` / `false`. - **Text columns keep their string value**, so `--customernum 00123` sends `"00123"` and `--phone "+4930123456"` keeps its `+`. Identifiers that merely look numeric are not damaged. - `-- null` always sends JSON `null`. For a field the schema doesn't know (a custom column, or with `--no-validate`), the value is converted only when it is a plain decimal number that round-trips exactly — a leading zero, a leading `+`, an exponent, hex, or a value beyond the safe integer range all stay text. :::tip To control types exactly, pass `--data` with explicit JSON: `--data '{"zip":"01067","amount":19.99}'`. ::: ::: --- ## update Update an existing record by ID. Same input modes as `create`. ``` zeyos update [--data ] [--field value ...] ``` **Aliases:** `edit` **Examples:** ```bash # Using --data JSON zeyos update ticket 42 --data '{"status":4}' # Using a JSON file zeyos update ticket 42 --data-file ./ticket-update.json # Using field flags zeyos update ticket 42 --status 4 --priority 2 # Update account name (accounts use --lastname, not --name) zeyos update account 15 --lastname "ACME Corporation" ``` --- ## delete Delete a record by ID. Prompts for confirmation by default. ``` zeyos delete [--force] ``` | Option | Description | |--------|-------------| | `--force` | Skip confirmation prompt | :::warning `--force` is a flag and takes no value — `--force=false` is a usage error, not a way to turn it off. Omit the flag to keep the confirmation. In a script or CI, stdin is usually closed, so the confirmation cannot be answered: the delete is **aborted** and the command exits `5`. Pass `--force` for non-interactive deletes. ::: **Aliases:** `rm`, `remove` **Examples:** ```bash # Interactive confirmation zeyos delete ticket 42 # Skip confirmation zeyos delete ticket 42 --force # Using aliases zeyos rm ticket 42 zeyos remove ticket 42 ``` --- ## resources List all curated CLI resources and their operations. This is the authoritative boundary for what the CLI supports directly. ``` zeyos resources ``` Shows a table of all CLI-supported resource types and available operations. Operational workflows can use `actionstep` / `actionsteps` / `time-entries` for follow-ups and effort records. Read-only platform schema definitions are available as `customfield` / `customfields` with `list`, `get`, and therefore `count` support. --- ## describe Show a resource's schema — fields, types, foreign keys and enum values — from the generated schema. Runs **offline** (no login required), so an agent can discover the data model before making any call. ``` # Field/type/enum/foreign-key listing for a resource zeyos describe tickets # Machine-readable schema zeyos describe accounts --json ``` Foreign keys are shown as `→ `, and enum fields list their valid values (e.g. `status` → `0=NOTSTARTED 1=AWAITINGACCEPTANCE …`). The operations available for the resource are listed at the bottom. --- ## doctor Check local readiness for coding agents. This runs offline and never prints tokens or client secrets. It proves that configuration is in place, **not** that a server accepts it. ```bash zeyos doctor agent zeyos doctor agent --json ``` The report covers: | Section | Checks | Affects `ok` and exit code | |---------|--------|----------------------------| | `cli`, `connection`, `auth`, `resources` | CLI version, base URL and instance, whether credentials are present and usable, and whether the resource registry loads | Yes | | `customFields` | Whether `ZEYOS_CUSTOM_FIELDS_FILE` is set, absolute, readable and valid, which instance it is bound to compared with the configured one, and the file's age | Yes, when set and broken or mismatched, because that blocks every request. Unset is fine | | `skills` | Installed skill bundles in the local and global skills directories of each agent | No, advisory | | `project` | `zeyos.app.json` in the current directory, and the `zeysdk` version if the binary is installed | No, informational | | `verification` | What was checked offline, and what was not checked live | Reports `offlineConfiguration.ok` | `verification.liveServer.performed` is always `false`. Doctor does not check that the instance is reachable, that tokens are accepted, what the user may do, or whether a custom-field snapshot still matches the instance. Use `zeyos whoami` for a read-only authenticated check. `customFields.status` is one of `not_set`, `ok`, `unbound` (no usable configured instance to compare), `relative_path`, `unreadable`, `invalid` or `instance_mismatch`. `ageDays` comes from the file's modification time, because a snapshot cannot record its own review date. It is a hint, not proof of freshness. For skills, doctor reads the `.zeyos-skills.json` lockfile in each directory and hashes every tracked file. Each entry in `skills.roots` has one of these statuses: | Status | Meaning | |--------|---------| | `ok` | Files match the lockfile, and the bundle matches the installed `@zeyos/client` | | `outdated` | Files match, but the bundle version differs from the installed client | | `drift` | Tracked files are missing, modified or cannot be verified. `files` gives the counts | | `legacy` | ZeyOS skill folders exist with no lockfile, so they cannot be verified | | `invalid` | The lockfile is unreadable, malformed, an unsupported version, or lists a path outside the skills root | | `absent` | No ZeyOS skills here. Human output hides these | Skills from other vendors in the same directory are ignored. Skill problems appear in `nextSteps` but do not fail the check, so a CI job that does not use skills is not blocked. The only `zeysdk` command doctor runs is `zeysdk version`. --- ## skills Discover, install and verify the bundled ZeyOS agent skill packs for any coding agent (Claude Code, Codex, opencode, Factory Droid, pi, …). The `zeyos` skill is the router that classifies each task. ``` # List the bundled skills zeyos skills list # Print a skill's instructions zeyos skills show zeyos-work-management # Install — interactive: pick a coding agent, then local vs. global zeyos skills install # Install non-interactively with flags zeyos skills install --target claude --global # all projects zeyos skills install --target opencode --local # this project only zeyos skills install zeyos-billing-insights -y # one skill, defaults zeyos skills install --dir ./vendor/skills # any directory # Preview an install or upgrade without writing anything zeyos skills install --target claude --global --preview # Compare an installed tree with its lockfile zeyos skills verify --target claude --global ``` Run bare, `install` prints the ZeyOS banner and prompts for **(a)** which coding agent to target and **(b)** whether to install for this project or globally for every project. Pass `--target` and/or `--global`/`--local` to skip the matching prompt; pass `-y`/`--yes` (or pipe non-interactively) to skip all prompts and use flags plus sensible defaults. Options for `install`: | Option | Description | |--------|-------------| | `--target ` | Coding agent: `claude`, `codex`, `opencode`, `droid`, `pi`, `agents` (prompted when omitted; otherwise auto-detected) | | `--global` | Install into the agent's home directory (all projects) | | `--local` | Install into the current project (default) | | `--dir ` | Install into an explicit directory (overrides `--target`) | | `--dry-run`, `--preview` | Print the plan and the bundle version change; write nothing | | `--force` | Overwrite locally modified files and existing files missing from the lockfile. It never deletes a modified file, changes a symlink, or touches paths outside the bundle's own directories | | `-y`, `--yes` | Skip prompts and use flags / sensible defaults | | `--no-logo` | Don't print the ZeyOS banner | | `--json` / `--yaml` | Print a machine-readable install summary (also silences the banner) | Per-agent skill directories: | Agent | `--local` | `--global` | |-------|-----------|------------| | `claude` | `.claude/skills/` | `~/.claude/skills/` | | `codex` | `.codex/skills/` | `~/.codex/skills/` | | `opencode` | `.opencode/skills/` | `~/.config/opencode/skills/` | | `droid` | `.factory/skills/` | `~/.factory/skills/` | | `pi` | `.pi/skills/` | `~/.pi/agent/skills/` | | `agents` | `.agents/skills/` | `~/.agents/skills/` | Skills are installed into `//` with everything they require. `install zeyos` includes `zeyos-dev` and all domain skills; a single domain skill brings `shared/` and the OKF bundle (`shared/okf/`). Links are rewritten so the tree needs no source checkout. `/.zeyos-skills.json` records the bundle version, target, skills and a SHA-256 of each installed file. Installing again upgrades every skill it lists: | Plan status | Meaning | |-------------|---------| | `added`, `updated`, `unchanged` | Normal install and upgrade results | | `removed` | An unmodified tracked file the bundle no longer ships, inside its own directories | | `locally-modified-kept` | Changed since installation; not overwritten | | `locally-modified-overwritten-with-force` | Changed since installation; replaced by `--force`. A modified file the bundle dropped is kept, even with `--force` | | `unknown-kept` | Differs from the bundle and is not in the lockfile, as after an older install; not overwritten | | `unknown-overwritten-with-force` | The same, replaced by `--force` | | `symlink-skipped` | The path or a directory above it is a symlink. It is reported and left untouched, even with `--force`. Remove the link to install there | | `outside-bundle-kept` | The lockfile lists a path outside the bundle's own directories. It stays on disk and is no longer tracked | | `conflict` | A file where a directory belongs, the reverse, or an unreadable file. The install stops before writing anything; `--preview` exits `1` | Files you add yourself are never touched. `zeyos skills verify` accepts `--dir`, `--target`, `--global` and `--local`, and never prompts: | Exit | Meaning | |------|---------| | `0` | Every file is present. Modified files and symlinks are listed, and `ok` is then `false` in `--json` output | | `1` | Files are missing, files from an older install were kept, or the lockfile is damaged or tracks nothing | | `4` | No lockfile in that directory | --- ## okf Work with the [Open Knowledge Format](https://www.zeyos.com/guide/okf/overview) bundle that ships with the client — a portable Markdown description of the ZeyOS data model (one concept per API-backed entity) plus curated metrics, playbooks, and query concepts. ```bash zeyos okf list # list concepts (type, id, title); --json for automation zeyos okf show tickets # print a concept (bare resource name, or entities/tickets) zeyos okf check # validate OKF v0.1 conformance (exit non-zero on error) zeyos okf export --out ./okf # copy the shipped bundle into a directory zeyos okf build --out ./okf # synthesize a bundle from the client's schema ``` | Subcommand | What it does | |-----------|--------------| | `list` | List the concepts in the bundle (`--json`/`--yaml` supported). | | `show ` | Print one concept doc. Accepts a bare resource (`tickets`) or full id (`entities/tickets`). | | `check` | Validate the bundle for OKF v0.1 conformance; exits non-zero on any error (CI-friendly). | | `export` | Copy the shipped `okf/` bundle into `--out` (default `./okf`); `--force` to overwrite. | | `build` | Synthesize a structural bundle from the client's schema into `--out` (default `./okf`). | Options: `--dir ` reads from an explicit bundle directory (`list`/`show`/`check`); `--out ` is the write target (`build`/`export`); `--force` overwrites an existing target. `export` ships the rich curated bundle; `build` is the lighter runtime projection. --- ## Command Aliases | Alias | Equivalent | |-------|-----------| | `show` | `get` | | `edit` | `update` | | `rm` | `delete` | | `remove` | `delete` | | `resource` | `resources` | | `skill` | `skills` | --- ## Exit Codes Every command reports its outcome through the exit code, so scripts and agents can tell failure modes apart without parsing stderr. | Code | Meaning | Typical cause | |------|---------|---------------| | `0` | Success | — | | `1` | Runtime or API failure | The request reached the server and failed | | `2` | Usage error | Unknown command, unknown flag, missing argument, a value passed to a boolean flag | | `3` | Auth error | No credentials, an unknown `--profile`, HTTP 401/403 | | `4` | Not found | The requested record does not exist | | `5` | Aborted | A confirmation prompt was declined, or could not be answered because stdin was closed | ### Machine-readable failures In `--json` or `--yaml` mode a failure writes a structured envelope to **stdout**, so a command that is piped to `jq` still tells you what went wrong. The human message goes to stderr as usual. ```bash $ zeyos list billing_invoces --json ``` ```json { "ok": false, "error": { "code": "unknown_entity", "exitCode": 2, "message": "Unknown entity: \"billing_invoces\". Did you mean \"billing_invoices\"?", "field": "billing_invoces", "suggestion": "billing_invoices", "actions": ["Run 'zeyos list' to see every entity."] } } ``` Successful output is **unchanged** — a list is still a bare array. Tell the two apart by shape: a failure is an object carrying `ok: false`. `error.code` is a stable slug. Current values include `unknown_entity`, `unknown_command`, `unknown_option`, `invalid_option_value`, `invalid_filter`, `unexpected_argument`, `flag_takes_no_value`, `missing_argument`, `auth_required`, `auth_failed`, `not_found`, `timeout`, `network`, and `api_` for a server error. ```bash zeyos get ticket 999999 --json case $? in 0) echo "found" ;; 4) echo "no such ticket" ;; 3) echo "run: zeyos login" ;; *) echo "failed" ;; esac ``` :::note `zeyos doctor agent` exits `3` when the local configuration is not ready, including a broken or mismatched custom-field snapshot, which makes it usable directly as a CI readiness check. It stays offline, so passing does not prove the server is reachable. ::: --- ## Timeouts Requests time out after **30 seconds** by default. Override per command with `--timeout `, or globally with the `ZEYOS_TIMEOUT_MS` environment variable. ```bash zeyos list transactions --preset open-invoices --timeout 120 ``` Read operations (`list`, `count`, `get`, `find`, `sum`) are retried automatically on timeouts and transient network errors. Writes are never retried on a timeout or a `503`, so a create cannot be duplicated by a retry. A `429` is retried for reads and writes, because it means the request was not processed. --- ## Number Formatting Columns the schema types as floating point (`double precision`, `numeric`, `decimal`, `real`, `money`) are rendered with grouped thousands and two decimal places in table and record views, and right-aligned: ``` ID TRANSACTIONNUM DATE STATUS ACCOUNT NETAMOUNT ───── ────────────── ────────── ────── ─────── ──────────── 10932 N.2210.0654-01 2022-11-06 8 8840 17.009,00 99999 R.2401.0001-01 2024-01-01 1 1234 1.234.567,89 ``` Integer columns are deliberately left untouched — IDs, foreign keys, enum codes and Unix timestamps are all integers, and grouping them would be misleading. The separators follow a locale, resolved in this order: 1. `ZEYOS_LOCALE` environment variable 2. `locale` in the resource config file 3. the host's default locale ```bash ZEYOS_LOCALE=de-DE zeyos list billing_invoices # 17.009,00 ZEYOS_LOCALE=en-US zeyos list billing_invoices # 17,009.00 ``` :::info Formatting applies to human-readable output only. `--json` and `--yaml` always emit the raw number, so scripts and agents are unaffected by the terminal's locale. ::: --- ## commands Publish the command graph — every command, its aliases, and the flags it accepts — as data. ``` zeyos commands [--json|--yaml] ``` `zeyos describe` exposes the *data* model; `zeyos commands` exposes the *command* model, so an agent can discover which flags exist without parsing prose help. Runs offline. ```bash zeyos commands --json | jq '.commands[] | select(.name=="list") | .flags' ``` Each entry carries `name`, `aliases`, `flags` (global flags folded in), and `acceptsArbitraryFields` — true for `create`/`update`, which take `-- ` for any column. A top-level `options` map gives each flag's type, so a caller knows which take a value. --- Canonical: https://www.zeyos.com/guide/cli/configuration # Configuration The CLI uses a layered configuration system for credentials, output preferences, and per-resource field display. :::info Coverage Boundary These settings apply to the CLI's curated resource registry. If you need a resource or request shape outside that registry, switch to [`@zeyos/client`](https://www.zeyos.com/guide/javascript-client/getting-started). ::: ## Credential Cascade The CLI first decides **which credential set** is the base, then lets environment credential variables field-override on top. The base is chosen by the first match in this order: | Priority | Source | Location | |----------|--------|----------| | 1 (highest) | `--profile ` flag | named profile (per command) | | 2 | `ZEYOS_PROFILE` env var | named profile | | 3 | Project pin | `.zeyos/profile` (walks up from CWD) | | 4 | Local config file | `.zeyos/auth.json` (walks up from CWD) | | 5 | Global active profile | `~/.config/zeyos/profiles.json` (`active`) | | 6 (lowest) | Global config file | `~/.config/zeyos/credentials.json` | On top of the chosen base, the credential **environment variables** (`ZEYOS_BASE_URL`, `ZEYOS_TOKEN`, …) always override individual fields — so you can keep `baseUrl` and client credentials in a profile while overriding just the token via `ZEYOS_TOKEN` in CI. If you have never created a profile, nothing changes: the CLI falls through to the legacy local `.zeyos/auth.json` and global `credentials.json` exactly as before. ## Profiles Profiles let you store several ZeyOS instances (e.g. `dev`, `prod`, `client-x`) and switch between them without re-running `login`. Each profile is a full credential set (URL, OAuth app, tokens) kept in `~/.config/zeyos/profiles.json`, with one marked **active**. ```bash # Create profiles (connection params now; tokens via login) zeyos profile add # interactive wizard zeyos profile add dev --base-url https://zeyos.example.com/dev zeyos profile add prod --base-url https://cloud.zeyos.com/acme --client-id app --secret "$SECRET" # Authenticate into a profile (also makes it active) zeyos login --profile prod # See and switch profiles zeyos profile list # active marked with *, shows token status zeyos profile use dev # switch the global active profile zeyos profile current # what resolves right now, and why # Per-project: pin a profile so cd-ing into a repo selects its instance cd ~/work/acme && zeyos profile use prod --local # writes ./.zeyos/profile # One-off override on any command (beats env, pin, and active) zeyos whoami --profile dev zeyos list tickets --profile prod ``` `zeyos profile add --from-current` snapshots whatever credentials are in effect (including tokens) into a new profile — handy for adopting an existing `.zeyos/auth.json` setup. Add `.zeyos/profile` to `.gitignore` alongside `.zeyos/auth.json`. ## Environment Variables | Variable | Maps to | Description | |----------|---------|-------------| | `ZEYOS_BASE_URL` | `baseUrl` | ZeyOS platform URL | | `ZEYOS_INSTANCE` | `instance` | ZeyOS instance name | | `ZEYOS_CLIENT_ID` | `clientId` | OAuth application ID | | `ZEYOS_CLIENT_SECRET` | `clientSecret` | OAuth application secret | | `ZEYOS_TOKEN` | `accessToken` | Access token | | `ZEYOS_REFRESH_TOKEN` | `refreshToken` | Refresh token | **Example:** ```bash # Use environment variables for CI/CD pipelines export ZEYOS_BASE_URL="https://cloud.zeyos.com/demo" export ZEYOS_TOKEN="your-access-token" zeyos list tickets ``` If you want the CLI to auto-refresh expired access tokens, also provide `ZEYOS_CLIENT_ID`, `ZEYOS_CLIENT_SECRET`, and a refresh token via config or environment variables. ## Local Config File The CLI stores credentials in `.zeyos/auth.json`, located in your project directory. When resolving config, the CLI walks up from the current working directory (like Git does for `.gitconfig`) until it finds a `.zeyos/auth.json` file. **File format:** ```json { "baseUrl": "https://cloud.zeyos.com/demo", "clientId": "myapp", "clientSecret": "...", "accessToken": "...", "refreshToken": "...", "expiresAt": 1735568880, "refreshTokenExpiresAt": 1738160880, "dateFormat": "YYYY-MM-DD" } ``` This file is created automatically when you run `zeyos login`. `expiresAt` and `refreshTokenExpiresAt` are stored as Unix timestamps in **seconds**. :::warning Always add `.zeyos/auth.json` to your `.gitignore` — it contains access tokens and secrets: ```gitignore .zeyos/auth.json ``` ::: ## Global Config File For credentials shared across all projects, use the global config: ```bash zeyos login --global ``` This saves to `~/.config/zeyos/credentials.json` (same format as the local file). Local config values override global values field-by-field, so a project can override only the fields it needs. ## Resource Field Configuration Customize which fields are displayed for each resource type. The CLI supports per-resource configs that control the `list` and `get` output. ### Config Cascade Resource configs are also resolved in priority order: | Priority | Location | Purpose | |----------|----------|---------| | 1 (highest) | `.zeyos/api/.json` | Project-specific overrides | | 2 | `~/.zeyos/api/.json` | User-wide preferences | | 3 (lowest) | `cli/config/.json` | Shipped defaults | ### Config Format ```json { "list": { "fields": { "ID": "ID", "Name": "lastname", "City": "contact.city", "Country": "contact.country", "Agent": "assigneduser.name", "Modified": "lastmodified" } }, "get": { "fields": [ "ID", "name", "status", "priority", "description", "duedate", "lastmodified" ], "params": { "extdata": 1, "tags": 1 } } } ``` **`list.fields`** — An object mapping display aliases to API field paths. Keys become column headers; values are the actual API fields (supports dot-notation for joins). **`get.fields`** — An array of field names to display in the detail view. Use `--all` to override and show everything. **`get.params`** — An object of query parameters to include by default. These are sent as URL query parameters (e.g. `?extdata=1&tags=1`) and control which additional data sections are returned. Common params: `extdata` (extended data fields), `tags` (record tags). :::note The `--extdata` and `--tags` CLI flags override the corresponding values in `get.params`. For example, running `zeyos get ticket 42` with `"params": {"extdata": 1}` in the config is equivalent to running `zeyos get ticket 42 --extdata`. ::: ### Shipped Defaults The CLI ships with configs for commonly used resources: | File | Resource | |------|----------| | `cli/config/ticket.json` | Tickets | | `cli/config/account.json` | Accounts | | `cli/config/task.json` | Tasks | | `cli/config/project.json` | Projects | | `cli/config/item.json` | Items | Resources without a config file return all fields from the API. :::tip Customizing for Your Project Create `.zeyos/api/ticket.json` in your project to override the default field display: ```bash mkdir -p .zeyos/api ``` ```json title=".zeyos/api/ticket.json" { "list": { "fields": { "ID": "ID", "Title": "name", "Client": "account.lastname", "Sprint": "extdata.sprint", "Due": "duedate" } } } ``` ::: ## Date Formatting Unix timestamps in table and record output are automatically formatted as human-readable dates. Configure the format in your auth config file: ```json title=".zeyos/auth.json" { "baseUrl": "https://cloud.zeyos.com/demo", "dateFormat": "YYYY-MM-DD HH:mm" } ``` **Available tokens:** | Token | Output | Example | |-------|--------|---------| | `YYYY` | 4-digit year | `2026` | | `MM` | 2-digit month | `03` | | `DD` | 2-digit day | `02` | | `HH` | 2-digit hour (24h) | `14` | | `mm` | 2-digit minute | `30` | | `ss` | 2-digit second | `00` | **Default format:** `YYYY-MM-DD` Date formatting only affects table and record output. JSON and YAML output always preserves raw Unix timestamps for programmatic use. **Auto-detected date fields:** `duedate`, `lastmodified`, `creationdate`, `created`, `date`, `startdate`, `enddate`, and any field name ending in `date` or `modified`. ## Output Formats | Flag | Format | Best For | |------|--------|----------| | *(none)* | Formatted table | Human reading in the terminal | | `--json` | Pretty-printed JSON | Scripting, piping to `jq`, APIs | | `--yaml` | YAML | Config files, readability | **Table output** is the default — it shows aligned columns with optional date formatting and pagination info. **JSON output** preserves all raw data exactly as returned by the API, including numeric timestamps and null values. **YAML output** is similar to JSON but more readable for nested structures. ```bash # Compare the outputs zeyos get ticket 42 # Table with formatted dates zeyos get ticket 42 --json # Raw JSON with Unix timestamps zeyos get ticket 42 --yaml # YAML with readable structure ``` ## Custom-field validation `ZEYOS_CUSTOM_FIELDS_FILE` is an **absolute path** to a UTF-8 JSON snapshot with exactly these top-level properties: ```json { "platform": "https://cloud.zeyos.com/your-instance", "customFields": { "accounts": { "region": { "type": "string", "enum": ["EMEA", "APAC"] } }, "tickets": {} } } ``` This is an illustrative contract; replace it with reviewed definitions from your instance. Keep private snapshots outside tracked files and omit credentials and record values. Use canonical API resource names. Populate keys from active `customfields` definitions: `context` identifies the owner, `source: 0` selects EXTDATA, and `reference` is the key. Verify pagination, visibility and application-defined keys before treating the map as complete. Display `type` is not a JSON type; specify constraints only when verified. See [the custom-field contract](https://www.zeyos.com/guide/javascript-client/making-requests#custom-field-contracts). ```bash export ZEYOS_CUSTOM_FIELDS_FILE=/absolute/private/path/custom-fields.json zeyos describe accounts --json zeyos list accounts --fields 'ID,extdata.region' --limit 5 --dry-run --json ``` The loader checks the snapshot's full instance URL against the resolved request target, including the instance path; trailing slashes are normalized. A mismatch blocks dispatch. Malformed metadata is an error, never silently ignored. Offline `describe` requires no login and displays `customFieldSource.platform` with `live: false`; it describes the selected snapshot, not a live tenant. Confirm that provenance against the intended profile before using it. `zeyos doctor agent` checks all of this at once: whether the variable is set, absolute and readable, whether the snapshot is valid, and whether its instance matches the configured one. A broken or mismatched snapshot exits `3`, because it would block every request. Doctor also reports the file's age from its modification time. The format has no field for a review date, and extra top-level properties are rejected, so record review details elsewhere. A missing resource map means metadata is unavailable; `{}` means known empty. Both reject unverified keys, with different diagnostics. CLI and MCP reload the snapshot per command/tool call, so refresh the file after configuration changes. There is no automatic discovery or freshness guarantee. Reading all extdata with `--extdata` does not require a key allowlist; referencing or writing individual keys does. These stricter checks require client/CLI 1.0.0 or later. Deploy the matching client and instructions together; older releases may accept these names unchecked. --- Canonical: https://www.zeyos.com/guide/cli/getting-started # CLI Getting Started The ZeyOS CLI gives you fast, scriptable access to the ZeyOS REST API from your terminal. It is the default entry point for coding agents and shell automation against the CLI's curated resource registry. ## Installation :::info Requirements Node.js 18.3+ is required. The CLI has zero external dependencies beyond the bundled `@zeyos/client` package. ::: ### Published package (recommended) Install the CLI globally from npm: ```bash npm install -g @zeyos/cli zeyos --help ``` ### Running from source If you are contributing to the CLI or need to run an unreleased version, clone the repository and run directly from the source tree: ```bash # Clone the repository git clone cd client # Run directly ./cli/bin/zeyos.mjs --help ``` For convenience, create a symlink so you can run `zeyos` from anywhere: ```bash ln -s $(pwd)/cli/bin/zeyos.mjs /usr/local/bin/zeyos ``` ## First Login Authenticate with your ZeyOS instance using the `login` command: ```bash # Interactive — prompts for URL, app ID, and secret zeyos login # Or provide all values upfront zeyos login \ --base-url https://cloud.zeyos.com/demo \ --client-id myapp \ --secret "$ZEYOS_CLIENT_SECRET" ``` For interactive use, omit `--secret`; the CLI prompts without echoing the secret to the terminal. Passing secrets as command-line arguments is best reserved for controlled automation. **What happens:** 1. The CLI opens your browser to the ZeyOS authorization page 2. You log in and authorize the application 3. The CLI exchanges the authorization code for access + refresh tokens 4. Credentials are saved to `.zeyos/auth.json` in your project :::tip Switching Instances Use `--clean` to discard all saved credentials and start fresh: ```bash zeyos login --clean ``` This is useful when switching between ZeyOS instances. ::: ## Your First Commands Once logged in, you can start working with your data: ```bash # Check who you're logged in as zeyos whoami # List tickets (table output by default) zeyos list tickets # Get a specific ticket with all details zeyos get ticket 42 --all # Count records zeyos count accounts # Create a new ticket zeyos create ticket --name "Fix login bug" --status 0 --priority 3 # Update a ticket's status zeyos update ticket 42 --status 4 # Delete a ticket zeyos delete ticket 42 ``` Inspect the curated CLI-supported resource set at any time: ```bash zeyos resources ``` If the resource you need does not appear there, switch to [`@zeyos/client`](https://www.zeyos.com/guide/javascript-client/getting-started). ## Discover the Schema Inspect a resource's fields, types, foreign keys, and enum values before querying. This works offline -- no login required -- so it is a fast way to learn the data model: ```bash zeyos describe tickets zeyos describe accounts --json ``` ## Install Agent Skills If you are driving the CLI from a coding agent (Claude Code, Codex, opencode, Factory Droid, pi, …), install the bundled ZeyOS skill packs so the agent picks up the right conventions: ```bash zeyos skills list # see the available skills zeyos skills install zeyos # interactive: pick an agent, then local vs. global zeyos skills install zeyos --target claude --global # or skip the prompts with flags zeyos skills verify --target claude --global # check the installed files ``` See the [Commands Reference](https://www.zeyos.com/guide/cli/commands#skills) for details. ## Connect an MCP Host The package also includes the `zeyos-mcp` stdio server for Claude Desktop and other MCP hosts. It mirrors the CLI’s schema validation, aliases, filters, presets, and credential profiles; see the [MCP Server guide](https://www.zeyos.com/guide/cli/mcp-server) for configuration and the write-safety gate. ## Working with Filters Query specific records using JSON filter expressions: ```bash # Tickets with status = 1 (In Progress) zeyos list tickets --filter '{"status":1}' # Combine multiple criteria (AND logic) zeyos list tickets --filter '{"status":1,"priority":3}' # Count matching records zeyos count tickets --filter '{"status":1}' ``` For larger filters, store the JSON in a file and use `--filter-file`: ```bash zeyos list tickets --filter-file ./filters/open-tickets.json --json zeyos count tickets --filter-file ./filters/open-tickets.json --json ``` For normal operational views, include `visibility: 0` on resources that have the column: ```bash zeyos list tickets --filter '{"visibility":0,"status":1}' ``` :::warning `transactions` and its billing/procurement entities, plus `payments`, `messages`, `actionsteps` and `addresses`, have **no** `visibility` column — filtering on it there returns an opaque HTTP 400. Run `zeyos describe ` to see which columns a resource actually has. ::: ## Sorting and Pagination ```bash # Sort by name ascending, then by last modified descending zeyos list tickets --sort "+name,-lastmodified" # Limit results and paginate zeyos list tickets --limit 10 zeyos list tickets --limit 10 --offset 10 # page 2 ``` When results fill the page limit, the CLI automatically shows pagination info: ``` Showing 1–10 of 47 (--offset 10 for next page) ``` ## Output Formats Choose the format that fits your workflow: ```bash # Default: formatted table (human-readable) zeyos list tickets # JSON: for scripting and piping zeyos list tickets --json # YAML: for config-friendly output zeyos list tickets --yaml ``` :::tip Piping JSON output works great with tools like `jq`: ```bash zeyos list tickets --json | jq '.[].name' ``` ::: ## JSON-First Automation For coding agents and non-interactive scripts, prefer `--json` output and JSON-first writes: ```bash zeyos create ticket --data '{"name":"Fix login bug","status":0,"priority":3,"visibility":0}' --json zeyos update ticket 42 --data '{"status":4}' --json ``` `zeyos whoami --json` does not print access tokens. If a local tool explicitly needs the current token, use `zeyos whoami --show-token --json` and treat the output as a secret. ## Credential Storage Tokens are saved to `.zeyos/auth.json` in your project directory (or the nearest parent that has one). For global credentials shared across projects: ```bash zeyos login --global ``` This saves to `~/.config/zeyos/credentials.json` instead. :::warning Add `.zeyos/auth.json` to your `.gitignore` — it contains access tokens. ::: ## Next Steps - **[Coding Agents](https://www.zeyos.com/guide/agent-workflows/coding-agents)** -- CLI-first workflows and escalation guidance - **[Commands Reference](https://www.zeyos.com/guide/cli/commands)** — Full reference for every command - **[Configuration](https://www.zeyos.com/guide/cli/configuration)** — Config files, environment variables, and resource field customization - **[MCP Server](https://www.zeyos.com/guide/cli/mcp-server)** — Connect Claude Desktop or another stdio MCP host --- Canonical: https://www.zeyos.com/guide/cli/mcp-server # MCP Server `zeyos-mcp` is the zero-dependency Model Context Protocol server included with `@zeyos/cli`. It exposes the CLI’s resource registry, aliases, operator normalization, business presets, local schema validation, and credential/profile resolution to MCP hosts such as Claude Desktop. The server uses newline-delimited JSON-RPC 2.0 over stdio. It writes protocol messages to stdout and reserves stderr for diagnostics. It supports MCP `2025-06-18` and retains `2025-03-26` negotiation for older hosts. The structured output contract follows the [MCP tools specification](https://modelcontextprotocol.io/specification/2025-06-18/server/tools). ## Install and run Install the package globally to make both executables available: ```bash npm install -g @zeyos/cli zeyos-mcp ``` To run it without a global install, ask `npx` to install the package and invoke its second binary explicitly: ```bash npx -y -p @zeyos/cli zeyos-mcp ``` Running `npx -y @zeyos/cli` by itself selects the package’s default `zeyos` executable, not `zeyos-mcp`. ## Host configuration The server uses exactly the same environment variables, named profiles, project pin, and stored credential files as the CLI. A Claude Desktop configuration using environment credentials looks like this: ```json { "mcpServers": { "zeyos": { "command": "npx", "args": ["-y", "-p", "@zeyos/cli", "zeyos-mcp"], "env": { "ZEYOS_BASE_URL": "https://cloud.zeyos.com/your-instance", "ZEYOS_TOKEN": "YOUR_ACCESS_TOKEN" } } } } ``` For an existing named CLI profile, avoid copying credentials into the host configuration: ```json { "mcpServers": { "zeyos": { "command": "zeyos-mcp", "env": { "ZEYOS_PROFILE": "production" } } } } ``` The same `command`, `args`, and `env` shape works in generic stdio MCP hosts. The server starts and provides schema/discovery tools even when credentials are missing; authenticated calls return the CLI’s actionable configuration guidance as a tool error. For individual custom-field references and writes, add `ZEYOS_CUSTOM_FIELDS_FILE` to the host environment with an absolute path to the [instance-specific snapshot](https://www.zeyos.com/guide/cli/configuration#custom-field-validation). `describe_resource` returns `customFields` and `customFieldSource` (snapshot URL, `live: false`). The server reloads metadata per call and checks the resolved instance before dispatch. Joined paths are checked through each foreign key to the terminal field; missing custom metadata returns an actionable error. Discovery remains offline and does not certify that the snapshot is current or complete. ## Tools | Tool | Purpose | |---|---| | `list_resource_types` | List canonical resource names, descriptions, and presets. | | `describe_resource` | Inspect local fields, types, enums, aliases, presets, and operations. | | `find_records` | Resolve human-readable names to record IDs with full-text search. | | `list_records` | Query and paginate records with normalized filters, presets, fields, sorting, or search. | | `count_records` | Count records server-side without fetching every row. | | `sum_records` | Page through matches and sum one numeric field. | | `get_record` | Fetch one exact record by ID. | | `create_record` | Create one validated record; hidden unless writes are enabled. | | `update_record` | Update one validated record; hidden unless writes are enabled. | There is deliberately no delete tool. ## Filters and structured results `list_records`, `count_records`, and `sum_records` accept canonical `filters`: ```json { "name": "list_records", "arguments": { "resource": "tickets", "filters": { "visibility": 0, "status": 4 }, "limit": 25 } } ``` The singular `filter` spelling remains a deprecated alias and produces a warning in a successful structured result. Supplying both keys fails validation before authentication or an API request. Upstream requests contain only `filters`. The human CLI flag remains `--filter`; request previews use `--dry-run`. Every tool advertises an `outputSchema` and behavior annotations. The result contains `structuredContent: { ok: true, data: ... }` on success, or `structuredContent: { ok: false, error: { code, message, actions? } }` together with `isError: true` for tool failures. Hosts can branch on `ok` and consume `data` without parsing JSON from text. The existing text payload is retained for compatibility. Resource discovery's `data` is an array; lists contain `data.rows`; count and sum tools expose their named values inside `data`. Billing `invoice` / `invoices` aliases resolve to `billing_invoice`, whose transaction type is bound to `3`. Use `documents` for formal document artifacts and `procurement_invoices` for supplier invoices. The aliases never route a billing query to document records. ## Write gating Read tools are always advertised. To expose `create_record` and `update_record`, set the gate when starting the host: ```json { "env": { "ZEYOS_MCP_ALLOW_WRITES": "1" } } ``` Write access remains off for every other value. Enabling the tools does not replace normal agent safeguards: resolve and read the exact target first, scope the payload, and obtain authorization for the specific write. --- Canonical: https://www.zeyos.com/guide/javascript-client/authentication # Authentication The ZeyOS client supports multiple authentication strategies that can be selected via the `auth.mode` configuration option. The client determines how to authenticate each request based on the chosen mode and the security requirements of the target API operation. ## Auth Modes | Mode | Behavior | |------|----------| | `auto` (default) | Tries bearer token first, attempts token refresh on 401, then falls back to session cookies | | `oauth` | Bearer token only; uses access tokens from the configured token store | | `session` | Session cookies only; relies on browser session or an explicit cookie value | | `none` | No authentication; requests are sent without any credentials | ## Session Mode Session mode is the simplest option when the user is already logged into ZeyOS in the same browser. The client sends requests with `credentials: 'include'`, allowing the browser to attach the existing session cookie automatically. ```js import { createZeyosClient } from '@zeyos/client'; const client = createZeyosClient({ platform: 'https://cloud.zeyos.com/demo/', auth: { mode: 'session', session: { enabled: true, credentials: 'include' }, }, }); // Requests use the browser's session cookie const tickets = await client.api.listTickets({ limit: 10 }); ``` :::tip When to use session mode Session mode is ideal for apps embedded inside the ZeyOS platform, browser extensions, or any scenario where the user has already authenticated with ZeyOS in the same browser. No tokens need to be stored or managed -- the browser handles everything. ::: ## Token Mode For standalone applications, server-side scripts, or situations where you have pre-obtained tokens, use OAuth mode with a token store. ```js import { createZeyosClient, MemoryTokenStore } from '@zeyos/client'; const tokenStore = new MemoryTokenStore({ accessToken: 'your-access-token', refreshToken: 'your-refresh-token', }); const client = createZeyosClient({ platform: 'https://cloud.zeyos.com/demo/', auth: { mode: 'oauth', oauth: { tokenStore }, }, }); // Requests use the bearer token from the store const accounts = await client.api.listAccounts({ limit: 25 }); ``` This is the safest default when you already have a valid access token. When `autoRefresh` is enabled and the token has expired or a bearer request receives a 401 response, the client uses the refresh token to obtain a new access token. This requires `clientId` and `clientSecret` to be configured: ```js const client = createZeyosClient({ platform: 'https://cloud.zeyos.com/demo/', auth: { mode: 'oauth', oauth: { tokenStore, autoRefresh: true, clientId: 'your-client-id', clientSecret: 'your-client-secret', }, }, }); ``` ## Token Store The built-in `MemoryTokenStore` keeps tokens in memory. Tokens are lost when the process exits or the page is refreshed. For persistent storage, implement a custom token store with `get()` and `set()` methods: ```js class LocalStorageTokenStore { async get() { const raw = localStorage.getItem('zeyos_tokens'); return raw ? JSON.parse(raw) : null; } async set(tokenSet) { if (tokenSet) { localStorage.setItem('zeyos_tokens', JSON.stringify(tokenSet)); } else { localStorage.removeItem('zeyos_tokens'); } } } const client = createZeyosClient({ platform: 'https://cloud.zeyos.com/demo/', auth: { mode: 'oauth', oauth: { tokenStore: new LocalStorageTokenStore(), }, }, }); ``` If you want `autoRefresh: true`, add `clientId` and `clientSecret` in a trusted environment. For browser-only apps, prefer session mode or a backend-assisted token flow rather than embedding a client secret in shipped code. A token set object contains the following fields: | Field | Type | Description | |-------|------|-------------| | `accessToken` | `string` | The OAuth 2.0 access token | | `refreshToken` | `string \| null` | The refresh token (if issued) | | `tokenType` | `string` | Token type, typically `'Bearer'` | | `expiresIn` | `number \| null` | Token lifetime in seconds from issuance | | `expiresAt` | `number \| null` | Absolute expiry as a Unix timestamp (seconds) | | `obtainedAt` | `number` | Unix timestamp when the token was obtained | | `refreshTokenExpiresIn` | `number \| null` | Refresh token lifetime in seconds | | `refreshTokenExpiresAt` | `number \| null` | Refresh token absolute expiry (Unix timestamp) | ### Token Set Helpers Two helpers are exported for working with token sets directly — handy when persisting and re-hydrating tokens: - **`normalizeTokenSet(input)`** — normalize a partial token set into the shape above, tolerating both camelCase and snake_case fields (`access_token`, `expires_in`, …). Returns `null` if the input has neither an access nor a refresh token. - **`tokenResponseToTokenSet(response)`** — convert a raw OAuth 2.0 token-endpoint response into a normalized token set (an alias of `normalizeTokenSet`). ```js import { normalizeTokenSet } from '@zeyos/client'; const stored = JSON.parse(localStorage.getItem('zeyos_tokens') ?? 'null'); const tokenSet = normalizeTokenSet(stored); // tolerant of snake_case, returns null if empty ``` ## OAuth 2.0 Flow For server-side or other trusted applications that can safely hold OAuth client credentials, use the standard OAuth 2.0 authorization code flow: ```js import { createZeyosClient, MemoryTokenStore } from '@zeyos/client'; const tokenStore = new MemoryTokenStore(); const client = createZeyosClient({ platform: 'https://cloud.zeyos.com/demo/', auth: { mode: 'oauth', oauth: { tokenStore, clientId: 'your-client-id', clientSecret: 'your-client-secret', }, }, }); // 1. Build the authorization URL and redirect the user const url = client.oauth2.buildAuthorizationUrl({ redirectUri: 'https://myapp.com/callback', state: crypto.randomUUID(), }); // 2. After the user authorizes, exchange the code for tokens const tokenSet = await client.oauth2.exchangeAuthorizationCode({ code: 'authorization-code-from-callback', redirectUri: 'https://myapp.com/callback', }); // 3. Tokens are automatically stored in the tokenStore // All subsequent API calls use the new tokens const user = await client.api.listAccounts({ limit: 1 }); ``` You can also parse the callback URL to extract the authorization code and detect errors: ```js const callback = client.oauth2.parseAuthorizationCallback( 'https://myapp.com/callback?code=abc123&state=xyz' ); if (callback.isError) { console.error(callback.error, callback.errorDescription); } else { const tokenSet = await client.oauth2.exchangeAuthorizationCode({ code: callback.code, }); } ``` :::warning The current OAuth helper expects `clientSecret` for authorization-code exchange and token refresh. Do not run that flow directly in shipped browser-only code. Use session mode in the browser or move the code exchange and refresh logic to a backend. ::: ## Token Operations The `client.oauth2` namespace provides methods for managing tokens throughout their lifecycle: ```js // Refresh the current access token const newTokenSet = await client.oauth2.refreshToken(); // Revoke a token (access or refresh) await client.oauth2.revokeToken({ token: 'token-to-revoke' }); // Introspect a token to check its validity and metadata const info = await client.oauth2.introspectToken({ token: 'token-to-check' }); ``` You can also manage the token set directly through the `client.auth` interface: ```js // Read the current token set const tokenSet = await client.auth.getTokenSet(); // Replace the stored token set await client.auth.setTokenSet({ accessToken: 'new-access-token', refreshToken: 'new-refresh-token', }); // Clear all stored tokens await client.auth.clearTokenSet(); ``` ## Auto Mode The default `auto` mode implements an intelligent fallback chain: 1. **Bearer token** -- if the token store contains an access token, use it 2. **Token refresh** -- if the stored token is expired or the bearer request returns 401 and a refresh token is available (with `clientId`/`clientSecret`), refresh the access token 3. **Session fallback** -- if bearer authentication is unavailable or fails, fall back to session cookies (when `session.enabled` is `true`) This makes `auto` mode the most flexible option. It works seamlessly when tokens are available and gracefully degrades to session authentication when they are not. ```js const client = createZeyosClient({ platform: 'https://cloud.zeyos.com/demo/', auth: { mode: 'auto', // this is the default oauth: { tokenStore, clientId: 'your-client-id', clientSecret: 'your-client-secret', autoRefresh: true, }, session: { enabled: true, credentials: 'include' }, }, }); ``` :::warning Never embed `clientSecret` in browser-facing JavaScript for production applications. Client secrets should only be used in server-side code or trusted environments. For browser-only apps, use session mode or a backend proxy that handles token exchange. ::: ## Legacy Authentication (`client.legacyAuth`) The `client.legacyAuth` namespace exposes the ZeyOS legacy session authentication API. These operations target a separate endpoint (`/{INSTANCE}/auth/v1/`) and are primarily used by ZeyOS platform apps that manage session lifecycle directly rather than through OAuth 2.0. | Method | Description | |--------|-------------| | `client.legacyAuth.login(input)` | Start a session using username and password credentials | | `client.legacyAuth.logout()` | Terminate the current session | | `client.legacyAuth.verify()` | Check whether the current session is still valid (HEAD request) | | `client.legacyAuth.getUserInfo()` | Retrieve information about the currently authenticated user | ```js // Verify current session validity await client.legacyAuth.verify(); // Get info about the logged-in user const userInfo = await client.legacyAuth.getUserInfo(); // Log out await client.legacyAuth.logout(); ``` :::info For most integrations, prefer OAuth 2.0 (via `client.oauth2`) or session cookie mode (via `auth.mode: 'session'`). The `legacyAuth` API is primarily intended for internal ZeyOS platform use and scripts that run within an already-authenticated ZeyOS session. ::: --- Canonical: https://www.zeyos.com/guide/javascript-client/getting-started # Getting Started The ZeyOS JavaScript client provides a generated, dependency-light interface to the ZeyOS REST API. It works in both browser environments and Node.js 18+, with built-in support for OAuth 2.0, session-based authentication, and automatic token refresh. Use the JavaScript client when: - the CLI resource registry is not enough - you are building a browser UI or a server-side integration - you need request-level control through `client.request()` - you want access to the full generated API surface instead of the CLI's curated subset ## Installation :::info ESM only `@zeyos/client` is an ES module (ESM) package. Use `import` (or dynamic `import()`) to load it. CommonJS `require()` is not supported. ::: The client is distributed as an ES module source package. If you are working inside this repository, import it directly from the source tree: ```js import { createZeyosClient } from './src/index.js'; ``` If you are consuming it from another project, install the package and import it by name: ```bash npm install @zeyos/client ``` ```js import { createZeyosClient } from '@zeyos/client'; ``` The client has zero runtime dependencies and relies only on the standard `fetch` API available in modern browsers and Node.js 18+. ## Creating a Client The `createZeyosClient` factory function returns a frozen client object with generated API methods, OAuth 2.0 helpers, and low-level request capabilities. ```js import { createZeyosClient } from '@zeyos/client'; const client = createZeyosClient({ platform: 'https://cloud.zeyos.com/demo/', validate: true, auth: { mode: 'session', session: { enabled: true, credentials: 'include' }, }, }); const tickets = await client.api.listTickets({ limit: 10 }); ``` Keep `validate: true` in new code. It checks generated methods and known-operation `client.request()` calls against the bundled schema before anything is sent, and throws `ZeyosValidationError` with a correction hint. It is off by default for compatibility. ### Custom Fields Belong to One Instance Custom `extdata` keys differ between ZeyOS instances, so the bundled schema cannot know them. With validation on, naming an individual key requires a reviewed `customFields` contract for that instance: ```js const client = createZeyosClient({ platform: 'https://cloud.zeyos.com/demo/', validate: true, customFields: { accounts: { region: { type: 'string', enum: ['EMEA', 'APAC'] } }, // Reviewed keys for this instance tickets: {}, // Known to have none }, }); client.schema.describe('accounts').customFields; // The definitions supplied above ``` The client does not fetch these definitions or check that they belong to the instance you are connected to. That is your responsibility: see [custom-field contracts](https://www.zeyos.com/guide/javascript-client/making-requests#custom-field-contracts) for binding a contract to its instance. Reading the whole collection with `extdata: 1` needs no contract. The returned `client` object exposes: - **`client.api`** -- Generated methods for all standard REST operations (e.g. `listTickets`, `createAccount`, `getTask`) - **`client.oauth2`** -- OAuth 2.0 token operations and authorization URL helpers - **`client.legacyAuth`** -- Legacy session authentication operations (`login`, `logout`, `verify`, `getUserInfo`) - **`client.request()`** -- Low-level escape hatch for custom or advanced requests. A method and path call skips schema validation; see [Low-Level Requests](https://www.zeyos.com/guide/javascript-client/making-requests#low-level-requests) - **`client.schema`** -- Runtime introspection and input validation (`describe`, `fields`, `resources`, `operations`, `validate`); see [Making Requests](https://www.zeyos.com/guide/javascript-client/making-requests#schema-introspection-and-validation) - **`client.auth`** -- Token management (`getTokenSet`, `setTokenSet`, `clearTokenSet`) - **`client.metadata`** -- Read-only info about the generated client (`generatedAt` timestamp, `services` array) Alongside the `createZeyosClient` factory, the package exports a few named helpers and error types you can import directly: ```js import { createZeyosClient, MemoryTokenStore, // in-memory token store (or implement get()/set() yourself) ZeyosApiError, // thrown on non-2xx responses ZeyosValidationError, // thrown by pre-flight validation (validate: true) normalizeListResult, // normalise a list response to { data, count? } normalizeCountResult, // normalise a count response to a number normalizeTokenSet, // normalise a raw token object to a TokenSet tokenResponseToTokenSet, // map an OAuth token response to a TokenSet } from '@zeyos/client'; ``` ## Platform Configuration The `platform` option tells the client where your ZeyOS instance lives. There are three ways to specify it: ### URL String Pass the full URL to your ZeyOS instance. The client extracts the origin and instance name automatically. ```js const client = createZeyosClient({ platform: 'https://cloud.zeyos.com/demo/', }); ``` ### Preset Use a named preset for the ZeyOS cloud platform, combined with an `instance` name: ```js const client = createZeyosClient({ platform: 'live', instance: 'demo', }); ``` The `live` preset resolves to `https://cloud.zeyos.com`. ### Object For full control, pass an object with `origin` and `instance`: ```js const client = createZeyosClient({ platform: { origin: 'https://cloud.zeyos.com', instance: 'demo', }, }); ``` ## Features - **Zero dependencies** -- no external packages; only the standard `fetch` API is required - **Browser + Node.js 18+** -- works in any environment with global `fetch` - **Auto token refresh** -- transparently refreshes expired access tokens before requests or after bearer 401 responses - **Generated API methods** -- the broader generated API surface is available as `client.api.()` - **Session + OAuth support** -- choose between cookie-based sessions, bearer tokens, or automatic fallback - **Low-level escape hatch** -- `client.request()` for custom endpoints and advanced use cases ## Configuration Reference | Option | Type | Description | |--------|------|-------------| | `platform` | `string \| { origin?: string, instance?: string, preset?: string, url?: string }` | ZeyOS instance URL, preset name (e.g. `'live'`), or object with `origin`/`instance` (and optionally `preset` or `url`) | | `platform.origin` | `string` | Base origin URL (e.g. `'https://cloud.zeyos.com'`) | | `platform.instance` | `string` | Instance name (e.g. `'demo'`) | | `platform.preset` | `string` | Named preset (e.g. `'live'`) used when `origin` is omitted | | `platform.url` | `string` | Full instance URL as an alternative to `origin`+`instance` | | `instance` | `string` | Top-level instance name shortcut (used with preset-style `platform`) | | `auth.mode` | `string` | Authentication mode: `'auto'` (default), `'oauth'`, `'session'`, or `'none'` | | `auth.oauth.clientId` | `string` | OAuth 2.0 client ID for token operations | | `auth.oauth.clientSecret` | `string` | OAuth 2.0 client secret for token operations | | `auth.oauth.tokenStore` | `TokenStore` | Token storage backend (must implement `get()` and `set()`) | | `auth.oauth.autoRefresh` | `boolean` | Automatically refresh access tokens before requests or after bearer 401 responses (default: `true`) | | `auth.session.enabled` | `boolean` | Enable session-based authentication (default: `true`) | | `auth.session.credentials` | `string` | Fetch credentials mode: `'include'`, `'same-origin'`, or `'omit'` | | `auth.session.cookie` | `string \| () => string \| Promise` | Explicit session cookie value or async function returning one (Node.js) | | `headers` | `HeadersInit` | Default headers applied to every request | | `fetch` | `typeof fetch` | Custom `fetch` implementation (defaults to `globalThis.fetch`) | | `retry` | `{ maxRetries?: number, retryOn?: number[], baseDelayMs?: number, maxDelayMs?: number } \| false` | Retry policy. By default, up to two retries: `429` for any operation, `503` for reads only. Pass `false` to disable. See [Making Requests](https://www.zeyos.com/guide/javascript-client/making-requests#retries-and-rate-limiting) | | `retryOnNetworkError` | `boolean` | Retry network errors, timeouts and `retryOn` statuses other than `429` for every operation (`true`) or none (`false`). Omit it to allow this for reads only | | `timeoutMs` | `number` | Deadline for each attempt, in milliseconds. Default: none | | `validate` | `boolean` | When `true`, validate each request against the schema before sending and throw `ZeyosValidationError` on problems (default: `false`). See [Making Requests](https://www.zeyos.com/guide/javascript-client/making-requests#schema-introspection-and-validation) | | `customFields` | `Record, description?: string }>>` | Reviewed custom-field keys per canonical resource, for one instance. See [custom-field contracts](https://www.zeyos.com/guide/javascript-client/making-requests#custom-field-contracts) | ## Next Steps - **[Application Developers](https://www.zeyos.com/guide/tutorials/application-developers)** -- choose the browser or server-side integration path - **[Authentication](https://www.zeyos.com/guide/javascript-client/authentication)** -- learn about session mode, OAuth 2.0 flows, and token management - **[Making Requests](https://www.zeyos.com/guide/javascript-client/making-requests)** -- explore CRUD operations, filtering, sorting, pagination, and error handling - **[Practical Guide](https://www.zeyos.com/guide/javascript-client/practical-guide)** -- real-world patterns and gotchas discovered during implementation - **[Sample Application](https://www.zeyos.com/guide/sample-apps/kanban)** -- run the included Kanban board demo to see the client in action --- Canonical: https://www.zeyos.com/guide/javascript-client/making-requests # Making API Requests The ZeyOS client generates methods for every REST operation defined in the ZeyOS OpenAPI specification. These methods provide a clean, high-level interface for all CRUD operations, filtering, sorting, and pagination. ## Generated Methods All standard API operations are available as `client.api.(input, options?)`. The `operationId` corresponds directly to the operation name in the ZeyOS OpenAPI specification. ```js // Examples of generated methods client.api.listTickets(...) client.api.getTicket(...) client.api.createTicket(...) client.api.updateTicket(...) client.api.deleteTicket(...) client.api.listAccounts(...) client.api.createTask(...) ``` Each method accepts an `input` object where you can mix path parameters, query parameters, and request body fields in a single flat object. The client automatically routes each property to the correct location based on the operation's parameter definitions. The examples below assume a client created with `validate: true`, so mistakes are caught before a request is sent: ```js const client = createZeyosClient({ platform: 'live', instance: 'demo', validate: true }); ``` ## CRUD Operations ### List Records Retrieve collections of records with optional filters, sorting, and pagination: ```js const tickets = await client.api.listTickets({ fields: ['ID', 'name', 'status', 'priority', 'duedate'], filters: { status: 1, visibility: 0 }, sort: ['-lastmodified'], limit: 50, }); ``` ### Get a Single Record Fetch a specific record by ID. Additional flags control which related data is included: ```js const ticket = await client.api.getTicket({ ID: 42, extdata: 1, tags: 1, }); ``` ### Create a Record Create a new record by passing the required fields. For operations **without** path parameters (like create), you can pass all fields as a flat object: ```js const newTicket = await client.api.createTicket({ name: 'Fix login bug', status: 0, priority: 3, description: 'Users cannot log in with SSO', visibility: 0, }); ``` :::caution Required fields the spec does not declare Some columns are `NOT NULL` with no database default, so a create that omits them fails server-side with an opaque HTTP 500 — even though the OpenAPI spec marks nothing as required. Most notably, **creating an `account` requires a `currency`** (e.g. `"EUR"`). `client.schema.validate('createAccount', …)` now flags this before you send it. ```js await client.api.createAccount({ lastname: 'Acme Corp', type: 1, currency: 'EUR' }); ``` ::: ### Update a Record Update an existing record with a PATCH request. Pass the `ID` and changed fields in one object: ```js await client.api.updateTicket({ ID: 42, status: 4, priority: 4, }); ``` :::tip Explicit body is also supported Use `body` or `data` when you want to separate URL parameters from payload fields manually: ```js await client.api.updateTicket({ ID: 42, body: { status: 4, priority: 4 } }); ``` ::: The PATCH response body contains the full updated record. Use it to confirm the server applied your changes: ```js const updated = await client.api.updateTicket({ ID: 42, status: 4, }); console.log(updated.status); // 4 -- confirmed by the server ``` ### Delete a Record Delete a record by ID: ```js await client.api.deleteTicket({ ID: 42 }); ``` ### Check Existence Use a HEAD request to check whether a record exists without downloading the full response body: ```js const exists = await client.api.existsTicket({ ID: 42 }); // Returns true if the record exists (2xx/3xx), throws ZeyosApiError on 404 ``` ## Field Selection Control which fields are returned in list responses. This reduces payload size and improves performance. ### Array Form Pass an array of field names to return only those fields: ```js const result = await client.api.listAccounts({ fields: ['ID', 'lastname', 'contact.city'], filters: { visibility: 0 }, }); ``` ### Object Form (with Aliases) Pass an object to rename fields in the response. Keys become the output names, values are the source field paths: ```js const result = await client.api.listAccounts({ fields: { 'Id': 'ID', 'Name': 'lastname', 'City': 'contact.city', 'Agent': 'assigneduser.name', }, filters: { visibility: 0 }, }); ``` :::note Dot-notation field paths (e.g. `contact.city`, `assigneduser.name`) allow you to select fields from related or nested objects. ::: ## Filtering Pass filters as `filters` (plural). It covers every field type — scalars, enums and GIN-indexed foreign keys: ```js // Standard filtering -- works for all field types const active = await client.api.listTickets({ filters: { status: 4, visibility: 0 }, }); // Filter by foreign key field (e.g. project, account) const projectTickets = await client.api.listTickets({ filters: { visibility: 0, project: projectId }, }); ``` ### Why not `filter` (singular)? The bundled OpenAPI spec names the parameter `filter`, but ZeyOS's [published API documentation](https://www.zeyos.com/api/llms-full.txt) uses `filters` in every worked example and never mentions the singular. There is one parameter; the spec is the outlier. Schema validation reflects that: `client.schema.validate()` and the CLI both reject `filter` and point you at `filters`. :::note Validation is **off by default** in the JS client — pass `validate: true` to `createZeyosClient()` (the CLI enables it for you). Without it, a `filter` key is forwarded to the server as-is. ::: ### Full-Text Search Use the `query` parameter to search across a resource's indexed text fields: ```js const results = await client.api.listAccounts({ fields: ['ID', 'lastname', 'contact.email'], filters: { visibility: 0 }, query: 'acme', limit: 20, }); ``` ## Distinct Results Pass `distinct: true` to deduplicate result rows. This is useful when using dot-notation joins that may produce multiple rows per record: ```js const result = await client.api.listAccounts({ distinct: true, fields: ['ID', 'lastname', 'contact.country'], filters: { visibility: 0 }, }); ``` ## Sorting Pass an array of field names prefixed with `+` (ascending) or `-` (descending): ```js // Sort by last modified, newest first const tickets = await client.api.listTickets({ sort: ['-lastmodified'], }); // Multi-field sort: priority descending, then name ascending const tickets = await client.api.listTickets({ sort: ['-priority', '+name'], }); ``` ## Pagination Use `limit` and `offset` to page through large result sets: ```js // Get total count first const countResult = await client.api.listTickets({ count: true, filters: { status: 1, visibility: 0 }, }); // countResult contains the total number of matching records // Fetch the first page const page1 = await client.api.listTickets({ limit: 50, offset: 0, filters: { status: 1, visibility: 0 }, sort: ['+ID'], }); // Fetch the second page const page2 = await client.api.listTickets({ limit: 50, offset: 50, filters: { status: 1, visibility: 0 }, sort: ['+ID'], }); ``` Always set an explicit `sort` when paging. Without one, the API does not promise that rows keep their order between requests. :::caution Offsets shift when data changes `offset` counts rows at the moment of each request, and the API has no server-side cursor. If a record is added, removed or moved between two pages, a later page can skip or repeat rows. For jobs that must see every change, use [keyset pagination with a checkpoint](https://www.zeyos.com/guide/tutorials/server-side-integrations#3-fetch-one-page). ::: :::tip Use `count: true` to get the total number of matching records without fetching the full dataset. This is useful for building pagination controls. ::: ### Auto-pagination To iterate an entire result set without managing `offset` yourself, use `client.paginate(operationId, input, opts)` — an async iterator that pages until a short/empty page (or `opts.max`) is reached. It removes the off-by-one and "I only got the first 1000 / 50 rows" mistakes the list caps otherwise invite. ```js // Stream every matching ticket, one page at a time for await (const ticket of client.paginate('listTickets', { filters: { visibility: 0 } }, { pageSize: 1000 })) { process(ticket); } // Eagerly collect up to a cap const recent = await client.collect( 'listTickets', { filters: { visibility: 0 }, sort: ['-lastmodified'] }, { pageSize: 500, max: 2000 } ); ``` `opts`: `pageSize` (default 1000, clamped to the server max of 10000), `max` (stop after N records), and `requestOptions` (forwarded to each underlying call, e.g. `{ signal, timeoutMs }`). `collect` is the eager array form of `paginate`. `paginate` pages by `offset`, so the caution above applies to it: use it for data that is stable during the read. It keeps no position, so a failed iteration restarts from the beginning. ## Normalising List Responses List endpoints are not uniform across the full surface area. Depending on the endpoint and response mode, you may see: - a plain array - an object wrapper with `data` - count metadata alongside the payload when `count: true` is used The normalization helpers are useful when you want call sites to share one response-shape contract: ```js import { normalizeCountResult, normalizeListResult } from '@zeyos/client'; // Without count -- result is a plain array const raw = await client.api.listTickets({ filters: { visibility: 0 } }); const { data } = normalizeListResult(raw); // data is always an array // With count metadata -- result may include both data and count const raw2 = await client.api.listTickets({ filters: { visibility: 0 }, count: true }); const { data: tickets, count } = normalizeListResult(raw2); // tickets: array, count: number // Count-only request -- result may be a number or an object with count metadata const countOnly = await client.api.listTickets({ filters: { visibility: 0 }, count: true }); const total = normalizeCountResult(countOnly); ``` ## Extended Data Many ZeyOS entities support **extended data** (extdata) -- custom fields defined through the platform's extensibility features. By default, extended data fields are not included in API responses. To include them, pass `extdata: 1` as a parameter. ### Including Extended Data in List Requests For list operations, `extdata` is sent as a body parameter: ```js const tickets = await client.api.listTickets({ fields: ['ID', 'name', 'status', 'priority', 'duedate'], filters: { status: 1, visibility: 0 }, sort: ['-lastmodified'], limit: 50, extdata: 1, }); ``` ### Including Extended Data in GET Requests For single-record GET operations, `extdata` is sent as a query parameter: ```js const ticket = await client.api.getTicket({ ID: 42, extdata: 1, tags: 1, }); ``` ### Selecting Specific Extended Data Fields You can reference individual extended data fields using dot notation in the `fields` parameter. Use the `extdata.fieldname` syntax to select only the custom fields you need: ```js const tickets = await client.api.listTickets({ fields: { 'Id': 'ID', 'Name': 'name', 'Region': 'extdata.region', 'CustomerType': 'extdata.customer_type', }, filters: { status: 1, visibility: 0 }, limit: 50, }); ``` :::note When you select specific `extdata.*` fields via the `fields` parameter, you do not need to pass `extdata: 1` separately -- the selected fields will be included automatically. ::: ## Expanding JSON and Binary Columns The `expand` parameter is used to inline the contents of **JSON columns** or **binary/file columns** that are normally returned as references or omitted for performance reasons. This applies to structured data columns such as `binfile` on messages, `items` on transactions, or `data` on objects. ```js // Expand the binary file content of a message const message = await client.api.getMessage({ ID: 123, expand: ['binfile'], }); // Expand the items array on a transaction const transaction = await client.api.getTransaction({ ID: 456, expand: ['items'], }); ``` :::caution Do not confuse `expand` with `extdata`. The `expand` parameter is strictly for JSON and binary columns -- it does not apply to extended data fields. To include extended data, use `extdata: 1` instead. ::: ## Schema Introspection and Validation The `client.schema` surface lets you (or an AI agent) discover the data model and validate inputs **without any network call** -- it is built from the generated schema. This is the fastest way to learn fields, types, foreign keys, and enum values before issuing a request. ```js client.schema.resources(); // all resource names client.schema.describe('tickets'); // { name, type, fields: { status: { type, enum }, account: { fk }, … } } client.schema.fields('accounts'); // ['ID', 'lastname', 'firstname', 'type', …] client.schema.operations('tickets'); // ['listTickets', 'getTicket', 'createTicket', …] client.schema.resourceForOperation('listDunningNotices'); // 'dunning' client.schema.suggestOperation('listDunning'); // 'listDunningNotices' ``` ### Validating a Call `client.schema.validate(operationId, input)` returns structured, self-correcting hints. It returns `{ valid, errors }` without a network call. Each dot-notation hop must be a documented foreign key and the terminal field must exist. This covers selected fields (including alias maps), nested filters, and sorting. Custom keys under `extdata` require an explicit field contract: ```js const result = client.schema.validate('createAccount', { name: 'Acme', currency: 'EUR' }); // { // valid: false, // errors: [{ field: 'name', message: 'Unknown field "name". Did you mean "lastname"?', suggestion: 'lastname' }] // } ``` It flags unknown fields (with a suggestion), `filter` used where `filters` is preferred, invalid enum values (listing the valid set), and missing required create fields. The ZeyOS spec carries no required-field metadata, so a curated supplement covers known NOT-NULL-without-default columns — notably `accounts` require `currency`: ```js client.schema.validate('createAccount', { lastname: 'Acme' }); // { valid: false, errors: [{ field: 'currency', message: 'Missing required field "currency" for accounts …', suggestion: 'currency' }] } ``` ### Pre-flight Validation Set `validate: true` on the client to validate generated API methods and known-operation `client.request()` calls before they are sent and throw a `ZeyosValidationError` (rather than letting the server reject it): ```js import { ZeyosValidationError } from '@zeyos/client'; const client = createZeyosClient({ platform: 'live', instance: 'demo', validate: true }); try { await client.api.createAccount({ name: 'Acme' }); // wrong field } catch (err) { if (err instanceof ZeyosValidationError) { console.log(err.operationId); // 'createAccount' console.log(err.errors); // structured hints (see above) } } ``` Validation is **off by default** in JavaScript and on by default in CLI/MCP data commands. Enable it in all new code. You can also enable it for one call by passing `{ validate: true }` as a request option. A raw `client.request({ method, path })` without an operation ID has no schema mapping, so even `validate: true` does not check it. You lose the checks for unknown fields, `filter` versus `filters`, enum values, join paths, custom-field keys and required create fields. The first sign of a mistake is then a server error or a silently wrong result. Prefer `client.request({ service, operationId, body })`, which validates like a generated method. ### Custom-field contracts Supply the complete reviewed key allowlist for each relevant API resource, using canonical resource names (for example `transactions`, not a CLI invoice alias): ```js const client = createZeyosClient({ auth: { mode: 'none' }, // Local validation only; configure auth for API access. validate: true, customFields: { accounts: { region: { type: 'string', enum: ['EMEA', 'APAC'] } }, contacts: { preferred: { type: 'boolean' } }, tickets: {}, }, }); client.schema.validate('listAccounts', { fields: ['ID', 'contact.city', 'contact.extdata.preferred'], filters: { 'extdata.region': 'EMEA' }, }); // valid: true for this supplied contract client.schema.validate('listAccounts', { fields: ['contact.invented'] }); // valid: false; unknown terminal field on contacts client.schema.validate('listAccounts', { fields: ['extdata.invented'] }); // valid: false; unknown custom field client.schema.validate('updateAccount', { id: 42, extdata: { region: 'INVALID' } }); // valid: false; value outside the configured enum (no request sent) ``` A definition `{}` validates only the key. Optional `type` supports JSON `string`, `number`, `integer`, `boolean`, `object`, and `array`; optional `enum` is an array of finite JSON scalar values, and `description` is a string. Constraints are explicit application contracts, **not** automatic conversions from the platform's display types. Equality and membership filter operands and write values use these constraints; ordering operands check type without requiring enum membership. This is not a complete filter-operator parser. A missing resource map returns `custom_fields_unavailable`; an empty map means the resource is known to have no allowed custom keys. Unknown keys return `unknown_custom_field`. A key containing dots is matched literally after `extdata.`; it is not interpreted as a nested JSON property. `extdata: 1` on reads still requests the collection without naming individual keys. Writes use an `extdata` object; a known key set to `null` deletes that value on update. Joined query paths are not writable fields: update the related resource explicitly. `client.schema.describe(resource).customFields` returns the supplied definitions, or `null` when unavailable. Metadata is copied at client creation; recreate the client after refreshing it. The library does not fetch tenant definitions or verify that your supplied contract belongs to the authenticated instance. CLI/MCP provide an [instance-bound snapshot loader](https://www.zeyos.com/guide/cli/configuration#custom-field-validation). ### Binding a Contract to Its Instance A contract reviewed for one instance can wrongly pass or reject keys on another, so keep its origin next to it. The CLI and MCP do this for you: they refuse a snapshot from a different instance, and `describe` reports `customFieldSource` with the snapshot's `platform` and `live: false`. In JavaScript, `client.schema.describe()` has no `customFieldSource`, and the snapshot loader is not exported, so your application makes the same check. The [snapshot format](https://www.zeyos.com/guide/cli/configuration#custom-field-validation) lets one file serve both: ```js import { readFile } from 'node:fs/promises'; import { createZeyosClient } from '@zeyos/client'; const normalize = (url) => url.replace(/\/+$/, ''); export async function createBoundClient({ platform, snapshotPath, auth }) { const snapshot = JSON.parse(await readFile(snapshotPath, 'utf8')); if (normalize(snapshot.platform) !== normalize(platform)) { throw new Error('Custom-field snapshot belongs to a different ZeyOS instance.'); } const client = createZeyosClient({ platform, auth, validate: true, customFields: snapshot.customFields, }); // Same shape as the CLI: an offline snapshot, not a live reading of the tenant. return { client, customFieldSource: { platform: normalize(snapshot.platform), live: false } }; } ``` Log `customFieldSource` at startup so a run shows which contract it used. It proves where the snapshot was meant to apply, not that it is current. Keep your review date and source separately from the snapshot, because the CLI and MCP loader rejects any extra top-level property in that file. Refresh it after configuration changes. To discover configured extdata definitions, query `customfields` using `context` for the owning entity, `source: 0` for EXTDATA, and `activity: 0` for active definitions. `reference` is the custom key. The `entity` column is the target of an entity-valued display field, **not** the owner. Page through every result and verify access/completeness; do not turn an incomplete or denied response into an empty allowlist. Retrieve `type` and `options` when reviewing value constraints, but do not infer JSON types or enums from labels alone. Record the instance, review date and provenance separately from credentials. ### Coverage and migration The validator now rejects join suffixes and custom keys that older clients accepted unchecked. Existing validated integrations using `extdata.*` must supply the reviewed contract before upgrading. CLI/MCP users set `ZEYOS_CUSTOM_FIELDS_FILE`; do not suppress errors with `--no-validate` merely to make a request run. Validation checks known metadata, not server permissions, record existence, all base-field types, every required parameter, join cardinality, arbitrary SQL, or native iXML/Zymba syntax. The curated create requirement remains account `currency`; the OpenAPI source does not supply a complete required-field contract. `group` and `having` return unsupported-validation errors rather than a silent pass. Operations with no bundled resource schema cannot receive field-level checks. A passing preflight still needs a bounded sandbox request for runtime behavior. ## Retries and Rate Limiting The client retries only specific failures, and whether a failure qualifies depends on the operation. The rule that matters: **a write is never repeated unless the server said it was not processed, or you opted in.** ### What Is Retried | Failure | Read operation | Write operation | |---------|----------------|-----------------| | `429 Too Many Requests` | Retried | Retried, because `429` means the request was not processed | | `503 Service Unavailable` | Retried | **Not retried**: a `503` can arrive after the write committed | | Other statuses, such as `500` or `502` | Not retried unless added to `retryOn` | Not retried, even if added to `retryOn` | | Network error or timeout | Retried | **Not retried** | | Caller abort through `AbortSignal` | Never | Never | An operation counts as a **read** when its method is `GET` or `HEAD`, or when its operation ID starts with `list`, `count`, `search`, `exists` or `get`. Everything else counts as a write. That includes a raw `client.request({ method: 'POST', path })` call, even if it runs a list query, because there is no operation ID to classify it. ```js // Customize the policy (these values are the defaults) const client = createZeyosClient({ platform: 'live', instance: 'demo', validate: true, retry: { maxRetries: 2, // Retries after the first attempt retryOn: [429, 503], // Candidate statuses; the table above still applies baseDelayMs: 300, // Backoff base maxDelayMs: 10000, // Cap for each wait }, }); // Disable retries entirely, including 429 const noRetry = createZeyosClient({ platform: 'live', instance: 'demo', retry: false }); ``` ### Delays - **After a status response**, the client waits for the `Retry-After` header if it is present (seconds or an HTTP date). Without one, it uses exponential backoff with jitter: `baseDelayMs * 2^attempt`, plus a random amount up to `baseDelayMs`. - **Every wait is capped at `maxDelayMs`.** A `Retry-After` longer than the cap is shortened, so a retry can arrive earlier than the server asked. If you must respect long rate-limit windows, raise `maxDelayMs` or handle the `429` in your own job. - **After a network error or timeout**, the client always uses backoff, because there is no response to read. Network failures and status retries share one `maxRetries` budget per request. Once it is spent, the last failure is thrown. ### Changing the Network Rule `retryOnNetworkError` overrides the read/write classification. Omit it for automatic behavior, or set `true` or `false` for a client or a single request. The request value wins. ```js await client.api.listTickets({ filters: { visibility: 0 } }, { retryOnNetworkError: false }); await client.api.updateTicket({ ID: 42, status: 4 }, { retryOnNetworkError: true }); // Same value each time ``` `true` also enables `503` and other `retryOn` statuses for that operation, not only network errors. Use it on a write only when repeating the request is harmless. An update that sets fixed values usually is, a create is not. ### Request Timeout and Job Budget `timeoutMs` bounds **one attempt**, and each retry gets a fresh deadline. There is no default in JavaScript: without it, a stalled connection can wait forever, and the network retry never runs. ```js // Per request await client.api.listTickets({ filters: { visibility: 0 } }, { timeoutMs: 8000 }); // Or a client-wide default const client = createZeyosClient({ platform: 'live', instance: 'demo', validate: true, timeoutMs: 8000 }); ``` A timeout rejects with an `Error` where `isTimeout === true`, `code === 'ETIMEDOUT'` and `name === 'TimeoutError'`. `timeoutMs` does not limit a whole call. One read can take `(maxRetries + 1) * timeoutMs` plus the waits, and a token refresh adds requests of its own. To limit a call or a job, pass an `AbortSignal`: ```js const signal = AbortSignal.timeout(60_000); // Budget for everything below for (const page of pages) { await client.api.listTickets(page, { signal, timeoutMs: 8000 }); } ``` Aborting your signal stops the current attempt or backoff wait immediately and is never retried. The error is your signal's abort reason, which has no `isTimeout` property, so you can tell it apart from a per-attempt timeout. Aborting stops the client from waiting. It does not undo a request the server already received. ### Uncertain Write Outcomes When a write times out, loses its connection, is aborted or returns a `5xx`, **you do not know whether it happened**. A timed-out create may have succeeded. Retrying blindly can create a duplicate, so check first: ```js import { ZeyosApiError, ZeyosValidationError, normalizeListResult } from '@zeyos/client'; function outcomeIsUncertain(err) { if (err instanceof ZeyosValidationError) return false; // Nothing was sent. if (err instanceof ZeyosApiError) return err.status >= 500; return true; // Timeout, abort or dropped connection. } async function findByExternalRef(externalRef) { const result = await client.api.listTickets({ fields: ['ID'], filters: { 'extdata.external_ref': externalRef }, limit: 2, }); return normalizeListResult(result).data; } /** externalRef is your own stable ID, generated once per intended ticket. */ export async function createTicketOnce(externalRef, fields) { const input = { ...fields, extdata: { external_ref: externalRef } }; try { return await client.api.createTicket(input); } catch (err) { if (!outcomeIsUncertain(err)) throw err; const matches = await findByExternalRef(externalRef); // If this fails, stay uncertain. if (matches.length > 1) throw new Error(`Duplicate tickets for ${externalRef}.`); if (matches.length === 1) return matches[0]; throw err; // Not visible yet. Leave the decision to a later reconcile. } } ``` This example needs `external_ref` in your `customFields` contract and on the instance. A request that timed out may still commit after your check, so "not found" is not proof of failure: rerun the check after a delay before creating again. If a resource has no field that can hold such a reference, match on business fields you control, and escalate to a person when the match is ambiguous. Do not use retries for writes with external effects, such as sending a message. Reading the record back cannot prove what happened outside ZeyOS. ## Error Handling All API errors are thrown as `ZeyosApiError` instances. This class extends `Error` and includes structured information about the failed request. When the server returns an error body with a message, a short snippet of it is folded into `err.message` (e.g. `api.listTickets failed with HTTP 400: unknown filter field: bogus`), so the thrown error says *why*, not just the status code. The full body is always on `err.body`. ```js import { ZeyosApiError } from '@zeyos/client'; try { await client.api.getTicket({ ID: 999 }); } catch (err) { if (err instanceof ZeyosApiError) { console.log(err.status); // 404 console.log(err.statusText); // 'Not Found' console.log(err.operationId); // 'getTicket' console.log(err.service); // 'api' console.log(err.method); // 'GET' console.log(err.url); // Full request URL console.log(err.body); // Error response body (parsed JSON or text) } } ``` Use these properties for debugging, but keep `err.body` and `err.message` out of production logs: they can contain record data from the response. The [server-side guide](https://www.zeyos.com/guide/tutorials/server-side-integrations#6-report-failures-without-leaking-data) shows a redacted log format. The `ZeyosApiError` properties: `JsonValue` means a parsed JSON scalar, array, or plain JSON object. Non-JSON error responses are returned as strings. | Property | Type | Description | |----------|------|-------------| | `status` | `number` | HTTP status code (e.g. `404`, `401`, `500`) | | `statusText` | `string` | HTTP status text (e.g. `'Not Found'`) | | `operationId` | `string` | The operation that failed (e.g. `'getTicket'`) | | `service` | `string` | The service key (e.g. `'api'`, `'oauth2'`) | | `method` | `string` | HTTP method used (e.g. `'GET'`, `'POST'`) | | `url` | `string` | The full request URL | | `body` | `JsonValue` | The parsed JSON response body, plain-text error body, or `null` | | `headers` | `Record` | Response headers as a plain object | ### Unknown Operations Calling an operation that does not exist rejects with a `ZeyosApiError` that suggests the closest match, instead of an opaque `is not a function` error -- useful when an operationId differs from the underlying table name: ```js await client.api.listDunning({}); // ZeyosApiError: Unknown operation 'api.listDunning'. Did you mean 'listDunningNotices'? ``` ### Validation Errors When the client is created with `validate: true`, malformed requests reject with a `ZeyosValidationError` **before** any network call (see [Schema Introspection and Validation](#schema-introspection-and-validation)). It carries `operationId` and a structured `errors` array. ## Low-Level Requests For endpoints not covered by the generated methods, or when you need full control over the request, use `client.request()`. ### By Operation ID Reference a known operation by its service and operation ID: ```js const result = await client.request({ service: 'api', operationId: 'listTickets', body: { filters: { status: 1, visibility: 0 }, limit: 10 }, }); ``` ### By Path and Method Specify the HTTP method and path directly for custom or undocumented endpoints. This skips schema validation and read classification, so a `POST` list sent this way is not retried after a network error, a timeout or a `503`: ```js const result = await client.request({ service: 'api', method: 'POST', path: '/tickets/', body: { filters: { status: 1, visibility: 0 } }, }); ``` ### Raw Responses Pass `raw: true` to receive the full response envelope instead of just the parsed body: ```js const response = await client.request({ service: 'api', operationId: 'listTickets', body: { limit: 10 }, raw: true, }); console.log(response.status); // 200 console.log(response.headers); // Response headers console.log(response.data); // Parsed body ``` ## Request Options All generated methods and `client.request()` accept an optional second argument with request-level options: | Option | Type | Description | |--------|------|-------------| | `signal` | `AbortSignal` | An `AbortController` signal to cancel the request | | `timeoutMs` | `number` | Abort each attempt after N ms (composes with `signal`); also settable client-wide as `timeoutMs` | | `retryOnNetworkError` | `boolean` | Force or disable retrying network errors, timeouts and `retryOn` statuses other than `429` for this call (default: reads only) | | `validate` | `boolean` | Pass `true` to validate this call even if the client has validation off | | `raw` | `boolean` | Return the full response envelope instead of just the data | | `auth` | `string \| { mode?: string, accessToken?: string, access_token?: string, refreshToken?: string, refresh_token?: string, clientId?: string, client_id?: string, clientSecret?: string, client_secret?: string }` | Override the authentication mode or credentials for this request | | `baseUrl` | `string` | Override the base URL for this request | | `bodyType` | `'json' \| 'form'` | Force a body encoding | Example with an abort controller: ```js const controller = new AbortController(); // Cancel after 5 seconds setTimeout(() => controller.abort(), 5000); const tickets = await client.api.listTickets( { limit: 100 }, { signal: controller.signal } ); ``` :::tip Persisting refreshed tokens When using token mode with `autoRefresh` in a trusted environment, tokens are updated in the token store automatically. If you use a `MemoryTokenStore`, those refreshed tokens will be lost on page reload. Use the `syncTokens` pattern to persist them: ```js async function syncTokens() { const tokenSet = await client.auth.getTokenSet(); if (tokenSet?.accessToken) { localStorage.setItem('zeyos_tokens', JSON.stringify(tokenSet)); } } // Call after important API operations const tickets = await client.api.listTickets({ limit: 50 }); await syncTokens(); ``` ::: --- Canonical: https://www.zeyos.com/guide/javascript-client/practical-guide # Practical Implementation Guide This guide documents patterns and gotchas discovered while building a real application on top of the ZeyOS JavaScript client. It supplements the reference documentation with things that only become apparent once you start making actual API calls. Create clients with `validate: true`. Many of the mistakes below are then rejected before a request is sent, with a hint naming the correct field. The examples assume that setting. ## HTTP Method Conventions ZeyOS uses an unconventional but consistent REST convention that often surprises developers: | Operation | HTTP Method | Notes | |-----------|-------------|-------| | List records | **POST** | Query params (filters, sort, fields) go in the request body | | Get a record | **GET** | | | Create a record | **PUT** | Not POST — ZeyOS uses PUT for creation | | Update a record | **PATCH** | Partial updates; only send fields you want to change | | Delete a record | **DELETE** | | | Check existence | **HEAD** | Returns `true` (no body) on success | The most important one to internalise: **list operations are POST requests**. This makes sense once you consider that complex queries with nested filters would quickly exceed URL length limits as query strings. ## Passing the Request Body for Update Operations Generated methods accept the natural flat style for operations that have both a path parameter and a request body: ```js await client.api.updateTicket({ ID: 42, status: 4, priority: 2 }); ``` The client routes known path/query/header parameters to the request URL and sends the remaining non-reserved keys as the body. Explicit `body` and `data` keys are still supported when you want to separate those concerns yourself: ```js await client.api.updateTicket({ ID: 42, body: { status: 4, priority: 2 } }); await client.api.updateTask({ ID: taskId, data: { name: 'New name', duedate: ts } }); ``` For low-level `client.request()` calls, prefer explicit `body` because there is no generated operation metadata to tell the client which keys are URL parameters. Pass `operationId` when one exists: a method and path call also skips validation, so `validate: true` will not catch a wrong field name there. ## Always use `filters` (plural) There is one filter parameter, and it is `filters`. Use it for every field type — scalars, enums, and GIN-indexed foreign keys alike. The singular `filter` appears once in the bundled OpenAPI spec and nowhere in ZeyOS's [published API documentation](https://www.zeyos.com/api/llms-full.txt), whose worked examples use `filters` throughout. Treat the spec as the outlier. The CLI's schema validation rejects `filter` outright and tells you to use `filters`. ```js // Scalars, enums and foreign keys all go in the same place const tickets = await client.api.listTickets({ filters: { visibility: 0, project: projectId }, sort: ['-lastmodified'], limit: 500, }); // A foreign key is no different const tasks = await client.api.listTasks({ fields: ['ID', 'tasknum', 'name', 'duedate', 'assigneduser'], filters: { ticket: ticketId, visibility: 0 }, sort: ['+name'], limit: 200, }); ``` :::tip Use `filters` for everything. If you pass `filter` with validation enabled, the client rejects it and names the correct key. ::: ## Use `visibility: 0` — where the column exists Many ZeyOS records have a `visibility` field controlling soft-deletion and archiving, where `0` = regular, `1` = archived, `2` = deleted. Records with `visibility > 0` are hidden from normal views, so include `visibility: 0` unless you intentionally want them: ```js const filters = { visibility: 0 }; // Add resource-specific filters after if (projectId) filters.project = projectId; ``` :::warning Not every resource has the column `visibility` exists on `accounts`, `contacts`, `tickets`, `tasks`, `projects`, `items`, `documents`, `notes`, `opportunities`, `appointments`, `campaigns`, `mailinglists`, `storages`, `contracts` and `ledgers`. It does **not** exist on `transactions` — nor any billing/procurement entity derived from it — nor on `payments`, `messages`, `actionsteps`, `addresses`, `users`, `prices` or `dunning`. Filtering on a column a resource lacks returns an **opaque HTTP 400** that does not name the offending field. Check with `client.schema.describe('')` or `zeyos describe ` when unsure. ::: ## Normalising List Responses List operations are not perfectly uniform across the whole surface area. Use the shared helper so every call site follows the same response-shape handling: ```js import { normalizeListResult } from '@zeyos/client'; const result = await client.api.listTickets({ filters: { visibility: 0 } }); const { data: tickets } = normalizeListResult(result); ``` Use `normalizeCountResult()` for count-only requests. ## Date and Timestamp Handling ZeyOS stores all dates as **Unix timestamps in seconds** (not milliseconds). When reading: ```js // Convert to a JavaScript Date const date = new Date(ticket.duedate * 1000); // Format for display const label = new Date(ticket.duedate * 1000).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric', }); // Check if overdue const isOverdue = ticket.duedate * 1000 < Date.now(); ``` When writing (e.g. from an HTML ``): ```js const dueDateVal = form.querySelector('#due-date').value; // '2026-03-15' const duedate = dueDateVal ? Math.floor(new Date(dueDateVal).getTime() / 1000) : null; await client.api.updateTicket({ ID: id, body: { duedate } }); ``` ## Selecting Fields for Performance Always pass a `fields` array in list requests. Without it, every field on every record is returned, which can significantly increase payload size and response time: ```js // ✗ Returns all fields for every ticket const tickets = await client.api.listTickets({ limit: 500 }); // ✓ Returns only what you need const tickets = await client.api.listTickets({ fields: ['ID', 'ticketnum', 'name', 'status', 'priority', 'duedate'], filters: { visibility: 0 }, limit: 500, }); ``` For single-record GET operations, field selection is not available — all standard fields are returned. Use query flags like `extdata: 1` and `tags: 1` to opt into additional data: ```js const ticket = await client.api.getTicket({ ID: id, extdata: 1, tags: 1 }); ``` ## Optimistic UI Updates with Server Verification For immediate feedback on user actions (like drag-and-drop), apply the change to local state first, then confirm with the server and revert if it fails. Use the response body to confirm the actual resulting value: ```js const fromStatus = ticket.status; // 1. Optimistic update — instant visual feedback ticket.status = toStatus; updateColumn(fromStatus); updateColumn(toStatus); try { // 2. Send PATCH — response body contains the updated record const updated = await client.api.updateTicket({ ID: ticket.ID, body: { status: toStatus }, }); // 3. Confirm — use the server's value in case it was clamped or rejected const confirmedStatus = updated?.status ?? toStatus; if (confirmedStatus !== toStatus) { ticket.status = confirmedStatus; updateColumn(toStatus); updateColumn(confirmedStatus); } } catch (err) { // 4. Revert on failure ticket.status = fromStatus; updateColumn(fromStatus); updateColumn(toStatus); showError(`Move failed: ${err.message}`); } ``` ## Persisting Refreshed Tokens When using token mode with `autoRefresh: true` in a trusted environment, the client silently refreshes expired access tokens. The refreshed tokens are stored in the `MemoryTokenStore` but lost on page reload unless you persist them explicitly. Call a sync function after important API operations: ```js async function syncTokens() { try { const ts = await client.auth.getTokenSet(); if (ts?.accessToken) { localStorage.setItem('zeyos_tokens', JSON.stringify({ accessToken: ts.accessToken, refreshToken: ts.refreshToken, expiresAt: ts.expiresAt, refreshTokenExpiresAt: ts.refreshTokenExpiresAt, })); } } catch { // Non-critical — silently ignore } } // Usage const tickets = await client.api.listTickets({ filters: { visibility: 0 } }); await syncTokens(); // Persist any refreshed tokens ``` ## Session Detection Without Tokens If you don't have an OAuth token but the user is already logged into ZeyOS in the same browser, you can detect their session via the userinfo endpoint: ```js async function trySessionAuth(instanceUrl) { try { const res = await fetch(`${instanceUrl}oauth2/v1/userinfo`, { credentials: 'include', }); if (res.ok) return await res.json(); } catch { // No session } return null; } const userInfo = await trySessionAuth('https://cloud.zeyos.com/demo/'); if (userInfo) { // Session is active — initialize in session mode const client = createZeyosClient({ platform: instanceUrl, validate: true, auth: { mode: 'session', session: { enabled: true, credentials: 'include' } }, }); } ``` :::note Session mode requires that your app is served from the same origin as ZeyOS, or that the ZeyOS instance is configured to allow cross-origin requests with credentials. If you are on a different domain, token mode is more reliable. ::: ## Navigating to ZeyOS Views To link users directly to a record inside the ZeyOS web interface, construct a URL in this format: ``` ?umi=&page=&id=&tab= ``` Common examples: ```js const baseUrl = 'https://cloud.zeyos.com/demo/'; // Link to a ticket `${baseUrl}?umi=tickets&page=details_ticket&id=${ticketId}&tab=0` // Link to a task (within the tickets module) `${baseUrl}?umi=tickets&page=details_ticket&id=${taskId}&tab=0` // Link to an account `${baseUrl}?umi=accounts&page=details_account&id=${accountId}&tab=0` ``` ## Extended Data (extdata) Many ZeyOS entities support custom fields via `extdata`. These are returned as a nested object. Keys differ per instance. With validation on, naming a key requires a reviewed [`customFields` contract](https://www.zeyos.com/guide/javascript-client/making-requests#custom-field-contracts) for that instance, so the first example below needs `region` and `customer_type` in the contract for `tickets`. Do not turn validation off to get past an unknown key: resolve the key from the instance's `customfields` definitions instead. `extdata: 1` reads the whole collection without a contract. ```js // Request extended data in a list const tickets = await client.api.listTickets({ fields: ['ID', 'name', 'extdata.region', 'extdata.customer_type'], filters: { visibility: 0 }, }); // Or include all extdata for single-record fetches const ticket = await client.api.getTicket({ ID: id, extdata: 1 }); console.log(ticket.extdata); // { region: 'EMEA', customer_type: 'Enterprise', ... } ``` When saving extended data back, pass it as a plain object: ```js await client.api.updateTicket({ ID: id, body: { extdata: { region: 'APAC', customer_type: 'SMB' }, }, }); ``` ## Common Status and Priority Values Ticket and task status and priority values are plain integers. The canonical values observed in the ZeyOS API: ### Ticket Status | Value | Label | |-------|-------| | `0` | Not Started | | `1` | Awaiting Acceptance | | `2` | Accepted | | `3` | Rejected | | `4` | Active | | `5` | Inactive | | `6` | Feedback Required | | `7` | Testing | | `8` | Cancelled | | `9` | Completed | | `10` | Failed | | `11` | Booked | ### Ticket Priority | Value | Label | |-------|-------| | `0` | Lowest | | `1` | Low | | `2` | Medium | | `3` | High | | `4` | Highest | ## Error Handling Checklist `ZeyosApiError` is thrown for all non-2xx responses. Key properties to check: ```js import { ZeyosApiError } from '@zeyos/client'; try { await client.api.updateTicket({ ID: id, body: data }); } catch (err) { if (!(err instanceof ZeyosApiError)) throw err; // Re-throw unexpected errors if (err.status === 401) { // Session expired or token invalid — redirect to login } else if (err.status === 403) { // Insufficient permissions } else if (err.status === 404) { // Record does not exist } else if (err.status === 409) { // Conflict — record was modified since last read (check If-Match header usage) } else { // Generic error. Log the category, not err.body: it can contain record data. console.error(err.status, err.operationId); } } ``` A thrown error does not always mean a write failed. After a timeout, a dropped connection or a `5xx`, the write may have succeeded, so [reconcile before retrying](https://www.zeyos.com/guide/javascript-client/making-requests#uncertain-write-outcomes). :::tip On 401, the client automatically retries with a refreshed token if `autoRefresh: true` is set, a refresh token is available, and OAuth client credentials are configured. You will only see a 401 error if the refresh also fails — typically meaning the user's session has fully expired. ::: --- Canonical: https://www.zeyos.com/guide/okf/keeping-fresh # Keeping OKF Fresh The OKF bundle is generated from the OpenAPI/dbref snapshots in `openapi/`. When ZeyOS updates its database model or API, those specs are re-exported and the bundle is regenerated. The design keeps that safe and observable. ## What protects curation 1. **Deterministic generation.** `npm run generate` runs the OKF producer alongside the client codegen. Re-running with unchanged specs produces **no diff** (no timestamp churn). 2. **Managed blocks.** Only the fenced `okf:generated` region of each entity concept is rewritten; curated `# Notes`/`# Metrics` prose is spliced back unchanged. See the [overview](https://www.zeyos.com/guide/okf/overview#generated-vs-curated-managed-blocks). 3. **`source_snapshot`.** The root `index.md` frontmatter carries a content hash of the schema. When the specs change, the hash changes — a staleness signal for consumers and the drift gate. 4. **`log.md` schema diff.** The producer diffs the new schema against the last snapshot and appends a dated entry (added/removed fields, changed enums, new foreign keys) — an OKF-native, human- and agent-readable record of *what changed when the model updated*. ## The spec-refresh runbook ```bash # 1. Replace the snapshots with the newly exported specs # openapi/api.json, openapi/dbref.json # 2. Regenerate the client and the OKF bundle npm run generate # 3. Review the diff and the changelog git diff okf/ agents/shared/zeyos-entity-reference.md cat okf/log.md # newest dated entry summarizes the schema changes # 4. Validate conformance + the drift gate npm run okf:check # 5. (Optional) enrich weak concepts — see ./04-loops.md npm run okf:refine -- --concept entities/ # 6. Commit git add okf agents/shared src/generated && git commit -m "Refresh schema + OKF" ``` ## The drift gate (CI) `npm run okf:check` runs `test/okf.test.js`, which regenerates the bundle and asserts the committed content matches a fresh regen. It fails the build when: - the specs changed but `okf/` wasn't regenerated and committed; - someone hand-edited a generated region; - a concept is non-conformant, an API-backed entity lacks a concept, or a structural cross-link is broken; - the shared reference's generated operationId table fell out of sync. --- Canonical: https://www.zeyos.com/guide/okf/loops # Refining with Loops OKF and the skill pack are refined with the same loop machinery the agent protocol already uses — a measurement loop and a refinement loop that close on each other. ## Measure: OKF as agent context The agent protocol (`npm run test:agent-protocol`) drives a real coding agent through business scenarios against a live demo instance and verifies each outcome independently. A `--context` axis chooses which knowledge the agent is pointed at: ```bash # Skills only (default), OKF only, or both npm run test:agent-protocol -- --context okf --scenario b03-billing-transaction-count npm run test:agent-protocol -- --context both --layer b ``` The bundle is exposed to the agent as `ZEYOS_OKF_ROOT` (mirroring `ZEYOS_SKILL_ROOT`). The loop runner sweeps the axis and reports per-context pass rates, so you can see whether OKF-as-context lifts accuracy and which concepts correlate with failures: ```bash npm run test:agent-loop -- --context skills,okf,both --read-only --agents opencode ``` The scorecard tells you which **skill** and which **OKF concept** to improve. ## Refine: generate → validate → judge → apply `npm run okf:refine` improves a concept's **curated** guidance (never the generated block): ```bash # Target a concept directly, or derive weak concepts from a run's scorecard npm run okf:refine -- --concept entities/tickets npm run okf:refine -- --scorecard test/agent-protocol/results//scorecard.json npm run okf:refine -- --concept entities/transactions --apply # write the accepted revision ``` Each target goes through: 1. **Generate** — a proposer model drafts improved curated notes from the current concept. 2. **Validate** — any field the proposal references must exist on the entity (checked against the client schema), so the model can't invent columns or enums. 3. **Judge** — a held-out judge model (`agentProtocol.judgeModel`, reusing `judge.mjs`) approves only if the revision is more accurate and useful and contradicts no schema fact. 4. **Apply** — with `--apply`, the accepted notes replace the curated tail; the generated managed block is never touched. Without `--apply`, proposals are written for review. ## The closed loop ``` specs + curation ──▶ okf/ ──▶ agent-protocol (--context okf) ──▶ scorecard ▲ │ └──────────── okf:refine (curated notes) ◀─────────────────┘ ``` Drive it self-paced with the Claude Code `/loop` skill: *measure → pick the weakest concept → refine → re-measure*, until pass rates plateau. --- Canonical: https://www.zeyos.com/guide/okf/overview # OKF Overview The **Open Knowledge Format (OKF v0.1)** is Google's minimal, vendor-neutral spec for sharing the metadata and curated context that surrounds data: a directory of Markdown files, each with YAML frontmatter whose only required field is `type`, optional `index.md` and `log.md`, and Markdown cross-links that turn the directory into a knowledge graph. Producers emit a bundle; consumers (coding agents, viewers, search) read it. `@zeyos/client` ships a conformant OKF bundle under [`okf/`](https://github.com/zeyos/client/tree/main/okf) that describes the ZeyOS data model, and tooling to produce, consume, validate, and refine it. ## Why OKF fits ZeyOS The client already derives a compact schema from the OpenAPI/dbref specs. OKF turns that — plus the curated business knowledge that used to live only in the skill pack — into a portable knowledge layer any agent or tool can read, independent of this client. The bundle is **canonical** for ZeyOS structural facts: the hand-maintained `agents/shared/zeyos-entity-reference.md` operationId table is now generated from the same source, so the skills and the OKF bundle can't drift apart. ## Bundle layout ``` okf/ index.md # root listing; frontmatter: okf_version, source_snapshot log.md # schema-change history (auto-appended on real changes) entities/ # one concept per API-backed entity — type: ZeyOS Entity index.md accounts.md tickets.md transactions.md … metrics/ # business metric definitions — type: Metric playbooks/ # step-by-step query workflows — type: Playbook concepts/ # cross-cutting rules / footguns — type: Reference ``` Each entity concept carries a **Schema** table (column, type, nullability, default, index, foreign key), **Foreign Keys** (cross-linked to the target entity), **Enums** (parsed from the schema), **Indexes** (including the GIN/partial indexes behind the `filters` footgun), and **Operations** (the real `@zeyos/client` operationIds, read straight from `api.json`). ## Generated vs curated: managed blocks Each entity concept mixes auto-generated structure with curated prose. The generated region is fenced by HTML-comment markers: ```markdown # Schema … # Notes Curated guidance — preserved across every regeneration. ``` The producer rewrites **only** the region between the markers. Curated `# Notes`/`# Metrics` prose (added by a human or the refinement loop) is preserved verbatim, so regenerating after a schema change never clobbers curation. See [Keeping OKF fresh](https://www.zeyos.com/guide/okf/keeping-fresh). ## Relationship to the skill pack Two projections of one knowledge core: - **Skills** (`agents/`) stay the task/runner-facing layer: the operating contract, "act, don't plan", and safety. - **OKF** (`okf/`) is the reference layer: the entity schema-of-record plus the metric, playbook, and concept catalog. Skills point into `okf/`; when a schema fact in a shared reference and in `okf/` ever disagree, `okf/` wins. --- Canonical: https://www.zeyos.com/guide/okf/producing-and-consuming # Producing & Consuming OKF ## CLI ```bash zeyos okf list # list concepts (type, id, title); --json for automation zeyos okf show tickets # print a concept (bare resource name or entities/tickets) zeyos okf check # validate the bundle for OKF v0.1 conformance (exit non-zero on error) zeyos okf export --out ./okf # copy the shipped bundle into a directory zeyos okf build --out ./okf # synthesize a bundle from the client's schema ``` `export` copies the rich shipped bundle (with curated metrics/playbooks/notes). `build` synthesizes a structural bundle from the client's introspection surface — useful where the shipped files aren't present, or to diff against a live instance. ## JavaScript client The OKF surface is exported from `@zeyos/client`: ```js import { buildOkf, loadOkfBundle, validateOkfBundle, validateOkfFiles, OKF_VERSION } from '@zeyos/client'; // Synthesize a conformant bundle from the client's generated schema (pure; browser-safe). const files = buildOkf(); // { 'entities/tickets.md': '…', … } const { valid, errors } = validateOkfFiles(files); // Load the shipped (or any) bundle from disk (Node only). const bundle = await loadOkfBundle('node_modules/@zeyos/client/okf'); bundle.version; // '0.1' bundle.concepts['entities/tickets'].frontmatter // { type: 'ZeyOS Entity', title: 'Tickets', … } // Validate a directory or an in-memory file map. await validateOkfBundle('okf'); ``` - `buildOkf({ schema?, services? })` — pure producer; defaults to the generated `SCHEMA`/`SERVICES`. - `loadOkfBundle(dir)` — Node-only reader (lazy `fs`), returns `{ version, files, concepts }`. - `validateOkfBundle(dirOrFiles)` / `validateOkfFiles(files)` — v0.1 conformance check. ## Build-time producer In this repo, `npm run okf:build` (or `npm run generate`, which runs it alongside the client codegen) emits the rich bundle from `openapi/{api,dbref}.json` plus the curated content in `scripts/data/okf-curation.mjs`. It also injects the generated operationId table into `agents/shared/zeyos-entity-reference.md`. Validate with `npm run okf:check`. --- Canonical: https://www.zeyos.com/guide/sample-apps/crm # CRM Account List The `samples/crm/` application shows how to build a read-heavy account UI with server-side search, joined contact fields, modal editing, and predictable pagination. ## Problem Solved Use this sample when you need a compact CRUD interface for operational data such as accounts or contacts and you want the server, not the browser, to do the filtering and sorting work. ## Auth Model The sample uses the same dual browser model as the Kanban app: - **Token mode** with a pre-obtained access token and optional stored refresh token - **Session mode** with a `/oauth2/v1/userinfo` probe Configuration can come from `data-zeyos-*` attributes, `localStorage`, or the `window.ZeyOS` console API. For long-lived browser sessions, prefer session mode or move OAuth refresh to a backend. Session mode only works from the same origin or when the ZeyOS instance allows credentialed CORS, so token mode is usually the local-development path. The sample keeps browser code free of client credentials. ## Main API Calls | Operation | Usage in the sample | |-----------|---------------------| | `listAccounts` | Fetch paginated CRM rows with aliased and joined fields | | `getAccount` | Load the full record before editing | | `createAccount` | Create new accounts from the modal | | `updateAccount` | Apply modal edits via `{ ID, body: data }` | | `deleteAccount` | Remove records from the dataset | ## Reusable Patterns - **Object-form `fields`**: the UI requests aliased fields such as `City: 'contact.city'` - **Server-side search**: full-text queries use the `query` parameter instead of client-side filtering - **Server-side sorting**: column headers map friendly names back to raw API fields - **Update helpers**: update helpers use `{ ID, body: data }` to keep path parameters separate from changed fields - **Token persistence**: runtime tokens are stored in `localStorage` for the next page load ## Safe to Copy The sample is a strong reference for: - dot-notation joins with aliased output fields - building sort maps from UI column names to API field paths - debounced search that resets pagination - small browser apps that still keep auth, state, API, and UI concerns separate When copying code outside this repository, replace source-tree imports such as `../../../src/index.js` with an import path that exists in your app: an npm package import, a vendored copy of `src/`, or a local symlink. ## Run Locally ```bash cd /path/to/zeyos/client python3 -m http.server 8080 ``` Open: ```text http://localhost:8080/samples/crm/ ``` ## Console API | Method | Description | |--------|-------------| | `ZeyOS.setUrl(url)` | Set the ZeyOS instance URL | | `ZeyOS.setToken(access, refresh?)` | Persist access and optional refresh tokens | | `ZeyOS.status()` | Print connection state, search, sort, and page info | | `ZeyOS.logout()` | Clear stored config and reload | | `ZeyOS.reconnect()` | Reload and re-run the auth boot sequence | ## What to Read Next - [Browser UI Playbook](https://www.zeyos.com/guide/tutorials/build-your-own-zeyos-frontend) - [Making Requests](https://www.zeyos.com/guide/javascript-client/making-requests) - [CRM sample README](https://www.zeyos.com/guide/sample-apps/crm) --- Canonical: https://www.zeyos.com/guide/sample-apps/dashboard # Dashboard The `samples/dashboard/` application shows how to build a read-only operational dashboard that combines multiple ZeyOS queries into one browser view. ## Problem Solved Use this sample when you need KPI cards, summary charts, and recent-activity panels without building a heavy frontend stack. ## Auth Model The dashboard follows the same browser auth model as the other samples: - **Token mode** with a pre-obtained access token and optional stored refresh token - **Session mode** via `/oauth2/v1/userinfo` Configuration can come from `data-zeyos-*` attributes, `localStorage`, or the `window.ZeyOS` console API. For long-lived browser sessions, prefer session mode or move OAuth refresh to a backend. Session mode only works from the same origin or when the ZeyOS instance allows credentialed CORS, so token mode is usually the local-development path. The sample avoids shipping client credentials in browser code. ## Main API Calls | Operation | Usage in the sample | |-----------|---------------------| | `listTickets` with `count: true` | Build total, active, and overdue KPI cards | | `listTickets` with explicit fields | Render recent ticket activity | | `listAccounts` with `count: true` | Build total account KPIs | | `listAccounts` with joins | Render recent account activity | ## Reusable Patterns - **Parallel data loading**: the dashboard fires independent queries with `Promise.all` - **Count-first dashboards**: KPI cards use count-enabled requests instead of loading full datasets - **Defensive count handling**: the sample treats count-enabled responses conservatively instead of assuming a single wrapper shape - **Read-only browser views**: the app demonstrates a useful UI that never mutates records - **Minimal field selection**: each summary query requests only the fields needed for display ## Safe to Copy The dashboard is a good template for: - status or backlog overview pages - home pages for internal operator portals - management dashboards that aggregate a few targeted ZeyOS queries - small browser apps that need strong perceived performance from parallel loading When copying code outside this repository, replace source-tree imports such as `../../../src/index.js` with an import path that exists in your app: an npm package import, a vendored copy of `src/`, or a local symlink. ## Run Locally ```bash cd /path/to/zeyos/client python3 -m http.server 8080 ``` Open: ```text http://localhost:8080/samples/dashboard/ ``` ## Console API | Method | Description | |--------|-------------| | `ZeyOS.setUrl(url)` | Set the ZeyOS instance URL | | `ZeyOS.setToken(access, refresh?)` | Persist access and optional refresh tokens | | `ZeyOS.status()` | Print effective connection state | | `ZeyOS.logout()` | Clear stored config and reload | | `ZeyOS.reconnect()` | Reload and re-run the auth boot sequence | ## What to Read Next - [Server-Side Integrations](https://www.zeyos.com/guide/tutorials/server-side-integrations) - [Making Requests](https://www.zeyos.com/guide/javascript-client/making-requests) - [Dashboard sample README](https://www.zeyos.com/guide/sample-apps/dashboard) --- Canonical: https://www.zeyos.com/guide/sample-apps/kanban # Kanban Board The `samples/kanban/` application shows how to build a browser-based work queue on top of ZeyOS using plain ES modules and the generated JavaScript client. ## Problem Solved Use this sample when you need a UI for operators who move work through statuses, inspect details, and create follow-up work without leaving a lightweight browser app. ## Auth Model The sample supports both browser auth models used throughout this docs set: - **Token mode** using a pre-obtained access token and optional stored refresh token - **Session mode** by probing `/oauth2/v1/userinfo` with `credentials: 'include'` Configuration can come from: - `data-zeyos-*` attributes in `samples/kanban/index.html` - persisted `localStorage` values - the `window.ZeyOS` console API For long-lived browser sessions, prefer session mode or move OAuth refresh to a backend. Session mode only works from the same origin or when the ZeyOS instance allows credentialed CORS, so token mode is usually the local-development path. The sample does not embed client credentials for browser-side refresh. ## Main API Calls | Operation | Usage in the sample | |-----------|---------------------| | `listTickets` | Load the board columns and context-filtered ticket lists | | `getTicket` | Fetch the full ticket for the detail dialog | | `createTicket` | Create new tickets from the modal | | `updateTicket` | Move tickets between columns and edit ticket details | | `deleteTicket` | Remove tickets from the board | | `listTasks` | Load tasks linked to a ticket | | `listProjects` | Populate the project context dropdown | ## Reusable Patterns - **Auth boot sequence**: URL resolution, token detection, session probe, then app boot - **Optimistic UI update**: move the card first, then confirm with `updateTicket`, then revert on failure - **Explicit update body**: all ticket updates in this sample use `{ ID, body }` for clarity (the flat spread form `{ ID, status }` also works) - **Context filtering**: a single filter object drives board views such as “all tickets” or “project tickets” - **Persistent UI settings**: board columns and runtime config live in `localStorage` ## Safe to Copy The following pieces are good starting points for a new UI: - the token/session boot sequence in `samples/kanban/js/main.js` - the session detection helper in `samples/kanban/js/auth.js` - the optimistic update pattern around `updateTicket` - the `window.ZeyOS` console API for local development and troubleshooting When copying code outside this repository, replace source-tree imports such as `../../../src/index.js` with an import path that exists in your app: an npm package import, a vendored copy of `src/`, or a local symlink. ## Run Locally Serve the repository root with any static file server: ```bash cd /path/to/zeyos/client python3 -m http.server 8080 ``` Then open: ```text http://localhost:8080/samples/kanban/ ``` ## Console API | Method | Description | |--------|-------------| | `ZeyOS.setUrl(url)` | Set the ZeyOS instance URL | | `ZeyOS.setToken(access, refresh?)` | Persist access and optional refresh tokens | | `ZeyOS.status()` | Print effective connection state | | `ZeyOS.logout()` | Clear stored config and reload | | `ZeyOS.reconnect()` | Reload and re-run the auth boot sequence | ## What to Read Next - [Browser UI Playbook](https://www.zeyos.com/guide/tutorials/build-your-own-zeyos-frontend) - [Practical Guide](https://www.zeyos.com/guide/javascript-client/practical-guide) - [Kanban sample README](https://www.zeyos.com/guide/sample-apps/kanban) --- Canonical: https://www.zeyos.com/guide/tutorials/application-developers # Application Developers This path is for developers building external applications that use ZeyOS as the central source of business data and business logic. For v1, the scope is explicitly **external integrations**: - browser UIs - server-side services - automation backends - connected internal tools These docs do **not** cover authoring native ZeyOS platform artifacts. ## Choose Your Starting Point | Goal | Start here | Why | |------|------------|-----| | Decide between browser, token, and server architectures | [Integration Architecture](https://www.zeyos.com/guide/tutorials/integration-architecture) | Compares auth and deployment models before you write code | | Build a browser UI or internal tool | [Browser UI Playbook](https://www.zeyos.com/guide/tutorials/build-your-own-zeyos-frontend) | Covers session mode, controlled browser token mode, list queries, CRUD, and UI-safe patterns | | Build a backend, worker, or scheduled integration | [Server-Side Integrations](https://www.zeyos.com/guide/tutorials/server-side-integrations) | Covers token storage, refresh, low-level requests, and sync jobs | | Reuse working implementation patterns | [Sample Applications](https://www.zeyos.com/guide/sample-apps/kanban) | Shows reusable frontend patterns without framework lock-in | | Need the full generated surface | [JavaScript Client](https://www.zeyos.com/guide/javascript-client/getting-started) | Full API coverage and low-level request escape hatch | ## Recommended Defaults - Prefer `@zeyos/client` for JavaScript applications. - Use `filters` in client code unless you have a reason to stay with raw API `filter`. - Use `visibility: 0` on resources that **have** the column — `accounts`, `contacts`, `tickets`, `tasks`, `projects`, `items`, `documents`, `notes`, `opportunities`, `appointments`, `campaigns`, `mailinglists`, `storages`, `contracts`, `ledgers` — unless you want archived (`1`) or deleted (`2`) records. **Most other resources have no `visibility` column** — `transactions` (and every billing/procurement entity), `payments`, `messages`, `actionsteps`, `addresses`, `users`, `prices`, `dunning` — and filtering on it there returns an opaque HTTP 400. Check with `zeyos describe ` when unsure. - Use explicit `body` objects for update operations that also include path parameters such as `ID`. - Treat `extdata` and `expand` as separate concepts: - `extdata` exposes custom fields - `expand` inlines JSON or binary columns ## Next Steps - Read [Integration Architecture](https://www.zeyos.com/guide/tutorials/integration-architecture) - Pick either the [Browser UI Playbook](https://www.zeyos.com/guide/tutorials/build-your-own-zeyos-frontend) or [Server-Side Integrations](https://www.zeyos.com/guide/tutorials/server-side-integrations) - Use the [API Reference](https://www.zeyos.com/guide/api-reference/data-retrieval) and [JavaScript Client docs](https://www.zeyos.com/guide/javascript-client/making-requests) as the detailed reference layers --- Canonical: https://www.zeyos.com/guide/tutorials/build-your-own-zeyos-frontend # Browser UI Playbook This playbook walks through building a browser-based interface on top of the ZeyOS REST API using the `@zeyos/client` JavaScript library. By the end, you will have a working single-page application that authenticates, queries data, renders a UI, and writes changes back with patterns you can reuse in any ZeyOS-connected browser app. We will build a minimal **Ticket Dashboard** step by step. No application framework or build step is required -- just ES modules, the ZeyOS client, and a browser. The starter HTML below uses the Tailwind CDN only for concise demo styling; remove it or self-host your CSS for production or stricter security environments. --- ## Prerequisites - A **ZeyOS instance** with some ticket data (e.g. `https://cloud.zeyos.com/demo/`) - An **access token** (obtain one via the [CLI](https://www.zeyos.com/guide/cli/getting-started) or the ZeyOS OAuth2 flow) - A **local HTTP server** to serve static files (e.g. `python3 -m http.server 8080` or `npx serve .`) --- ## Step 1: Project Setup Create a project folder with this structure: ``` my-zeyos-app/ index.html app.js zeyos-client/ # symlink or copy of the @zeyos/client src/ directory ``` Link the ZeyOS client source so your browser can import it: ```bash # Symlink (Linux/macOS) ln -s /path/to/zeyos/client/src ./zeyos-client # Or copy cp -r /path/to/zeyos/client/src ./zeyos-client ``` Create a minimal `index.html`: ```html My ZeyOS App

My ZeyOS Ticket Dashboard

Loading...
``` --- ## Step 2: Initialize the Client Create `app.js` and set up the ZeyOS client. You have two authentication options: ### Option A: Token Mode Use this when you already have an OAuth access token. This is most useful for development and controlled demos: ```js import { createZeyosClient, MemoryTokenStore, normalizeListResult } from './zeyos-client/index.js'; const client = createZeyosClient({ platform: 'https://cloud.zeyos.com/demo/', validate: true, auth: { mode: 'oauth', oauth: { tokenStore: new MemoryTokenStore({ accessToken: 'YOUR_ACCESS_TOKEN', }), }, }, }); ``` Use session mode or a backend token broker for long-lived browser apps. Do not embed `clientSecret` in shipped browser code just to enable OAuth refresh. ### Option B: Session Mode Use this when you are already logged into ZeyOS in the same browser: ```js import { createZeyosClient, normalizeListResult } from './zeyos-client/index.js'; const client = createZeyosClient({ platform: 'https://cloud.zeyos.com/demo/', validate: true, auth: { mode: 'session', session: { enabled: true, credentials: 'include' }, }, }); ``` :::tip For a real application, never hardcode tokens in source files. Persist them only if you control the environment, or prefer session mode or a backend-assisted token flow. See the [Kanban sample](https://www.zeyos.com/guide/sample-apps/kanban) for a reusable config pattern. ::: `validate: true` catches wrong field names, enum values and join paths in the browser before a request is sent. To name individual custom fields, add a reviewed `customFields` contract for this instance to the same options: ```js customFields: { tickets: { region: { type: 'string', enum: ['EMEA', 'APAC'] } }, }, ``` Custom fields differ per instance, so a contract must ship with a build for that instance. See [custom-field contracts](https://www.zeyos.com/guide/javascript-client/making-requests#custom-field-contracts). --- ## Step 3: Fetch and Display Data Add a function to load tickets and render them as a table: ```js async function loadTickets() { const result = await client.api.listTickets({ fields: ['ID', 'ticketnum', 'name', 'status', 'priority', 'duedate', 'assigneduser'], filters: { visibility: 0 }, sort: ['-lastmodified'], limit: 50, }); const { data: tickets } = normalizeListResult(result); return tickets; } ``` Key things to note: - **List operations are POST requests.** The client handles this; you just pass an object. - **Include `visibility: 0`** to exclude archived/deleted records — on resources that have the column. `transactions`, `payments` and `messages` do not, and filtering on it there returns an opaque HTTP 400. - **Use `filters` (plural)** for best compatibility across all field types. This handles both simple equality filters and GIN-indexed foreign key fields. - **Always specify `fields`** to keep payloads small. Without it, every field on every record is returned. - **Normalise the response.** Use `normalizeListResult()` so list calls share one array/object-wrapper handling path. Use `normalizeCountResult()` for count-only requests. Now render it: ```js const STATUS_LABELS = { 0: 'Not Started', 1: 'Awaiting Acceptance', 2: 'Accepted', 3: 'Rejected', 4: 'Active', 5: 'Inactive', 6: 'Feedback Required', 7: 'Testing', 8: 'Cancelled', 9: 'Completed', 10: 'Failed', 11: 'Booked', }; const PRIORITY_LABELS = { 0: 'Lowest', 1: 'Low', 2: 'Medium', 3: 'High', 4: 'Highest', }; function formatDate(unix) { if (!unix) return ''; return new Date(unix * 1000).toLocaleDateString(); } function renderTickets(tickets) { if (tickets.length === 0) { return '

No tickets found.

'; } const rows = tickets.map(t => `
`).join(''); return `
${t.ticketnum ?? t.ID} ${esc(t.name ?? '')} ${STATUS_LABELS[t.status] ?? t.status} ${PRIORITY_LABELS[t.priority] ?? t.priority} ${formatDate(t.duedate)} ${esc(t.assigneduser ?? '')}
${rows}
# Name Status Priority Due Assigned
`; } function esc(str) { return String(str).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); } ``` Wire it up: ```js async function boot() { try { const tickets = await loadTickets(); document.getElementById('content').innerHTML = renderTickets(tickets); } catch (err) { document.getElementById('content').innerHTML = `

Error: ${esc(err.message)}

`; } } boot(); ``` Start your server and open the page -- you should see a table of tickets. --- ## Step 4: Filter by Project Add a project selector above the table. First, load the available projects: ```js async function loadProjects() { try { const result = await client.api.listProjects({ fields: ['ID', 'name'], filters: { visibility: 0 }, sort: ['+name'], limit: 500, }); return normalizeListResult(result).data; } catch { return []; } } ``` Then build a ` ${options} `; } async function boot() { const projects = await loadProjects(); const app = document.getElementById('content'); app.innerHTML = renderProjectFilter(projects) + '
Loading...
'; const loadAndRender = async (projectId) => { const filters = { visibility: 0 }; if (projectId) filters.project = Number(projectId); const result = await client.api.listTickets({ fields: ['ID', 'ticketnum', 'name', 'status', 'priority', 'duedate', 'assigneduser'], filters, sort: ['-lastmodified'], limit: 50, }); const { data: tickets } = normalizeListResult(result); document.getElementById('tickets').innerHTML = renderTickets(tickets); }; document.getElementById('project-filter').addEventListener('change', e => { loadAndRender(e.target.value); }); await loadAndRender(''); } ``` --- ## Step 5: Create a New Ticket Add a simple form and use the `createTicket` API. Note: **create operations use PUT**, not POST. ```js function renderCreateForm() { return `
`; } ``` Handle form submission: ```js document.getElementById('create-form').addEventListener('submit', async e => { e.preventDefault(); const name = document.getElementById('f-name').value.trim(); if (!name) return; try { await client.api.createTicket({ name, priority: Number(document.getElementById('f-priority').value), status: 0, visibility: 0, }); document.getElementById('f-name').value = ''; await loadAndRender(''); // Refresh the list } catch (err) { alert(`Create failed: ${err.message}`); } }); ``` For generated create and update operations, the flat input style works for normal record fields. If a create fails with a timeout or connection error, it may still have succeeded. Refresh the list and let the user check before they submit again. The client does not retry these failures for writes, so it cannot create a duplicate on its own, but a second click can. See [uncertain write outcomes](https://www.zeyos.com/guide/javascript-client/making-requests#uncertain-write-outcomes). --- ## Step 6: Update a Ticket Update a record by passing the record ID and the changed fields: ```js async function updateTicketStatus(ticketId, newStatus) { const updated = await client.api.updateTicket({ ID: ticketId, status: newStatus, }); // The response contains the full updated record -- use it to // confirm the change was applied return updated; } ``` If you prefer explicit separation, `body` and `data` are also supported: ```js await client.api.updateTicket({ ID: ticketId, body: { status: newStatus } }); ``` --- ## Step 7: Delete a Ticket Deletion is straightforward: ```js async function removeTicket(ticketId) { await client.api.deleteTicket({ ID: ticketId }); } ``` Always confirm with the user before deleting -- there is no undo. --- ## Step 8: Fetch Related Data Most ZeyOS entities are related to each other. For example, tickets can have tasks. To load tasks for a specific ticket: ```js async function loadTasks(ticketId) { const result = await client.api.listTasks({ fields: ['ID', 'tasknum', 'name', 'status', 'duedate', 'assigneduser'], filters: { ticket: ticketId, visibility: 0 }, sort: ['+name'], limit: 200, }); return normalizeListResult(result).data; } ``` You can also use **dot-notation joins** to pull fields from related records in a single request: ```js const tickets = await client.api.listTickets({ fields: { Id: 'ID', Name: 'name', ProjectName: 'project.name', AssignedTo: 'assigneduser.name', AccountName: 'account.lastname', }, filters: { visibility: 0 }, limit: 50, }); ``` This returns flattened objects like `{ Id: 42, Name: '...', ProjectName: 'Acme', AssignedTo: 'Jane', AccountName: 'Lightexx AG' }` -- no extra API calls needed. Each step of a path must be a documented relationship on that resource; `client.schema.describe('tickets')` lists them, and `validate: true` rejects the rest. --- ## Step 9: Handle Errors Gracefully Every API error throws a `ZeyosApiError` with rich context: ```js import { ZeyosApiError } from './zeyos-client/index.js'; try { await client.api.getTicket({ ID: 999999 }); } catch (err) { if (err instanceof ZeyosApiError) { switch (err.status) { case 401: // Token expired and auto-refresh failed showLoginScreen(); break; case 403: showMessage('You do not have permission to view this record.'); break; case 404: showMessage('Record not found.'); break; default: showMessage(`Request failed (HTTP ${err.status}). Please try again.`); } } else { // Network error, timeout, etc. showMessage('Network error. Please check your connection.'); } } ``` --- ## Step 10: Working with Dates All ZeyOS date fields are **Unix timestamps in seconds** (not milliseconds). This catches most JavaScript developers off guard since `Date.now()` returns milliseconds. ```js // Reading: multiply by 1000 const jsDate = new Date(ticket.duedate * 1000); // Writing: divide by 1000 const duedate = Math.floor(new Date('2026-06-15').getTime() / 1000); await client.api.updateTicket({ ID: id, body: { duedate } }); // Checking overdue const isOverdue = ticket.duedate * 1000 < Date.now(); ``` --- ## Summary: Quick Reference Card | Task | Code | |------|------| | List records | `client.api.listTickets({ fields: [...], filters: {...}, sort: [...], limit: N })` | | Get one record | `client.api.getTicket({ ID: 42 })` | | Create | `client.api.createTicket({ name: '...', status: 0, visibility: 0 })` | | Update | `client.api.updateTicket({ ID: 42, body: { status: 4 } })` | | Delete | `client.api.deleteTicket({ ID: 42 })` | | Related data | `client.api.listTasks({ filters: { ticket: 42 } })` | | Dot-notation join | `fields: { Account: 'account.lastname', Agent: 'assigneduser.name' }` | | Date read | `new Date(record.duedate * 1000)` | | Date write | `Math.floor(date.getTime() / 1000)` | --- ## Where to Go Next - **[Practical Guide](https://www.zeyos.com/guide/javascript-client/practical-guide)** -- deeper coverage of `filter` vs `filters`, token persistence, and optimistic UI patterns - **[Making Requests](https://www.zeyos.com/guide/javascript-client/making-requests)** -- full reference for field selection, sorting, pagination, extended data, and error handling - **[Data Retrieval](https://www.zeyos.com/guide/api-reference/data-retrieval)** -- the complete REST query language with advanced filter operators, full-text search, and composite expressions - **[Kanban Sample](https://www.zeyos.com/guide/sample-apps/kanban)** -- a reusable sample browser app with drag-and-drop, modals, and session detection - **[Authentication](https://www.zeyos.com/guide/javascript-client/authentication)** -- OAuth 2.0 flows, session mode, legacy auth, and token management --- ## Available Resources The ZeyOS API provides access to over 50 resource types. Here are the ones most commonly used when building custom frontends: | Resource | Operations | Use case | |----------|-----------|----------| | `Tickets` | list, get, create, update, delete | Issue tracking, support, project work | | `Tasks` | list, get, create, update, delete | Task management within tickets/projects | | `Accounts` | list, get, create, update, delete | CRM contacts and companies | | `Projects` | list, get, create, update, delete | Project organisation | | `Contacts` | list, get, create, update, delete | Contact details (addresses, phone, email) | | `Appointments` | list, get, create, update, delete | Calendar and scheduling | | `Transactions` | list, get, create, update, delete | Invoices, quotes, orders | | `Items` | list, get, create, update, delete | Products and services | | `Documents` | list, get, create, update, delete | File management | | `Notes` | list, get, create, update, delete | Notes attached to any entity | | `Messages` | list, get, create, update, delete | Email and messaging | | `Opportunities` | list, get, create, update, delete | Sales pipeline | | `Users` | list, get | Team members and permissions | Every resource follows the same API patterns shown in this guide. Once you know how to work with tickets, you can work with any resource. --- Canonical: https://www.zeyos.com/guide/tutorials/integration-architecture # Integration Architecture Choose the integration model before you choose the framework. In ZeyOS projects, the main design decision is usually where authentication happens and where long-lived business operations run. ## Comparison | Model | Where code runs | Auth model | Best for | Main constraint | |-------|-----------------|------------|----------|-----------------| | Browser session mode | Browser | Existing `ZEYOSID` session cookie | Internal tools on the same origin, embedded UIs, operator-facing apps | Requires browser session access and compatible credentialed requests | | Browser token mode | Browser | Pre-obtained OAuth tokens | Development, controlled demos, pre-provisioned browser apps | Do not embed a client secret in production browser code | | Server token mode | Server, worker, cron, API backend | OAuth tokens stored server-side | Integrations, sync jobs, scheduled tasks, multi-step business logic | Requires token persistence and refresh handling | ## Recommended Choices ### Use browser session mode when: - the user is already logged into ZeyOS - the UI runs on the same origin or an allowed credentialed origin - you want the simplest browser auth flow with no token storage in the page ### Use browser token mode when: - you already have tokens from a trusted flow - you are building a local demo or internal prototype - you can avoid embedding client secrets in shipped browser code ### Use server token mode when: - the application runs without a live browser session - you need retries, scheduling, background work, or webhook handling - you want one place to centralize token refresh and request logging Browser token mode is intentionally limited. Use it for pre-obtained access tokens and controlled demos. If you need authorization-code exchange or automatic refresh, move that responsibility to server token mode because the current client helpers require `clientId` and `clientSecret`. ## Interface Selection | Need | Interface | |------|-----------| | JavaScript application code with full API coverage | `@zeyos/client` | | Shell-driven operational workflows | `zeyos` CLI | | Another language or custom SDK | REST/OpenAPI | ## Cross-Cutting Rules - List operations are `POST` requests in ZeyOS, even though they behave like queries. - Prefer `filters` in client code for consistent handling of scalar and foreign-key fields. - Use `visibility: 0` on resources that **have** the column — `accounts`, `contacts`, `tickets`, `tasks`, `projects`, `items`, `documents`, `notes`, `opportunities`, `appointments`, `campaigns`, `mailinglists`, `storages`, `contracts`, `ledgers` — unless you want archived (`1`) or deleted (`2`) records. **Most other resources have no `visibility` column** — `transactions` (and every billing/procurement entity), `payments`, `messages`, `actionsteps`, `addresses`, `users`, `prices`, `dunning` — and filtering on it there returns an opaque HTTP 400. Check with `zeyos describe ` when unsure. - Use `body: { ... }` for updates that also pass `ID`. - Treat count-enabled responses defensively. Different endpoints or client layers may return either a count wrapper or a list wrapper with count metadata. ## Recommended Reading Order 1. [Browser UI Playbook](https://www.zeyos.com/guide/tutorials/build-your-own-zeyos-frontend) for user-facing frontends 2. [Server-Side Integrations](https://www.zeyos.com/guide/tutorials/server-side-integrations) for services, workers, and scheduled jobs 3. [Making Requests](https://www.zeyos.com/guide/javascript-client/making-requests) for the full request model --- Canonical: https://www.zeyos.com/guide/tutorials/server-side-integrations # Server-Side Integrations This guide covers the recommended backend pattern for ZeyOS: keep OAuth tokens on the server, refresh them automatically, and isolate business sync logic behind small job functions. ## When to Use This Pattern Use a server-side integration when you are building: - scheduled sync jobs - background workers - webhook handlers - internal APIs that proxy or enrich ZeyOS data - automation services that should not depend on a live browser session ## 1. Store State Safely A sync job keeps two pieces of state on disk: OAuth tokens and its checkpoint. Both need the same guarantees, so write them through one helper. The helper tells a missing file apart from malformed data and from a permission failure, and it replaces files atomically with private permissions. A crash during a write leaves the previous version intact. ```js // state-file.mjs import { randomBytes } from 'node:crypto'; import { open, readFile, rename, rm } from 'node:fs/promises'; import { basename, dirname, join } from 'node:path'; export class StateFileError extends Error { constructor(kind, path, action) { super(`${kind} state file ${basename(path)}: ${action}`); this.name = 'StateFileError'; this.kind = kind; // 'permission' | 'malformed' | 'unreadable' } } /** Resolves to undefined only when the file does not exist. */ export async function readStateFile(path) { let text; try { text = await readFile(path, 'utf8'); } catch (err) { if (err.code === 'ENOENT') return undefined; if (err.code === 'EACCES' || err.code === 'EPERM') { throw new StateFileError('permission', path, 'grant the job user read access.'); } throw new StateFileError('unreadable', path, `check the file (${err.code}).`); } try { return JSON.parse(text); } catch { // JSON parser messages can quote the input, and this file holds secrets. throw new StateFileError('malformed', path, 'restore it from a backup or re-authenticate.'); } } /** Temp file in the same directory, flushed, then renamed over the target. */ export async function writeStateFile(path, value) { const suffix = `${process.pid}.${randomBytes(6).toString('hex')}.tmp`; const tempPath = join(dirname(path), `.${basename(path)}.${suffix}`); try { const handle = await open(tempPath, 'wx', 0o600); try { await handle.writeFile(`${JSON.stringify(value, null, 2)}\n`); await handle.sync(); } finally { await handle.close(); } await rename(tempPath, path); } catch (err) { await rm(tempPath, { force: true }); if (err.code === 'EACCES' || err.code === 'EPERM') { throw new StateFileError('permission', path, 'grant the job user write access to the directory.'); } throw err; } } ``` The client requires only a `get()` and `set()` pair from a token store. This one is for development. In production, back the same two methods with a secrets manager or database. ```js // token-store.mjs import { rm } from 'node:fs/promises'; import { normalizeTokenSet } from '@zeyos/client'; import { readStateFile, StateFileError, writeStateFile } from './state-file.mjs'; export class JsonFileTokenStore { constructor(path) { this.path = path; } async get() { const stored = await readStateFile(this.path); if (stored === undefined) return null; // Not signed in yet. const tokenSet = normalizeTokenSet(stored); if (!tokenSet) { throw new StateFileError('malformed', this.path, 'it holds no token set; re-authenticate.'); } return tokenSet; } async set(tokenSet) { if (!tokenSet) { await rm(this.path, { force: true }); // client.auth.clearTokenSet() return; } await writeStateFile(this.path, tokenSet); } } ``` Only a missing file means "not signed in". A corrupt or unreadable store throws, and the error reaches the caller of the API method. Treating those cases as empty would discard a working refresh token and send the job to a confusing `401`. Keep the file outside your repository and do not log its contents. ### Sharing a Token Store Between Workers The client coordinates refreshes only **inside one client instance**: concurrent requests share a single refresh. Nothing coordinates separate processes. The client has no lock, no compare-and-swap and no hook around a refresh, and a token store sees only the resulting `get()` and `set()` calls. A lock therefore cannot be added inside a store. The client does read the store at the start of every operation, so a worker picks up tokens written by another process on its next call. What remains is the race between two workers refreshing at the same moment. Choose one approach: | Approach | How it works | Use when | |----------|--------------|----------| | **Single refresher** (recommended) | Workers set `autoRefresh: false` and only read the store. One process calls `client.oauth2.refreshToken()` before expiry. | Any multi-process deployment | | **Lock around an explicit refresh** | Each worker sets `autoRefresh: false` and takes a lock before calling `client.oauth2.refreshToken()`, as shown below. | There is no natural owner for a refresher | | **Accept duplicate refreshes** | Leave `autoRefresh` on and let workers race. | Only after confirming your instance keeps the previous refresh token valid | The third approach depends on server behavior this client does not establish. Its code handles a response with no new refresh token by keeping the old one, and also handles a rotated one, but ZeyOS's rotation policy is not documented here. If refresh tokens rotate, the losing worker presents a used token, its refresh fails, and the failed call surfaces as an error even though the store already holds valid tokens. Test this against a sandbox instance before relying on it. ```js // refresh.mjs: run this from workers configured with autoRefresh: false. import { withLockFile } from './lock-file.mjs'; const REFRESH_MARGIN_SECONDS = 120; export async function refreshIfDue(client, lockPath) { await withLockFile(lockPath, async () => { // Re-read inside the lock: another worker may have finished a refresh. const tokenSet = await client.auth.getTokenSet(); const now = Math.floor(Date.now() / 1000); if (tokenSet?.expiresAt == null || tokenSet.expiresAt > now + REFRESH_MARGIN_SECONDS) return; await client.oauth2.refreshToken(); }); } ``` A failed `set()` needs attention in every approach. The server has already issued new tokens at that point, and if it rotated the refresh token, the job has lost the only copy. A failed write during a refresh before the request is also swallowed by the client, which continues with the expired token, so monitor store write failures separately. The lock uses an exclusive create, which either succeeds or reports who holds it: ```js // lock-file.mjs import { open, readFile, rm, stat } from 'node:fs/promises'; export class LockHeldError extends Error { constructor({ pid, ageSeconds, stale }) { super(`${stale ? 'Stale lock' : 'Another run holds the lock'}: pid ${pid ?? 'unknown'}, ${ageSeconds}s old.`); this.name = 'LockHeldError'; this.stale = stale; // true: no live run owns it, so nothing will clear it. } } function isRunning(pid) { try { process.kill(pid, 0); // Signal 0 tests for the process without touching it. return true; } catch (err) { return err.code === 'EPERM'; // It exists under another user. } } async function describeLock(path, maxAgeMs) { try { const [text, { mtimeMs }] = await Promise.all([readFile(path, 'utf8'), stat(path)]); const pid = Number.parseInt(text, 10); const ageMs = Date.now() - mtimeMs; const owned = pid > 0; return { pid: owned ? pid : null, ageSeconds: Math.round(ageMs / 1000), // An empty file is a holder between create and write, until it ages out. stale: (owned && !isRunning(pid)) || ageMs > maxAgeMs, }; } catch (err) { if (err.code === 'ENOENT') return { pid: null, ageSeconds: 0, stale: false }; // Just released. throw err; } } export async function withLockFile(path, task, { maxAgeMs = 60 * 60_000 } = {}) { let handle; try { handle = await open(path, 'wx', 0o600); } catch (err) { if (err.code === 'EEXIST') throw new LockHeldError(await describeLock(path, maxAgeMs)); throw err; } try { await handle.writeFile(`${process.pid}\n`); return await task(); } finally { await handle.close(); await rm(path, { force: true }); } } ``` A process that is killed leaves its lock behind. The next run reports the lock as stale, because its process is gone or it is older than `maxAgeMs`, and exits non-zero so a monitor sees that the job has stopped. It does not remove the lock itself: confirm that the recorded process is gone, then delete the file. Set `maxAgeMs` above your longest run, because a process ID can be reused after a reboot. This lock works on one host's local filesystem. Across hosts, use a lock from your database or scheduler. ## 2. Build the Client The code in Sections 2, 3 and 4 forms one module, `sync.mjs`. ```js import { createZeyosClient } from '@zeyos/client'; import { JsonFileTokenStore } from './token-store.mjs'; const tokenStore = new JsonFileTokenStore('/var/lib/my-sync/tokens.json'); const client = createZeyosClient({ platform: 'https://cloud.zeyos.com/demo/', validate: true, timeoutMs: 30_000, auth: { mode: 'oauth', oauth: { clientId: process.env.ZEYOS_CLIENT_ID, clientSecret: process.env.ZEYOS_CLIENT_SECRET, tokenStore, autoRefresh: true, }, }, }); ``` - `validate: true` rejects unknown fields, invalid enum values and invalid join paths before any request is sent. Validation is off by default in JavaScript. - `timeoutMs` bounds each attempt. Without it, a stalled connection can wait forever and the retry logic never runs. - `autoRefresh: true` refreshes an expired access token before a request, or after a bearer `401`. Your token store must persist the result. - If the job names individual `extdata` keys, add a reviewed [`customFields` contract](https://www.zeyos.com/guide/javascript-client/making-requests#custom-field-contracts) bound to this instance. ## 3. Fetch One Page ZeyOS has **no server-side cursor**. The API offers `limit` (up to 10000), `offset`, `sort`, `filters` and `count`, and a sync job builds its position from those. Do not page a changing dataset with `offset`, and that includes `client.paginate()`, which is offset-based. When a record on an earlier page changes, it moves to the end of a `lastmodified` ordering, every later row shifts up by one, and the next page silently skips a record. Instead, use **keyset pagination**: sort by `lastmodified` with `ID` as the tie-breaker, and ask for rows strictly after the last one you processed. A row that changes during the run moves further ahead of the position, so it is read later and never skipped. ```js import { normalizeListResult } from '@zeyos/client'; export const PAGE_SIZE = 500; const TICKET_FIELDS = ['ID', 'ticketnum', 'name', 'status', 'priority', 'visibility', 'lastmodified']; /** Rows strictly after (lastmodified, ID) in the sort order below. */ function ticketPageQuery(after) { return { fields: TICKET_FIELDS, // No visibility filter: archiving a ticket must reach the downstream system. filters: { 1: ['OR', { lastmodified: { '>': after.lastmodified } }, { 2: ['AND', { lastmodified: { '=': after.lastmodified } }, { ID: { '>': after.ID } }, ] }, ], }, sort: ['+lastmodified', '+ID'], limit: PAGE_SIZE, }; } export async function fetchTicketPage(after, { signal } = {}) { const result = await client.api.listTickets(ticketPageQuery(after), { signal }); return normalizeListResult(result).data; } ``` The `ID` tie-breaker matters because `lastmodified` has one-second resolution. A bulk update can give more rows the same timestamp than fit on one page, and a `lastmodified`-only position would stall or lose rows at that boundary. Keep each sync unit focused: one resource, one query shape, one return shape. If you use aliases in `fields`, keep `lastmodified` and `ID` under their real names, because the position is read from them. ## 4. Run a Resumable Sync A pass reads every row after its starting point, and may span several runs. The checkpoint stores two positions and is saved only after the downstream system has accepted the rows: - `through` is where the last complete pass ended. The next pass starts one overlap window behind it. - `position` is how far the current pass has got, or `null` between passes. A run that stops early leaves it set, and the next run continues from there. The overlap applies only when a new pass starts. If every run began behind `through`, a window holding more rows than one run can process would use up the budget each time, and the job would never get past those rows. Because a resumed pass continues from `position`, which only moves forward, every run that processes a row makes lasting progress. ```js import { readStateFile, writeStateFile } from './state-file.mjs'; const SYNC_PLATFORM = 'https://cloud.zeyos.com/demo'; const OVERLAP_SECONDS = 300; const START = { lastmodified: 0, ID: 0 }; const isAfter = (a, b) => a.lastmodified > b.lastmodified || (a.lastmodified === b.lastmodified && a.ID > b.ID); function positionOf(row) { if (!Number.isSafeInteger(row?.lastmodified) || !Number.isSafeInteger(row?.ID)) { throw new Error(`Ticket ${row?.ID} has no usable sync position.`); } return { lastmodified: row.lastmodified, ID: row.ID }; } async function loadCheckpoint(path) { const saved = await readStateFile(path); if (saved === undefined) return { through: START, position: null }; if (saved?.platform !== SYNC_PLATFORM || saved?.resource !== 'tickets') { throw new Error('Checkpoint belongs to another instance or resource.'); } return { through: positionOf(saved.through), position: saved.position === null ? null : positionOf(saved.position), }; } function saveCheckpoint(path, checkpoint) { return writeStateFile(path, { platform: SYNC_PLATFORM, resource: 'tickets', ...checkpoint, savedAt: new Date().toISOString(), // For operators; the sync never reads it. }); } export async function runTicketSync({ checkpointPath, upsert, signal, maxPages = 10_000 }) { const { through, position: resumed } = await loadCheckpoint(checkpointPath); // Only a new pass re-reads the overlap window, so a crash or late commit is covered // without letting that window hold the job back. let position = resumed ?? { lastmodified: Math.max(0, through.lastmodified - OVERLAP_SECONDS), ID: 0 }; // Offline preflight: fail on a bad query before the first request. const preflight = client.schema.validate('listTickets', ticketPageQuery(position)); if (!preflight.valid) throw new Error(`Invalid sync query: ${preflight.errors[0].code}`); let records = 0; try { for (let page = 0; page < maxPages; page += 1) { const rows = await fetchTicketPage(position, { signal }); for (const row of rows) { signal?.throwIfAborted(); const next = positionOf(row); // Stop rather than advance past rows if the server ignored the ordering. if (!isAfter(next, position)) throw new Error(`Ticket ${row.ID} arrived out of order.`); await upsert(row, { signal }); position = next; records += 1; } if (rows.length < PAGE_SIZE) { // The overlap can end behind `through`, so never move it backwards. const reached = isAfter(position, through) ? position : through; await saveCheckpoint(checkpointPath, { through: reached, position: null }); return { complete: true, records, checkpoint: reached }; } await saveCheckpoint(checkpointPath, { through, position }); } } catch (err) { // Keep the rows already accepted, so a slow page cannot repeat forever. The // original failure matters more than a failed save here. await saveCheckpoint(checkpointPath, { through, position }).catch(() => {}); throw err; } return { complete: false, records, checkpoint: position }; // Budget reached: resume next run. } ``` Run it behind a lock, so scheduled runs cannot overlap, and with a budget for the whole job: ```js import { describeFailure } from './failures.mjs'; import { LockHeldError, withLockFile } from './lock-file.mjs'; export async function main(upsert) { try { const summary = await withLockFile('/var/lib/my-sync/tickets.lock', () => runTicketSync({ checkpointPath: '/var/lib/my-sync/tickets.checkpoint.json', upsert, signal: AbortSignal.timeout(15 * 60_000), // Budget for all pages and retries. })); console.log(JSON.stringify({ job: 'ticket-sync', ...summary })); } catch (err) { // An overlapping run is normal. A stale lock falls through and fails the job, // because every later run would otherwise stop here without anyone noticing. if (err instanceof LockHeldError && !err.stale) { console.log(JSON.stringify({ job: 'ticket-sync', skipped: err.message })); return; } console.error(JSON.stringify(describeFailure(err, { resource: 'tickets', operationId: 'listTickets' }))); process.exitCode = 1; } } ``` ### Crash and Change Behavior | Event | Result | |-------|--------| | Hard crash during a page | The checkpoint still holds the end of the previous page. The next run repeats this page, so rows may be delivered twice. | | Timeout or error during a page | The accepted rows are saved first, and the next run continues after them. A row may be delivered twice if `upsert` committed but did not return. | | Crash after a complete pass | The next run starts a new pass, which re-reads the overlap window. | | Record changes during the run | Its `lastmodified` moves ahead of the position, so it is read later in this run or in the next one. | | Record changes repeatedly while the job runs | It may be delivered several times. The downstream write keeps the newest version. | | Write commits late with an older timestamp | Covered if it lands within the overlap window behind the point where the pass ended. | | Budget or `maxPages` reached | The run stops with saved progress. A `maxPages` stop returns `complete: false` and a budget stop throws. The next run continues from `position`, without repeating the overlap. | | More rows share a timestamp, or sit in the overlap, than one run can process | Several runs finish the pass. Each continues inside the same timestamp, because the position includes `ID`. | | Rows change faster than the job processes them | The position still advances, but the pass may not finish until the rate drops. | | A killed run leaves its lock | The next run exits non-zero with a stale-lock failure and does not sync until an operator clears it. | ### Make Downstream Writes Idempotent Every row can arrive more than once, by design. `upsert` must therefore key on the instance and `ID`, and must refuse to replace newer data. It receives the job's `signal` as `upsert(row, { signal })`: pass that to your database or HTTP call, so a slow downstream write cannot outlive the job's budget. ```sql INSERT INTO zeyos_tickets (instance, id, name, status, visibility, lastmodified) VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT (instance, id) DO UPDATE SET name = excluded.name, status = excluded.status, visibility = excluded.visibility, lastmodified = excluded.lastmodified WHERE excluded.lastmodified >= zeyos_tickets.lastmodified; ``` Treat `visibility` as data: `1` is archived and `2` is deleted. Resources without that column need a different rule, and `client.schema.describe('')` shows what each one has. ### Reconcile Deletions An incremental sync sees only rows that still exist and match. A hard-deleted record simply stops appearing, and whether archiving updates `lastmodified` is not guaranteed here. Rows with an empty `lastmodified` never match the filter either. Run a periodic ID pass, for example nightly, to catch all three: ```js export async function listAllTicketIds({ signal } = {}) { const ids = []; let after = 0; for (;;) { const result = await client.api.listTickets({ fields: ['ID'], filters: { ID: { '>': after } }, sort: ['+ID'], limit: PAGE_SIZE, }, { signal }); const { data: rows } = normalizeListResult(result); ids.push(...rows.map((row) => row.ID)); if (rows.length < PAGE_SIZE) return ids; after = rows.at(-1).ID; } } ``` Compare this list with your downstream IDs. IDs missing downstream need a `getTicket` and an upsert. Before removing a record that is missing upstream, require a pass that finished without errors, because an incomplete list looks exactly like a mass deletion. Keep that removal explicit and reviewable. ### What "Complete" Means `complete: true` means one thing: a pass ended on a short page, so at that moment no row after the position was visible to this credential. `complete: false` means the pass continues on the next run. It does **not** mean the downstream system equals ZeyOS. - **It is point-in-time.** Records can change immediately afterwards, and the next run picks them up. - **It is scoped to the credential.** Rows the authenticated user cannot read are not returned, and nothing reports them as absent. - **`count` is not a completeness check.** It returns only a number, ignores `sort`, `offset` and `limit`, and changes while the job runs. - **Timestamps are Unix seconds in UTC.** They carry no timezone, so no local conversion belongs in the position. Only compare `lastmodified` with other server values, never with your own clock. - **The overlap is a tuning value.** Five minutes is an example, not a guarantee about commit delays on your instance. It is measured from where a pass ended, so a pass that chases live changes for longer than the overlap narrows that cover. - **Joined data is not tracked.** A change to a linked contact does not necessarily update the ticket's `lastmodified`. The [composite filter example](https://www.zeyos.com/guide/api-reference/data-retrieval#composite-filters-and-or-not) shows how to include a joined timestamp, but the two-field position in this tutorial does not cover that case. - **Check this query on a sandbox instance.** The composite filter and two-field sort follow the API reference, and `positionOf` and the ordering check stop the job if a server behaves differently. Bundled metadata does not list `lastmodified` as indexed, so test page times on large tables before choosing `PAGE_SIZE`. ## 5. Use `client.request()` for Advanced Cases Use the generated methods first. Drop to `client.request()` when you need a custom path or an operation outside your current helper layer. By operation ID, which keeps the validation and read classification of the generated method: ```js const response = await client.request({ service: 'api', operationId: 'listTickets', body: { filters: { visibility: 0, priority: 4 }, limit: 25, }, }); ``` By explicit path and method: ```js const response = await client.request({ service: 'api', method: 'POST', path: '/tickets/', body: { filters: { visibility: 0, status: 4 }, limit: 25, }, }); ``` A method and path call has no operation schema, which costs you two protections: - **No preflight validation**, even with `validate: true`. Unknown fields, invalid enum values, join paths and custom-field keys all reach the server unchecked. - **No read classification.** A `POST` list sent this way counts as a possible write, so it is not retried after a network error, a timeout or a `503` unless you pass `retryOnNetworkError: true`. ## 6. Report Failures Without Leaking Data Log what an operator can act on: the resource, the operation, a category and the next step. `err.body` and `err.message` can contain record data from the response, so neither belongs in a log line. ```js // failures.mjs import { ZeyosApiError, ZeyosValidationError } from '@zeyos/client'; import { LockHeldError } from './lock-file.mjs'; import { StateFileError } from './state-file.mjs'; const NEXT_STEP = { validation: 'Fix the query; no request was sent.', authentication: 'Re-authenticate and check the token store.', permission: 'Grant the integration user access to this resource.', not_found: 'Reconcile the record ID.', rate_limited: 'Reduce request volume; the next run resumes from the checkpoint.', upstream: 'The next run resumes from the checkpoint. Reconcile any write first.', request: 'Review the request against the API reference.', timeout: 'Check connectivity. Reconcile any write before retrying it.', cancelled: 'The job budget ended or a caller aborted. The next run resumes.', state_file: 'Repair the state file named in the message.', stale_lock: 'Confirm the recorded process is gone, then delete the lock file.', network: 'Check connectivity. Reconcile any write before retrying it.', }; function categorize(err) { if (err instanceof ZeyosValidationError) return 'validation'; if (err instanceof StateFileError) return 'state_file'; if (err instanceof LockHeldError) return 'stale_lock'; if (err instanceof ZeyosApiError) { if (err.status === 401) return 'authentication'; if (err.status === 403) return 'permission'; if (err.status === 404) return 'not_found'; if (err.status === 429) return 'rate_limited'; return err.status >= 500 ? 'upstream' : 'request'; } if (err.isTimeout) return 'timeout'; if (err.name === 'AbortError' || err.name === 'TimeoutError') return 'cancelled'; return 'network'; } export function describeFailure(err, context) { const category = categorize(err); return { ...context, category, ...(err.status ? { status: err.status } : {}), ...(err instanceof ZeyosValidationError ? { problems: err.errors.map(({ field, code }) => ({ field, code })) } : {}), ...(err instanceof StateFileError || err instanceof LockHeldError ? { detail: err.message } : {}), next: NEXT_STEP[category], }; } ``` ```js import { describeFailure } from './failures.mjs'; try { await client.api.updateTicket({ ID: 42, body: { status: 7 } }); } catch (err) { console.error(JSON.stringify(describeFailure(err, { resource: 'tickets', operationId: 'updateTicket', ID: 42 }))); throw err; } ``` Reads are safe to run again, which is why the sync can simply stop and resume. Writes are not: the `updateTicket` call above may have succeeded even if it threw. See [Retries and Rate Limiting](https://www.zeyos.com/guide/javascript-client/making-requests#retries-and-rate-limiting) for what the client retries, and [uncertain write outcomes](https://www.zeyos.com/guide/javascript-client/making-requests#uncertain-write-outcomes) before you retry one. ## 7. Know the Limits - Prefer `filters` in client code. - Use `visibility: 0` on resources that **have** the column — `accounts`, `contacts`, `tickets`, `tasks`, `projects`, `items`, `documents`, `notes`, `opportunities`, `appointments`, `campaigns`, `mailinglists`, `storages`, `contracts`, `ledgers` — unless you want archived (`1`) or deleted (`2`) records, as a sync usually does. **Most other resources have no `visibility` column** — `transactions` (and every billing/procurement entity), `payments`, `messages`, `actionsteps`, `addresses`, `users`, `prices`, `dunning` — and filtering on it there returns an opaque HTTP 400. Check with `zeyos describe ` when unsure. - Use explicit `body` objects for update operations that pass `ID`. - Treat count-enabled responses defensively and normalize them in one place inside your backend. ## Next Steps - [Integration Architecture](https://www.zeyos.com/guide/tutorials/integration-architecture) - [Making Requests](https://www.zeyos.com/guide/javascript-client/making-requests) - [Authentication](https://www.zeyos.com/guide/javascript-client/authentication) --- Canonical: https://www.zeyos.com/ixml # Introduction iXML is an XML-based server-side language used by ZeyOS for services, workflows, data orchestration, and integration logic. It is the primary orchestration layer for ZeyOS applications, responsible for handling HTTP request/response cycles, database interactions, file processing, external API communication, and business rule enforcement. If you are new to ZeyOS customization, iXML is the first language to learn. Unlike general-purpose programming languages, iXML is purpose-built for the ZeyOS runtime environment. Every iXML document is valid XML, which means it inherits XML's strict syntactic rules — proper nesting, mandatory closing tags, attribute quoting, and entity escaping. This design decision allows ZeyOS to parse iXML documents with standard XML parsers and provides IDE support through DTD and XSD schemas. The tradeoff is that certain characters common in programming (such as `<` and `>`) must be entity-escaped when they appear inside attribute values. iXML follows a **substitution-based evaluation model** rather than the expression-based evaluation found in most mainstream languages. Variables are referenced through dollar-sign substitution (`$variableName`), and values flow through statements via a mechanism called **result propagation**. Understanding these two concepts — substitution and propagation — is essential before working with any iXML command. ## What iXML is used for In a typical ZeyOS application, iXML code lives in three directories within the application structure. Each directory serves a different purpose, but all use the same iXML language: - **`services/`** — API route handlers (remotecall endpoints), scheduled jobs (timing services), and entity lifecycle hooks (before/after modification triggers). Services are the entry points for all application logic. - **`resources/`** — Reusable domain logic, shared templates, helper functions, and configuration modules. Resources are included by services and other resources using the `` command. - **`weblets/`** — Embedded UI components that render within the ZeyOS interface. Weblets generate HTML output with embedded data from the ZeyOS platform. In practical terms, iXML typically handles: - Receiving and parsing incoming HTTP requests (JSON, form data, XML payloads) - Validating input against business rules - Orchestrating database reads and writes, often within transactions - Calling external REST and SOAP APIs - Generating structured responses (JSON, HTML, PDF, CSV) - Managing error conditions and returning consistent error envelopes ### See also - [Syntax Essentials](https://www.zeyos.com/ixml/ixml-guide/syntax-essentials) — variables, strings, control flow, and the commands you use in every service - [Reusability and OOP](https://www.zeyos.com/ixml/ixml-guide/reusability-and-oop) — functions, macros, classes, and includes for structuring larger codebases --- ## Core definitions These definitions form the conceptual baseline used throughout the iXML reference and this guide. Every term below has a precise meaning that the language runtime enforces. ### Terminology The iXML language specification distinguishes between the structural components of an iXML document and their semantic roles: | Term | Meaning | |---|---| | **Element** | Simple XML element — a single tag with attributes and optional content | | **Structure** | Compound XML structure containing one or more child elements with their own semantics | | **Operation** | The semantic definition of an element — what an element *does* when it executes | | **Construct** | The semantic definition of a structure — what a compound structure *does*, including the behavior of its children | | **Statement** | An executable instance of an operation or construct — the concrete occurrence in your code | | **Code** | A series of statements that execute in sequence | The distinction between *operation* and *construct* determines whether an iXML tag stands alone or contains child elements with their own semantics. For example, `` is an operation — it assigns a value and has no semantic children. In contrast, `` is a construct — it can contain ``, ``, and `` children, each with their own execution rules. ### Occurrences Child elements within a construct are governed by occurrence rules that specify how many times a child element may appear: | Occurrence | Meaning | |---|---| | `single` | The element may appear **at most once**. If it appears more than once, the behavior is undefined. | | `multiple` | The element may appear **any number of times**, including not at all. | ### Input types Every attribute in the iXML reference declares an input type that governs how the attribute value is interpreted at runtime. When you write an attribute value in your iXML code, the runtime does not treat all values as plain strings — instead, it processes each value according to the declared input type of that attribute. | Type | Meaning | |---|---| | `var` | String referencing a variable (`name`) or an array item with a specific key (`name[key]`, `name.key`). Unnecessary whitespace characters are automatically stripped. | | `bool` | Applicable if set and not empty. Any non-empty string in the attribute value is treated as `true`; an empty string or the absence of the attribute is treated as `false`. | | `int` | Arithmetic expression cast to an integer number. The full expression is evaluated first, then the result is truncated to an integer. | | `float` | Arithmetic expression cast to a floating point number. The full expression is evaluated, preserving the fractional part. | | `date` | Arithmetic expression cast to an integer number and interpreted as a Unix timestamp. The resulting integer represents seconds since January 1, 1970 (UTC). | | `string` | Arbitrary byte sequence or series of Unicode (UTF-8) characters. Variable substitution is performed on the value before it is passed to the command. | | `regexp` | String interpreted as a Perl-compatible regular expression pattern enclosed in delimiters (e.g. `/[a-z]+/i`). | | `type` | Meta type identifier (case-insensitive). Used for attributes that accept type names like `string`, `int`, `array`, etc. | | `ixml` | Arbitrary iXML code. The content is parsed and executed as a series of statements. | ### Result types Every operation or construct that produces a value declares a result type: | Type | Meaning | |---|---| | `null` | `NULL` — the absence of a meaningful value | | `bool` | Boolean — either `TRUE` or `FALSE` | | `int` | Integer number — a whole number without a fractional part | | `float` | Floating point number — a number with a fractional part | | `string` | String — an arbitrary byte sequence or series of Unicode characters | | `array` | Array — a collection of items in an ordered map that associates keys to values | | `function` | Function — a closed subroutine with its own local context | | `macro` | Macro — an open subroutine that executes in the caller's context | | `class` | Class — an array prototype used for object-oriented programming | ### Predicates Predicates are behavioral flags that modify how a command's result is handled by the runtime: | Predicate | Meaning | |---|---| | `no-result-propagation` | The result is **not** propagated to the parent statement. | When a command carries the `no-result-propagation` predicate, its result cannot be used inline via result propagation. For example, `` and `` definitions carry this predicate — they produce a value (the function or class itself), but that value is never propagated to a parent `` or ``. The value is only stored in the variable specified by the `var` attribute. --- ## iXML document envelope A complete iXML document should use the standard XML envelope with the iXML namespace and the DTD reference. This is the envelope in the SDK's current default template: ```xml ``` Each part of the envelope serves a specific purpose: | Part | Purpose | |---|---| | `` | Standard XML declaration specifying the XML version (`1.0`) and character encoding (`UTF-8`). This must be the very first line of the file with no preceding whitespace. | | `` | Optional DTD reference for validation. When present, the DTD enables basic structural validation of the document against the iXML element definitions. | | `xmlns` | The iXML namespace URI. This declares that all unqualified elements in the document belong to the iXML namespace. | When testing snippets with `zeysdk run --ixml` or the Development Console, the outer envelope can be omitted for convenience. Application files (services, resources, and weblets) should use the full envelope. :::note Older templates, and the envelope shown in the language reference, also declare `xsi:schemaLocation` pointing to `ixml.xsd`. Do not add it to new files. That XSD URL was reported to return 404, so use the DTD reference only. ::: --- Canonical: https://www.zeyos.com/ixml/ixml-guide/data-types # Data Types iXML is a loosely typed language where the data type of a variable is often determined by the context in which it is used. However, the underlying runtime recognizes distinct intrinsic data types, and understanding them is essential for avoiding common bugs. ## Type system overview XML does not know intrinsic data types — in iXML, textual content is **string by default** unless converted by context or explicit cast. This is one of the most important facts for new iXML developers: ```xml 42 ``` The `` command always produces a string. If your logic depends on numeric or boolean behavior, you must cast explicitly with [`cast`](https://www.zeyos.com/ixml/ref/cast) or rely on automatic evaluation in contexts that expect a specific type (like `int` attributes). ## Available types iXML recognizes nine types, divided into **primitive** (assigned by value) and **complex** (assigned by reference): | Type | Category | Neutral Literal | Description | |------|----------|----------------|-------------| | `null` | Primitive | `NULL` | Absence of a meaningful value | | `bool` | Primitive | `FALSE` | Truth value (`TRUE` or `FALSE`) | | `int` | Primitive | `0` | Whole number without fractional part | | `float` | Primitive | `0.0` | Number with fractional part | | `string` | Primitive | `''` (empty) | Byte sequence or Unicode characters | | `array` | Complex | — | Ordered key-value collection | | `function` | Complex | — | Closed subroutine with local context | | `macro` | Complex | — | Open subroutine in caller's context | | `class` | Complex | — | Array prototype for OOP | Use [`typeof`](https://www.zeyos.com/ixml/ref/typeof) to inspect a variable's type at runtime: ```xml 42 $t ``` ## Detailed type guides - **[Strings](https://www.zeyos.com/ixml/ixml-guide/data-types/strings)** — the default type, string operations, and encoding - **[Numbers](https://www.zeyos.com/ixml/ixml-guide/data-types/numbers)** — integers, floats, arithmetic context, and notation - **[Booleans](https://www.zeyos.com/ixml/ixml-guide/data-types/booleans)** — truth values, truthiness rules, and comparison behavior - **[Null and Undefined](https://www.zeyos.com/ixml/ixml-guide/data-types/null-and-undefined)** — the difference between null, undefined, and empty - **[Arrays](https://www.zeyos.com/ixml/ixml-guide/data-types/arrays)** — ordered key-value collections, the primary complex type - **[Type Conversion](https://www.zeyos.com/ixml/ixml-guide/data-types/type-conversion)** — casting rules, comparison behavior, and the conversion table --- Canonical: https://www.zeyos.com/ixml/ixml-guide/data-types/arrays # Arrays The **Array** type (`array`) is the primary complex data structure in iXML. It is an ordered map that associates keys to values. - **typeof result**: `'array'` - **Assignment**: Assigned by **reference** (not copied on assignment) ## Key features - **Associative**: Keys can be strings (`array.key`) or integers (`array[0]`). - **Ordered**: Iteration order is preserved. - **Nested**: Arrays can contain other arrays, enabling complex data structures. ## Creating arrays ```xml red green blue John 30 ``` [`array`](https://www.zeyos.com/ixml/ref/array) ## By-reference behavior Arrays are assigned by reference. When you assign an array to another variable, both variables point to the same data: ```xml 1 changed $original.a ``` Use [`clone`](https://www.zeyos.com/ixml/ref/clone) if you need an independent copy: ```xml ``` ## Accessing array items Use dot notation for known keys and bracket notation for dynamic keys: ```xml $person.name $person[name] name $person[$key] ``` See [Variables and Substitution](https://www.zeyos.com/ixml/ixml-guide/introduction/variables-and-substitution) for the full key chain syntax. :::warning Destructive operations Many array commands in iXML are **destructive by default** — they modify the source array in-place. Commands that support a `var_result` attribute let you opt into non-destructive behavior. ::: ## In this chapter - **[Construction](https://www.zeyos.com/ixml/ixml-guide/data-types/arrays/construction)** — `array`, `item`, `array:range`, `array:assoc`, `array:populate` - **[Inspection](https://www.zeyos.com/ixml/ixml-guide/data-types/arrays/inspection)** — `array:length`, `array:keyexists`, `array:valueexists`, `array:pos`, `array:first`, `array:last`, `array:rand` - **[Stack and Queue](https://www.zeyos.com/ixml/ixml-guide/data-types/arrays/stack-queue)** — `array:push`, `array:pop`, `array:shift`, `array:unshift` - **[Slicing and Restructuring](https://www.zeyos.com/ixml/ixml-guide/data-types/arrays/slicing)** — `array:concat`, `array:slice`, `array:extract`, `array:pad`, `array:reverse`, `array:flip`, `array:chunk` - **[Set Operations](https://www.zeyos.com/ixml/ixml-guide/data-types/arrays/set-operations)** — `array:merge`, `array:unique`, `array:diff`, `array:intersect`, `array:union` - **[Filtering and Sorting](https://www.zeyos.com/ixml/ixml-guide/data-types/arrays/filtering-sorting)** — `array:filter`, `array:keys`, `array:values`, `array:sort` --- Canonical: https://www.zeyos.com/ixml/ixml-guide/data-types/arrays/construction # Array Construction ## `array` — Build array [Full reference →](https://www.zeyos.com/ixml/ref/array) The `ARRAY` command is the primary mechanism for constructing structured data in iXML. It creates an associative array (a collection of key-value pairs) and binds it to a variable. ### Syntax ```xml value1 value2 ``` ### Attributes | Name | Type | Required | Default | Description | |---|---|---|---|---| | `var` | var | Yes | — | The variable name to bind the resulting array to. | | `key` | string | No | (next consecutive numeric key) | When used as a nested element, specifies the key in the parent array. | ### Examples **Array with explicit string keys:** ```xml Bill Gates Steve Jobs $names.bg and $names.sj are competitors! ``` **Array with implicit numeric keys:** ```xml Bill Gates Steve Jobs $names[0] and $names[1] are competitors! ``` --- ## `item` — Array item [Full reference →](https://www.zeyos.com/ixml/ref/item) The `ITEM` command defines a single key-value pair within an `` construct. ### Syntax ```xml value value ``` ### Attributes | Name | Type | Required | Default | Description | |---|---|---|---|---| | `var` | var | No | — | Variable name. When specified, the item's value is also stored in this variable independently. | | `key` | string | No | (next consecutive numeric key) | The key for this item within the parent array. | --- ## `array:range` — Create numeric range array [Full reference →](https://www.zeyos.com/ixml/ref/array-range) The `ARRAY:RANGE` command creates an array populated with values from a numeric range. ### Syntax ```xml ``` ### Attributes | Name | Type | Required | Default | Description | |---|---|---|---|---| | `var` | var | Yes | — | The variable name to bind the resulting array to. | | `from` | int | Yes | — | The starting value of the range (inclusive). | | `to` | int | Yes | — | The ending value of the range (inclusive). | | `step` | int | No | `1` | The increment between consecutive values. | ### Example ```xml ``` --- ## `array:assoc` — Associate keys and values [Full reference →](https://www.zeyos.com/ixml/ref/array-assoc) The `ARRAY:ASSOC` command creates a new associative array by combining parallel arrays of keys and values. ### Syntax ```xml ``` ### Example ```xml bgsj Bill GatesSteve Jobs ``` --- ## `array:populate` — Import array into variable scope [Full reference →](https://www.zeyos.com/ixml/ref/array-populate) The `ARRAY:POPULATE` command takes all key-value pairs from an array and imports them into the local variable scope as individual variables. ### Syntax ```xml ``` ### Attributes | Name | Type | Required | Default | Description | |---|---|---|---|---| | `var` | var | Yes | — | The source array. | | `prefix` | string | No | — | A prefix prepended to each key to form the variable name. | ### Example ```xml 123 User ID: $user_id ``` --- Canonical: https://www.zeyos.com/ixml/ixml-guide/data-types/arrays/filtering-sorting # Filtering and Sorting ## `array:filter` — Filter array [Full reference →](https://www.zeyos.com/ixml/ref/array-filter) Filters an array using an expression or function. ### Syntax ```xml pattern ``` ## `array:keys` — Get keys [Full reference →](https://www.zeyos.com/ixml/ref/array-keys) Returns all keys of an array. ### Syntax ```xml ``` ## `array:values` — Get values [Full reference →](https://www.zeyos.com/ixml/ref/array-values) Returns all values of an array (re-indexed). ### Syntax ```xml ``` ## `array:sort` — Sort array [Full reference →](https://www.zeyos.com/ixml/ref/array-sort) Sorts an array by value, key, or natural order. ### Syntax ```xml ``` ### Attributes | Name | Type | Default | Description | |---|---|---|---| | `var` | var | — | The array to sort. | | `type` | type | `asc` | `asc`, `desc`, `key`, `key-desc`, `natural`, `rand`. | --- Canonical: https://www.zeyos.com/ixml/ixml-guide/data-types/arrays/inspection # Array Inspection ## `array:length` — Get array length [Full reference →](https://www.zeyos.com/ixml/ref/array-length) Returns the number of items in an array. ### Syntax ```xml ``` ### Example ```xml Count: ``` --- ## `array:keyexists` — Check if key exists [Full reference →](https://www.zeyos.com/ixml/ref/array-keyexists) Checks whether a specific key exists within an array. ### Syntax ```xml keyToCheck ``` ### Example ```xml sj ``` --- ## `array:valueexists` — Check if value exists [Full reference →](https://www.zeyos.com/ixml/ref/array-valueexists) Checks whether a specific value exists within an array. ### Syntax ```xml valueToFind ``` --- ## `array:pos` — Find position of value [Full reference →](https://www.zeyos.com/ixml/ref/array-pos) Searches an array for a value and returns the **key** of the matching item. ### Syntax ```xml valueToFind ``` ### Attributes | Name | Type | Required | Default | Description | |---|---|---|---|---| | `var` | var | Yes | — | The array to search. | | `type` | type | No | `first` | `first` or `last`. | | `offset` | int | No | — | Items to skip. | --- ## `array:first` — Get first item [Full reference →](https://www.zeyos.com/ixml/ref/array-first) Returns the first item of an array (key and value). ### Syntax ```xml ``` --- ## `array:last` — Get last item [Full reference →](https://www.zeyos.com/ixml/ref/array-last) Returns the last item of an array. ### Syntax ```xml ``` --- ## `array:rand` — Get random item [Full reference →](https://www.zeyos.com/ixml/ref/array-rand) Returns a randomly selected item. ### Syntax ```xml ``` --- Canonical: https://www.zeyos.com/ixml/ixml-guide/data-types/arrays/set-operations # Set Operations ## `array:merge` — Merge arrays [Full reference →](https://www.zeyos.com/ixml/ref/array-merge) Merges one or more arrays into the target array. String keys overwrite, numeric keys append. ### Syntax ```xml ``` ## `array:unique` — Remove duplicates [Full reference →](https://www.zeyos.com/ixml/ref/array-unique) Removes duplicate values. ### Syntax ```xml ``` ## `array:diff` — Set difference [Full reference →](https://www.zeyos.com/ixml/ref/array-diff) Computes the difference between arrays. ### Syntax ```xml ``` ## `array:intersect` — Set intersection [Full reference →](https://www.zeyos.com/ixml/ref/array-intersect) Computes the intersection of arrays. ### Syntax ```xml ``` ## `array:union` — Set union [Full reference →](https://www.zeyos.com/ixml/ref/array-union) Computes the union of arrays. ### Syntax ```xml ``` --- Canonical: https://www.zeyos.com/ixml/ixml-guide/data-types/arrays/slicing # Slicing and Restructuring ## `array:slice` — Extract slice [Full reference →](https://www.zeyos.com/ixml/ref/array-slice) Extracts a portion of an array. ### Syntax ```xml ``` ## `array:concat` — Concatenate arrays [Full reference →](https://www.zeyos.com/ixml/ref/array-concat) Joins multiple arrays into one. ### Syntax ```xml ``` ## `array:extract` — Extract keys [Full reference →](https://www.zeyos.com/ixml/ref/array-extract) Extracts specific keys from an array into a new array. ### Syntax ```xml ``` ## `array:pad` — Pad array [Full reference →](https://www.zeyos.com/ixml/ref/array-pad) Pads an array to a specified length with a value. ### Syntax ```xml ``` ## `array:reverse` — Reverse array [Full reference →](https://www.zeyos.com/ixml/ref/array-reverse) Reverses the order of items. ### Syntax ```xml ``` ## `array:flip` — Flip keys/values [Full reference →](https://www.zeyos.com/ixml/ref/array-flip) Exchanges keys with their associated values. ### Syntax ```xml ``` ## `array:chunk` — Chunk array [Full reference →](https://www.zeyos.com/ixml/ref/array-chunk) Splits an array into chunks of a specific size. ### Syntax ```xml ``` --- Canonical: https://www.zeyos.com/ixml/ixml-guide/data-types/arrays/stack-queue # Stack and Queue Operations :::warning Destructive These operations modify the array in-place. ::: ## `array:push` — Append item [Full reference →](https://www.zeyos.com/ixml/ref/array-push) Appends a value to the end of an array. Equivalent to `$var[] = value`. ### Syntax ```xml value ``` --- ## `array:pop` — Pop item [Full reference →](https://www.zeyos.com/ixml/ref/array-pop) Removes and returns the last item (LIFO). ### Syntax ```xml ``` --- ## `array:shift` — Shift item [Full reference →](https://www.zeyos.com/ixml/ref/array-shift) Removes and returns the first item (FIFO). Numeric keys are re-indexed. ### Syntax ```xml ``` --- ## `array:unshift` — Prepend item [Full reference →](https://www.zeyos.com/ixml/ref/array-unshift) Prepends a value to the beginning of an array. Numeric keys are re-indexed. ### Syntax ```xml value ``` --- Canonical: https://www.zeyos.com/ixml/ixml-guide/data-types/booleans # Booleans The **Boolean** type (`bool`) represents a truth value — either `TRUE` or `FALSE`. - **Neutral literal**: `FALSE` - **typeof result**: `'bool'` ## Creating booleans Booleans are typically produced by comparison operations, type checks, or explicit commands: ```xml 1 ``` [`true`](https://www.zeyos.com/ixml/ref/true) | [`false`](https://www.zeyos.com/ixml/ref/false) | [`cast`](https://www.zeyos.com/ixml/ref/cast) ## Truthiness rules Different values convert to boolean as follows: | Value | Boolean equivalent | |-------|--------------------| | `undefined` | `FALSE` | | `NULL` | `FALSE` | | `FALSE` | `FALSE` | | `TRUE` | `TRUE` | | `0` (int) | `FALSE` | | `0.0` (float) | `FALSE` | | Any other number | `TRUE` | | `''` (empty string) | `FALSE` | | `'0'` (string zero) | `FALSE` | | Any other string | `TRUE` | | Any array | `TRUE` | :::note The string `"0"` is falsy when cast to boolean — this catches many developers off guard. Always use explicit type checking with [`is`](https://www.zeyos.com/ixml/ref/is) or [`typeof`](https://www.zeyos.com/ixml/ref/typeof) when the distinction matters. ::: ## Booleans in conditionals The `` construct compares two values with a comparison function — it does not directly evaluate boolean truthiness. To check if a variable is truthy, compare it against an empty string: ```xml flag is truthy ``` For type-based branching, use [`is`](https://www.zeyos.com/ixml/ref/is): ```xml x is a boolean ``` ## Boolean to string When cast to string, `TRUE` becomes `"1"` and `FALSE` becomes `""` (empty string). This matters when outputting boolean values: ```xml Flag: $flag ``` --- Canonical: https://www.zeyos.com/ixml/ixml-guide/data-types/null-and-undefined # Null and Undefined iXML distinguishes between three "empty" states that are often confused: | State | typeof | Meaning | |-------|--------|---------| | **Undefined** | `'undefined'` | Variable has never been declared — it has neither type nor value | | **Null** | `'null'` | Variable is defined but holds no data | | **Empty string** | `'string'` | Variable is defined and holds a string with zero length | ## Null The **NULL** type represents a variable that has been defined but holds no data. Set a variable to null explicitly with the [`null`](https://www.zeyos.com/ixml/ref/null) command: ```xml $t ``` NULL is the typical result when a database lookup returns no rows or when an optional value is absent. ## Undefined An **undefined** variable has never been assigned any value. It has neither type nor value: ```xml $t ``` You can make a defined variable undefined again with [`unset`](https://www.zeyos.com/ixml/ref/unset): ```xml hello $t ``` ## Comparison behavior In loose comparison, `undefined`, `NULL`, `FALSE`, and `''` (empty string) all compare equal to each other: ```xml undefined equals empty string ``` This means you cannot distinguish between these states using `` alone. Use [`typeof`](https://www.zeyos.com/ixml/ref/typeof) for precise type checking: ```xml x is explicitly null ``` ## Neutral literal behavior When an uninitialized (undefined) variable is used in a typed context, it defaults to the **neutral literal** of that type: - In arithmetic context: `0` - In string context: `''` - In boolean context: `FALSE` This allows operations on uninitialized variables without errors, but can mask bugs if you expect a variable to have been set. --- Canonical: https://www.zeyos.com/ixml/ixml-guide/data-types/numbers # Numbers iXML supports two numeric types: 1. **Integer** (`int`) — whole numbers without a fractional part. 2. **Float** (`float`) — numbers with a fractional part. ## Neutral literals - Integer: `0` - Float: `0.0` ## Creating numbers Remember that `` always creates strings. To create actual numeric values, use [`cast`](https://www.zeyos.com/ixml/ref/cast): ```xml 42 3.14 ``` ## Automatic arithmetic evaluation Whenever a number is explicitly expected (attributes of type `int` or `float`), the corresponding expression is automatically evaluated arithmetically. You can write expressions directly in these attribute values: ```xml $i ``` ## Number notation Integer numbers can be specified in multiple bases: | Base | Prefix | Example | Decimal | |---|---|---|---| | Decimal (base 10) | none | `123` | 123 | | Hexadecimal (base 16) | `0x` | `0x1A` | 26 | | Octal (base 8) | `0` | `017` | 15 | | Binary (base 2) | `0b` | `0b1101` | 13 | Floating point numbers support **decimal** and **scientific E notation** (`1.5e3` = `1500.0`, `2.5e-2` = `0.025`). :::warning The octal prefix `0` can cause unexpected results. `010` is octal 10 = decimal `8`. Use bare decimal notation to avoid this ambiguity. ::: ## Inline arithmetic The `$()` syntax embeds arithmetic results into string context: ```xml 1950 This person is $(2025 - $birthyear) years old! ``` ## Numeric comparison When both sides of an `` comparison look numeric, iXML performs numeric comparison: ```xml These are numerically equal ``` See [Type Conversion](https://www.zeyos.com/ixml/ixml-guide/data-types/type-conversion) for the full comparison behavior table. ## Math operations For advanced math operations (abs, rounding, trigonometry, random, etc.), see the [Standard Library: Math & DateTime](https://www.zeyos.com/ixml/ixml-guide/standard-library/math-datetime) chapter. --- Canonical: https://www.zeyos.com/ixml/ixml-guide/data-types/strings # Strings The **String** type (`string`) represents an arbitrary byte sequence or series of Unicode (UTF-8) characters. It is the most common type in iXML because all XML content is inherently textual. - **Neutral literal**: `''` (empty string) - **typeof result**: `'string'` ## Strings are the default Every `` operation produces a string, regardless of what the content looks like: ```xml 42 $t ``` Even though `42` looks like a number, it is stored as the string `"42"`. To get an integer, use [`cast`](https://www.zeyos.com/ixml/ref/cast): ```xml 42 ``` ## Common string operations iXML provides a comprehensive set of string manipulation commands: | Task | Command | Example | |------|---------|---------| | Get length | [`length`](https://www.zeyos.com/ixml/ref/length) | `$str` | | Concatenate | [`concat`](https://www.zeyos.com/ixml/ref/concat) | `$arr` | | Lowercase | [`tolower`](https://www.zeyos.com/ixml/ref/tolower) | `$str` | | Uppercase | [`toupper`](https://www.zeyos.com/ixml/ref/toupper) | `$str` | | Trim whitespace | [`trim`](https://www.zeyos.com/ixml/ref/trim) | `$str` | | Find position | [`pos`](https://www.zeyos.com/ixml/ref/pos) | `needle` | | Extract substring | [`substr`](https://www.zeyos.com/ixml/ref/substr) | `$str` | | Pad string | [`pad`](https://www.zeyos.com/ixml/ref/pad) | `$str` | | Regex match | [`match`](https://www.zeyos.com/ixml/ref/match) | `$str` | | Regex replace | [`replace`](https://www.zeyos.com/ixml/ref/replace) | `$str` | | Split to array | [`split`](https://www.zeyos.com/ixml/ref/split) | `$str` | ## String in comparisons An empty string (`''`) is considered "falsy" — it compares equal to `NULL`, `FALSE`, and `undefined` in loose comparison: ```xml x is empty ``` A non-empty string (even `"0"`) is truthy in boolean context, except when compared numerically. See [Type Conversion](https://www.zeyos.com/ixml/ixml-guide/data-types/type-conversion) for the full comparison table. ## Encoding and escaping For encoding/decoding strings (Base64, URL encoding, HTML escaping, JSON, etc.), see the [Standard Library: Encoding](https://www.zeyos.com/ixml/ixml-guide/standard-library/encoding) chapter. --- Canonical: https://www.zeyos.com/ixml/ixml-guide/data-types/type-conversion # Type Conversion and Assignment ## Assignment Model The way iXML assigns values depends on the type: - **Primitive variables** (null, bool, int, float, string) are assigned **by value**. Copies are created. - **Complex variables** (array, function, macro, class) are assigned **by reference**. Both variables point to the same data. ## Type Conversion Table The following table shows how values behave across type inspection, comparison, and casting. | Value | `TYPEOF` | `IF=''` | `IF!=''` | `IF='0'` | `CAST(bool)` | `CAST(int)` | `CAST(float)` | `CAST(string)` | |---|---|---|---|---|---|---|---|---| | `` | `'undefined'` | true | false | false | `false` | `0` | `0` | `''` | | `NULL` | `'null'` | true | false | false | `false` | `0` | `0` | `''` | | `TRUE` | `'bool'` | false | true | false | `true` | `1` | `1` | `'1'` | | `FALSE` | `'bool'` | true | false | false | `false` | `0` | `0` | `''` | | `0` | `'int'` | false | true | true | `false` | `0` | `0` | `'0'` | | `123` | `'int'` | false | true | false | `true` | `123` | `123` | `'123'` | | `0.0` | `'float'` | false | true | true | `false` | `0` | `0` | `'0'` | | `'0'` | `'string'` | false | true | true | `false` | `0` | `0` | `'0'` | | `'abc'` | `'string'` | false | true | false | `true` | `0` | `0` | `'abc'` | | `''` | `'string'` | true | false | false | `false` | `0` | `0` | `''` | | `` | `'array'` | false | true | false | `true` | `0` | `0` | `'[array#N]'` | :::warning **Numeric Comparison Trap**: `` is true because numeric comparison is performed when both sides look numeric. ::: --- Canonical: https://www.zeyos.com/ixml/ixml-guide/database-operations # Database Operations ZeyOS database access operates on two distinct layers: 1. **[Object-Level Operations](https://www.zeyos.com/ixml/ixml-guide/database-operations/object-operations)**: Work with ZeyOS business entities (Contacts, Accounts, etc.). They handle ExtData, tags, and business logic automatically. 2. **[SQL-Level Operations](https://www.zeyos.com/ixml/ixml-guide/database-operations/sql-operations)**: Specific SQL interface for complex queries, joins, and aggregations. 3. **[Extending the Data Model](https://www.zeyos.com/ixml/ixml-guide/database-operations/extending-data-model)**: Augment entities with custom fields using ExtData, without changing the database schema. 4. **[Tags](https://www.zeyos.com/ixml/ixml-guide/database-operations/tags)**: Label records with free-form tags for classification, filtering, and workflow routing. ## Architecture Guidelines - **Use Object-Level Operations** for standard CRUD (Create, Read, Update, Delete) on single entities. - **Use SQL-Level Operations** for reporting, bulk updates, or when you need specific fields/joins. ## Connection Management By default, operations run against the ZeyOS application database. Use **[Connections](https://www.zeyos.com/ixml/ixml-guide/database-operations/connections)** to connect to external databases. --- Canonical: https://www.zeyos.com/ixml/ixml-guide/database-operations/connections # Connections and Raw SQL ## External database connections Use [`db:connection`](https://www.zeyos.com/ixml/ref/db-connection) to connect to an external database. All operations inside the block run against that connection: ```xml ``` By default, all database operations run against the ZeyOS application database. You only need `db:connection` for external databases. ## Parameterized queries with `db:prepare` and `db:run` [`db:run`](https://www.zeyos.com/ixml/ref/db-run) executes arbitrary SQL and returns the result set. **Always** use [`db:prepare`](https://www.zeyos.com/ixml/ref/db-prepare) to construct queries with dynamic values — this prevents SQL injection and ensures proper value escaping. :::warning Never concatenate user input directly into SQL strings. Always use `db:prepare` with parameter binding. ::: ### Basic pattern Every `db:run` call should use a prepared statement: ```xml $userInput SELECT firstname, lastname FROM contacts WHERE email = ? $sql ``` The `?` placeholder is replaced by the corresponding `` value, properly escaped for the database. ### Result set formats The `type` attribute on `db:run` controls how results are returned: | Type | Description | |------|-------------| | `assoc` | Array of associative arrays (column names as keys) | | `num` | Array of numerically indexed arrays | | `list` | Flat array of values from the first column | | `value` | Single scalar value (first column of first row) | | `self` | Raw result set handle | ### Value binding vs identifier binding `db:prepare` supports two placeholder types: - **`?`** — Value binding. The parameter is escaped and quoted as a value. Use for data. - **`$`** — Identifier binding. The parameter is quoted as an identifier. Use for table/column names. ```xml contacts active SELECT * FROM $ WHERE status = ? $sql ``` Here, `$` is replaced by the identifier `contacts` (quoted), and `?` is replaced by the value `active` (escaped). ### Multiple parameters Parameters are bound in order of appearance: ```xml $minAge $maxAge $status SELECT * FROM contacts WHERE age BETWEEN ? AND ? AND status = ? $sql $contact.firstname $contact.lastname ``` ### Write operations `db:prepare` + `db:run` also works for INSERT, UPDATE, and DELETE: ```xml $name $email INSERT INTO contacts (name, email) VALUES (?, ?) $sql ``` For structured write operations on ZeyOS entities, prefer the object-level [`db:set`](https://www.zeyos.com/ixml/ref/db-set) command instead, which handles ExtData, tags, and business logic automatically. --- Canonical: https://www.zeyos.com/ixml/ixml-guide/database-operations/extending-data-model # Extending the Data Model ZeyOS has a fixed database schema with standard entities — contacts, accounts, transactions, tasks, tickets, and more — each with predefined fields. But applications often need to store additional data specific to their use case: a CRM might need a "lead score" on contacts, or a billing app might need a "payment terms" field on accounts. **ExtData** solves this by providing a key-value overlay that augments any entity with custom fields — without altering the underlying schema. Each application can extend entity types independently, without conflicting with other applications or the core data model. ## How it works ExtData fields are stored as key-value pairs associated with an entity record. Each field has a string key and a string value. You can read, write, and delete ExtData fields using the `extdata` namespace, or include them inline when using `db:set` for entity operations. ## Reading custom fields Use [`extdata:get`](https://www.zeyos.com/ixml/ref/extdata-get) to retrieve custom field values from an entity: ```xml company competitor Company: $data.company ``` If no `` children are specified, all ExtData fields are returned. ## Writing custom fields Use [`extdata:set`](https://www.zeyos.com/ixml/ref/extdata-set) to write custom field values. Supports both inline data and variable-based data: ```xml Microsoft Apple ``` ## Read-modify-write pattern ```xml company Apple ``` ## Removing custom fields Use [`extdata:unset`](https://www.zeyos.com/ixml/ref/extdata-unset) to remove fields: ```xml company ``` ## Using ExtData with db:set When creating or updating entities with [`db:set`](https://www.zeyos.com/ixml/ref/db-set), you can include ExtData fields inline alongside standard entity fields. This lets you set core data and custom fields in a single operation: ```xml John Smith Engineering EMP-0042 Employee ``` ## When to use ExtData vs. standard fields - Use **standard fields** (via `db:data`) for data that maps to ZeyOS's built-in entity schema — `firstname`, `lastname`, `email`, `status`, etc. - Use **ExtData** for application-specific data that doesn't exist in the core schema — user preferences, computed values, integration IDs from external systems, custom categorizations, or any domain-specific attribute your application needs. ExtData fields are scoped to the application. Multiple apps can each define their own ExtData fields on the same entity type without interfering with each other. --- Canonical: https://www.zeyos.com/ixml/ixml-guide/database-operations/object-operations # Object-Level Operations These commands work with high-level ZeyOS entities. ## `db:get` — Retrieve Entity [Full reference →](https://www.zeyos.com/ixml/ref/db-get) Retrieves an object by ID. ```xml firstname lastname ``` Related: "[db:field reference →](https://www.zeyos.com/ixml/ref/db-field)" ## `db:set` — Create/Update Entity [Full reference →](https://www.zeyos.com/ixml/ref/db-set) Creates a new object or updates an existing one. Supports ExtData and Tags. ```xml John Value Customer ``` Related: "[db:data reference →](https://www.zeyos.com/ixml/ref/db-data) | [extdata:data reference →](https://www.zeyos.com/ixml/ref/extdata-data) | [tags:name reference →](https://www.zeyos.com/ixml/ref/tags-name)" ## `db:remove` — Delete Entity [Full reference →](https://www.zeyos.com/ixml/ref/db-remove) Deletes an object by ID. ```xml ``` ## `db:lookup` — Find Entity ID [Full reference →](https://www.zeyos.com/ixml/ref/db-lookup) Finds an ID matching criteria. ```xml test@example.com ``` Related: "[db:is reference →](https://www.zeyos.com/ixml/ref/db-is)" --- Canonical: https://www.zeyos.com/ixml/ixml-guide/database-operations/sql-operations # SQL-Level Operations ## `db:select` — Select Query Builds and executes a SELECT query. ### Syntax ```xml firstname lastname contacts user ``` ### Joins Use `` with `` or ``. ```xml ``` ## `db:insert` / `db:update` / `db:delete` Structured write operations use `db:insert`, `db:update`, and `db:delete`. ## Transactions Wrap write operations in `db:transaction` for atomicity. ```xml ``` --- Canonical: https://www.zeyos.com/ixml/ixml-guide/database-operations/tags # Tags Tags are free-form labels that can be attached to any ZeyOS entity record (contacts, accounts, transactions, tickets, etc.). Unlike ExtData, which stores structured key-value data, tags are simple strings used for categorization, filtering, and workflow routing. A single record can carry any number of tags, and the same tag name can appear on records across different entity types. Common uses for tags include: - **Classification** — labeling contacts as "VIP", "Partner", or "Prospect" - **Workflow state** — marking records as "Needs Review" or "Approved" - **Filtering** — selecting subsets of records in queries and exports - **Segmentation** — grouping records for reports or bulk operations ## Adding tags Use `tags:add` to attach one or more tags to a record. Specify the entity type and record ID, then list the tag names as `tags:name` children: ```xml VIP Newsletter ``` Adding a tag that already exists on the record is a no-op — it will not create duplicates. [`tags:add` reference →](https://www.zeyos.com/ixml/ref/tags-add) ### Tags inside `db:set` When creating or updating records with `db:set`, you can include `tags:name` children directly alongside `db:data` and `extdata:data`: ```xml Ada Lovelace Customer A-Class ``` This sets the record's tags as part of the same operation. Note that `db:set` **replaces** all existing tags with the ones you specify (like `tags:reset`), whereas `tags:add` appends to the existing tag set. ## Removing tags Use `tags:remove` to detach specific tags from a record: ```xml Prospect ``` [`tags:remove` reference →](https://www.zeyos.com/ixml/ref/tags-remove) ## Replacing all tags Use `tags:reset` to remove all existing tags and replace them with a new set: ```xml Customer Active ``` After this call, the record has exactly two tags — regardless of what was there before. [`tags:reset` reference →](https://www.zeyos.com/ixml/ref/tags-reset) ## Reading tags ### Listing tags on a record Use `tags:list` to retrieve all tag names for a specific record as an array: ```xml $tag&n; ``` [`tags:list` reference →](https://www.zeyos.com/ixml/ref/tags-list) ### Checking if tags exist Use `tags:exists` to check whether a record has any tags at all: ```xml This contact has tags. ``` To check for a specific tag, combine `tags:list` with an array search: ```xml VIP Contact is a VIP. ``` [`tags:exists` reference →](https://www.zeyos.com/ixml/ref/tags-exists) ### Passing tags via variable All tag commands accept `var_names` to supply tag names from an array variable instead of inline `tags:name` children: ```xml Customer Priority ``` ## Filtering queries by tags ### `db:intags` — SQL-level tag filtering Use `db:intags` inside `db:select` to filter results to records that carry specific tags: ```xml c.firstname c.lastname c.email contacts VIP ``` You can also pass tag names from a variable: ```xml VIP Partner c.firstname c.lastname contacts ``` [`db:intags` reference →](https://www.zeyos.com/ixml/ref/db-intags) ### Tag joins — `db:innertags` and `db:lefttags` For more complex queries, you can join on the tags table directly. Use `db:innertags` to include only records with a specific tag, or `db:lefttags` to include all records while making the tag data available for conditional logic: ```xml c.firstname c.lastname contacts ``` [`db:innertags` reference →](https://www.zeyos.com/ixml/ref/db-innertags) | [`db:lefttags` reference →](https://www.zeyos.com/ixml/ref/db-lefttags) ## Practical patterns ### Tag-based workflow routing ```xml Urgent Escalated ``` ### Bulk tagging from a query ```xml ID transactions 0 $DATENOW Overdue ``` --- Canonical: https://www.zeyos.com/ixml/ixml-guide/development-environment # Development Environment There are two ways to create and run iXML scripts in ZeyOS: 1. **ZeyOS Development Console** — a browser-based code editor built into ZeyOS for writing, testing, and debugging iXML directly in the platform. 2. **ZeyOS SDK** (`zeysdk`) — a command-line toolkit for local development, version control, and deployment workflows. Both approaches execute iXML against the same ZeyOS runtime, so the language behavior is identical. The difference is in the development workflow. **Choose the Development Console** when you want to: - Quickly test a snippet or explore a concept - Debug an issue on a running instance - Make small changes without setting up a local environment **Choose the SDK** when you want to: - Develop full applications with multiple services and resources - Use version control (git) for your codebase - Work in your preferred IDE with XML/iXML schema support - Deploy across multiple instances ## In this section - [Development Console](https://www.zeyos.com/ixml/ixml-guide/development-environment/dev-console) — browser-based iXML editor and runner - [SDK Workflow](https://www.zeyos.com/ixml/ixml-guide/development-environment/sdk) — command-line development, sync, and testing - [App Structure](https://www.zeyos.com/ixml/ixml-guide/development-environment/app-structure) — how ZeyOS apps are organized - [Coding Agents and Validation](https://www.zeyos.com/ixml/ixml-guide/development-environment/coding-agents) — reference-driven editing, XML formatting, parameter checks, and database verification - [Execution Contexts](https://www.zeyos.com/ixml/ixml-guide/development-environment/execution-contexts) — identity, inputs, output, and side effects for weblets, services, hooks, and `zeysdk run` - [Minimal App](https://www.zeyos.com/ixml/ixml-guide/development-environment/minimal-app) — a small, complete app with a resource, a protected service, a weblet, and settings - [Verification Checkpoints](https://www.zeyos.com/ixml/ixml-guide/development-environment/verification-checkpoints) — local checks, remote compilation, deployment, and runtime verification as separate steps - [Troubleshooting](https://www.zeyos.com/ixml/ixml-guide/development-environment/troubleshooting) — symptoms, causes, confirmation steps, and fixes Working with a coding agent? This section covers the **Build a ZeyOS app** route. The [Agent Setup](https://www.zeyos.com/guide/agent-workflows/agent-setup) page also covers **Connect an external application** and **Use ZeyOS business data**. --- Canonical: https://www.zeyos.com/ixml/ixml-guide/development-environment/app-structure # App Structure A ZeyOS app is the unit of packaging, sync, and deployment. Understanding app structure is essential because iXML behavior depends on where code is executed within the app. ## Directory layout A typical ZeyOS app contains: ``` app-name/ ├── zeyos.app.json # App metadata and identity ├── assets/ # Generic files ├── services/ # Entry points: API, background tasks, hooks ├── resources/ # Reusable domain logic and helpers ├── weblets/ # UI-linked iXML integrations └── settings/ # Per-instance configuration ├── defaultsettings.json └── settings..json ``` A useful mental model: **services** orchestrate requests, **resources** encapsulate logic, and **settings** provide runtime variability. ## Runtime contexts Each entry point has its own identity, globals, and output behavior. This section is an overview. [Execution Contexts](https://www.zeyos.com/ixml/ixml-guide/development-environment/execution-contexts) has the full matrix, and [Minimal App](https://www.zeyos.com/ixml/ixml-guide/development-environment/minimal-app) shows all the parts working together. ### Service context Files in `services/` are invoked as runtime entry points. This is where you: - Parse inbound payloads - Enforce auth and guard clauses - Call reusable resource logic - Shape final response payloads Services are registered in `zeyos.app.json` with a `type`. The SDK uses these uppercase names: `REMOTECALL` (HTTP API endpoint), `TIMING` (scheduled job), and the entity hooks `AFTER_CREATION`, `BEFORE_MODIFICATION`, `AFTER_MODIFICATION`, `AFTER_CREATION_MODIFICATION`, `BEFORE_DELETION`, and `AFTER_DELETION`. Older manifests with lowercase values still work, because the SDK normalizes them. A `REMOTECALL` service can run without an authenticated caller, so "enforce auth" above is your code's job. See [REMOTECALL services](https://www.zeyos.com/ixml/ixml-guide/development-environment/execution-contexts#remotecall-services). Inside `rest:resource`, the request can hide globals, so [bind them with `use`](https://www.zeyos.com/ixml/ixml-guide/development-environment/execution-contexts#bind-globals-inside-restresource). ### Resource context Files in `resources/` are consumed via [`include`](https://www.zeyos.com/ixml/ref/include), [`call`](https://www.zeyos.com/ixml/ref/call), or [`expand`](https://www.zeyos.com/ixml/ref/expand). This context should hold reusable logic that is independent from a specific transport boundary. `include` loads a resource by default, and its `type` attribute selects `service` or `weblet` instead. What it returns depends on the resource's MIME type. See [Includes and JSON resources](https://www.zeyos.com/ixml/ixml-guide/development-environment/execution-contexts#includes-and-json-resources). ### Weblet context Files in `weblets/` are linked to UI action flows. They compose resource functions and app settings to produce user-facing behavior. Weblets run in the user's browser session. That session does not authenticate a `REMOTECALL` service, so see [Weblets](https://www.zeyos.com/ixml/ixml-guide/development-environment/execution-contexts#weblets) before connecting the two. ## App settings and `APPSETTINGS` Settings under `settings/` are the preferred place for tenant-specific values and environment toggles. Avoid hardcoding instance-specific values directly into service logic. `defaultsettings.json` ships with the app, and `settings..json` overrides it recursively for one instance. See [Settings](https://www.zeyos.com/ixml/ixml-guide/development-environment/minimal-app#settings). Use `APPSETTINGS` for values such as: - External endpoint URLs - Credentials references and key aliases, with secrets only in instance settings - Feature toggles and rollout switches - Integration mode flags This allows one codebase to run against multiple instances with different behavior per instance, without branching code. ## Context switching and scope behavior When iXML enters function/class/include/resource boundaries, it creates local execution context. Variable visibility follows the [precedence rules](https://www.zeyos.com/ixml/ixml-guide/introduction/scope-and-assignment#visibility-precedence) explained in the Introduction. Key implications: - Local variables are context-bound. - Globals (``) should be explicit and rare. - Free variables (``) should be intentional and readable. ## Recommended service-to-resource flow ```xml 401 $id
Content-Type: application/json
``` This pattern keeps transport details local to `services/` while maintaining reusable, testable logic in `resources/`. The leading dot in `.AccountController` addresses a resource in the current app. It is confirmed by the platform source and used in real apps, but `ref/ixmldoc.xml` does not describe it. ## Common mistakes - Treating `resources/` as route handlers instead of reusable modules. - Hardcoding instance-specific values instead of using `settings/` and `APPSETTINGS`. - Using globals as implicit data transport between unrelated contexts. - Mixing UI-specific behavior into shared domain modules. - Assuming a `REMOTECALL` route is protected because the platform did not reject the call. ## Related topics - [Execution Contexts](https://www.zeyos.com/ixml/ixml-guide/development-environment/execution-contexts) - [Minimal App](https://www.zeyos.com/ixml/ixml-guide/development-environment/minimal-app) - [Verification Checkpoints](https://www.zeyos.com/ixml/ixml-guide/development-environment/verification-checkpoints) - [Troubleshooting](https://www.zeyos.com/ixml/ixml-guide/development-environment/troubleshooting) --- Canonical: https://www.zeyos.com/ixml/ixml-guide/development-environment/coding-agents # Coding Agents and Native Code Validation Coding agents work best when each change follows the same sequence: retrieve the relevant contract, adapt a verified example, review the patch, and compile it without execution. Run a behavioral test only when the task calls for one. Use [Agent Setup](https://www.zeyos.com/guide/agent-workflows/agent-setup) for Kilo Code, Pi or OpenCode and the [project instructions Markdown file](https://www.zeyos.com/ai/agent-instructions.md) to configure this workflow. Agent Setup organizes agent work into three routes: - **Build a ZeyOS app:** this page and the rest of this section, including [Execution Contexts](https://www.zeyos.com/ixml/ixml-guide/development-environment/execution-contexts), [Minimal App](https://www.zeyos.com/ixml/ixml-guide/development-environment/minimal-app), [Verification Checkpoints](https://www.zeyos.com/ixml/ixml-guide/development-environment/verification-checkpoints), and [Troubleshooting](https://www.zeyos.com/ixml/ixml-guide/development-environment/troubleshooting). - **Connect an external application:** the REST API and Client, covered in Agent Setup. - **Use ZeyOS business data:** the `zeyos` CLI and data tools, also covered in Agent Setup. ## Choose the right evidence | Question | Source in this repository | | --- | --- | | Is an iXML command or attribute supported? | `ref/ixmldoc.xml`, including prose, children, and result bindings | | Does a Zymba method exist and what does it accept? | `ref/zymbaref.json` and `ref/zymbadoc.md` | | Is a database identifier or relationship real? | `ref/dbref.json`, `ref/simpledb.sql`, and `ref/views.txt` | | What does an API operation accept? | The corresponding file in `ref/openapi/` | | What will an SDK command do? | The corresponding file in `ref/sdk/` and installed SDK help | | Has this example run successfully? | Dated entries in `LEARN.md`, including later failures | | How does this application pass inputs? | The calling service/resource/weblet, manifest, and application lessons | | Which identity and globals does this code get? | [Execution Contexts](https://www.zeyos.com/ixml/ixml-guide/development-environment/execution-contexts) | Search for the exact symbol, then read its complete reference entry. Looking up only the command name misses defaults, result bindings, and context restrictions. Treat a conflict between the reference and a runtime observation as a compatibility issue to investigate. Do not change read-only reference material to make a generated example appear valid. The Zymba guide tree is deferred draft material. Use its official reference and execution evidence when working on native Zymba; do not infer support from PHP or JavaScript. ## Format XML without changing behavior Keep existing indentation and envelope conventions. For new examples, use two spaces per nesting level, double-quoted attributes, and one logical statement per line. Put long attribute lists on separate indented lines. Keep short text values on one line and use ` />` consistently for empty elements. These are editing conventions, not language requirements. This is a readable form of the comparison fragment recorded as successful on 6 February 2026: ```xml This is true ``` Its expected result is `This is true`. The default comparison is equality. There is no `condition` attribute. For XML syntax inside attribute values, escape `<` as `<`, `>` as `>`, `&` as `&`, and a double quote as `"`. Preserve intentional entity references instead of double-escaping them. A comparison such as `func="<"` is the documented representation of less-than. Raw `<` inside an attribute failed in the execution log. Preserve text nodes, CDATA, and embedded HTML, JSON, or SQL. A formatter must not collapse significant spaces, move a literal across a command boundary, or rewrite an established namespace/envelope merely to satisfy a generic XML tool. Native snippets can contain multiple statements and prefixed command names; a generic namespace-aware parser may reject a fragment that the ZeyOS runtime accepts. Document-specific entities such as `&n;` also need the appropriate parser context. ### Preserve result propagation An element can receive a result from a child statement. Literal text alongside embedded code is not ordinary string concatenation. The reference explains that these literal values are discarded, and this exact fragment returned `27` in `LEARN.md`: ```xml $(15 + (7.5 * 2 - 3))471 ``` Do not reformat or rewrite that expression assuming it produces `27471`. When building text, use a documented string operation or substitution into a separate text-only statement: ```xml iXML Hello $name ``` This exact fragment returned `Hello iXML`. `$` substitutes a value; it is not part of the declared variable name. `` is invalid. ### Make types explicit `set` content defaults to a string. The following recorded example returns `int` after an explicit cast: ```xml 42 $t ``` The formatted examples above reuse historically executed fragments; they are not new execution results for the current instance. Keep standalone test fragments distinct from complete application files, which may require an envelope and an entry-point context. ## Validate parameters and structure For every changed command, inspect: 1. The exact command name and its documented attributes, input types, defaults, and allowed values. 2. Valid parents, children, occurrence constraints, and whether the content is text or executable iXML. 3. Result bindings and whether the result propagates to its parent. 4. Conditional requirements and restrictions described in prose. 5. The actual input contract of any called function, include, service, or hook. The reference's attribute list is not a complete required-parameter schema. An absent default does not establish that the attribute is mandatory. Likewise, an empty OpenAPI `required` list does not prove that an API accepts an empty payload. Combine metadata with prose, application contracts, database constraints, and runtime evidence. For example, this function and call were executed successfully in the log: ```xml $firstname $lastname Ada Lovelace $name ``` Expected output: `Ada Lovelace`. An agent checking required application inputs must inspect how `firstname` and `lastname` are used and what the caller promises. XML well-formedness alone cannot detect a missing business input. Exercise omitted, empty, malformed, and valid input cases where the application's behavior depends on them. ## Validate database requests Write a short identifier map before writing the query. For each table or view, selected field, condition, sort expression, and join, record its exact name and source. Check column case, types, defaults, identity/generated flags, foreign keys, and enums. A CLI alias is not a database table name, and a UI label is not a column name. For this recorded read example, the map is `users` → table; `u` → local alias; `u.ID` → the `ID` column on `users`: ```xml u.ID users u.ID $users[0].ID ``` This returned `1` in the historical sandbox. Another instance can return a different ID or no row. The example assumes a visible user exists; application code must handle an empty result before using the first row. `db:select` uses `var` for the generated SQL string and `var_result` for retrieved rows. `type="assoc"` requests rows with field names as keys. Capturing the SQL text is not a database validation pass, and this command is documented as generating and executing the query; do not advertise it as a dry-run facility. For joins, follow every foreign key to its destination. `accounts.contact` references `contacts.ID`; that relationship does not imply any proposed field on `contacts` exists. Check each terminal field and expected cardinality. For native SQL, use documented query construction/preparation and distinguish escaped values from allowlisted identifiers. Never invent placeholder syntax or interpolate untrusted data directly. Check business meaning after checking identifiers: a one-to-many join can duplicate totals, a nullable relationship can remove rows, and a page limit can make a count incomplete. Validate the expected result shape, null handling, stable ordering, and empty-result behavior. ### External requests use a different validator The `zeyos` CLI's `describe` and `--dry-run`, and `client.schema.validate()`, catch common API mistakes before a request. Client/CLI 1.0.0 and later check complete documented FK paths and custom keys supplied through `customFields` (JavaScript) or an instance-bound `ZEYOS_CUSTOM_FIELDS_FILE` snapshot (CLI/MCP). Missing definitions fail explicitly. Confirm snapshot provenance and refresh after configuration changes; offline discovery does not query the tenant. Older releases accepted joined suffixes and custom fields unchecked, so use the matching client build. These checks do not validate native SQL or compile scripts. The curated required-create supplement covers account currency; check other required inputs against the endpoint and runtime. See [the API validation workflow](https://www.zeyos.com/guide/agent-workflows/agent-setup#prevent-invented-tables-and-fields). ## Apply the same discipline to Zymba When local references are unavailable, use the [authoritative symbol index](https://www.zeyos.com/ai/zymba/symbols.json) and [namespace index](https://www.zeyos.com/ai/zymba/namespaces.json). Entries retain exact declarations, a source hash, and a JSON pointer into the [official reference mirror](https://www.zeyos.com/__doc/zymbaref.json). Fetch only the relevant namespace, such as [Array](https://www.zeyos.com/ai/zymba/namespace.Array.json), or the [reference working set](https://www.zeyos.com/zymba/llms-full.txt). Preserve duplicate declarations and static/instance distinctions. Read raw parameter/default/return metadata and annotations; a generated signature does not establish runtime correctness or a complete required-input contract. This reference index does not promote the deferred Zymba guide drafts. Before calling a method, verify the class, receiver, argument order, optional/defaulted and nullable parameters, return value, and mutation behavior. A nullable parameter and an optional parameter are different concepts; read the default and signature information rather than assuming a nullable argument can be omitted. The execution log provides useful regression cases: | Mistake | Reference-backed behavior | | --- | --- | | Concatenating strings with `+` | Use `.`; `+` performs numeric addition | | Calling `@String.length` | Use `count` or `@String.countChars` | | Calling `@String.joinValues` | Use `@Array.joinValues` with its documented arguments | | Checking `is array` | Arrays are objects; use the documented type system | | Using PHP magic constants | `__FILE__`, `__LINE__`, and `__FUNCTION__` failed in the recorded runtime | Do not replace those checks with a generic PHP/JavaScript linter. Use the ZeyOS compiler for native compilation checks and separate execution tests for behavior. Preserve the application's conventions for service parameters and object methods. ## Compile without executing Use the SDK's compiler commands when the task asks to validate native code. Check `zeysdk compile --help` and `zeysdk lint --help` in the installed SDK first. The [official SDK debugging documentation](https://www.zeyos.com/sdk/debugging/) describes both commands; the pinned `ref/openapi/sdk.json` separately documents `PUT /compile` and `PUT /run`. From an existing linked application directory, compile a changed asset or a small inline fragment: ```bash # Replace the example path with an existing application asset. zeysdk compile services/example.ixml zeysdk compile --ixml 'iXMLHello $name' zeysdk compile --zymba '$a = "Hello "; $b = "World"; echo $a . $b;' ``` These commands compile the supplied code **without executing it**. The inline examples are based on the guide's historical snippets; this documentation update did not submit them to the compiler. A compile result is not their printed output. For project-level checks: ```bash zeysdk lint zeysdk lint --all ``` `lint` checks modified, locally present scripts selected from the application manifest and standard settings files. `--all` checks all eligible locally present `.ixml`, `.zy` and `.zymba` files in that set; it is not an unrestricted directory scan. Record which files were checked. A successful exit with no eligible files is not evidence that a changed script was validated; compile that asset directly or correct its registration. **Compiler-only is not offline.** Both commands require a linked application, authentication and network access, and send source to the configured instance. `lint --all` skips the preliminary server-state comparison, not compilation requests. Confirm the intended instance and permission to send source; use the existing private credentials. Neither command requires syncing or running the application merely to obtain compiler diagnostics. Do not replace an unavailable compiler check with `run`. Compilation catches errors reported by the native compiler. Continue the reference and schema checks above: a compiler pass does not prove that dynamic database identifiers exist, inputs are complete, permissions are sufficient, or business behavior is correct. ## Report validation precisely | Stage | What it establishes | What remains | | --- | --- | --- | | Reference review | Names, signatures, result bindings, and documented constraints | Runtime/version differences and application behavior | | Local structure/schema checks | Errors within the checker's explicit coverage | Native compilation, dynamic values and missing metadata | | SDK `compile` / `lint` | Native compiler diagnostics for the reported files on the selected instance, without execution | Runtime inputs, database semantics, permissions and business behavior | | Sandbox execution | Observed behavior for supplied inputs | Other inputs and invocation contexts | | Entry-point integration test | Behavior through the intended hook, form, service, or HTTP request | Production-specific data and operational conditions | [Verification Checkpoints](https://www.zeyos.com/ixml/ixml-guide/development-environment/verification-checkpoints) turns these stages into four steps with expected outcomes and failure cases. [Troubleshooting](https://www.zeyos.com/ixml/ixml-guide/development-environment/troubleshooting) lists common symptoms. `zeysdk run` executes code. Use it only when execution is part of the authorized task, with a confirmed sandbox and bounded inputs. An inline REST example in the historical log produced HTTP 404 because the invocation did not simulate the inbound route; compilation and inline execution do not establish route behavior. Keep findings in the application's designated validation log with the date, revision, reference version/hash, target instance, checker, checked files and compiler diagnostics. For execution tests, also record inputs, expected and actual outputs, and invocation context. Redact private data. Updating this guide's pinned `LEARN.md` requires the separate release workflow. If compiler access is unavailable, finish local/reference checks and report native compilation as not performed. If execution was not requested, report it as not performed; it need not become the default next action. Never label unexecuted code as runtime-validated. ## Improve automation incrementally A useful future checker can index iXML command/child contracts and Zymba method signatures from the references, resolve static database identifiers, and report source locations for each diagnostic. Dynamic names and incomplete metadata must yield a request for runtime evidence, not a false pass. This is a proposed extension, not an existing compiler supplied by the guide. Start a regression collection with the failures above. Enable complete API relationship-path validation and supply reviewed tenant custom-field metadata before expanding unattended writes. Native SQL still requires reference and runtime checks. Preserve reference provenance and test formatting changes for result equivalence. Use restricted tools and credentials to enforce operation scope; instructions provide working guidance, not isolation from a production system. --- Canonical: https://www.zeyos.com/ixml/ixml-guide/development-environment/dev-console # Development Console The ZeyOS Development Console is a browser-based code editor built into the ZeyOS platform. It allows you to write and execute iXML scripts directly on a running ZeyOS instance without installing any local tools. ## What the Development Console provides - **Code editor** — a syntax-aware editor for writing iXML directly in the browser, with basic highlighting and validation. - **Direct execution** — run iXML snippets against the current instance and see output immediately. - **Debug output** — view `debug:output` and `debug:dump` messages alongside your script's regular output. - **Quick iteration** — test ideas, debug issues, and explore the iXML API without leaving the browser. ## When to use the Development Console The Development Console is ideal for: - **Learning iXML** — experiment with language features and see results instantly. - **Quick debugging** — run diagnostic queries or inspect data on a live instance. - **Prototyping** — test a snippet before integrating it into an application. - **Ad-hoc operations** — run one-off data queries or transformations. For building full applications with version control and multi-instance deployment, use the [SDK](https://www.zeyos.com/ixml/ixml-guide/development-environment/sdk) instead. ## Example A simple snippet in the Development Console: ```xml Hello from the Development Console $greeting ``` Running this produces the output `Hello from the Development Console` directly in the browser. --- Canonical: https://www.zeyos.com/ixml/ixml-guide/development-environment/execution-contexts # Execution Contexts The same iXML file can behave differently depending on how it was started. Identity, available globals, and HTTP behavior all come from the **execution context**, not from the file. Most app bugs that survive compilation are context bugs: code that works in `zeysdk run` fails in a weblet, or a service that works with a token rejects a browser call. Identify the context first, then read the row that applies. Test in that same context. A pass in one context says little about another. ## How to read the evidence labels | Label | Meaning | | --- | --- | | **Reference** | Stated in the official iXML, SDK, or API reference | | **Source-confirmed** | Read in the platform or SDK source for this release. It describes behavior at contract level and has not been executed for this page. A later platform release can change it | | **Logged** | Dated execution result in the guide's validation log | | **Reported** | Field observation that was not independently verified. Check it in your target | ## Context matrix | Context | Identity and auth source | Inputs and globals | Output and HTTP | Side effects | How to test it | | --- | --- | --- | --- | --- | --- | | **REST API** (external client, for contrast) | Token sent by the client, or the session cookie of a signed-in browser | The documented request only. Your iXML does not run here | JSON as defined by the OpenAPI reference | Record writes can fire entity hooks | HTTP request against a sandbox, or the `zeyos` CLI | | **Weblet, framed or page** | Browser session cookie. No valid session means no execution | `REQUEST`, `GET`, `POST`, `FILES`, `SERVER`, `SELFURL`, `VIEW`, `SELFID`, `SELFNS`, `APPSETTINGS`. Record context arrives as request parameters such as `$REQUEST.id` | Your output and headers go to the frame | No implicit transaction. Use `db:transaction` | Open the real view while signed in | | **Weblet in a `.form` view** | Runs while the page is built for the signed-in user | `ENTITY`, `ID`, `DATA`, `EXTDATA`, `TAGS`, `FORM`, `SELFID`, `SELFNS`, `APPSETTINGS`. No `REQUEST`. New-record dialogs differ: no `FORM`, and normally no record variables | Form controls are collected. Plain output is discarded | Form data is saved by the host page | Open the record form and save it | | **`REMOTECALL` service** | Bearer token of the access or legacy type, read into `AUTH`. The cookie is ignored. A configured access key is a separate branch | `AUTH` when authenticated, `REQUEST`, `GET`, `POST`, `FILES`, `SERVER`, `SELFURL`, `SELFID`, `SELFNS`, `APPSETTINGS`. Inside `rest:resource`: route parameters, plus query arguments or POST form fields as locals, which [can hide these globals](#bind-globals-inside-restresource) | Your output, headers, and status go to the caller. CORS is open | No implicit transaction. Unauthenticated calls can still run, even for a restricted application | HTTP request with a token, without one, with an invalid one, and with forged query arguments | | **`zeysdk run`** | SDK token of a developer. No end-user session | `SELFID`, `SELFNS`, `APPSETTINGS`, `DEBUG`. No `AUTH`, `REQUEST`, `SELFURL`, or `VIEW` | Output, status, and headers are returned as a report | Real. Writes, mail, and external calls all happen | Only for the code path itself, on a sandbox | | **`TIMING` service** | Scheduler. No user | `SELFID`, `SELFNS`, `APPSETTINGS` | Discarded. Errors go to the platform log | No implicit transaction. One concurrent run per service | Wait for the schedule and inspect the effects | | **Entity hook** | Runs inside the operation that changed the record. No `AUTH` | `ENTITY`, `ID`, and [event-specific values](#entity-hooks), plus `SELFID`, `SELFNS`, `APPSETTINGS` | Discarded | Inside the record transaction. An error aborts the operation | Trigger the real entity event | The globals, auth branches, and output handling in this table are **Source-confirmed**. Asset types and the `REMOTECALL` and `TIMING` metadata are **Reference**. `APPSETTINGS` is the one input every application context shares. Everything else must be checked per context. The global names above are the iXML forms. Zymba has its own access syntax, so check `ref/zymbadoc.md` for Zymba assets. ## Weblets A weblet's `type` decides how the browser reaches it. Its `view` decides where it appears. ZeySDK 3.3.0 accepts these types: | Type | Presentation (**Source-confirmed**) | | --- | --- | | `INTEGRATED` | Menu entry that opens a page inside the ZeyOS shell | | `STANDALONE` | Menu entry that navigates the same tab | | `DETACHED` | Menu entry that opens a new tab | | `POPUP_FRAMED`, `POPUP_PLAIN` | Menu entry that opens a dialog with a frame | | `EMBEDDED_FRAMED`, `EMBEDDED_COLLAPSED`, `EMBEDDED_PLAIN` | Frame inside the view, with or without a collapsible heading | All of these reach the same weblet endpoint, with one exception: a weblet registered on a view ending in `.form` is not a page. ZeyOS runs it on the server while it builds the form, so it must be iXML and it gets the record variables directly. Choose the path before you write code, because `$REQUEST` and `$ID` are not interchangeable across the two. The endpoint requires a valid session. Without one it answers 401. An inactive weblet or application answers 403, as does a restricted application the user has no permission for. A weblet with a configured `url` redirects to that URL instead of running code. `view` controls placement, not access. A weblet's URL is reachable by any signed-in user, regardless of its `view`, unless the application is restricted and the user lacks permission for it. `$REQUEST.id` is an ordinary query parameter. The session proves who the user is, not that this user may see that record. Check access before you use the value in a query. See [Weblets](https://www.zeyos.com/ixml/ixml-guide/user-interface/weblets#check-record-access) for an example and for the limits on requests that change data. ## REMOTECALL services The service URL has this form, where `` is your instance URL: ```text /remotecall/./ ``` `$SELFURL` holds the numeric form of the same address, `/remotecall/~`. These are the corrections most often needed (**Source-confirmed**, with the SDK access-key flag as **Reference**): - **The browser session cookie does not authenticate a `REMOTECALL` call.** The platform's auth step reads only the `Authorization` header. - **A valid token populates `AUTH`.** It holds `user`, `application`, and `lang`. Access tokens and legacy tokens are accepted. SDK and API tokens are not. - **`AUTH.user` means any valid token.** It does not show which application the token was issued to. Compare `AUTH.application` where that matters. - **An unauthenticated call can still execute.** A missing token and an invalid token both leave `AUTH` unset, and your script runs anyway. The platform does not answer 401 for you. Every protected handler must authorize explicitly. A restricted application does not change this: its permission check applies only when a user was identified, so an anonymous call still runs. - **An access key is a separate branch.** If the service has an `accesskey`, the platform rejects a call without the key with 401. On success `AUTH` holds the key, not a user, so `$AUTH.user` is empty. - **CORS is not authorization.** Every `REMOTECALL` response sends `Access-Control-Allow-Origin: *`. That header controls which pages may read a response. It does not restrict who can call the service. - **A JSON body does not fill `REQUEST`.** Read it with `var_body` on `rest:resource` and decode it inside `try`. ### Bind globals inside `rest:resource` Inside a handler, the request can create local variables, and locals hide globals (**Reference**). Query arguments become locals. For POST, form fields do instead (**Source-confirmed**). A global you read without binding it is therefore under the caller's control. This matters most for authorization. Here `?AUTH[user]=1` passes the check with no token: ```xml 401 401 ``` :::warning **The general rule:** inside `rest:resource`, any platform global (`AUTH`, `APPSETTINGS`, `REQUEST`, and so on) can be replaced by the request. Bind each one with `use` as the first child, before reading it. Do the same for your own globals, including resource controllers, macros, and shared arrays. ::: - **A check before `rest:server` is not sufficient.** It reads the real `AUTH`, but `$AUTH.*` inside the handler can still be forged, for example `$AUTH.lang`. - **Functions are not affected.** A function that the handler calls has its own context, so `$APPSETTINGS` inside a resource function reads the real global. - **Route parameters win over query arguments** of the same name. - **Any local you read before assigning it is also caller-controlled.** Assign your locals first. This behavior was reproduced in a local run of the iXML engine with stand-in globals. Add a forged-argument test to every protected service. See [Checkpoint 4](https://www.zeyos.com/ixml/ixml-guide/development-environment/verification-checkpoints#checkpoint-4-runtime-verification). :::warning The `session` command reads the cookie on its own, independently of the auth step. If a browser sends the cookie to a `REMOTECALL` URL, `session` may return that user while `AUTH` stays unset. Do not build authorization on this. It has not been verified at runtime, it skips the restricted-application check, and a cookie-authorized service is exposed to cross-site request forgery. ::: ### Calling your own service from a weblet The browser may send its cookie with the call, but the auth step ignores it, so a weblet frame that calls its own `REMOTECALL` service arrives unauthenticated. Do not paper over this by accepting anonymous calls or by shipping a long-lived token to the browser. First ask whether the browser needs to call a service at all. The weblet already runs server-side in the user's session and can call the same resource directly, as the [minimal app](https://www.zeyos.com/ixml/ixml-guide/development-environment/minimal-app) does. If the browser must call the service, that is a security design and it needs review. This guide does not publish a recipe. The design must define: - **Principal binding:** which user the credential represents, set on the server from the session, never from a request parameter. - **Route binding:** which routes, methods, and records it permits. - **Expiry:** a short lifetime, checked on the server. - **Replay policy:** whether reuse is allowed and how it is detected or limited. - **Server-side key handling:** any signing key stays in instance settings and never reaches the page, the logs, or a response. - **Verification:** tests for a missing, expired, tampered, wrong-user, and wrong-route credential, each run through a real HTTP request. ## `zeysdk run` `zeysdk run` is console execution with application globals. It is not a simulated weblet and not an inbound HTTP request. - It sends your **local** file to the instance and executes it there, so it can run code you have not pushed. Anything that file includes resolves to the **deployed** assets on that instance. - It runs with the app's namespace and settings, so `$APPSETTINGS` and `.name` includes work. - There is no session, no `AUTH`, no `REQUEST`, no `SELFURL`, and no `VIEW`. `session` returns NULL. - Console requests use PUT, so `GET` and `POST` routes in `rest:server` do not match. **Logged:** on 6 February 2026, an inline REST example returned HTTP 404 and `null`. - **Effects are real.** The console has no dry-run mode. Use `run` to check a function or a query shape. Do not use it to conclude that a weblet, service route, or hook works. For compile-only checks, use `zeysdk compile`. See [Verification Checkpoints](https://www.zeyos.com/ixml/ixml-guide/development-environment/verification-checkpoints). ## TIMING services The scheduler calls the service by `schedule` and `interval`. `schedule` is the start minute of the day, from `0` to `1439`, and `interval` is the repeat period in minutes. With an `interval` of `1`, `schedule` is ignored. The platform prevents concurrent runs of one service. All of this is **Source-confirmed**. Older `ref/sdk` pages show a `0` to `2359` time format, which the current SDK rejects. A skipped run is logged, not queued, so a job must be able to resume on its next run. There is no user and no request. Log what the job did, because output is discarded. ## Entity hooks Hook types are `AFTER_CREATION`, `BEFORE_MODIFICATION`, `AFTER_MODIFICATION`, `AFTER_CREATION_MODIFICATION`, `BEFORE_DELETION`, and `AFTER_DELETION`. Each needs an `entity` (**Reference**). Inputs depend on the event (**Source-confirmed**): | Event | Inputs besides `ENTITY` and `ID` | | --- | --- | | Creation | `NEW`, `NEW_EXTDATA`, `NEW_TAGS`, `MOD`, `MOD_EXTDATA`, `MOD_TAGS_ADD`. No `OLD` | | Modification | All of the above, plus `OLD`, `OLD_EXTDATA`, `OLD_TAGS`, `MOD_TAGS_REMOVE` | | Deletion | `OLD`, `OLD_EXTDATA`, `OLD_TAGS` only | `AFTER_CREATION_MODIFICATION` runs for both creation and modification, so check `OLD` before using it. A hook runs inside the transaction of the change that triggered it (**Source-confirmed**). This has three consequences: - An uncaught error aborts the user's operation, including from an `AFTER_*` hook. - Slow work in a hook slows every save of that entity. Move heavy work to a `TIMING` service. - External side effects such as mail or HTTP calls are not rolled back with the transaction. While a modification hook runs, a further change to the same record does not fire modification hooks again (**Source-confirmed**). ## Includes and JSON resources `include` loads a resource by default. Use `type` to load a service or a weblet (**Reference**): ```xml ``` The leading dot addresses an asset in the current app. It expands to the app identifier, so `.Greeting` in `example.minimal` means `example.minimal.Greeting`. This form is **Source-confirmed** and used throughout real apps, but `ref/ixmldoc.xml` does not describe it. It fails in a script that has no app namespace. What a resource returns depends on its **MIME type**, not its extension (**Source-confirmed**): | Resource MIME type | `include` returns | | --- | --- | | `application/ixml+xml`, `text/x-zymba` | The executed code's result | | `application/json` | Decoded value | | `application/xml`, `text/xml` | Parsed tree | | `text/csv`, `text/tab-separated-values` | Parsed rows | | Other text types | Text, with `$name` replaced from the include's parameters | The SDK derives the MIME type from the extension when you add a file, which is why the two usually agree. A JSON resource is already decoded, so `decode:json` on it is a mistake: ```xml $raw $defaults.title ``` ## Session locale The `session` command returns an array, or NULL when there is no session (**Reference**). The reference does not list its keys. The user's locale is in `data.lang` (**Source-confirmed**, and **Reported** from an app build in November 2026). A top-level `lang` key is not part of the contract, and `ref/dbref.json` lists no `lang` column on `users`. ```xml $session.data.lang ``` In a `REMOTECALL` service, use `$AUTH.lang`. Always handle an empty value. `session` returns NULL only when there is neither a session user nor a valid cookie. That makes the result context-dependent: a hook fired from a signed-in page sees that user, while the same hook fired by a `TIMING` service does not. Do not rely on `session` in hooks or in `REMOTECALL`. ## Common mistakes - Treating a `zeysdk run` pass as proof that a weblet or service route works. - Assuming a `REMOTECALL` handler is protected because the platform did not return 401. - Reading `$AUTH` or `$APPSETTINGS` inside `rest:resource` without binding it with `use`. - Treating `view` as an access control. - Reading `$REQUEST.id` in a `.form` weblet, or `$ID` in a framed weblet. - Sending a user-specific token to the browser to work around the missing session. - Doing slow or external work inside a `BEFORE_*` hook. ## Related topics - [Minimal App](https://www.zeyos.com/ixml/ixml-guide/development-environment/minimal-app) - [Verification Checkpoints](https://www.zeyos.com/ixml/ixml-guide/development-environment/verification-checkpoints) - [Troubleshooting](https://www.zeyos.com/ixml/ixml-guide/development-environment/troubleshooting) - [App Structure](https://www.zeyos.com/ixml/ixml-guide/development-environment/app-structure) - [Weblets](https://www.zeyos.com/ixml/ixml-guide/user-interface/weblets) ## Evidence | Item | Record | | --- | --- | | Guide source | Revision `5271871` plus the working-tree changes of this update | | References | `ref/ixmldoc.xml`, `ref/sdk/`, `ref/dbref.json`, `ref/openapi/sdk.json`, `LEARN.md` | | SDK inspected | `zeysdk` 3.3.0, source and offline help | | Checked | Static review against the references, reading the platform and SDK source, and running the `rest:resource` binding examples in a local copy of the iXML engine with stand-in globals | | Not performed | Remote compilation, deployment, and execution on an instance. Apart from the dated log entry it cites and the local engine run, this page's claims have not been executed | Open runtime checks are listed in `contributing/REVIEW_BACKLOG.md`. --- Canonical: https://www.zeyos.com/ixml/ixml-guide/development-environment/minimal-app # Minimal App This chapter builds the smallest app that shows how the parts of a ZeyOS app fit together: one shared resource, one protected service, one weblet, and instance settings. Use it as a starting point, and as a working example of the [execution contexts](https://www.zeyos.com/ixml/ixml-guide/development-environment/execution-contexts) rule that each entry point authenticates differently. :::note[Verified: remote compiled on sdk-alpha, 5 November 2026] `resources/Greeting.ixml`, `services/api.ixml` and `weblets/hello.ixml` compile with `zeysdk compile` (engine iXML 2.00-00) on a sandbox instance. They have not been deployed or run on an instance. See the [Evidence](#evidence) section before you rely on them. ::: The app has this shape. The same files live in the guide repository under `examples/minimal-app/`. ```text minimal-app/ ├── zeyos.app.json ├── README.md ├── resources/ │ └── Greeting.ixml ├── services/ │ └── api.ixml ├── settings/ │ ├── defaultsettings.json │ ├── settingscode.ixml │ └── usersettingscode.ixml └── weblets/ └── hello.ixml ``` `resources/` holds the logic, while `services/` and `weblets/` are thin entry points that handle identity and output. The two empty settings scripts come from the scaffold. ## Create the scaffold `zeysdk create` writes the directories, an empty `README.md`, `.zeysdk/sync.json`, and three settings files. This step is local. ```bash zeysdk create example.minimal --name "Minimal Example" --vendor "Example" ``` The command also creates an `assets/` directory, which this app does not use. Git does not track empty directories, so it is absent from the example. ## Manifest `zeyos.app.json` names the app and registers every asset. ```json { "identifier": "example.minimal", "name": "Minimal Example", "version": 10000, "vendor": "Example", "resources": { "Greeting": { "name": "Greeting", "mimetype": "application/ixml+xml", "public": 0 } }, "services": { "api": { "name": "API", "type": "REMOTECALL", "entity": "", "schedule": 0, "interval": 1, "url": "", "mimetype": "application/ixml+xml" } }, "weblets": { "hello": { "name": "Hello", "type": "EMBEDDED_FRAMED", "view": "contacts.details_contact.head", "width": 0, "height": 120, "url": "", "langaliases": null, "mimetype": "application/ixml+xml" } } } ``` Each entry's key plus its MIME type's extension gives the file path, so `Greeting` with `application/ixml+xml` is `resources/Greeting.ixml`. | Field | Meaning | | --- | --- | | `identifier` | App ID. Letters, numbers, `.`, `_`, and `-`, starting and ending with a letter or number | | `name`, `vendor` | Display values. The SDK sends these with a push | | `version` | Legacy field. ZeySDK 3.3.0 does not write it in a new scaffold, and a push does not send it. It stays here because the reference's manifest example includes it | | `mimetype` | Decides how the platform treats the asset | | `public` | `1` makes a resource publicly reachable. Keep it `0` for code | | `type` | Service or weblet type. The SDK stores these in uppercase. Older apps with lowercase values still work, because the SDK normalizes them | | `view` | Where a weblet appears. Valid names are in `ref/sdk/views.md` | | `width`, `height` | Frame size in pixels for popup and embedded weblets | ### Asset identifiers Each asset's full identifier is `.`. In this app they are `example.minimal.Greeting`, `example.minimal.api`, and `example.minimal.hello`. Inside the app, `.Greeting` addresses the same resource without repeating the app name. See [Includes and JSON resources](https://www.zeyos.com/ixml/ixml-guide/development-environment/execution-contexts#includes-and-json-resources) for the evidence level of this form. ### Weblet placement `EMBEDDED_FRAMED` on `contacts.details_contact.head` shows a framed panel in the contact detail head. The example sets an explicit `height`. Automatic sizing with height `0` is [not independently verified](https://www.zeyos.com/ixml/ixml-guide/development-environment/troubleshooting#reported-not-independently-verified--check-in-your-target). ## Settings `settings/defaultsettings.json` ships with the app and must never contain secrets. ```json { "greeting": { "defaultPrefix": "Hello", "fallbackName": "there", "prefix": { "de_DE": "Hallo", "en_US": "Hello" } } } ``` An optional `settings/settings..json` holds the values for one linked instance. A push sends only the file for the active instance. At runtime, ZeyOS merges the two recursively into `APPSETTINGS` (**Source-confirmed**). Instance values win. Nested objects merge key by key, so an instance file can override `greeting.prefix.de_DE` and keep the rest. List items merge by position rather than being replaced as a whole, so an instance list shorter than the default keeps the default's extra items. Put secrets only in instance settings, keep those files out of version control, and never send a settings value to a browser or a log. ## Resource The resource defines a function on a global, the same controller style real apps use. It knows nothing about HTTP or sessions, so every entry point can share it. ```xml $name $APPSETTINGS.greeting.fallbackName $APPSETTINGS.greeting.defaultPrefix $lang $APPSETTINGS.greeting.prefix[$lang] $prefix, $clean! ``` Every iXML file here uses the envelope from the SDK's current default template, which references only the DTD. :::warning Do not add `xsi:schemaLocation` pointing to `ixml.xsd`. Older templates include it, but that XSD URL was reported to return 404. Only the DTD at `https://developers.zeyos.com/schema/ixml.dtd` is used here. ::: ## REMOTECALL service The service handles one route and authorizes explicitly. An unauthenticated call still runs this file, so the check is what protects the route. ```xml Authentication required 401
WWW-Authenticate: Bearer
Content-Type: application/json
$name $AUTH.lang $text
Content-Type: application/json
``` Key points: - `$name` comes from the `:name` route segment. A route segment takes priority over a query argument of the same name. - **`` must come first.** Inside `rest:resource`, query arguments become local variables, and locals hide globals. Without ``, the request `?AUTH[user]=1` would create a local `AUTH` and pass the check with no token. The same applies to `Greeting`, which a request could otherwise replace to break the call. - With `AUTH` bound, `AUTH.user` is set only for a valid token, so `empty` covers a missing token, an invalid one, and a forged query argument. - A check placed before `rest:server` is not enough on its own, because `$AUTH.*` inside the handler could still be forged. See [Bind globals inside `rest:resource`](https://www.zeyos.com/ixml/ixml-guide/development-environment/execution-contexts#bind-globals-inside-restresource). - Any valid token passes this check. If a route is meant for one application's tokens, also compare `AUTH.application`. - `return` carries the HTTP status. `` stops the handler. - This check establishes **who** is calling. A route that changes data must also check **what** that user may do, and it should re-check ownership of any record it touches. - `rest:server` buffers output, so `header` can follow other statements inside the handler. Do not produce output before `rest:server`. This follows the [recommended service-to-resource flow](https://www.zeyos.com/ixml/ixml-guide/development-environment/app-structure#recommended-service-to-resource-flow): the service owns transport and authorization, and the resource owns the logic. A call looks like this. The token is a user access token, not your SDK token. ```bash curl -i \ -H "Authorization: Bearer $ZEYOS_ACCESS_TOKEN" \ "https://cloud.zeyos.com//remotecall/example.minimal.api/greeting/Ada" ``` Expected result with a valid token: status 200 and a body such as `{"greeting":"Hello, Ada!"}`. Without a token, or with `?AUTH[user]=1` and no token: status 401 and `{"error":"Authentication required"}`. ## Weblet The weblet runs in the user's session, so it reads the identity from `session` and calls the resource on the server. It does not call the `REMOTECALL` service from the browser. The browser may send its cookie with that call, but [the service's auth step ignores it](https://www.zeyos.com/ixml/ixml-guide/development-environment/execution-contexts#calling-your-own-service-from-a-weblet). ```xml $session.name $session.data.lang $text
Content-Type: text/html; charset=utf-8
$safeText

]]>
``` Key points: - The text is encoded with `encode:html` before it reaches the page, because a display name is data. - `encode:html` assigns to a variable first. Literal text next to an embedded statement in `output` [would be discarded](https://www.zeyos.com/ixml/ixml-guide/introduction/result-propagation). - `$session.data.lang` is the session locale. The resource falls back to `defaultPrefix` if it is empty or unknown. Expected result: a frame in the contact head showing `Hello, !`. ## Deploy and test Run the four [verification checkpoints](https://www.zeyos.com/ixml/ixml-guide/development-environment/verification-checkpoints) in order. In short: ```bash zeysdk link zeysdk compile resources/Greeting.ixml zeysdk compile services/api.ixml zeysdk compile weblets/hello.ixml zeysdk push --dry-run ``` Review the plan, push to a sandbox, then test the service over HTTP and the weblet in the browser. `zeysdk run` cannot test either entry point. ## Common mistakes - Removing the `AUTH.user` check because the service "only reads data". - Removing `` because the handler seems to work without it. It does, until someone adds a query argument. - Putting an API key in `defaultsettings.json`, which ships with the app. - Calling the service from weblet JavaScript and expecting the session to carry over. - Using `include` for a service or weblet without its `type`. - Writing `Hello $name`, which loses `Hello`. ## Related topics - [Execution Contexts](https://www.zeyos.com/ixml/ixml-guide/development-environment/execution-contexts) - [Verification Checkpoints](https://www.zeyos.com/ixml/ixml-guide/development-environment/verification-checkpoints) - [Troubleshooting](https://www.zeyos.com/ixml/ixml-guide/development-environment/troubleshooting) - [App Structure](https://www.zeyos.com/ixml/ixml-guide/development-environment/app-structure) - [SDK Workflow](https://www.zeyos.com/ixml/ixml-guide/development-environment/sdk) ## Evidence | Item | Record | | --- | --- | | Guide source | Revision `5271871` plus the working-tree changes of this update | | References | `ref/ixmldoc.xml`, `ref/sdk/`, `ref/sdk/views.md`, `ref/dbref.json` | | SDK inspected | `zeysdk` 3.3.0: `create` output in a scratch directory, offline help, and source | | Checked | Every command and attribute against `ref/ixmldoc.xml`, manifest-to-file consistency, chapter listings against the example files, JSON parsing, and XML well-formedness with `xmllint`. The service's auth check, including forged query arguments, and the weblet were also run in a local copy of the iXML engine with stand-in globals, without an instance | | Remote compilation | `zeysdk compile` with SDK 3.3.0 on the sandbox `sdk-alpha`, 5 November 2026: all three scripts compile (engine iXML 2.00-00). The two empty settings scripts were not submitted. | | Not performed | Deployment and execution on an instance. No runtime result on this page has been observed on an instance | `xmllint` confirms XML well-formedness only. It is not native validation. The expected outputs above are derived from the reference and the local engine run, not observed on an instance. Open runtime checks are listed in `contributing/REVIEW_BACKLOG.md`. --- Canonical: https://www.zeyos.com/ixml/ixml-guide/development-environment/sdk # SDK Workflow :::tip Full SDK Reference For the complete command reference with all options and examples, see the **[ZeyOS SDK Documentation](https://www.zeyos.com/sdk)**. ::: The ZeyOS SDK (`zeysdk`) is a command-line tool for developing, testing, and deploying ZeyOS applications locally. It provides a structured workflow for managing application code, synchronizing with ZeyOS instances, and validating iXML snippets. ## Command reference | Command | Purpose | Typical frequency | |---|---|---| | `zeysdk create ` | Create new app scaffold | Once per app | | `zeysdk link --token --url ` | Bind local app to target instance | Once per instance | | `zeysdk pull [-f]` | Fetch server state into local app | Before major edits | | `zeysdk push` | Make the server match local state, including deletions | After verified changes | | `zeysdk compare` | Inspect local/remote differences | Before sync | | `zeysdk sync [-f]` | Synchronize local and remote files | Frequently | | `zeysdk compile ` | Compile on the instance without executing | After each change | | `zeysdk lint [--all]` | Compile registered scripts without executing | Before deployment | | `zeysdk run --ixml ''` | Execute iXML on the instance as console code | When execution is authorized | | `zeysdk watch` | Watch and auto-sync on file changes | During active development | | `zeysdk rollout` | Deploy to instances | For releases | ## First-time setup ```bash mkdir -p myproject cd myproject zeysdk create myapp --name "My App" cd myapp zeysdk link --token --url zeysdk pull -f ``` Use this once, then keep the app linked for all future development. ## Daily development loop ```bash zeysdk compare zeysdk sync zeysdk run --ixml 'ok' ``` Recommended order: 1. `compare` first to understand drift between local and remote. 2. `sync` only after reviewing expected changes. 3. `run` while editing to test snippets on a sandbox, when execution is authorized. Effects are real. ## Snippet validation Use `zeysdk run --ixml` for fast feedback on any iXML code: ```bash zeysdk run --ixml 'iXMLHello $name' ``` The command returns runtime diagnostics including load time, execution time, memory usage, output size, and the output payload or any parser/runtime errors. If this fails, fix parser or language assumptions before modifying service files. `zeysdk run` is console execution with application globals. It has no session, no `AUTH`, and no inbound request, so it does not show how a weblet or service route behaves. See [`zeysdk run`](https://www.zeyos.com/ixml/ixml-guide/development-environment/execution-contexts#zeysdk-run). To check syntax without executing, use `zeysdk compile`. ## Conflict and safety guidelines - `pull -f` can overwrite local files — use with caution. - `sync` can create conflict situations if both local and remote changed. - `compare` is mandatory before any destructive or broad sync decision. - A whole-project `push` deletes server assets that have no local copy. Preview it with `zeysdk push --dry-run`. `--keep-remote` protects only remote-only assets with no sync baseline, and `--force` skips the prompt without adding any authorization. - SDK requests time out after 30 seconds by default. A timeout does not show that the server stopped, so check the effects before you retry. ## Verify your work Follow the four [Verification Checkpoints](https://www.zeyos.com/ixml/ixml-guide/development-environment/verification-checkpoints): local inspection, remote compilation, deployment, and runtime verification. When something fails, start with [Troubleshooting](https://www.zeyos.com/ixml/ixml-guide/development-environment/troubleshooting). [Minimal App](https://www.zeyos.com/ixml/ixml-guide/development-environment/minimal-app) is a complete example to start from. ## Common mistakes - Skipping `compare` and assuming only local changes exist. - Testing only at the end of a large editing session. - Ignoring runtime error details that already explain the fix. --- Canonical: https://www.zeyos.com/ixml/ixml-guide/development-environment/troubleshooting # Troubleshooting Start from the symptom. Before changing code, identify the [execution context](https://www.zeyos.com/ixml/ixml-guide/development-environment/execution-contexts), because the same symptom has different causes in different contexts. Evidence labels have the same meaning as on that page. ## Blank weblet **Likely causes** - The frame request failed with 401 or 403, or the app is inactive. - The weblet is on a `.form` view, where plain output is discarded. - The asset has no deployed code, so the platform returns an empty response. - Output text was discarded by [result propagation](#text-disappears-from-output). - A script error occurred after the frame loaded. **How to confirm** 1. Open browser developer tools and find the request for the frame. 2. Read its status and response body. Status 200 with an empty body means the script produced nothing. 3. Check the `view` ending and the `type` in the manifest. 4. Run `zeysdk compare` to confirm that the deployed code matches yours. **Fix** Correct the status cause, deploy the code, or move the weblet to a view that matches how it renders. Use form commands in `.form` views and `output` elsewhere. `zeysdk run` cannot reproduce this problem, because it has no session and no frame. ## A protected route accepts a request with no token **Likely causes** - The handler reads `AUTH` without ``, so a query argument or form field supplies it. - The check sits before `rest:server`, and the handler still reads a forgeable `$AUTH.*`. - The handler has no check. The platform does not reject anonymous calls for you, even for a restricted application. **How to confirm** Send `?AUTH[user]=1` with no token, using `curl -g`. A 200 confirms the problem. **Fix** Add `use` as the first child of every `rest:resource`, for every global the handler reads. See [Bind globals inside `rest:resource`](https://www.zeyos.com/ixml/ixml-guide/development-environment/execution-contexts#bind-globals-inside-restresource). Treat any past use of such a route as unauthenticated. ## Unauthenticated or forbidden service call from a weblet **Likely causes** - The weblet's JavaScript called `REMOTECALL`, and the cookie does not authenticate that call, so `AUTH` is unset. - The `Authorization` header holds an SDK token, not an access token. - The application is restricted and the token's user lacks permission, which gives 403. An anonymous call is not stopped by this check and still runs. - The service or application is inactive, which also gives 403. **How to confirm** Inspect the failing request in developer tools and check for an `Authorization` header. Repeat the call with `curl` and a known-good access token. If that works, the service is fine and the browser call is the problem. **Fix** Prefer calling the resource server-side from the weblet, as the [minimal app](https://www.zeyos.com/ixml/ixml-guide/development-environment/minimal-app#weblet) does. If the browser must call the service, follow the [design requirements](https://www.zeyos.com/ixml/ixml-guide/development-environment/execution-contexts#calling-your-own-service-from-a-weblet). Do not remove the service's check, and do not treat CORS as the cause or the fix. The response is already open to every origin. ## Wrong include target or type **Likely causes** - The identifier is misspelled, or its `.` prefix was dropped. - The target is a service or weblet, but `include` defaulted to `resource`. - The target exists locally but has not been deployed. `include` resolves on the instance. - The script has no app namespace, so `.name` cannot resolve. **How to confirm** The error names the type and identifier it looked for, as written in the `include`, for example `Unable to load enhancement "resource:.api" in INCLUDE`. Compare that with the manifest. A compile pass does not check this. **Fix** Correct the identifier, push the target, or set the type (**Reference**): ```xml ``` ## Already-decoded JSON **Likely causes** The value came from an `application/json` resource, which `include` returns decoded. `decode:json` then receives an array instead of a string. **How to confirm** Check the resource's `mimetype` in the manifest. Inspect the value with `typeof`: ```xml $kind ``` **Fix** Delete the `decode:json`. The reverse problem also exists: a JSON **request body** arrives as a string, so read it with `var_body` and decode it inside `try`. See [Includes and JSON resources](https://www.zeyos.com/ixml/ixml-guide/development-environment/execution-contexts#includes-and-json-resources). ## Missing or empty session locale **Likely causes** - The code reads `$session.lang`. The locale is in `$session.data.lang` (**Source-confirmed**). - There is no session user and no valid cookie, as in `TIMING` or `zeysdk run`. - The code runs in a hook or in `REMOTECALL`, where the result depends on what triggered it. Do not rely on `session` there. **How to confirm** Check the context. `session` returns NULL without a session (**Reference**). To check the value on a sandbox, output the named key, such as `$session.data.lang`, from a weblet. :::warning Never dump the whole `session` array. It can include credential columns from the user record. ::: **Fix** Use `$AUTH.lang` in `REMOTECALL` and always provide a fallback: ```xml $session.data.lang en_US ``` ## Empty lint selection **Symptom:** `zeysdk lint` prints `No modified iXML or Zymba files to lint.` and exits `0`. **Likely causes** - Nothing differs from the last synchronized state, for example straight after a push. - The changed script is not registered in the manifest. **How to confirm** Run `zeysdk lint --all` and read the file list. Check that your file appears. **Fix** Compile the file directly with `zeysdk compile `, or register it. Record an empty selection as **no files checked**, never as a pass. ## Uncertain completion after a timeout **Symptom:** `Request timed out after 30000ms`, or a dropped connection, during `run` or `push`. **Likely causes** The client stopped waiting. That shows nothing about the server, which may have finished, may still be running, or may have stopped. See [Timeouts and uncertain completion](https://www.zeyos.com/ixml/ixml-guide/development-environment/verification-checkpoints#timeouts-and-uncertain-completion). **How to confirm** Check the effects directly: records, mail, external systems, and the platform log. After a push, run `zeysdk compare`. **Fix** Do not retry blindly. The SDK's request layer does not resend these requests, so a second `run` is a second execution. `zeysdk watch` does retry a failed push on its own, so stop it first. Make long work idempotent and resumable, and move it to a `TIMING` service. ## Text disappears from output **Symptom:** `` shows only part of what you wrote. **Cause:** literal text used alongside an embedded statement is discarded (**Reference**). **Logged** on 6 February 2026: `$(15 + (7.5 * 2 - 3))471` returned `27`, not `27471`. **Fix:** assign to a variable first, then substitute it. ```xml Hello $name $name Hello $safeName ``` See [Result propagation](https://www.zeyos.com/ixml/ixml-guide/introduction/result-propagation). ## `replace` changes nothing or the wrong text **Cause:** in `replace`, `value` is the **search** value. The new text goes in `replacement`, and the subject is the element content (**Reference**). ```xml My name is Bill Gates! ``` ## A route works over HTTP but returns 404 in `zeysdk run` **Cause:** this is expected. The console has no inbound route and its requests use PUT, so `GET` and `POST` routes do not match. **Logged** on 6 February 2026: HTTP 404 and `null`. **Fix:** test the route with an HTTP request. See [Checkpoint 4](https://www.zeyos.com/ixml/ixml-guide/development-environment/verification-checkpoints#checkpoint-4-runtime-verification). ## Reported, not independently verified — check in your target These items come from an app build reported in November 2026. They were not reproduced for this guide, and none of them is established behavior. Test each one on your target before relying on either the report or the safe form. | Report | What the reference says | How to check | Conservative form | | --- | --- | --- | --- | | After `assign`, `array:merge` on the copy also changed the source array | Complex variables are assigned by reference, and `array:merge` is destructive unless `var_result` is set | Assign, merge, then print the source | Merge with `var_result`, or use `clone` | | JSON `false` and a missing key both read as an empty value | `is` separates `defined`, `false`, and `empty`. `array:keyexists` tests for a key | Decode `{"a":false}` and compare `$data.a` with `$data.b` | Use `array:keyexists` for presence and `is` for type | | No rounding inside `$( )`, and empty operands break arithmetic | `math:round` exists. Uninitialized variables default to the neutral literal | Compare `math:round` with an inline expression on sample values | Use `math:round`, and default empty operands to `0` first | | `db:is` with an empty value on an integer column fails | Not stated | On a sandbox, run a read with an empty value | Check the value with `is type="empty"` before adding the condition | | An embedded weblet with height `0` sizes itself to its content | Not stated | Register a test weblet and resize its content | Set an explicit `height` until confirmed on your target | ## Related topics - [Execution Contexts](https://www.zeyos.com/ixml/ixml-guide/development-environment/execution-contexts) - [Verification Checkpoints](https://www.zeyos.com/ixml/ixml-guide/development-environment/verification-checkpoints) - [Minimal App](https://www.zeyos.com/ixml/ixml-guide/development-environment/minimal-app) - [Debugging](https://www.zeyos.com/ixml/ixml-guide/error-handling/debugging) --- Canonical: https://www.zeyos.com/ixml/ixml-guide/development-environment/verification-checkpoints # Verification Checkpoints "It compiles" and "it works" are different claims. This page splits verification into four checkpoints, each with its own expected outcome. A pass at one checkpoint says nothing about the next. Record which ones you actually ran. | Checkpoint | Needs | Proves | Does not prove | | --- | --- | --- | --- | | [1. Local inspection](#checkpoint-1-local-inspection) | Your files | Well-formed XML, names checked against the reference, consistent manifest | That the native compiler accepts the code | | [2. Remote compilation](#checkpoint-2-remote-compilation) | Linked app, network, developer permission | The instance's compiler accepts the checked files | Any runtime behavior | | [3. Deployment](#checkpoint-3-deployment) | Same, plus permission to change the instance | The instance holds the reviewed change set | That the code works in its context | | [4. Runtime verification](#checkpoint-4-runtime-verification) | A sandbox and the real entry point | Observed behavior for the inputs you tried | Other inputs and production conditions | ## Checkpoint 1: local inspection Nothing leaves your machine at this checkpoint. **XML well-formedness.** This check is partial. It finds broken XML and nothing else. ```bash xmllint --noout --nonet resources/Greeting.ixml ``` Expected outcome: exit status `0`. `xmllint` also prints `Namespace prefix ... is not defined` for commands such as `rest:server`, because iXML prefixes are not declared XML namespaces. These messages are expected. A parser error such as an unescaped `<` in an attribute is a real failure. | Failure case | Result | | --- | --- | | Raw `<` in an attribute | `xmllint` fails. Escape it as `<` | | `` | `xmllint` **passes**. The native parser rejects it, as logged on 6 February 2026. This is why the reference check below is required | | Invented command such as `array:count` | `xmllint` **passes**. The native parser rejects it, as logged on 6 February 2026 | **Reference lookup.** Look up every changed command in `ref/ixmldoc.xml`, including attributes, children, and result bindings. Check database identifiers in `ref/dbref.json` and weblet views in `ref/sdk/views.md`. [Coding Agents and Validation](https://www.zeyos.com/ixml/ixml-guide/development-environment/coding-agents#validate-parameters-and-structure) has the full checklist. **Manifest and asset consistency.** For each manifest entry, confirm that: - The file exists at `/.`, and the extension fits its `mimetype`. - The `type` is on the SDK's list and uses the SDK's uppercase spelling. - Hook services have an `entity`, and `TIMING` services have a `schedule` and `interval`. - Each weblet's `view` exists in `ref/sdk/views.md`. - Every `include` target exists in the same manifest, with the right `type` selector. - Every `rest:resource` binds each global it reads with `use` as its first child. See [Bind globals inside `rest:resource`](https://www.zeyos.com/ixml/ixml-guide/development-environment/execution-contexts#bind-globals-inside-restresource). - No secret appears in `defaultsettings.json` or any tracked file. **What `zeysdk` checks offline.** `zeysdk --help`, `zeysdk --help`, and `zeysdk version` work without a link. Local arguments are validated before a remote call, so `zeysdk add` and `zeysdk update` reject unknown types and unsafe paths. No SDK command compiles code offline. ## Checkpoint 2: remote compilation `zeysdk compile` and `zeysdk lint` send your source to the linked instance's compiler and report the result. They do not execute the code, but they are **not offline**. They need a linked app, a valid token, developer permission, and network access. Confirm the instance is the one you intend, and that you may send it this source. ```bash zeysdk whoami zeysdk compile services/api.ixml zeysdk compile --ixml 'Hello' zeysdk compile --zy 'return 1;' zeysdk lint zeysdk lint --all ``` `compile` takes a path or one inline source. `--zymba` is the long form of `--zy`. `lint` compiles the eligible scripts the app knows about. By default it selects only locally present scripts that differ from the last synchronized state. `--all` selects every locally present script, skipping the server comparison, but it still sends source. Expected outcome: `compile` reports success, and `lint` prints `OK` for each file you meant to check, with a summary count. Record the file list. On failure both commands print the compiler message and exit nonzero. | Failure case | Meaning | | --- | --- | | Lint prints `No modified iXML or Zymba files to lint.` or `No iXML or Zymba files found.` | **Not a pass.** Zero files were checked, and the exit status is still `0` | | A script is missing from the lint list | It is not registered in the manifest. Register it, or compile it by path | | `requires the zeysdk.sync.json file` or missing `id`/`url` | The app is not linked. Run `zeysdk link` | | 401 or `Lacking developer permission` | Fix the token or the user's permission. This is not a code result | **What compile cannot prove.** It checks the code you sent, on that instance's compiler. It does not show that database identifiers exist, that a handler's auth check cannot be forged, that an `include` target is deployed, that a settings key is present, that inputs are complete, that authorization is correct, or that the page renders. Runtime errors in dynamic values appear only at checkpoint 4. ## Checkpoint 3: deployment `push` changes the instance. It does more than upload: a whole-project push makes the server match your working copy. ```bash zeysdk whoami zeysdk compare zeysdk snapshot create "Before push" zeysdk push --dry-run zeysdk push ``` Expected outcome: the dry run names the application, instance, and URL, and lists every upload and every server deletion. You can explain each line. The real push then applies that plan, and a following `zeysdk compare` shows no unexpected drift. - **Scope.** `zeysdk push services/api.ixml` plans only the named paths. Without paths the plan covers the whole project, including deletions. - **Deletions.** A service, weblet, resource, or asset that exists on the server but not locally is deleted, even if this checkout never synchronized it. - **`--keep-remote`** protects remote-only assets that have **no sync baseline** in this checkout. It does not protect every missing local asset. If this checkout synchronized an asset and you then removed it locally, a push still deletes it from the server. - **`--force`** skips the confirmation prompt. It does not grant authorization, and it does not make the plan safer. Use it only after reviewing the same change set. - **Dry run limits.** A dry run reads the server and may make read-only requests. The server does not lock, so a change made after the preview is not part of what you reviewed. - **Snapshots** are local ZIP archives of project files. They restore your working copy, not the instance. :::warning Neither a snapshot nor a `sync.json` backup rolls back the server. Plan recovery before you push to a shared instance. ::: | Failure case | Meaning | | --- | --- | | The plan deletes something you did not touch | Another developer's work, or a stale checkout. Stop and run `zeysdk compare`. Consider `--keep-remote` or a targeted push | | The plan targets the wrong instance | Stop. Run `zeysdk use ` | | A push fails partway | Local recovery restores sync state only. Compare before retrying | ## Checkpoint 4: runtime verification Test through the real entry point, on a sandbox, with bounded inputs. See [Execution Contexts](https://www.zeyos.com/ixml/ixml-guide/development-environment/execution-contexts) for why `zeysdk run` does not substitute for these. | Context | How to test | Expected outcome | Failure cases to try | | --- | --- | --- | --- | | Weblet | Open the target view in a signed-in browser. Inspect the frame request in developer tools | Frame status 200 and the expected content | Signed-out user gets 401. User without access to a restricted app gets 403. Empty and hostile `$REQUEST` values stay safe | | `REMOTECALL` | Send HTTP requests with `curl` | Valid access token gets 200 and the expected body | No token, an invalid token, an SDK token, and no token with a forged `?AUTH[user]=1` must all be rejected **by your handler**. A real token with forged arguments must keep its real identity. A 200 on any rejected case is a security failure | | `TIMING` | Let the schedule fire on a sandbox, then inspect the data and log | One run, with expected effects | A rerun does not repeat completed work. A failed run is logged and the next run recovers | | Entity hook | Create, change, or delete a real record through the UI or API | The expected change, and only for the intended records | A thrown error aborts the save. Non-matching records are untouched | The three HTTP checks every protected service needs: ```bash # Expected: 200 curl -i -H "Authorization: Bearer $ZEYOS_ACCESS_TOKEN" \ "https://cloud.zeyos.com//remotecall/example.minimal.api/greeting/Ada" # Expected: 401 from your handler curl -i \ "https://cloud.zeyos.com//remotecall/example.minimal.api/greeting/Ada" # Expected: 401. A 200 means a request can forge AUTH. curl -i -g \ "https://cloud.zeyos.com//remotecall/example.minimal.api/greeting/Ada?AUTH[user]=1" ``` `-g` stops `curl` from treating the brackets as a pattern. For a `POST` route, send the same field in the form body. ### Timeouts and uncertain completion SDK requests time out after 30 seconds by default. Set `ZEYSDK_REQUEST_TIMEOUT_MS` to a positive integer to change this. The limit applies to the client's wait. A disconnected client does not prove the server stopped. The script may still be running, or may already have finished. The SDK's request layer does not automatically resend a `run`, `compile`, or push request, and retries only reads. `zeysdk watch` is the exception: it keeps a failed batch and pushes it again with backoff, so stop it before you investigate. Outside `watch`, any repeat comes from you. Before you retry after a timeout: 1. Treat the outcome as unknown, not failed. 2. Check the effects: records, sent mail, external calls, and the platform log. 3. Retry only what did not happen, and only if the operation is safe to repeat. For work that may take longer, use a `TIMING` service with a resumable, time-bounded batch instead of raising the timeout. ## Record what you ran Keep the date, revision, instance, checked file list, and result for each checkpoint. Mark any checkpoint you skipped as **not performed**. Never describe compiled code as runtime-validated. ## Related topics - [Execution Contexts](https://www.zeyos.com/ixml/ixml-guide/development-environment/execution-contexts) - [Minimal App](https://www.zeyos.com/ixml/ixml-guide/development-environment/minimal-app) - [Troubleshooting](https://www.zeyos.com/ixml/ixml-guide/development-environment/troubleshooting) - [SDK Workflow](https://www.zeyos.com/ixml/ixml-guide/development-environment/sdk) - [Coding Agents and Validation](https://www.zeyos.com/ixml/ixml-guide/development-environment/coding-agents) --- Canonical: https://www.zeyos.com/ixml/ixml-guide/error-handling # Error Handling & Debugging Defensive coding in iXML is essential because services usually handle untrusted input, communicate with external dependencies, and orchestrate database side effects that can fail at any point. Without proper error handling, a single malformed JSON payload or a database constraint violation can crash an entire service, returning raw internal error messages to the client and leaving data in an inconsistent state. iXML provides a structured error handling mechanism through the `TRY` construct and explicit error/warning commands, combined with a `debug` namespace for development-time inspection. This section covers the full error handling and debugging toolkit. ## Sub-chapters - [Try / Catch](https://www.zeyos.com/ixml/ixml-guide/error-handling/try-catch) — The `try`/`catch`/`else`/`finally` construct, execution flow, and practical examples - [Throwing Errors](https://www.zeyos.com/ixml/ixml-guide/error-handling/throwing-errors) — The `error` and `warning` commands for explicitly raising exceptions - [API Error Patterns](https://www.zeyos.com/ixml/ixml-guide/error-handling/api-error-patterns) — Stable response envelopes, HTTP status codes, and implementation patterns for ZeyOS API services - [Debugging & Troubleshooting](https://www.zeyos.com/ixml/ixml-guide/error-handling/debugging) — Parser errors, runtime errors, debug commands, and systematic debugging strategies - [Best Practices](https://www.zeyos.com/ixml/ixml-guide/error-handling/best-practices) — Guidelines and common mistakes for robust error handling --- Canonical: https://www.zeyos.com/ixml/ixml-guide/error-handling/api-error-patterns # API Error Patterns ## Stable API error contract When building ZeyOS services that expose APIs, establish a consistent response envelope that clients can rely on. Every endpoint should return the same top-level structure regardless of whether the operation succeeded or failed. This reduces client-side complexity and makes services easier to monitor and debug. ### Recommended response shapes **Success response:** ```json { "result": 1, "data": { ... } } ``` **Error response:** ```json { "error": "Human-readable error message", "details": "Technical details for debugging" } ``` ### Implementation pattern ```xml 1 Operation failed $err
Content-Type: application/json
``` This pattern ensures that: - The client always receives valid JSON, never a raw error dump - The top-level key (`result` vs. `error`) immediately signals success or failure - Technical error details are available for debugging without leaking sensitive internals - The HTTP `Content-Type` header is always set correctly, even on error ### HTTP status codes Use the `status` attribute of the `
` command to set appropriate HTTP status codes in error scenarios: ```xml
Bad request: $err ``` Common status codes for API services: | Code | Meaning | When to use | |---|---|---| | `200` | OK | Successful operations (default) | | `201` | Created | Successful resource creation | | `400` | Bad Request | Invalid input, malformed JSON, missing required fields | | `401` | Unauthorized | Missing or invalid authentication | | `403` | Forbidden | Authenticated but insufficient permissions | | `404` | Not Found | Requested resource does not exist | | `500` | Internal Server Error | Unexpected failures, database errors | A `401` or `403` check inside `rest:resource` must [bind `AUTH` with `use`](https://www.zeyos.com/ixml/ixml-guide/development-environment/execution-contexts#bind-globals-inside-restresource), or a query argument can forge it. --- Canonical: https://www.zeyos.com/ixml/ixml-guide/error-handling/best-practices # Best Practices ## Best practices - **Fail early at input boundaries.** Validate incoming data immediately after decoding it. Throw explicit `` messages for missing or invalid fields before processing begins. - **Always include a `` block** in any `` that wraps code interacting with external systems (databases, APIs, file I/O). Unhandled errors from these sources produce raw technical messages that should never reach the client. - **Use `` for response formatting.** Place your `
` and `` statements in the `` block to ensure the response is always sent, even when errors occur. - **Include enough error detail for troubleshooting** but avoid leaking sensitive internals. The `details` field should help a developer reproduce the issue, not expose database schemas or internal paths. - **Keep success and failure code paths equally explicit.** Use the `` block for success-only logic rather than assuming "no error means success" at the end of the `` body. - **Return consistent error shapes.** Every service should use the same JSON envelope structure for errors. Inconsistent error formats create client-side complexity. - **Wrap system boundaries in try/catch.** JSON parsing, HTTP requests, database operations, and any external service calls should always be protected. ## Common mistakes - **Letting parser/runtime errors bubble to the client unchanged.** Without `/`, a `` failure produces a raw "Syntax error" message as the entire response — no JSON wrapper, no HTTP status code, no content type header. - **Returning different error shapes per endpoint.** This forces every client to handle errors differently for each API call. - **Swallowing exceptions without recording context.** An empty `` block hides failures, making debugging extremely difficult. - **Placing output statements after the `` block instead of in ``.** If an error occurs and the `` block doesn't produce output, the client receives an empty response. - **Setting HTTP status and response payload inconsistently.** For example, returning a `200` status with an error payload, or a `500` status with a success payload. - **Using `` for control flow instead of `/`.** While `` works for early exits, it incurs the overhead of exception handling. For expected conditions (like "record not found"), use `` with `` instead. --- Canonical: https://www.zeyos.com/ixml/ixml-guide/error-handling/debugging # Debugging & Troubleshooting Troubleshooting iXML is faster when you follow a strict reduction workflow: isolate, reproduce, inspect, and fix. This page provides systematic processes for parser errors, runtime exceptions, and data-shape bugs, along with the full `debug` namespace for development-time inspection. ## Debugging Workflow 1. **Reproduce** the issue with a minimal snippet using `zeysdk run --ixml`. 2. **Capture** the exact error message and location. 3. **Reduce** by removing branches and variables until the root cause is obvious. 4. **Fix** the minimal case, then rebuild toward the full version. 5. **Verify** the fix works in the original context. ## Parser Errors Parser errors occur before execution. The iXML engine validates XML structure, element names, and attribute names during parsing. Note that error messages show attribute and element names in uppercase — iXML itself is case-insensitive. ### Unknown Attribute ``` ERROR: iXML\ParserException: Unknown attribute "CONDITION" in element "IF" ``` **Cause:** Using an attribute that does not exist on the element. **Common cases:** - `` — Use `value1`, `value2`, `func` instead. - `` — Remove the `$`; it is substitution syntax, not part of variable names. - `` — `type` attribute does not exist on `encode:base64`. **Fix:** Check the command's attribute list in the documentation. ### Malformed XML ``` ERROR: iXML\ParserException: expected '>' ``` **Cause:** Invalid XML syntax — unescaped characters in attributes, missing closing tags, or mismatched elements. **Common cases:** - `func="<"` — Must be `func="<"` (XML requires escaping `<` and `>` in attribute values). - Missing closing tag or mismatched nesting. ### Unknown Element ``` ERROR: iXML\ParserException: Unknown element "DB:ISNOT" ``` **Cause:** Using a command that does not exist. **Common cases:** - `` — Does not exist. Use `` or ``. - `` — Does not exist in the encode namespace. - `` — Does not exist. Use ``. - `` — Does not exist. Use `$array`. ## Runtime Errors Runtime errors occur during execution. They can be caught with `/`. ### Type Errors **Symptom:** Unexpected behavior or empty output. **Diagnosis:** ```xml 42 Type: $t ``` **Fix:** Use `` for explicit type conversion: ```xml Type: $t ``` :::tip **Common mistake:** `` always creates strings. `42` creates the string `"42"`, not the integer `42`. Use `` after setting if you need a numeric type. ::: ### Data Shape Inspection When output is unexpected, serialize intermediate structures to see their actual shape: ```xml $debug ``` Use this before database writes and before final API output to verify the data structure. ### Database Errors **Common issues:** - **Type mismatch:** `contacts.type` is `smallint`, not a string. Using `Customer` causes a PostgreSQL type error. - **NULL comparison:** `NULL` does not check for NULL. SQL NULL requires `IS NULL` syntax — use ``. - **Access forbidden:** System tables like `information_schema.columns` are blocked in ZeyOS. ### Defensive Error Handling Wrap risky operations in `/`: ```xml $body 400 Invalid JSON: $err ``` ## Debug Commands The `debug` namespace provides four commands for development-time inspection. The command descriptions below cover the available debug operations. ### debug:output [Full reference →](https://www.zeyos.com/ixml/ref/debug-output) Writes a message to the debug console (separate from application output — you will not see it in `zeysdk run --ixml` results): ```xml Processing ID: $id, Status: $status ``` ### debug:dump [Full reference →](https://www.zeyos.com/ixml/ref/debug-dump) Dumps all variables or a specific variable with recursive structure inspection: ```xml ``` ### debug:log [Full reference →](https://www.zeyos.com/ixml/ref/debug-log) Writes to the debug log file (persisted, unlike console output): ```xml Error processing contact $id: $error_msg ``` ### debug:exclude [Full reference →](https://www.zeyos.com/ixml/ref/debug-exclude) Temporarily disables a code block without removing it. Unlike XML comments, the code inside is still parsed and validated — so syntax errors in disabled code will still be caught: ```xml inactive ``` ## Debugging Strategies ### Service Endpoint Debugging When debugging API handlers, check in this order: 1. **Route and method** — Is the correct service being invoked? 2. **Body decode** — Is the request body valid and parsed correctly? 3. **Validation** — Are required fields present and correctly typed? 4. **Database operations** — Are queries returning expected results? This order prevents misdiagnosing downstream failures caused by upstream issues. ### Iterative Reduction When facing a complex bug, reduce the code iteratively: ```xml Step 1: Start c.ID, c.name $json ``` ### Regression Prevention After fixing a bug: 1. Keep a minimal reproducer snippet. 2. Run it again and capture the passing output. 3. Document the issue to avoid repeating the same mistake. --- Canonical: https://www.zeyos.com/ixml/ixml-guide/error-handling/throwing-errors # Throwing Errors ## `error` — Throw error The `ERROR` command explicitly throws an error, breaking out of the current control flow. The error propagates upward until it is caught by a `/` block. If no `/` block is present, the error terminates execution entirely. The content of the `` element becomes the error message string that is received by the `` block's `var` attribute. You can use variable substitution in the error message to include dynamic context information. [Full reference →](https://www.zeyos.com/ixml/ref/error) ### Syntax ```xml Error message text with $variable substitution ``` The content (type: string) is the error message. ### When to use explicit errors Use `` to enforce business rules, validate preconditions, or signal application-level failures that the caller should handle. For expected conditions (like "record not found"), prefer `` with `` instead — `` incurs exception-handling overhead and should be reserved for truly exceptional situations. ```xml Email address is required Amount must be non-negative, got: $payload.amount ``` ### Example ```xml iXML An error has occurred! $error ``` ### See also - [Try / Catch](https://www.zeyos.com/ixml/ixml-guide/error-handling/try-catch) — catching errors - [`warning`](#warning--throw-warning) — the non-fatal alternative --- ## `warning` — Throw warning The `WARNING` command throws a warning, which behaves similarly to an error: it breaks out of the current control flow and can be caught by a `/` block. Warnings are a ZeyOS-specific mechanism for signaling non-critical issues that should be logged or reported but may not necessarily require the same handling as a full error. [Full reference →](https://www.zeyos.com/ixml/ref/warning) ### Syntax ```xml Warning message text ``` The content (type: string) is the warning message. :::note Like errors, warnings are caught by `/`. The `` block receives the warning message in its `var` attribute, just as it would for an error. There is no built-in way to distinguish between caught errors and caught warnings at the `` level — if you need to differentiate, use a convention in your message format or set a flag variable before throwing. ::: ### Example ```xml This is a warning! ``` ### See also - [`error`](#error--throw-error) — the standard error-throwing mechanism - [Try / Catch](https://www.zeyos.com/ixml/ixml-guide/error-handling/try-catch) — catching warnings --- Canonical: https://www.zeyos.com/ixml/ixml-guide/error-handling/try-catch # Try / Catch ## `try` — Error handling construct The `TRY` construct is the primary control flow structure for handling errors, warnings, exceptions, and other special conditions that change the normal execution flow. It establishes a **protected region** of code: if any statement within the `TRY` block throws an error (either explicitly with `` or implicitly from a runtime failure), execution immediately jumps to the `` block instead of terminating the entire service. The `TRY` construct supports up to four sections, each with distinct execution semantics: 1. **The `try` body** — the protected code that might fail 2. **`catch`** — code that runs only when an error occurs 3. **`else`** — code that runs only when no error occurs 4. **`finally`** — code that always runs, regardless of whether an error occurred This four-part structure gives you precise control over both the success and failure paths of any operation, while ensuring that cleanup code (like closing resources or sending response headers) always executes. [Full reference →](https://www.zeyos.com/ixml/ref/try) ### Syntax ```xml some operation Error: $errorMessage Success (done) ``` ### Child element: `catch` — Error-triggered execution Occurrence: "**single**" The `` block executes **only** when an error is thrown within the `` body. When an error occurs, execution of the `` body immediately stops at the point of failure — any statements after the error are skipped — and control transfers to the `` block. The `` block provides access to the error message through its `var` attribute. This variable receives a string containing the error message text, which you can use for logging, user-facing error responses, or conditional error handling logic. If the `var` attribute is omitted, the `` block still executes on error, but you lose access to the error message. This is acceptable when you know what the error will be and don't need the specific message, but in most cases you should capture it for logging or diagnostics. Full reference → ### Child element: `else` — Success-path execution Occurrence: "**single**" The `` block executes **only** when the `` body completes successfully without any errors. This is the inverse of `` — exactly one of `` or `` will execute for any given `` invocation, never both. The `` block is useful for actions that should only happen on success, such as committing a transaction, sending a success response, or updating a status flag. Placing these actions in the `` block rather than at the end of the `` body ensures they never execute if an error occurred partway through the body. ### Child element: `finally` — Guaranteed execution Occurrence: "**single**" The `` block **always** executes after the `` body and whichever of `` or `` ran. It executes regardless of whether an error occurred, making it the ideal place for cleanup code that must run in all circumstances — sending HTTP headers, closing connections, outputting final response wrappers, or releasing resources. Full reference → ### Execution flow Understanding the exact order of execution is critical for writing correct error handling code. Here are the two possible flows: **When no error occurs:** 1. The `` body executes completely 2. The `` block executes (if present) 3. The `` block executes (if present) **When an error occurs:** 1. The `` body executes until the error is thrown 2. Remaining statements in the `` body are **skipped** 3. The `` block executes (if present), with the error message in `var` 4. The `` block executes (if present) :::note If `` is not present and an error occurs, the error propagates upward to the next enclosing `` block, or terminates execution if there is none. Always include a `` block unless you intentionally want errors to propagate. ::: :::tip You can nest `` blocks to handle errors at different scopes. An inner `` handles its own scope; if re-thrown with ``, the outer `` handles it. ::: ### Examples **Basic error handling:** This example demonstrates the simplest form of try/catch — protecting code that might fail and capturing the error message: ```xml iXML An error has occurred! $error ``` The `` statement throws an exception with the message "An error has occurred!". Execution immediately jumps to the `` block, which receives the message in the `$error` variable and outputs it. The `` closing tag and any subsequent statements in the `` body are skipped. **Extended error handling with `else` and `finally`:** This example shows the full four-part structure. The `` block overwrites the initial status because no error occurs, and `` outputs the final value regardless: ```xml iXML My name is unknown! My name is $name! $output ``` Since no error occurs in the `` body, the `` block is skipped, the `` block sets `output` to "My name is iXML!", and the `` block outputs the result. **Defensive JSON parsing:** One of the most common error handling patterns in ZeyOS services is protecting against malformed JSON input. The `` command throws an error when the input is not valid JSON, which you can catch and convert into a structured error response: ```xml $BODY 1 Invalid JSON input $err
Content-Type: application/json
``` If the JSON is valid, the response contains `{"result": 1, "data": {...}}`. If parsing fails, the response contains `{"error": "Invalid JSON input", "details": "Syntax error"}`. In both cases, the client receives a properly formatted JSON response with appropriate HTTP headers. **Database error wrapping:** When performing multi-step database operations, wrap the entire sequence in `/` combined with `` to ensure atomicity. If any statement fails, the transaction is automatically rolled back and the error is caught: ```xml $payload.lastname $payload.firstname $contactId Welcome task
Database operation failed $err 1 $contactId
Content-Type: application/json
``` The `` block ensures the JSON response is always sent, whether the database operations succeeded or failed. The `` block only populates success data if no error occurred. The `` block sets the HTTP status to 500 and populates error details. **External service error handling:** When calling external REST or SOAP services, network failures, timeouts, and unexpected response formats can all throw errors. Wrap external calls in `/` and provide fallback behavior: ```xml /status 0 $err ``` --- Canonical: https://www.zeyos.com/ixml/ixml-guide/introduction/arithmetic # Arithmetic Expressions An arithmetic expression is a placeholder for its result. Whenever a number is explicitly expected (attributes of type `int` or `float`), the corresponding expression is automatically evaluated arithmetically. Its result is then implicitly cast to the appropriate input type. This automatic evaluation means that you can write complex mathematical expressions directly in attribute values. For example, `` evaluates `from` to `6` and `to` to `5` (truncated to integer because the `to` attribute has type `int`). All **C-like arithmetic and bitwise operators** as well as their respective precedences are applicable to arithmetic expressions. This includes addition (`+`), subtraction (`-`), multiplication (`*`), division (`/`), modulo (`%`), and bitwise operations (`&`, `|`, `^`, `~`, `<<`, `>>`), as well as parentheses for grouping. ## Number notation Integer numbers can be specified in multiple bases: | Base | Prefix | Example | Decimal Equivalent | |---|---|---|---| | Decimal (base 10) | none | `123` | 123 | | Hexadecimal (base 16) | `0x` | `0x1A` | 26 | | Octal (base 8) | `0` | `017` | 15 | | Binary (base 2) | `0b` | `0b1101` | 13 | Floating point numbers can be specified in **decimal** (base 10) notation or **scientific E notation**. Scientific notation uses the format `e`, where the result equals `mantissa x 10^exponent`. For example, `1.5e3` equals `1500.0`, and `2.5e-2` equals `0.025`. :::warning The octal prefix `0` can cause unexpected results. If you write ``, the runtime interprets `010` as octal 10, which is decimal `8`. Always use the explicit `0o` or bare decimal notation to avoid this ambiguity. ::: ## Expression evaluation in command attributes Arithmetic expressions are evaluated automatically wherever an attribute of type `int` or `float` is expected: ```xml (0x12 - 19.5) * $number ``` In this example, `0x12` (hexadecimal 18) minus `19.5` equals `-1.5`, which is then multiplied by the value of `$number`. The [`math:abs`](https://www.zeyos.com/ixml/ref/math-abs) command takes the absolute value of the result. ## Inline arithmetic substitution The `$()` syntax embeds arithmetic results directly into string context, even where an arithmetic type is not expected. This allows you to perform calculations inside `` content, `` values, and other string contexts: ```xml 1950 This person is $(2012 - $birthyear) years old! ``` Inline arithmetic may be nested within array access, enabling computed indices: ```xml Bill Gates Steve Jobs $names[0] and $names[$(0 + 1)] are competitors! ``` ### See also - [eval](https://www.zeyos.com/ixml/ref/eval) — evaluating expressions and storing the result - [cast](https://www.zeyos.com/ixml/ref/cast) — converting string values to numeric types --- Canonical: https://www.zeyos.com/ixml/ixml-guide/introduction/quick-reference # Quick Reference Use this chapter as a day-to-day lookup while writing iXML. It covers all namespaces with brief syntax reminders and links to the full documentation. ## Variables and Expressions | Task | Pattern | |------|---------| | Set variable | `value` | | Set with key | `value` | | Unset variable | `` | | Substitute variable | `$name` | | Key chain access | `$name.key` or `$name[0]` | | Arithmetic | `$(3 + 2)` | | Inline function call | `$fn(arg)` | | Cast type | `` | | Check type | `` | | Check class | `` | | String length | `$str` | | Array/string size | `$arr` | | Assign by reference | `` | | Clone variable | `` | | Swap variables | `` | | Evaluate expression | `$expr` | | Global variable | `` | | Literal dollar sign | `$$` | | No substitution | `$not_substituted` | ## String Operations | Task | Pattern | |------|---------| | Lowercase | `$str` | | Uppercase | `$str` | | Trim | `$str` | | Substring | `$str` | | Find position | `$str` | | Replace | `$str` | | Regex replace | `$str` | | Split | `$str` | | Pad | `$str` | | Concat | `` | | Match regex | `$str` | | Char code to char | `65` | | Char to code | `A` | ## Control Flow | Task | Pattern | |------|---------| | If equals | `...` | | If with operator | `...` | | If regex | `...` | | If/else | `......` | | Type check | `...` | | Switch | `......` | | For loop | `...` | | Foreach | `...` | | While loop | `...` | | Break loop | `` | | Next iteration | `` | | Exit execution | `` | | Return value | `value` | ## Comparison Operators (func attribute) | Operator | Description | |----------|-------------| | `=` | Equals | | `!=` | Not equals | | `<` | Less than | | `<=` | Less than or equal | | `>` | Greater than | | `>=` | Greater than or equal | | `=*` | Case-insensitive equals | | `!=*` | Case-insensitive not equals | | `_` | Contains | | `!_` | Does not contain | | `^` | Starts with | | `!^` | Does not start with | | `$` | Ends with | | `!$` | Does not end with | | `~` | Regex match | | `!~` | Regex does not match | | `~~` | LIKE pattern | | `!~~` | NOT LIKE pattern | ## Functions, Classes, and Modules | Task | Pattern | |------|---------| | Define function | `...` | | Call function | `` | | Define macro | `...` | | Expand macro | `` | | Define class | `......` | | Instantiate | `` | | Include resource | `` | | Pass parameter | `` | ## Error Handling | Task | Pattern | |------|---------| | Try/catch | `......` | | Try/else (success) | `......` | | Try/finally | `......` | | Raise error | `message` | | Raise warning | `message` | ## Array Operations | Task | Pattern | |------|---------| | Create array | `v` | | Push item | `value` | | Pop item | `` | | Shift item | `` | | Unshift item | `value` | | Sort | `` | | Reverse | `` | | Merge | `` | | Slice | `` | | Splice | `` | | Search | `value` | | Key exists | `` | | Keys | `` | | Values | `` | | Join | `` | | Unique | `` | | Filter | `` | | Map | `` | | Walk | `` | | Chunk | `` | | Column | `` | | Combine | `` | ## Database | Task | Pattern | |------|---------| | Get entity | `` | | Set entity | `` | | Remove entity | `` | | Lookup entity | `$email` | | Select rows | `...` | | Insert row | `Ada` | | Update rows | `New...` | | Delete rows | `...` | | Raw SQL | `SELECT ...` | | Transaction | `...` | | NULL check | `` | | NOT NULL check | `` | | IN list | `` | | Full-text search | `` | **Result types:** `num` (default), `assoc`, `list`, `av`, `single`, `self`, `value` ## REST and HTTP | Task | Pattern | |------|---------| | REST server | `...` | | REST resource | `...` | | HTTP request | `` | | URL parse | `$url` | | Query string | `` | | Set HTTP status | `404` | | Set header | `
Content-Type: application/json
` | ## Encoding / Decoding | Format | Encode | Decode | |--------|--------|--------| | JSON | `` | `$raw` | | XML entities | `$text` | `$safe` | | CSV | `` | `$csv` | | Base64 | `$data` | `$enc` | | URL | `$val` | `$enc` | | Hash | `$data` | — | | HMAC | `$data` | — | | AES encrypt | `$data` | `$enc` | | Compress | `$data` | `$data` | ## PDF | Task | Pattern | |------|---------| | Create document | `...` | | Add section | `...` | | Header/footer/body | `...` | | Style text | `...` | | Block text | `text` | | Inline text | `text` | | Table row | `text` | | Image | `` | | Barcode | `data` | | Line break | `` | | Page break | `` | | Get position | `` | | Set position | `` | ## Excel | Task | Pattern | |------|---------| | Create workbook | `...` | | Set cell | `42` | | Get cell | `` | | Move cursor | `` | | Add sheet | `Title` | | Apply style | `A1:B5` | | Auto-filter | `A1:D100` | | Export to array | `` | | Generate file | `` | ## File Operations | Task | Pattern | |------|---------| | Read file | `` | | Write file | `content` | | Append | `content` | | Read CSV | `` | | Read JSON | `` | | Write CSV | `` | | Write JSON | `` | | File exists | `` | | File type | `` | | Path info | `` | | List directory | `` | | Glob | `` | | Delete | `` | | Make directory | `` | ## ZIP | Task | Pattern | |------|---------| | Open archive | `...` | | Write entry | `content` | | Read entry | `` | | List entries | `` | | Check entry | `` | | Delete entry | `` | ## ZeyOS Platform | Task | Pattern | |------|---------| | Get custom fields | `` | | Set custom fields | `` | | List tags | `` | | Add tags | `VIP` | | Store temp data | `content` | | Read temp data | `` | | Get temp path | `` | | Store binary | `data` | | Next number | `` | | Authenticate | `` | | Get session | `` | | Get config | `` | | Run Zymba | `...` | ## Common Pitfalls | Pitfall | Correct Approach | |---------|-----------------| | `` | Use `value1`, `value2`, `func` attributes | | `` | Remove `$` — it is substitution syntax only | | `func="<"` in attribute | Escape as `func="<"` | | `42` is integer | It's a string — use `` | | `NULL` | Use `` | | `` | Does not exist — use `` | | `` | Does not exist — use `` | | `` | Does not exist — use `$arr` | | `` without `var` | Always use `var` to capture the parsed result | | CSV delimiter assumed comma | Default is `;` — specify `delimiter=","` explicitly | --- Canonical: https://www.zeyos.com/ixml/ixml-guide/introduction/result-propagation # Result Propagation Any CDATA value of a statement may be replaced with the result of an embedded statement, unless it is explicitly precluded (by the `no-result-propagation` predicate). An incompatible result type is thereby implicitly cast to the appropriate input type. Result propagation is one of iXML's most distinctive features. It allows you to compose statements by nesting them, with the inner statement's result flowing upward to become the content of the outer statement. This eliminates the need for temporary variables in many common patterns and enables a concise, functional style of coding. ## Basic propagation Instead of storing a value in a variable and then referencing it, you can embed the producing statement directly inside the consuming statement: ```xml $(15 + (7.5 * 2 - 3)) $number $(15 + (7.5 * 2 - 3)) ``` In the second form, the `` statement (without a `var` attribute) computes the value `27` and propagates it as the content of the `` statement. ## Simultaneous storage and propagation The result may still be stored in a result variable if specified, while also propagating upward: ```xml $(15 + (7.5 * 2 - 3)) ,$number ``` The first `` receives the propagated result `27` and outputs it. Simultaneously, the value is stored in `$number`, which the second `` references. ## Last scalar result wins When multiple embedded statements produce results within the same parent, the **last scalar result** is used for propagation: ```xml 10 $($number + 100) $($number + 5) ``` The first `` stores `10` in `number` and propagates `"10"`. The second produces `110`. The third produces `15`. Since the **last** scalar result wins, the `` receives `15`. The intermediate results are discarded from the propagation chain (though the side effect of storing `10` in `$number` persists). ## Deep nesting with control flow The embedded code may also be deeply nested. Results of `IF`, `IS`, and `SWITCH` are propagated to the topmost statement, enabling inline conditional output: ```xml 1945 This person is very old! $(2012 - $born) ``` ## CDATA alongside embedded code is discarded Any CDATA values (plain text) that are used alongside embedded code are discarded. Once you embed any statement inside a parent element, all plain text content in that parent is ignored: ```xml $(15 + (7.5 * 2 - 3)) 471 ``` The literal text `471` is discarded because embedded code is present. Only the result of the `` statement (`27`) is propagated. ## Propagation with function calls Result propagation is also applicable to user-defined functions. The `` command propagates its return value to the parent statement: ```xml iXML ``` ### See also - [set](https://www.zeyos.com/ixml/ref/set) — the most common statement used in propagation patterns - [call](https://www.zeyos.com/ixml/ref/call) — function calls with result propagation - [Syntax Essentials](https://www.zeyos.com/ixml/ixml-guide/syntax-essentials) — how `if`, `is`, and `switch` propagate results --- Canonical: https://www.zeyos.com/ixml/ixml-guide/introduction/scope-and-assignment # Scope and Assignment ## Scope and visibility Variables in iXML exist within a **scope** — a boundary that determines where the variable can be referenced. Understanding scope is essential for writing correct iXML code, especially when working with functions, includes, and closures. Variables typically have a **single local scope** and are therefore only visible within the immediate context, unless they have been specifically declared as global variables, in which case they have **global scope** and are independent of the context within which they are defined. **Closures** may be used to establish a referencing environment for free function variables. Those variables are explicitly bound to corresponding variables from the immediate context within which a function is defined, thereby directly referencing non-local variables from the parent scope outside of the inherent context of that function. ### Visibility precedence When multiple variables with the same name exist at different scope levels, iXML resolves the reference using a strict precedence order from highest to lowest priority: 1. **Special local variables** — `this`, `return`, `arguments`. These are automatically provided by the runtime inside functions and cannot be overridden by any other mechanism. 2. **Free function variables** — declared with `` in closures. These are bound references to variables in the function's defining scope. 3. **Regular local variables** — parameters passed on context switch (via `` in ``, ``, or ``). These are the most common variables in everyday code. 4. **Global variables** — declared with ``. These have the lowest precedence, meaning any local variable with the same name shadows the global. By default, variables are local to context boundaries (function calls, includes, REST resources). Use `` when cross-context sharing is intentional, but prefer parameter passing for cleaner, more testable code. ### See also - [function](https://www.zeyos.com/ixml/ref/function) — context isolation and closure binding - [global](https://www.zeyos.com/ixml/ref/global) — declaring global variables - [include](https://www.zeyos.com/ixml/ref/include) — context boundaries in code inclusion --- ## Assignment model The way iXML assigns values to variables depends on the type of value being assigned. This distinction has profound implications for how your code behaves, particularly when passing complex data between functions, modifying arrays during iteration, and building object-oriented structures. - **Primitive non-composite variables** (null, bool, int, float, string) are always assigned **by value**. When you assign a primitive to another variable, the runtime creates an independent copy. Changes to one variable do not affect the other. - **Complex variables** (array, function, macro, class) are always assigned **by reference**. When you assign a complex value to another variable, both variables point to the same underlying data. Changes through one variable are visible through the other. - Assignment by reference may cause **circular references**. Avoid creating data structures where array A contains a reference to array B, which in turn contains a reference back to array A — this can cause stack depth exceptions. - Using a variable before assigning a value to it is possible — it then **defaults to the neutral literal** of the accordant input type. For example, an uninitialized variable used in an arithmetic context defaults to `0`. :::warning The by-reference behavior of arrays is one of the most important concepts to internalize. When you pass an array to a function via ``, the function receives a reference to the original array — modifications inside the function affect the caller's data. Use `` if you need an independent copy. ::: ### See also - [assign](https://www.zeyos.com/ixml/ref/assign) — explicit by-reference assignment - [clone](https://www.zeyos.com/ixml/ref/clone) — creating independent copies of complex variables - [typeof](https://www.zeyos.com/ixml/ref/typeof) — inspecting the runtime type of a variable - [cast](https://www.zeyos.com/ixml/ref/cast) — explicitly converting between types --- Canonical: https://www.zeyos.com/ixml/ixml-guide/introduction/variables-and-substitution # Variables and Substitution The name of a variable is a placeholder for its value — the data it holds. Referencing that value is called **variable substitution**. Variables referenced inside an expression are expanded to their respective values. For this purpose, variables are represented by a dollar sign (`$`) followed by the name of the variable. Variable substitution is the mechanism that makes iXML dynamic. Without substitution, every `` statement would produce static text. With substitution, you can embed computed values, database results, API responses, and user input directly into your output, your conditionals, and your data structures. ## Basic substitution The simplest form of variable substitution uses the dollar sign followed by a variable name. The variable name must consist of alphanumeric characters and underscores only, and it is **case-sensitive**: ```xml iXML My name is $name! ``` When the runtime encounters `$name` in the output string, it looks up the variable `name` in the current scope, finds the value `"iXML"`, and replaces `$name` with that value. The exclamation mark after `$name` is not part of the variable name because it is a punctuation character — the runtime treats any punctuation character as the end of a variable name. ## Substitution rules - **Variable names are case-sensitive** and consist of alphanumeric characters and underscores only. The variable `$Name` and the variable `$name` are two distinct variables. - The next **punctuation character** implicitly ends the variable name, though it may explicitly be concluded by a trailing dollar sign (`$`). For example, in the string `$name!`, the `!` terminates the variable name. If you need to concatenate a variable with alphanumeric text, use the trailing dollar sign: `$name$suffix` reads two variables, but `$name$$suffix` reads `$name`, outputs a literal `$`, then reads `suffix`. - A dollar sign (`$`) is passed unchanged if **not followed by** an alphanumeric character or underscore. For example, `$!` outputs a literal `$!` because `!` is not a valid variable name character. - Two adjacent dollar signs (`$$`) are substituted with one single dollar sign (`$`) as an **escape syntax**. This is the only way to produce a literal dollar sign in output. ### Dollar sign escaping When you need to output a literal dollar sign — for example, in currency formatting — use the double-dollar escape: ```xml Price: $$100 ``` ### Invalid variable declaration The `$` symbol is for substitution only — it is not part of variable names. Including a dollar sign in the `var` attribute of any command is a parser error: ```xml hello ``` The correct form is `hello`. When you later reference this variable, you write `$a` — the dollar sign belongs to the *substitution expression*, not to the *variable name*. --- ## Array key access and key chains iXML provides a shortcut syntax for referencing array items directly by specifying the corresponding key. This syntax supports two notations — **array notation** (square brackets) and **object notation** (dot syntax) — which can be mixed freely and nested to arbitrary depth. ### Array notation (square brackets) Within square brackets, a key is **not interpreted as a literal** but as an **expression**, resulting in further variable substitution. This means you can use variables as dynamic keys, enabling patterns like lookup tables and computed array access. Empty brackets `[]` have context-dependent behavior: - In **substitution** (`$arr[]`) or **getting/unsetting**: interpreted as an empty key literal - In **setting** (`value`): implicitly generates the next consecutive numeric key, similar to pushing onto an array ```xml Bill Gates Steve Jobs $names[bg] and $names[sj] are competitors! ``` Dynamic key lookup with nested substitution — the keys themselves are resolved from variables: ```xml Bill Gates Steve Jobs bg sj $names[$key1] and $names[$key2] are competitors! ``` ### Object notation (dot syntax) A key appended with a dot is **always interpreted as a literal**. No variable substitution occurs within dot-notation keys. This makes dot notation ideal for accessing known, fixed keys — it is more readable and slightly faster than bracket notation for this purpose. The restriction is that dot-notation keys may consist of **alphanumeric characters and underscores only**. If you need to access a key containing special characters (spaces, hyphens, dots), you must use bracket notation. ```xml Bill Gates Steve Jobs $names.bg and $names.sj are competitors! ``` ### Key chains Several keys may be strung together to form a **key chain** of arbitrary length, which allows multidimensional arrays to be accessed directly. Array notation and dot notation may be mixed freely within a single key chain: ```xml Bill Gates Steve Jobs $names[microsoft].bg and $names.apple[sj] are competitors! ``` ### Deeply nested references References of array items may be deeply nested within the array notation. This enables indirection patterns where one array acts as a lookup table for keys into another array: ```xml Bill Gates Steve Jobs bg sj $names[$keys.bill_gates] and $names[$keys[steve_jobs]] are competitors! ``` Nested references can be stacked to any depth, but deeply nested expressions become difficult to read and debug — consider using intermediate variables for clarity in production code. --- ## Inline expression forms iXML provides two special substitution syntaxes for embedding computed values directly into strings: **arithmetic inline substitution** for mathematical expressions, and **inline function substitution** for calling functions with a single argument. ### Arithmetic inline substitution Arithmetic expressions embedded inside another expression are replaced by their result. They are represented by a leading dollar sign (`$`) followed by parentheses (`()`): ```xml 1950 This person is $(2012 - $birthyear) years old! ``` The `$()` syntax works anywhere that normal substitution works — in `` content, in `` content, in attribute values of type `string`, and even inside other substitution expressions. Computed array indices: ```xml Bill Gates Steve Jobs $names[0] and $names[$(0 + 1)] are competitors! ``` ### Inline function substitution A leading variable reference followed by a single argument in parentheses calls the function and substitutes its return value: ```xml $return iXML My name is $lcase($name)! ``` The runtime calls the function with `$name` as the value of the special `return` variable, executes the function body, and inserts the return value into the output string. :::note The inline function syntax `$func($arg)` passes the argument as the `return` variable. This is a shorthand — for multiple arguments, use the [`call`](https://www.zeyos.com/ixml/ixml-guide/reusability-and-oop/functions) command with `` children instead. ::: ### See also - [set](https://www.zeyos.com/ixml/ref/set) — the primary variable assignment command - [call](https://www.zeyos.com/ixml/ref/call) — invoking functions with multiple parameters - [output](https://www.zeyos.com/ixml/ref/output) — producing output with embedded substitution --- Canonical: https://www.zeyos.com/ixml/ixml-guide/reusability-and-oop # Reusability and Object-Orientation Large iXML codebases become maintainable only when reuse boundaries are explicit. iXML provides five primary mechanisms for structuring reusable code: - **[Functions](https://www.zeyos.com/ixml/ixml-guide/reusability-and-oop/functions)**: Closed subroutines with local scope. - **[Macros](https://www.zeyos.com/ixml/ixml-guide/reusability-and-oop/macros)**: Open subroutines that share the caller's context. - **[Classes](https://www.zeyos.com/ixml/ixml-guide/reusability-and-oop/classes)**: Array prototypes for object-oriented design. - **[Includes](https://www.zeyos.com/ixml/ixml-guide/reusability-and-oop/functions#include--include-code-source)**: External code source execution. - **[Client Bindings](https://www.zeyos.com/ixml/ixml-guide/reusability-and-oop/clients)**: SOAP and REST service integration. Each mechanism has different scoping, performance, and composability characteristics. Choosing the right one is critical for building robust ZeyOS applications. ## Decision Framework | Need | Recommended construct | Why | |---|---|---| | Small, context-aware template fragment | `macro` + `expand` | No context overhead; direct access to surrounding variables makes it natural for output patterns | | Reusable logic with clear input/output contract | `function` + `call` | Context isolation prevents accidental side effects; parameters and return value create an explicit API | | State and behavior that belong together | `class` + `new` | Properties hold state; methods provide behavior; inheritance enables hierarchical reuse | | Code in a separate file or ZeyOS resource | `include` | Natural module boundary; local context provides isolation between files | | External SOAP/REST web service | Client Bindings | Automatic WSDL/Route parsing; operations become callable functions | --- Canonical: https://www.zeyos.com/ixml/ixml-guide/reusability-and-oop/classes # Classes and Objects A **class** in iXML is an array prototype. It defines properties, a constructor, and methods. An **object** is an instance of a class, created with ``. ## `class` — Class definition [Full reference →](https://www.zeyos.com/ixml/ref/class) ### Syntax ```xml default value $paramValue $this.propName ``` ### Constituents - **Properties**: Default key-value pairs. - **Constructor**: Automatic initialization routine. - **Methods**: Functions bound to the instance check. ## `new` — New instance [Full reference →](https://www.zeyos.com/ixml/ref/new) Creates a new object from a class. ### Syntax ```xml value ``` ## `method` — Method definition Full reference → Defines a function bound to the class. Inside a method, `$this` refers to the object instance. ## Inheritance Classes can inherit from parent classes using ``. - The child inherits all properties and methods. - Constructors execute in chain order: Parent first, then Child. - Methods can be overridden by redefining them in the child. ## Introspection - **`instanceof`**: Check if an object is an instance of a class. - **`classinfo`**: Retrieve structural information about a class. --- Canonical: https://www.zeyos.com/ixml/ixml-guide/reusability-and-oop/clients # Client Bindings ## `soap:client` — SOAP Client [Full reference →](https://www.zeyos.com/ixml/ref/soap-client) Defines SOAP operations from a WSDL. ### Syntax ```xml OperationName ``` ## `rest:client` — REST Client [Full reference →](https://www.zeyos.com/ixml/ref/rest-client) Defines RESTful resources. ### Syntax ```xml Authorization: Bearer $token /items/:id 123 ``` ### Route Parameters parameters passed to `` fill the `:name` placeholders in the route path. --- Canonical: https://www.zeyos.com/ixml/ixml-guide/reusability-and-oop/functions # Functions and Includes ## `function` — Function definition [Full reference →](https://www.zeyos.com/ixml/ref/function) A function in iXML is a **closed subroutine** — a named, reusable block of code that executes in its own isolated local context. ### Syntax ```xml result value ``` ### Context Isolation The critical characteristic of a function is **context isolation**. When a function is called, iXML creates a fresh local context. Variables declared inside are invisible outside, and vice versa, unless explicitly bound. ### Special Local Variables | Variable | Description | |---|---| | `this` | Reference to the calling object (if called as a method). | | `return` | The return value. Defaults to `NULL`. | | `arguments` | Array containing all passed parameters. | ## `call` — Call function [Full reference →](https://www.zeyos.com/ixml/ref/call) Invokes a function. Execution transfers to the function and returns when complete. ### Syntax ```xml value ``` ### Attributes | Name | Type | Description | |---|---|---| | `func` | var | Function variable to call. | | `var` | var | Variable to store the return value. | | `var_params` | var | Array variable containing parameters. | | `var_this` | var | Explicitly bind `this` context. | ## `include` — Include code source [Full reference →](https://www.zeyos.com/ixml/ref/include) Executes code from an external resource file. ### Syntax ```xml value ``` ### Description `include` loads and executes the content of a ZeyOS resource. It is the primary way to modularize code into separate files. - By default, includes execute in a **new local context** (like a function call). - Parameter passing works exactly like ``. - Use `once="true"` to prevent re-loading the same library multiple times. --- Canonical: https://www.zeyos.com/ixml/ixml-guide/reusability-and-oop/macros # Macros ## `macro` — Macro definition [Full reference →](https://www.zeyos.com/ixml/ref/macro) A macro is an **open subroutine**. Unlike a function, it executes within the **current context** of the caller. It has direct access to all variables in the calling scope. ### Syntax ```xml Hello $name ``` ### When to use Macros are ideal for: - Template fragments - Repetitive output patterns - Simple utility routines where context overhead is unwanted **Warning**: Macros can accidentally overwrite variables in the caller's scope. ## `expand` — Expand macro [Full reference →](https://www.zeyos.com/ixml/ref/expand) Invokes a macro. ### Syntax ```xml ``` ### Example ```xml World Hello $name ``` --- Canonical: https://www.zeyos.com/ixml/ixml-guide/reusability-and-oop/patterns # Object-Oriented Patterns This section covers practical patterns for building maintainable applications. ## The `this` Reference `this` is a direct reference to the current object. - **Access**: `$this.property` - **Method Call**: `$this.method()` ## Factory Pattern Use a factory class to decide which class to instantiate at runtime (e.g., selecting a print template based on data). ```xml ``` ## Singleton Pattern Combine `` with a conditional check to ensure only one instance exists. ```xml ``` ## Composition Prefer composition over deep inheritance. Objects can contain other objects as properties. ```xml ``` --- Canonical: https://www.zeyos.com/ixml/ixml-guide/standard-library # Standard Library iXML comes with a rich standard library for common application tasks. ## Content Generation - **[PDF Generation](https://www.zeyos.com/ixml/ixml-guide/standard-library/pdf)**: Create professional PDF documents using HTML/CSS or low-level primitives. - **[Excel & ZIP](https://www.zeyos.com/ixml/ixml-guide/standard-library/excel-zip)**: Generate and read Excel spreadsheets and ZIP archives. ## Data Processing - **[Math & DateTime](https://www.zeyos.com/ixml/ixml-guide/standard-library/math-datetime)**: Mathematical functions and date/time manipulation. - **[Encoding & Decoding](https://www.zeyos.com/ixml/ixml-guide/standard-library/encoding)**: JSON, XML, Base64, and cryptographic utilities. ## System Interaction - **[Filesystem](https://www.zeyos.com/ixml/ixml-guide/standard-library/filesystem)**: Read, write, and manage files in the ZeyOS VFS. - **[HTTP & REST](https://www.zeyos.com/ixml/ixml-guide/standard-library/http-rest)**: Build RESTful services and handle HTTP requests. --- Canonical: https://www.zeyos.com/ixml/ixml-guide/standard-library/encoding # Encoding and Decoding iXML provides encode/decode operations for cryptographic hashing, phonetic encoding, compression, encryption, character escaping, transfer encoding, and structured data formats. Most encode operations have a complementary decode counterpart. ## Cryptographic Hashing ### encode:hash Generates a cryptographic hash using a specified algorithm. Supports HMAC when a `key` is provided. ```xml $request_data ``` :::warning Use SHA-256 or higher for security-sensitive operations. Never use plain MD5/SHA-1 for password storage. ::: [Full reference →](https://www.zeyos.com/ixml/ref/encode-hash) ### encode:md5 / encode:sha1 Convenience commands for MD5 (32-char hex) and SHA-1 (40-char hex) hashing. Equivalent to `encode:hash` with the respective algorithm. ```xml My name is iXML! ``` [encode:md5 reference →](https://www.zeyos.com/ixml/ref/encode-md5) | [encode:sha1 reference →](https://www.zeyos.com/ixml/ref/encode-sha1) ### encode:crc32 Generates a 32-bit cyclic redundancy checksum for error detection (not security). Returns a signed integer. ```xml My name is iXML! ``` [Full reference →](https://www.zeyos.com/ixml/ref/encode-crc32) ## Phonetic Encoding ### encode:soundex / encode:metaphone Generate phonetic keys for fuzzy name matching. Soundex produces a 4-character code; Metaphone uses English pronunciation rules for higher accuracy. ```xml Smith Knight ``` :::note Metaphone is more accurate than Soundex for English text. Prefer `encode:metaphone` for English-language applications. ::: [encode:soundex reference →](https://www.zeyos.com/ixml/ref/encode-soundex) | [encode:metaphone reference →](https://www.zeyos.com/ixml/ref/encode-metaphone) ## Compression ### encode:deflate / decode:deflate Compress and decompress data using the DEFLATE algorithm (RFC 1951). The `level` attribute (0-9) controls the compression/speed trade-off. ```xml $large_text $compressed ``` :::note DEFLATE produces raw compressed data without headers or checksums. Use `encode:zlib` for compression with error detection. ::: [encode:deflate reference →](https://www.zeyos.com/ixml/ref/encode-deflate) | [decode:deflate reference →](https://www.zeyos.com/ixml/ref/decode-deflate) ### encode:zlib / decode:zlib Compress and decompress using the ZLIB format (RFC 1950), which wraps DEFLATE with headers and an Adler-32 checksum for error detection. ```xml $data $compressed ``` [encode:zlib reference →](https://www.zeyos.com/ixml/ref/encode-zlib) | [decode:zlib reference →](https://www.zeyos.com/ixml/ref/decode-zlib) ## Encryption ### encode:crypt / decode:crypt Symmetric-key encryption and decryption. Requires a `cipher` (e.g. `aes-256-cbc`), `key`, and stores/consumes an initialization vector (IV). ```xml $plaintext $ciphertext ``` :::warning Never hardcode encryption keys in source code. Store IVs alongside ciphertext -- they are required for decryption. Use authenticated encryption modes (GCM) when possible. ::: [encode:crypt reference →](https://www.zeyos.com/ixml/ref/encode-crypt) | [decode:crypt reference →](https://www.zeyos.com/ixml/ref/decode-crypt) ## Character Escaping ### encode:xml / decode:xml Encode/decode XML entities (`&` `"` `<` `>`). Always XML-encode user-supplied content before embedding in XML documents. ```xml My name is "iXML" & I am a XML derivative! ``` [encode:xml reference →](https://www.zeyos.com/ixml/ref/encode-xml) | [decode:xml reference →](https://www.zeyos.com/ixml/ref/decode-xml) ### encode:html / decode:html Like XML encoding but also converts newlines to `
` and preserves spacing with ` `. `decode:html` strips HTML tags and decodes entities. ```xml $user_comment ``` :::warning Always HTML-encode user-supplied content to prevent XSS attacks. ::: [encode:html reference →](https://www.zeyos.com/ixml/ref/encode-html) | [decode:html reference →](https://www.zeyos.com/ixml/ref/decode-html) ### encode:url / decode:url Percent-encode/decode text for safe use in URLs (RFC 1738). ```xml https://example.com/search?q=$search_query ``` [encode:url reference →](https://www.zeyos.com/ixml/ref/encode-url) | [decode:url reference →](https://www.zeyos.com/ixml/ref/decode-url) ## Transfer Encoding ### encode:base64 / decode:base64 Encode/decode data using MIME Base64 (RFC 2045). Used for embedding binary data in text formats like JSON, XML, and email. ```xml My name is iXML! SGVsbG8gV29ybGQ= ``` [encode:base64 reference →](https://www.zeyos.com/ixml/ref/encode-base64) | [decode:base64 reference →](https://www.zeyos.com/ixml/ref/decode-base64) ### encode:quotedprint / decode:quotedprint Quoted-printable encoding (RFC 2045) for mostly-ASCII text. More efficient than Base64 when data is primarily printable characters. ```xml Hello World! My name is iXML! ``` [encode:quotedprint reference →](https://www.zeyos.com/ixml/ref/encode-quotedprint) | [decode:quotedprint reference →](https://www.zeyos.com/ixml/ref/decode-quotedprint) ## Structured Data Formats ### encode:json / decode:json Convert between iXML variables and JSON (RFC 4627). Associative arrays become JSON objects; numeric arrays become JSON arrays. Use `pretty="true"` for formatted output. ```xml success User created {"status":"success","user":{"id":123,"name":"John"}} $data.user.name ``` :::warning `decode:json` must use the `var` attribute. Without it, the result is an unusable internal reference. ::: [encode:json reference →](https://www.zeyos.com/ixml/ref/encode-json) | [decode:json reference →](https://www.zeyos.com/ixml/ref/decode-json) ### encode:csv / decode:csv Encode/decode comma-separated values. Input/output is a multidimensional array (rows of columns). Default delimiter is `;`. ```xml First Name;Last Name Bill;Gates Steve;Jobs $row.0 $row.1 ``` [encode:csv reference →](https://www.zeyos.com/ixml/ref/encode-csv) | [decode:csv reference →](https://www.zeyos.com/ixml/ref/decode-csv) ### encode:binary / decode:binary Pack/unpack array values into binary data using Perl/PHP-compatible format strings. Used for binary protocols and file formats. ```xml 0x01 42 1024 ``` [encode:binary reference →](https://www.zeyos.com/ixml/ref/encode-binary) | [decode:binary reference →](https://www.zeyos.com/ixml/ref/decode-binary) --- Canonical: https://www.zeyos.com/ixml/ixml-guide/standard-library/excel-zip # Excel and ZIP iXML provides dedicated namespaces for working with Excel spreadsheets (`excel`) and ZIP archives (`zip`). ## Excel Workbooks ### excel:workbook Creates a new workbook or opens an existing one. All Excel operations must be nested inside this element. ```xml Name 36 ``` [Full reference →](https://www.zeyos.com/ixml/ref/excel-workbook) ## Cell Operations ### excel:set Sets the value of a cell. Use `type` to control storage: `text`, `numeric`, `date`, `bool`, `null`, or `auto` (default). ```xml Product Name 49.99 2024-01-15 ``` :::tip Omitting `type="numeric"` for numbers may store them as strings, causing Excel formulas to fail. ::: [Full reference →](https://www.zeyos.com/ixml/ref/excel-set) ### excel:get Gets the value of a cell. The `type` attribute controls the return: `raw` (default), `calculated`, or `formatted`. ```xml ``` [Full reference →](https://www.zeyos.com/ixml/ref/excel-get) ## Cursor Navigation ### excel:setpos Sets the cursor position by indices (0-based) or cell coordinates. ```xml ``` [Full reference →](https://www.zeyos.com/ixml/ref/excel-setpos) ### excel:getpos Gets the current cursor position as coordinates and/or indices. ```xml ``` [Full reference →](https://www.zeyos.com/ixml/ref/excel-getpos) ### excel:next Shifts the cursor by a given offset. Default advances one column. ```xml ``` [Full reference →](https://www.zeyos.com/ixml/ref/excel-next) ### excel:length Gets the number of sheets, rows, and columns for the current cursor context. ```xml ``` [Full reference →](https://www.zeyos.com/ixml/ref/excel-length) ## Sheet Management ### excel:addsheet Adds a new sheet to the workbook. ```xml Summary Summary Data ``` [Full reference →](https://www.zeyos.com/ixml/ref/excel-addsheet) ### excel:removesheet Removes a sheet by index (0-based). [Full reference →](https://www.zeyos.com/ixml/ref/excel-removesheet) ## Row and Column Management ### excel:addrow / excel:removerow Insert or remove rows. Accepts optional `index` and `height` attributes. [Full reference: addrow →](https://www.zeyos.com/ixml/ref/excel-addrow) | [removerow →](https://www.zeyos.com/ixml/ref/excel-removerow) ### excel:addcol / excel:removecol Insert or remove columns. Accepts optional `index` and `width` attributes. [Full reference: addcol →](https://www.zeyos.com/ixml/ref/excel-addcol) | [removecol →](https://www.zeyos.com/ixml/ref/excel-removecol) ## Styling ### excel:style Applies styling to a cell range. The style is defined as a nested array with keys for `font`, `alignment`, `borders`, `fill`, and `numberFormat`. ```xml 1 12 solid CCCCFF thin A1:B1 ``` [Full reference →](https://www.zeyos.com/ixml/ref/excel-style) ### excel:autofilter Enables auto-filter dropdown menus on a header range. ```xml A1:D100 ``` [Full reference →](https://www.zeyos.com/ixml/ref/excel-autofilter) ## Data Export ### excel:array Extracts all cell values into a 3D array: `data[sheet][row][col]`. ```xml $data[0][0][0] ``` [Full reference →](https://www.zeyos.com/ixml/ref/excel-array) ### excel:create Generates the workbook as binary data. Supported formats: `csv`, `ods`, `xls`, `xlsx`. ```xml
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
Content-Disposition: attachment; filename="report.xlsx"
$book ``` [Full reference →](https://www.zeyos.com/ixml/ref/excel-create) ## Practical Excel Patterns ### Data Export with Styling ```xml Name Email Company Revenue 1FFFFFF solid 4472C4 A1:D1 2 c.name, c.email, c.company, c.volume $c.name $c.email $c.company $c.volume $(($row + 1)) A1:D$row ``` ### Reading an Uploaded Excel File ```xml 0 $data[0][$i][0] $data[0][$i][1] $(($imported + 1)) Imported $imported records ``` --- ## ZIP Archives The `zip` namespace provides commands for creating and reading ZIP archives. All operations (except `zip:archive` itself) must be nested inside a `zip:archive` element. Changes are committed when `` is reached. ### zip:archive Creates a new archive or opens an existing one. ```xml This is the readme. Name;Age&n;Ada;36 ``` [Full reference →](https://www.zeyos.com/ixml/ref/zip-archive) ### zip:write Writes content to an entry in the archive, creating or overwriting it. [Full reference →](https://www.zeyos.com/ixml/ref/zip-write) ### zip:read Reads the content of an archive entry into a variable. ```xml $readme ``` [Full reference →](https://www.zeyos.com/ixml/ref/zip-read) ### zip:exists Checks whether an entry exists within the archive. [Full reference →](https://www.zeyos.com/ixml/ref/zip-exists) ### zip:status Returns status information (name, size, compressed size, CRC, mtime) about an entry. [Full reference →](https://www.zeyos.com/ixml/ref/zip-status) ### zip:delete Deletes an entry from the archive. [Full reference →](https://www.zeyos.com/ixml/ref/zip-delete) ### zip:rename Renames an entry within the archive. [Full reference →](https://www.zeyos.com/ixml/ref/zip-rename) ### zip:list Lists all entry filenames in the archive. ```xml $name&n; ``` [Full reference →](https://www.zeyos.com/ixml/ref/zip-list) ## Practical ZIP Pattern: Bundling Multiple Exports ```xml Report Data Report PDF $excel_data $pdf_data {"generated": "$timestamp"}
Content-Type: application/zip
Content-Disposition: attachment; filename="reports.zip"
$zipdata ``` --- Canonical: https://www.zeyos.com/ixml/ixml-guide/standard-library/filesystem # File & Storage Operations ZeyOS provides three distinct storage mechanisms, each designed for different use cases: | Storage | Persistence | IDs | Best for | |---------|------------|-----|----------| | `file` | Permanent | Path | Import/export files, configuration, structured data | | `temp` | Session / auto-cleanup | String | Intermediate processing, PDF templates, transient data | | `bin` | Permanent | Integer | Generated documents, cached exports, binary attachments | --- ## File System (`file` namespace) The `file` namespace provides commands for reading, writing, and managing files and directories within the application sandbox. :::warning File system access is restricted to the application's sandbox. Operations outside the sandbox will fail. ::: ### Reading Files #### file:read Reads the entire content of a file into a variable. ```xml $content ``` [Full reference →](https://www.zeyos.com/ixml/ref/file-read) #### file:readcsv Reads a CSV file into a multidimensional array (`rows[row][col]`). The default delimiter is `;` (semicolon) -- specify `delimiter=","` for standard comma-separated files. ```xml $row[0] - $row[1]&n; ``` [Full reference →](https://www.zeyos.com/ixml/ref/file-readcsv) #### file:readjson Reads a file and parses its content as JSON into a variable. ```xml API Key: $config.apiKey ``` [Full reference →](https://www.zeyos.com/ixml/ref/file-readjson) ### Writing Files #### file:write Writes content to a file, creating or overwriting it. ```xml Report generated at $timestamp ``` [Full reference →](https://www.zeyos.com/ixml/ref/file-write) #### file:writecsv Writes a multidimensional array as CSV to a file. ```xml NameEmail Adaada@example.com ``` [Full reference →](https://www.zeyos.com/ixml/ref/file-writecsv) #### file:writejson Writes the JSON representation of a variable to a file. ```xml 2 false ``` [Full reference →](https://www.zeyos.com/ixml/ref/file-writejson) #### file:append Appends content to a file without overwriting. Creates the file if it does not exist. ```xml [$timestamp] $message&n; ``` [Full reference →](https://www.zeyos.com/ixml/ref/file-append) ### File Information #### file:exists Checks whether a file or directory exists. Returns the canonicalized path on success, or `FALSE` if not found. ```xml File does not exist! ``` [Full reference →](https://www.zeyos.com/ixml/ref/file-exists) #### file:typeof Returns the type of a file system entry: `file`, `dir`, `link`, or `undefined`. ```xml Type: $type ``` [Full reference →](https://www.zeyos.com/ixml/ref/file-typeof) #### file:status Returns detailed status information (size, timestamps, etc.) about a file or directory. ```xml Size: $info.size bytes, Modified: $info.mtime ``` [Full reference →](https://www.zeyos.com/ixml/ref/file-status) #### file:pathinfo Parses a filename into components: `dirname`, `basename`, `extension`, and `filename`. ```xml Dir: $info.dirname, Ext: $info.extension, Name: $info.filename ``` [Full reference →](https://www.zeyos.com/ixml/ref/file-pathinfo) ### File Management #### file:delete Deletes a file. This operation is irreversible. ```xml ``` [Full reference →](https://www.zeyos.com/ixml/ref/file-delete) #### file:rename Renames or moves a file or directory. ```xml ``` [Full reference →](https://www.zeyos.com/ixml/ref/file-rename) #### file:link Creates a symbolic link pointing to a target path. [Full reference →](https://www.zeyos.com/ixml/ref/file-link) #### file:send Sends a file directly to the client as the HTTP response body (for serving downloads). ```xml ``` [Full reference →](https://www.zeyos.com/ixml/ref/file-send) ### Directory Operations #### file:listdir Lists all filenames within a directory. ```xml $filename&n; ``` [Full reference →](https://www.zeyos.com/ixml/ref/file-listdir) #### file:glob Finds all pathnames matching a wildcard pattern (`?`, `*`, `[...]`). ```xml Found CSV: $info.filename&n; ``` [Full reference →](https://www.zeyos.com/ixml/ref/file-glob) #### file:makedir Creates a new directory. Parent directories are created recursively. ```xml ``` [Full reference →](https://www.zeyos.com/ixml/ref/file-makedir) #### file:removedir Removes an empty directory. Delete all files within it first. [Full reference →](https://www.zeyos.com/ixml/ref/file-removedir) --- ## Temporary Data Store (`temp` namespace) The `temp` namespace provides temporary storage that persists across requests within the same application context. When you write data with `temp:write`, it returns a string ID that you use for all subsequent operations. Temporary files are automatically cleaned up by ZeyOS. ```xml Temporary content ``` ### Using temp for PDF templates The most common use of `temp:path` is bridging between iXML resources and the PDF engine. Since `pdf:templates` requires a filesystem path, you store resource content in temp and pass the path: ```xml $template_data Content on letterhead ``` [`temp:write`](https://www.zeyos.com/ixml/ref/temp-write) | [`temp:read`](https://www.zeyos.com/ixml/ref/temp-read) | [`temp:path`](https://www.zeyos.com/ixml/ref/temp-path) | [`temp:delete`](https://www.zeyos.com/ixml/ref/temp-delete) --- ## Binary Data Store (`bin` namespace) The `bin` namespace provides permanent binary storage. Unlike `temp`, binary data persists indefinitely and uses integer IDs. Use it when you need to store generated files (PDFs, exports, images) that should be retrievable later. ```xml $pdf_data ``` A common pattern is generating a PDF and storing it for later download: ```xml $pdf $doc_id ``` [`bin:write`](https://www.zeyos.com/ixml/ref/bin-write) | [`bin:read`](https://www.zeyos.com/ixml/ref/bin-read) --- ## Practical Patterns ### File-based Import Workflow ```xml Processing: $info.basename&n; 0 Row $i: $row[0] - $row[1]&n; $(($i + 1)) ``` ### JSON Configuration Loading ```xml 25 en Page size: $config.pageSize ``` ### CSV Import with Database Insert ```xml $row.firstname $row.lastname $row.email ``` --- Canonical: https://www.zeyos.com/ixml/ixml-guide/standard-library/http-rest # REST and HTTP Operations The iXML language provides facilities for building RESTful APIs and consuming HTTP services. REST operations cover two use cases: **REST server** for building APIs, and **REST client** for consuming external APIs. HTTP operations provide low-level request capabilities. ## REST Server (Building APIs) ### rest:server / rest:resource [`rest:server`](https://www.zeyos.com/ixml/ref/rest-server) defines a REST server with route-based dispatch. `rest:resource` defines individual endpoints. **Key concepts:** - Route parameters use `:name` syntax (`/users/:id`) - Variadic routes use `*name` (`/files/*path`) - Query parameters are automatically extracted as local variables. For POST, form fields are used instead, and route parameters take priority - Set HTTP status with `404` - Each handler executes in its own local context — use `` to import parent variables :::warning The request can hide any global inside a handler, because locals take priority. `?AUTH[user]=1` creates a local `AUTH`. Bind every global you read with `` as the first child of `rest:resource`. See [Bind globals inside `rest:resource`](https://www.zeyos.com/ixml/ixml-guide/development-environment/execution-contexts#bind-globals-inside-restresource). ::: ```xml $id 404
Content-Type: application/json
$body 400
Content-Type: application/json
{"error": "Email is required"}
201
Content-Type: application/json
{"result": 1, "id": $new_id} 400
Content-Type: application/json
{"error": "Invalid JSON"}
Content-Type: application/json
{"result": 1}
``` ### Accessing request data ```xml ID: $id Search: $q Auth: $headers.Authorization $body ``` :::warning **Output Buffering:** All output within `rest:server` is buffered. Do not send output before the `rest:server` block, as this will commit HTTP headers prematurely. ::: --- ## REST Client (Consuming APIs) ### rest:client / rest:bind [`rest:client`](https://www.zeyos.com/ixml/ref/rest-client) establishes a connection context for an external API. [`rest:bind`](https://www.zeyos.com/ixml/ref/rest-bind) creates callable functions bound to endpoints. ```xml Content-Type: application/json Accept: application/json /products/ /products/:id /products/:id /products/:id {"name": "Wildberries", "price": 4.99} $response Created product ID: $product.id $product.id {"price": 5.99} ``` **Parameter passing:** - Path parameters: first N params fill `:param` placeholders in order - Request body: remaining params become the body (POST, PUT, PATCH) - Use `type="raw"` on `rest:bind` to get full response with `status`, `type`, `header`, `body` --- ## HTTP Operations ### http:request [`http:request`](https://www.zeyos.com/ixml/ref/http-request) sends a low-level HTTP request with full control over headers, body, and response handling. ```xml Content-Type: application/json Authorization: Bearer $api_token {"name": "Jane Doe", "email": "jane@example.com"} $response User: $user.name Error: $info.status ``` The `var_info` array contains: `status` (HTTP code), `type` (Content-Type), `header` (raw headers), `body` (response body). ### http:urlinfo [`http:urlinfo`](https://www.zeyos.com/ixml/ref/http-urlinfo) parses a URL into its components (`scheme`, `host`, `port`, `path`, `query`, `fragment`, `args`): ```xml Host: $info.host, Path: $info.path, Role: $info.args.role ``` ### http:query [`http:query`](https://www.zeyos.com/ixml/ref/http-query) builds a URL-encoded query string from an associative array: ```xml iXML programming 10 $qs ``` --- ## Response patterns ### Consistent JSON envelope ```xml
Content-Type: application/json
{"result": 1, "data": } 400 {"error": "Validation failed", "details": "$message"} ``` ### HTTP status codes | Code | When to use | |------|-------------| | 200 | Successful GET, PUT, PATCH, DELETE | | 201 | Successful POST creating a resource | | 400 | Invalid input, malformed JSON | | 401 | Missing or invalid authentication | | 404 | Resource not found | | 500 | Unexpected server error | --- Canonical: https://www.zeyos.com/ixml/ixml-guide/standard-library/math-datetime # Mathematics and Date/Time The **Math** namespace provides arithmetic, trigonometry, rounding, random numbers, base conversion, and number formatting. The **Date** namespace provides Unix timestamp operations: retrieving, creating, decomposing, parsing, and formatting dates. :::note Timestamp model All date/time operations use **Unix timestamps** — seconds since January 1, 1970 00:00:00 UTC. There is no separate Date type; dates are always numeric timestamps. ::: --- ## Basic arithmetic ### `math:abs` — Absolute value Returns the non-negative magnitude of a number. ```xml -13.74 ``` [Full reference →](https://www.zeyos.com/ixml/ref/math-abs) --- ### `math:sign` — Extract sign Returns `1` (positive), `0` (zero), or `-1` (negative). ```xml -13.74 ``` [Full reference →](https://www.zeyos.com/ixml/ref/math-sign) --- ### `math:inc` — Increment variable Increments a variable in place by a given amount (default 1). This is the idiomatic way to build counters in iXML. ```xml 1 $number ``` [Full reference →](https://www.zeyos.com/ixml/ref/math-inc) --- ### `math:dec` — Decrement variable Decrements a variable in place by a given amount (default 1). ```xml 2 $number ``` [Full reference →](https://www.zeyos.com/ixml/ref/math-dec) --- ### `math:mod` — Floating-point modulo Returns the remainder of dividing `x` by `y`. Supports fractional values. ```xml ``` [Full reference →](https://www.zeyos.com/ixml/ref/math-mod) --- ## Exponential and logarithmic functions ### `math:exp` — Natural exponential Raises Euler's number (*e*) to the given power. ```xml 10 ``` [Full reference →](https://www.zeyos.com/ixml/ref/math-exp) --- ### `math:ln` — Natural logarithm Computes the base-*e* logarithm. Input must be positive. ```xml 22026.465794807 ``` [Full reference →](https://www.zeyos.com/ixml/ref/math-ln) --- ### `math:log` — Logarithm with base Computes the logarithm with a specified base (default 10). ```xml 1024 ``` [Full reference →](https://www.zeyos.com/ixml/ref/math-log) --- ### `math:pow` — Raise to power Raises a value to the given exponent (default 2). ```xml 2 ``` [Full reference →](https://www.zeyos.com/ixml/ref/math-pow) --- ### `math:sqrt` — Square root Returns the square root of a non-negative value. ```xml 25 ``` [Full reference →](https://www.zeyos.com/ixml/ref/math-sqrt) --- ### `math:hypot` — Hypotenuse Computes sqrt(x^2 + y^2) with better numerical precision than doing it manually. ```xml ``` [Full reference →](https://www.zeyos.com/ixml/ref/math-hypot) --- ## Constants ### `math:pi` — Circle constant Returns the mathematical constant pi with full floating-point precision. ```xml ``` [Full reference →](https://www.zeyos.com/ixml/ref/math-pi) --- ## Trigonometric functions All trigonometric functions operate in **radians**. Use `math:convert` to convert between degrees and radians. ### Standard trigonometric functions | Command | Description | |---|---| | `math:sin` | Sine of an angle | | `math:cos` | Cosine of an angle | | `math:tan` | Tangent of an angle | ```xml 1 ``` [`math:sin` reference →](https://www.zeyos.com/ixml/ref/math-sin) | [`math:cos` reference →](https://www.zeyos.com/ixml/ref/math-cos) | [`math:tan` reference →](https://www.zeyos.com/ixml/ref/math-tan) ### Inverse (arc) functions | Command | Description | |---|---| | `math:asin` | Arc sine — returns radians (input in [-1, 1]) | | `math:acos` | Arc cosine — returns radians (input in [-1, 1]) | | `math:atan` | Arc tangent — returns radians in (-pi/2, pi/2) | | `math:atan2` | Two-argument arc tangent — quadrant-aware, returns radians in (-pi, pi] | ```xml ``` [`math:asin` →](https://www.zeyos.com/ixml/ref/math-asin) | [`math:acos` →](https://www.zeyos.com/ixml/ref/math-acos) | [`math:atan` →](https://www.zeyos.com/ixml/ref/math-atan) | [`math:atan2` →](https://www.zeyos.com/ixml/ref/math-atan2) ### Hyperbolic functions | Command | Description | |---|---| | `math:sinh` | Hyperbolic sine | | `math:cosh` | Hyperbolic cosine | | `math:tanh` | Hyperbolic tangent | | `math:asinh` | Inverse hyperbolic sine | | `math:acosh` | Inverse hyperbolic cosine (input >= 1) | | `math:atanh` | Inverse hyperbolic tangent (input in (-1, 1)) | ```xml 1 ``` [`math:sinh` →](https://www.zeyos.com/ixml/ref/math-sinh) | [`math:cosh` →](https://www.zeyos.com/ixml/ref/math-cosh) | [`math:tanh` →](https://www.zeyos.com/ixml/ref/math-tanh) | [`math:asinh` →](https://www.zeyos.com/ixml/ref/math-asinh) | [`math:acosh` →](https://www.zeyos.com/ixml/ref/math-acosh) | [`math:atanh` →](https://www.zeyos.com/ixml/ref/math-atanh) --- ## Rounding ### `math:round` — Round to precision Rounds a value to a specified number of decimal places. Supports 10 rounding strategies via the `type` attribute. Default mode is `half-infinity` (ties round away from zero). **Nearest-value modes:** `half-infinity` (default), `half-zero`, `half-up`, `half-down`, `half-even` (banker's rounding), `half-odd`. **Directed modes:** `infinity` (away from zero), `zero` (truncate), `up` (ceiling), `down` (floor). ```xml 17.213482971852 ``` For financial calculations, use `half-even` to minimize cumulative rounding error: ```xml 2.555 ``` [Full reference →](https://www.zeyos.com/ixml/ref/math-round) --- ### `math:ceil` — Ceiling Rounds a value **up** to the next highest integer. ```xml 17.213482971852 ``` [Full reference →](https://www.zeyos.com/ixml/ref/math-ceil) --- ### `math:floor` — Floor Rounds a value **down** to the next lowest integer. ```xml 17.213482971852 ``` [Full reference →](https://www.zeyos.com/ixml/ref/math-floor) --- ## Random numbers ### `math:rand` — Generate random number Generates a random integer. Optionally specify `min` and `max` for a range (inclusive). ```xml You rolled: $diceRoll ``` [Full reference →](https://www.zeyos.com/ixml/ref/math-rand) --- ## Number conversion ### `math:convert` — Convert number representation Converts between number bases and angular units. Supported representations: `bin`, `oct`, `dec` (default), `hex`, `deg`, `rad`. ```xml 7cd5a72c ``` Particularly useful for converting degrees to radians before trigonometric operations: ```xml sin(45deg) = 45 ``` [Full reference →](https://www.zeyos.com/ixml/ref/math-convert) --- ## Number formatting ### `math:format` — Format number for display Produces a display string with fixed decimal places, configurable decimal point (`decpoint`), and thousands separator (`separator`). Returns a **string**, not a number -- do not use the result in further arithmetic. ```xml 1792702.213482971852 ``` German locale example: ```xml 1792702.213482971852 ``` [Full reference →](https://www.zeyos.com/ixml/ref/math-format) --- ## Date and time ### `date:now` — Current timestamp Returns the current Unix timestamp. Use `micro="true"` for microsecond precision (returns a float). ```xml Current timestamp: $timestamp ``` [Full reference →](https://www.zeyos.com/ixml/ref/date-now) --- ### `date:info` — Decompose timestamp Breaks a timestamp into components: `day`, `month`, `year`, `hour`, `minute`, `second`, `wday` (0=Sunday..6=Saturday), `yday`. If no timestamp given, uses the current time. ```xml Today is $now.year-$now.month-$now.day ``` Supports the `timezone` attribute for time zone-aware decomposition: ```xml 946684800 In Berlin: $berlin.year-$berlin.month-$berlin.day $berlin.hour:$berlin.minute ``` [Full reference →](https://www.zeyos.com/ixml/ref/date-info) --- ### `date:create` — Create timestamp from components Constructs a Unix timestamp from `day`, `month`, `year` (required) and optionally `hour`, `minute`, `second`, `timezone`. Returns `NULL` for invalid dates. ```xml ``` [Full reference →](https://www.zeyos.com/ixml/ref/date-create) --- ### `date:parse` — Parse date string Converts a date string into a Unix timestamp. Accepts ISO 8601, common formats, and relative expressions. Returns `NULL` if unparseable. Use `timezone` to set the assumed time zone for strings without explicit zone info. :::warning Always verify parsed results. Ambiguous formats (e.g., `01/02/2025` -- is that Jan 2 or Feb 1?) may not parse as expected. Prefer ISO 8601 (`YYYY-MM-DD`) when possible. ::: ```xml 2000-01-01 ``` [Full reference →](https://www.zeyos.com/ixml/ref/date-parse) --- ### `date:format` — Format timestamp Converts a timestamp to a string using **PHP-compatible format characters**. If no timestamp given, uses the current time. Default format is `c` (ISO 8601). **Common format characters:** | Char | Output | Example | |---|---|---| | `Y` | 4-digit year | `2025` | | `m` | Month (zero-padded) | `03` | | `d` | Day (zero-padded) | `15` | | `H` | Hour 24h (zero-padded) | `14` | | `i` | Minute (zero-padded) | `30` | | `s` | Second (zero-padded) | `00` | | `c` | ISO 8601 full | `2025-03-15T14:30:00+01:00` | | `D` | Day name (3-letter) | `Sat` | | `F` | Month name (full) | `March` | ```xml 946684800 ``` European format with time zone: ```xml 946684800 ``` :::warning Literal characters matching format codes must be backslash-escaped. For example, `\a\t` produces the text "at", not the `a` (am/pm) and `t` (days in month) format codes. ::: [Full reference →](https://www.zeyos.com/ixml/ref/date-format) --- Canonical: https://www.zeyos.com/ixml/ixml-guide/standard-library/pdf # PDF Generation iXML provides a complete PDF generation engine through the `pdf` namespace. You can create multi-page documents with headers, footers, styled text, tables, images, barcodes, and imported templates -- all from XML markup. ## Document Structure Every PDF follows a strict hierarchy: ``` pdf:document <- document root (produces binary output) ├── pdf:file <- embedded file attachment (PDF/A) └── pdf:section <- page layout definition (one or more) ├── pdf:templates <- background template pages │ └── pdf:template <- individual template page mapping ├── pdf:header <- repeated header content ├── pdf:footer <- repeated footer content └── pdf:body <- main page content ├── pdf:style <- typography and layout styling ├── pdf:block <- block-level text ├── pdf:inline <- inline flow text ├── pdf:row <- table row │ └── pdf:col <- table column ├── pdf:image <- image ├── pdf:barcode <- barcode (1D or 2D) ├── pdf:linebreak <- vertical spacing ├── pdf:pagebreak <- force new page ├── pdf:include <- insert external PDF pages ├── pdf:getpos <- read current position └── pdf:setpos <- set current position ``` Content elements can appear inside `pdf:header`, `pdf:footer`, or `pdf:body`. ## Document Root ### pdf:document Creates a PDF document. All sections and content are defined as children. The resulting binary is stored in the variable specified by `var`. Supports metadata attributes (`title`, `author`, `subject`, `keywords`), `pdfa` for PDF/A-3 compliance, `compress` and `subsetting` for size optimization, and `unit` for measurement (default `mm`). [Full reference ->](https://www.zeyos.com/ixml/ref/pdf-document) ### pdf:signature Adds a digital signature to the PDF document using PEM-encoded certificates and private keys. Child of `pdf:document` (single occurrence). Full reference -> ### pdf:file Embeds a file attachment in the PDF document. Primarily used with PDF/A-3 for structured data embedding (e.g., ZUGFeRD e-invoicing XML). [Full reference ->](https://www.zeyos.com/ixml/ref/pdf-file) ## Page Layout ### pdf:section Defines a page section with its own orientation, format, margins, header, footer, and body. A document can contain multiple sections with different page setups. Common formats: `A4`, `A3`, `A5`, `LETTER`, `LEGAL`. [Full reference ->](https://www.zeyos.com/ixml/ref/pdf-section) ### pdf:templates Defines a set of template pages to overlay as a background, loaded from an external PDF via `filename`. Contains `pdf:template` children mapping source pages to target pages. Commonly used for letterheads and branded backgrounds. [Full reference ->](https://www.zeyos.com/ixml/ref/pdf-template) ### pdf:template Maps a page from the template PDF to a target page in the generated document. Child of `pdf:templates`. Use `source` for the template page number and `target` for the output page number (omit `target` to apply to all pages). [Full reference ->](https://www.zeyos.com/ixml/ref/pdf-template) ## Typography and Styling ### pdf:style Defines visual styling for child content. Styles cascade -- each child inherits from its parent and overrides specific properties. Controls alignment, font (`font`, `fontsize`, `fontstyle`), colors (`textcolor`, `bgcolor`), borders (`border`, `borderwidth`, `bordercolor`), `lineheight`, `padding`, and `rotate`. [Full reference ->](https://www.zeyos.com/ixml/ref/pdf-style) Font style characters can be combined: `B` (bold), `I` (italic), `U` (underline), `O` (overline), `D` (line-through). For example, `fontstyle="BIU"` produces bold italic underlined text. Border sides are specified as a string of characters: `"TBLR"` draws all four borders, `"TB"` draws top and bottom only. ## Text Content ### pdf:block Prints a block of text. Each block starts on a new line and occupies the full available width. Supports `html` mode for basic HTML tags (``, ``, ``, `
`), absolute positioning via `x`/`y`, and explicit `width`/`height`. [Full reference ->](https://www.zeyos.com/ixml/ref/pdf-block) :::note When using `html="t"`, HTML tags must be XML-escaped (e.g., `<b>` for ``) because the content is parsed as XML first. ::: ### pdf:inline Prints text as inline flow content. Unlike `pdf:block`, inline text does not start a new line -- multiple inline elements flow on the same line. [Full reference ->](https://www.zeyos.com/ixml/ref/pdf-inline) ## Table Layout ### pdf:row Defines a row of columns for tabular content. Row height adjusts to the tallest column. Contains one or more `pdf:col` children. [Full reference ->](https://www.zeyos.com/ixml/ref/pdf-row) ### pdf:col Defines a column within a `pdf:row`. Each column has its own `width`, and supports `html`, `nowrap`, and `height`. [Full reference ->](https://www.zeyos.com/ixml/ref/pdf-col) :::tip Always set explicit `width` on every `pdf:col`. Without widths, columns expand to the right margin, making multi-column layouts unpredictable. ::: ## Media Content ### pdf:image Inserts an image from a file path or URL. Supports `width`, `height`, `dpi`, and absolute positioning via `x`/`y`. When only one dimension is specified, the image scales proportionally. [Full reference ->](https://www.zeyos.com/ixml/ref/pdf-image) ### pdf:barcode Prints a 1D or 2D barcode. Supports QR codes (`QRCODE`, `QRCODE,M`, etc.), Code 128 (`C128`), EAN (`EAN8`, `EAN13`), DataMatrix, PDF417, and many more. The element content is the data to encode. [Full reference ->](https://www.zeyos.com/ixml/ref/pdf-barcode) ## Page Flow Control ### pdf:linebreak Adds vertical spacing by the given `offset` (in document units). [Full reference ->](https://www.zeyos.com/ixml/ref/pdf-linebreak) ### pdf:pagebreak Forces a new page. Optionally shifts the ordinate to a given `offset` on the new page. [Full reference ->](https://www.zeyos.com/ixml/ref/pdf-pagebreak) :::warning `pdf:pagebreak` behaves like `pdf:linebreak` when used inside `pdf:header` or `pdf:footer`. It only creates a new page when used inside `pdf:body`. ::: ### pdf:include Imports and inserts pages from an external PDF document. Use `from` and `to` to select a page range. Useful for appending terms and conditions or pre-designed content. [Full reference ->](https://www.zeyos.com/ixml/ref/pdf-include) :::note `pdf:include` inserts pages as full content pages, while `pdf:templates` overlays a template as a background behind your generated content. `pdf:include` has no effect inside `pdf:header` or `pdf:footer`. ::: ## Position Control ### pdf:getpos Reads the current drawing position. Stores the page number (`var_page`, `var_subpage`) and coordinates (`var_x`, `var_y`) into variables. Essential for dynamic layout control and page numbering in headers/footers. [Full reference ->](https://www.zeyos.com/ixml/ref/pdf-getpos) ### pdf:setpos Sets the current drawing position explicitly via `page`, `x`, and `y`. Enables absolute positioning and overlay effects. [Full reference ->](https://www.zeyos.com/ixml/ref/pdf-setpos) --- ## Practical Patterns ### Minimal Document ```xml Hello, PDF! $pdf PDF size: $len bytes ``` ### Serving a PDF as HTTP Response ```xml Invoice content here
Content-Type: application/pdf
Content-Disposition: attachment; filename="invoice-1234.pdf"
$pdf ``` :::tip Use `Content-Disposition: inline` to display the PDF in the browser instead of triggering a download. ::: ### Invoice with Line Items This example demonstrates a realistic invoice layout combining most PDF commands: ```xml Page $p INVOICE Customer: Ada Lovelace Date: 2024-01-15 Description Qty Unit Price Total Enterprise License 1 1,200.00 1,200.00 Support Package 1 300.00 300.00 Training Sessions 3 150.00 450.00 Total 1,950.00 PAY-INV-2024-001 ``` ### Dynamic Table with Page Break Control This pattern shows how to handle variable-length data with automatic page breaks: ```xml Item Qty Price $item.name $item.qty $item.price ``` ### Alternating Row Colors ```xml 0 #F8F8F8 $item.name $item.value $(($row_index + 1)) ``` --- Canonical: https://www.zeyos.com/ixml/ixml-guide/syntax-essentials # Syntax Essentials The Standard namespace (`std`) contains the operations you will use in virtually every iXML service. These commands handle assigning, inspecting, converting, and formatting data, as well as controlling execution flow. ## In this chapter - **[Variables](https://www.zeyos.com/ixml/ixml-guide/syntax-essentials/variables)** — `set`, `unset`, `null`/`true`/`false`, `assign`, `clone`, `swap`, `cast`, `eval`, `verbatim`, `global` - **[Type Introspection](https://www.zeyos.com/ixml/ixml-guide/syntax-essentials/type-introspection)** — `typeof`, `instanceof`, `classinfo`, `equals` - **[String Operations](https://www.zeyos.com/ixml/ixml-guide/syntax-essentials/strings)** — `length`/`size`, `char`/`ord`, `concat`, `tolower`/`toupper`, `trim`, `pos`, `substr`, `pad`, `match`, `replace`, `split`, `convert` - **[Output & Serialization](https://www.zeyos.com/ixml/ixml-guide/syntax-essentials/output)** — `output`, `header`, `sleep`, `serialize`/`unserialize` - **[Conditionals](https://www.zeyos.com/ixml/ixml-guide/syntax-essentials/conditionals)** — `if`, `is`, `switch` - **[Loops](https://www.zeyos.com/ixml/ixml-guide/syntax-essentials/loops)** — `for`, `foreach`, `while`, `repeat`/`until` - **[Branching](https://www.zeyos.com/ixml/ixml-guide/syntax-essentials/branching)** — `break`, `continue`, `return`, `exit` :::note Documentation for `include` and `param` has been moved to **[Reusability and OOP](https://www.zeyos.com/ixml/ixml-guide/reusability-and-oop/functions)** to better align with function and class concepts. ::: --- ## Common pitfalls - **`42` creates a string**, not an integer. Use `` for numeric behavior. - **`` shares references** for arrays/functions. Use `` for independent copies. - **`` counts characters**, `` counts bytes. Choose based on whether you're working with text or binary data. - **XML escaping in attributes**: `<` must be `<` and `>` must be `>` in attribute values. This commonly catches comparison operators: `func="<"`. --- Canonical: https://www.zeyos.com/ixml/ixml-guide/syntax-essentials/branching # Branching and Flow Control ## `break` — Break loop [Full reference →](https://www.zeyos.com/ixml/ref/break) Exits the current loop immediately. ### Syntax ```xml ``` --- ## `continue` — Continue loop Full reference → Skips the rest of the current iteration and continues with the next one. ### Syntax ```xml ``` --- ## `return` — Return from function [Full reference →](https://www.zeyos.com/ixml/ref/return) Exits the current function context. ### Syntax ```xml value ``` --- ## `exit` — Terminate execution [Full reference →](https://www.zeyos.com/ixml/ref/exit) Immediately halts the entire iXML execution script. ### Syntax ```xml ``` --- Canonical: https://www.zeyos.com/ixml/ixml-guide/syntax-essentials/conditionals # Conditionals ## `if` — Conditional value execution [Full reference →](https://www.zeyos.com/ixml/ref/if) The `IF` command is the primary conditional construct in iXML. It evaluates a comparison and executes its content only if the result is true. ### Syntax ```xml ``` ### Attributes | Name | Type | Description | |---|---|---| | `value1` | string | Left operand. | | `value2` | string | Right operand. | | `func` | function | Comparison operator (default `=`). | ### Comparison Operators | `func` | Description | |---|---| | `=` | Equals. | | `!=` | Not equals. | | `>` | Greater than. | | `<` | Less than. | | `>=` | Greater or equal. | | `<=` | Less or equal. | | `~` | Regex match. | | `!~` | Regex non-match. | --- ## `is` — Conditional type check [Full reference →](https://www.zeyos.com/ixml/ref/is) The `IS` command checks the validity, type, or state of a single variable. ### Syntax ```xml ``` ### Common Types - `valid` (defined and not null/false) - `empty` (null, false, empty string, empty array) - `array`, `string`, `int`, `float`, `bool` - `numeric` --- ## `switch` — Multi-way branching [Full reference →](https://www.zeyos.com/ixml/ref/switch) | `case` reference → | `default` reference → The `SWITCH` command simplifies chains of if-elseif statements comparing a single value against multiple options. ### Syntax ```xml Active! Pending... Unknown status ``` --- Canonical: https://www.zeyos.com/ixml/ixml-guide/syntax-essentials/loops # Loops ## `foreach` — Iterate array [Full reference →](https://www.zeyos.com/ixml/ref/foreach) The `FOREACH` loop iterates over an array, assigning the current key and value to variables for each iteration. ### Syntax ```xml Key: $key, Value: $val ``` --- ## `for` — Counting loop [Full reference →](https://www.zeyos.com/ixml/ref/for) The `FOR` loop iterates through a range of integers or steps. ### Syntax ```xml Count: $i ``` --- ## `while` — Conditional loop [Full reference →](https://www.zeyos.com/ixml/ref/while) The `WHILE` loop repeats as long as a condition is true. ### Syntax ```xml ``` --- ## `repeat` / `until` — Post-test loop Full reference → | Full reference → The `REPEAT/UNTIL` loop always executes at least once and repeats until a condition becomes true. ### Syntax ```xml ``` --- Canonical: https://www.zeyos.com/ixml/ixml-guide/syntax-essentials/output # Output & Serialization ## `output` — Send to response The primary mechanism for producing HTTP response body content: ```xml Hello World! ``` [Full reference →](https://www.zeyos.com/ixml/ref/output) --- ## `header` — Set HTTP header Sets HTTP response headers and optionally the status code: ```xml
Content-Type: application/json
``` Specifying a header without a value removes it from the response. [Full reference →](https://www.zeyos.com/ixml/ref/header) --- ## `sleep` — Pause execution Pauses for a specified duration in seconds (supports fractional seconds): ```xml 0.5 ``` [Full reference →](https://www.zeyos.com/ixml/ref/sleep) --- ## `serialize` / `unserialize` Converts variables to/from a storable string representation (PHP-compatible format). Preserves types and structure, unlike JSON which loses type information: ```xml Bill Gates Steve Jobs $data $restored.bg ``` [`serialize`](https://www.zeyos.com/ixml/ref/serialize) | [`unserialize`](https://www.zeyos.com/ixml/ref/unserialize) --- Canonical: https://www.zeyos.com/ixml/ixml-guide/syntax-essentials/strings # String Operations ## `length` / `size` `length` returns character count (Unicode code points). `size` returns byte count. For ASCII text these are the same; for multibyte UTF-8 they differ. ```xml iXML $len ``` [`length`](https://www.zeyos.com/ixml/ref/length) | [`size`](https://www.zeyos.com/ixml/ref/size) --- ## `char` / `ord` Convert between characters and UTF-8 code points: ```xml 65 A ``` [`char`](https://www.zeyos.com/ixml/ref/char) | [`ord`](https://www.zeyos.com/ixml/ref/ord) --- ## `concat` — Append to string Appends a value to an existing variable. More efficient than `$x$suffix` in loops: ```xml i XML $name ``` [Full reference →](https://www.zeyos.com/ixml/ref/concat) --- ## `tolower` / `toupper` Case conversion. `toupper` supports modes: `chars` (all), `words` (first letter of each word), `first` (first character only): ```xml iXML bill gates ``` [`tolower`](https://www.zeyos.com/ixml/ref/tolower) | [`toupper`](https://www.zeyos.com/ixml/ref/toupper) --- ## `trim` — Strip whitespace Strips whitespace from both ends (default), left only, or right only: ```xml iXML ``` [Full reference →](https://www.zeyos.com/ixml/ref/trim) --- ## `pos` — Find position Finds the position of a search value within a string: ```xml iXML ``` [Full reference →](https://www.zeyos.com/ixml/ref/pos) --- ## `substr` — Extract substring Returns a portion of a string by offset and optional length: ```xml Name: iXML ``` [Full reference →](https://www.zeyos.com/ixml/ref/substr) --- ## `pad` — Pad string Pads to a given length. Positive length pads right; negative pads left: ```xml 123 ``` [Full reference →](https://www.zeyos.com/ixml/ref/pad) --- ## `match` — Regex match Performs a global regex match, returning the count and match data: ```xml My name is iXML! Found $count match: $m[0][0] ``` [Full reference →](https://www.zeyos.com/ixml/ref/match) --- ## `replace` — Search and replace Supports both literal and regex replacement. Regex mode supports backreferences (`\0` full match, `\1` first capture group): ```xml Bill Gates Bill Gates ``` [Full reference →](https://www.zeyos.com/ixml/ref/replace) --- ## `split` — Split to array Splits a string into an array by delimiter or regex pattern: ```xml Bill Gates, Steve Jobs $names[0] ``` [Full reference →](https://www.zeyos.com/ixml/ref/split) --- ## `convert` — Character encoding Converts between character encodings (e.g., UTF-8 to ISO-8859-1). [Full reference →](https://www.zeyos.com/ixml/ref/convert) --- Canonical: https://www.zeyos.com/ixml/ixml-guide/syntax-essentials/type-introspection # Type Introspection ## `typeof` — Get variable type Returns the intrinsic data type as a string (`'undefined'`, `'null'`, `'bool'`, `'int'`, `'float'`, `'string'`, `'array'`, `'function'`, `'macro'`, `'class'`): ```xml iXML $type ``` [Full reference →](https://www.zeyos.com/ixml/ref/typeof) --- ## `instanceof` — Check class membership Checks whether a variable is an instance of a given class or its subclasses: ```xml $isAnimal ``` [Full reference →](https://www.zeyos.com/ixml/ref/instanceof) --- ## `classinfo` — Reflect on class structure Returns an array with class metadata (prototype, parents, constructors, inheritance chain). [Full reference →](https://www.zeyos.com/ixml/ref/classinfo) --- ## `equals` — Strict equality Checks strict equality **without type coercion** — the string `"123"` and integer `123` are not equal: ```xml 123 123 ``` [Full reference →](https://www.zeyos.com/ixml/ref/equals) --- Canonical: https://www.zeyos.com/ixml/ixml-guide/syntax-essentials/variables # Variables ## `set` — Set variable The most fundamental command in iXML. Assigns a string value to a variable, creating it if it doesn't exist. The value is **always of type `string`** unless replaced by an embedded statement via result propagation. ```xml Bill Gates $firstname $lastname ``` Setting array items — these three forms are equivalent: ```xml Bill Gates Steve Jobs Larry Ellison ``` Auto-increment key with empty brackets: ```xml first second ``` [Full reference →](https://www.zeyos.com/ixml/ref/set) --- ## `unset` — Remove variable Removes a variable entirely, making it `undefined`. Different from setting to `NULL` or empty string. ```xml hello ``` [Full reference →](https://www.zeyos.com/ixml/ref/unset) --- ## `null` / `true` / `false` — Set typed values Set a variable to a specific typed value directly: ```xml ``` [`null`](https://www.zeyos.com/ixml/ref/null) | [`true`](https://www.zeyos.com/ixml/ref/true) | [`false`](https://www.zeyos.com/ixml/ref/false) --- ## `assign` — Type-preserving assignment Copies a value from one variable to another, preserving the original data type. Complex types (arrays, functions) are assigned **by reference**. ```xml iXML $name2 ``` [Full reference →](https://www.zeyos.com/ixml/ref/assign) --- ## `clone` — Independent copy Creates a **shallow copy** independent of the original. Essential for arrays where `assign` would create a shared reference. ```xml Bill Gates Steve Jobs Larry Ellison $original[1] and $copy[1] ``` [Full reference →](https://www.zeyos.com/ixml/ref/clone) --- ## `swap` — Exchange two variables Exchanges values in a single atomic step: ```xml first second $a, $b ``` [Full reference →](https://www.zeyos.com/ixml/ref/swap) --- ## `cast` — Type conversion Converts a variable to a specified data type. Essential because `` always creates strings. ```xml 123 $number is now an integer! ``` Supported types: `bool`, `int`, `float`, `string`. See [Type Conversion](https://www.zeyos.com/ixml/ixml-guide/data-types/type-conversion) for conversion rules. [Full reference →](https://www.zeyos.com/ixml/ref/cast) --- ## `eval` — Evaluate expression Evaluates a string with variable substitution, resolving references dynamically: ```xml $ iXML My name is $dollar$name! ``` [Full reference →](https://www.zeyos.com/ixml/ref/eval) --- ## `verbatim` — Prevent substitution Returns content **without** variable substitution — the opposite of `eval`: ```xml Show me the $dollars! ``` [Full reference →](https://www.zeyos.com/ixml/ref/verbatim) --- ## `global` — Declare global variable Declares a variable with global scope, visible across all contexts including functions and includes: ```xml MyApp $appName ``` Use sparingly — prefer passing data as parameters. [Full reference →](https://www.zeyos.com/ixml/ref/global) --- Canonical: https://www.zeyos.com/ixml/ixml-guide/user-interface # User Interface & Forms ZeyOS applications can extend the platform's user interface in two ways: 1. **Form-based weblets** — Use ZeyOS's built-in `form` namespace to build structured data-entry forms declaratively. The form engine handles layout, input validation, and data persistence automatically. No HTML, CSS, or JavaScript required. 2. **Custom weblets** — Build fully custom UI components using HTML, CSS, and JavaScript. Custom weblets give you complete control over the interface and can interact with ZeyOS services via AJAX calls. Both approaches run as **weblets** — embedded UI panels that appear within entity detail views (contacts, tickets, accounts, contracts, etc.). Weblets are defined in the `weblets/` directory of a ZeyOS application and are linked to specific entity types through the app manifest (`zeyos.app.json`). A third special case is **exports**, which generate downloadable files (CSV, Excel, PDF) from the current view's selected records. ## In this chapter - **[Weblets](https://www.zeyos.com/ixml/ixml-guide/user-interface/weblets)** — How weblets work, their lifecycle, and building custom HTML/JS interfaces - **[Forms](https://www.zeyos.com/ixml/ixml-guide/user-interface/forms)** — The declarative form namespace for building native ZeyOS data-entry forms - **[Exports](https://www.zeyos.com/ixml/ixml-guide/user-interface/exports)** — Generating exports from the current view's filtered and selected records --- Canonical: https://www.zeyos.com/ixml/ixml-guide/user-interface/exports # Exports Exports are a special type of weblet that generate downloadable data (CSV, Excel, PDF, etc.) from entity list views. What makes exports unique is the **tempfile selector** — a mechanism that passes the user's current view state (filters, search terms, and manual selections) into your iXML script, so the export contains exactly the records the user expects. ## How the tempfile selector works When a user triggers an export from an entity list view, ZeyOS: 1. Collects all record IDs matching the current view — respecting active filters, search queries, and any manually selected rows 2. Writes those IDs into a temporary file 3. Passes the temp file's ID to your export script via `$REQUEST.tempfile` Your script reads this selector and uses it to query exactly the right records: ```xml ``` The `var_struct="selector"` attribute parses the temp file content into a structure that can be used directly with [`db:in`](https://www.zeyos.com/ixml/ref/db-in) to filter a database query. ## Basic export pattern The typical export follows three steps: read the selector, query the data, and format the output. ```xml a.lastname a.firstname t.date t.transactionnum t.status t.netamount transactions a.lastname text/csv export.csv Last Name,First Name,Date,Transaction No.,Status,Net Amount $row.lastname,$row.firstname,$row.date,$row.transactionnum,$row.status,$row.netamount ``` [`temp:read`](https://www.zeyos.com/ixml/ref/temp-read) | [`db:in`](https://www.zeyos.com/ixml/ref/db-in) | [`db:select`](https://www.zeyos.com/ixml/ref/db-select) ## The selector and `db:in` The key integration point is `` — it takes the selector variable and generates a SQL `IN (...)` clause that filters the query to only the selected IDs. This works with any entity field, not just `ID`: ```xml ``` You can combine `db:in` with additional `db:is` conditions to further narrow results: ```xml t.transactionnum t.netamount transactions 2 ``` ## Excel export For structured exports, use the [Excel namespace](https://www.zeyos.com/ixml/ixml-guide/standard-library/excel-zip) to generate `.xlsx` files: ```xml lastname firstname email phone contacts lastname Last Name First Name Email Phone $c.lastname $c.firstname $c.email $c.phone application/vnd.openxmlformats-officedocument.spreadsheetml.sheet contacts_export.xlsx $wb ``` ## Tips - The selector respects whatever the user sees in their view — if they've filtered by status, date range, or search term, only matching IDs are included. - If the user manually selected specific rows (via checkboxes), only those IDs are passed. - Always use `var_struct="selector"` with `temp:read` so the IDs are in the correct format for `db:in`. - Set `return.contenttype` and `return.filename` to control the download behavior in the browser. --- Canonical: https://www.zeyos.com/ixml/ixml-guide/user-interface/forms # Forms The `form` namespace provides a declarative UI toolkit for building structured data-entry forms inside ZeyOS **weblets**. Layout containers and input controls render natively in the ZeyOS interface — no HTML or CSS required. The form namespace only works in weblets; using it in services, resources, or standalone `zeysdk run` produces no visible output. When a form is submitted, each control's `id` attribute becomes a key in the entity's extended data (`EXTDATA`). Use `$EXTDATA.fieldId` to read previously submitted values on subsequent loads. ## In this chapter - `hbox` and `vbox` — horizontal and vertical layout containers - `placeholder` — empty spacer element - `title`, `description`, `link`, `image`, `rule` — display elements - `textbox` — text input with 17 type variants - `checkbox` — two-state toggle - `listbox` and `listitem` — single and multi-select lists - `entitybox` — ZeyOS entity record selector --- ## Layout containers ### `hbox` — Horizontal box Arranges child form elements side by side in a row. The most common use is placing two or more fields on one line, or creating multi-column layouts with nested `vbox` containers. Accepts optional `align`, `width`, `height`, and `padding` attributes. Full reference → ```xml ``` Multi-column layout with `vbox` nesting: ```xml ``` --- ### `vbox` — Vertical box Stacks child form elements vertically. Use it inside an `hbox` to create columns, or standalone to control width and padding. Accepts optional `align`, `width`, `height`, and `padding` attributes. Full reference → ```xml Locations $location Departments $dept ``` --- ### `placeholder` — Empty placeholder Reserves empty space in a layout. Use it to balance columns when one side of an `hbox` has fewer controls, or to insert spacing between sections. Accepts optional `width` and `height` attributes. Full reference → ```xml ``` --- ## Display elements ### `title` — Section title Renders an emphasized heading that visually separates form sections. Full reference → ```xml Contact Information Address ``` --- ### `description` — Labeled description Displays read-only text attached to a label. Use it to show computed values, status information, or messages that the user cannot edit. Accepts an optional `label` attribute. Full reference → ```xml $EXTDATA.created $EXTDATA.creator_name $EXTDATA.status ``` Summary row with computed values: ```xml $formatNum($totalNet) $formatNum($totalGross) $formatNum($balance) ``` --- ### `link` — Hyperlink Renders a clickable hyperlink with a label. The content is the display text, and the `url` attribute sets the target. Full reference → ```xml Open Portal View Docs ``` --- ### `image` — Image Renders an image with optional `label`, `caption`, and clickable `url`. The content provides the image source path or URL. Full reference → --- ### `rule` — Horizontal rule Renders a horizontal divider line between form sections. Accepts an optional `width` attribute. Full reference → ```xml General Details ``` --- ## Input controls ### `textbox` — Text input The most versatile form control. Renders an editable input whose behavior changes based on the `type` attribute — from single-line text to a rich HTML editor, from a date picker to a currency input. Key attributes: `id`, `label`, `type`, `width`, `placeholder`, `required`, `readonly`, `disabled`, `caption`, `pattern`. Full reference → #### Input types | Type | Description | Extra attributes | |---|---|---| | `single` | Single-line text input | `caption`, `pattern` | | `multi` | Multi-line text area | `height` | | `html` | Rich HTML editor | `height` | | `email` | Email address input with validation | `caption`, `pattern` | | `url` | URL input with validation | `caption`, `pattern` | | `tel` | Telephone number input | `caption`, `pattern` | | `password` | Password input (masked characters) | `caption`, `pattern` | | `num` | Numeric input | `caption` | | `money` | Monetary value input | `caption` | | `price` | Price input (general) | `caption` | | `pricebilling` | Price input (billing context) | `caption` | | `priceprocurement` | Price input (procurement context) | `caption` | | `priceproduction` | Price input (production context) | `caption` | | `country` | Country code selector (ISO 3166-1 alpha-2) | `caption` | | `currency` | Currency code selector (ISO 4217) | `caption` | | `unit` | Unit code selector (UN/CEFACT Recommendation 20) | `caption` | | `date` | Date picker | `caption` | | `datetime` | Date and time picker | `caption` | #### Example — Contact form with mixed types ```xml Contact Details ``` #### Example — Date range filter ```xml ``` #### Example — Numeric inputs with loop ```xml ``` #### Example — Readonly computed field ```xml $($subtotal * (1 + $taxrate / 100)) ``` --- ### `checkbox` — Check box Renders a two-state toggle. The content defines the value submitted when checked. When unchecked, the field is absent from `EXTDATA` (test with ``). Key attributes: `id`, `label`, `caption`, `selected`, `disabled`. Full reference → #### Example — Simple option ```xml 1 ``` #### Example — Dynamic checkbox group A common pattern is generating checkboxes from data, using unique IDs: ```xml Services $service $service ``` #### Example — Approval workflow ```xml $session.name||$DATENOW ``` When checked, the value stored is a composite string like `"John Doe||2026-02-08"`, recording both who approved and when. --- ### `listbox` — Selection list Renders a dropdown (`type="single"`) or a scrollable checkbox list (`type="multi"`). Items are defined with `` children. For multi-select, selected values are joined with the `delimiter` (default `", "`) into a single string — use `` or `` to parse them back. Key attributes: `id`, `label`, `type`, `width`, `required`, `disabled`, `caption` (placeholder for single), `height` (for multi), `delimiter` (for multi). Full reference → ### `listitem` — List item Defines a single option within a ``. The content is the submitted value; the `caption` attribute is the display text (defaults to the value). Use `selected="true"` to pre-select. Full reference → #### Example — Static dropdown ```xml 1 2 3 4 ``` #### Example — Dynamic dropdown from database ```xml name firstname lastname users $user.name ``` #### Example — Multi-select with height ```xml $cat ``` #### Example — Percentage selector with loop ```xml $($pct / 100) ``` --- ### `entitybox` — Entity selector A specialized selector for choosing a ZeyOS entity record (contact, account, ticket, etc.) by ID, with auto-complete search. Use the `tag` attribute to filter results. Key attributes: `id`, `entity`, `tag`, `label`, `caption`, `placeholder`, `width`, `required`, `disabled`. Full reference → #### Example — Contact and account selectors ```xml ``` #### Example — Filtered entity selection ```xml ``` Only contacts tagged "Vendor" appear in the search results. #### Example — Dynamic entity type ```xml projects tickets transactions ``` --- ## Common layout patterns ### Two-column form ```xml ``` ### Sectioned form with dividers ```xml General Information Active Inactive Financial Details Notes ``` ### Conditional form sections ```xml $EXTDATA.approved_by $EXTDATA.approved_date 1 ``` ### Data-driven form generation Generate form controls dynamically from configuration or database queries: ```xml $opt 1 ``` --- ## Complete weblet example A realistic weblet form for contract management, combining all form elements: ```xml Contract Details service maintenance consulting $EXTDATA.contract_num Financial Monthly Quarterly Annually Services $svc Notes 1 1 $EXTDATA.approved_by $EXTDATA.approved_date Pending approval ``` --- Canonical: https://www.zeyos.com/ixml/ixml-guide/user-interface/weblets # Weblets A **weblet** is a UI panel embedded within a ZeyOS entity detail view. When a user opens a contact, ticket, account, or any other entity record, weblets appear as tabs or panels alongside the standard entity fields. They are the primary extension point for adding custom interfaces to ZeyOS. ## How weblets work Weblets are iXML files stored in the `weblets/` directory of a ZeyOS application. Each weblet is registered in `zeyos.app.json` and linked to one or more entity types. When a user navigates to a matching entity record, ZeyOS executes the weblet's iXML and renders the result inside the entity detail view. ### Context variables The available variables depend on how the weblet runs. A form weblet is registered on a view ending in `.form` and runs while ZeyOS builds the page. Every other weblet is requested by the browser, usually in a frame. | Variable | Form weblet | Framed or page weblet | |----------|-------------|-----------------------| | `$ID` | The ID of the current entity record | Not set. Read the record from `$REQUEST.id` and [check access](#check-record-access) | | `$ENTITY` | The entity type (e.g., `contacts`, `tickets`) | Not set | | `$EXTDATA` | Previously saved form data for this record | Not set | | `$REQUEST` | Not set | HTTP request parameters | | `$SELFURL`, `$VIEW` | Not set | This weblet's URL and its registered view | | `$APPSETTINGS` | Application settings for the current instance | Application settings for the current instance | New-record dialogs differ from record forms. There is no `FORM`, and normally no record variables, because the record does not exist yet. See [Execution Contexts](https://www.zeyos.com/ixml/ixml-guide/development-environment/execution-contexts#weblets) for the full list, the weblet types, and how to test each path. These details are confirmed from the platform source. They have not been run for this page. ### Two approaches **Form-based weblets** use the [`form` namespace](https://www.zeyos.com/ixml/ixml-guide/user-interface/forms) to build native ZeyOS input controls declaratively. The form engine handles rendering, layout, and data persistence through `EXTDATA`. This is the fastest way to build data-entry interfaces. **Custom weblets** output raw HTML, CSS, and JavaScript using iXML's `` command. This gives you full control over the UI, including custom visualizations, interactive dashboards, or integration with external JavaScript libraries. ## Custom HTML weblets A custom weblet outputs HTML directly. You can use any standard web technology — HTML, CSS, JavaScript — and load data from the weblet's own URL. ### Basic structure ```xml $REQUEST.id ownergroup $account.ownergroup
Content-Type: text/plain
Access denied
.dashboard { padding: 16px; font-family: sans-serif; } .metric { display: inline-block; margin: 8px; padding: 16px; background: #f5f5f5; border-radius: 4px; } .metric-value { font-size: 24px; font-weight: bold; } .metric-label { font-size: 12px; color: #666; } ]]> COUNT(*) SUM("t"."netamount") transactions $accountId
]]>$stats.total
Transactions
]]>$stats.revenue
Total Revenue
]]> ``` ### Check record access A weblet's URL is reachable by any signed-in user, regardless of its `view`, unless the application is restricted and the user lacks permission for it. The `view` only decides where the weblet appears. `$REQUEST.id` is a value the user can change, so a weblet that reads a record from it must check access itself. The example above allows a record only when it is owned by one of the user's groups. `db:get` and `db:select` do not apply this check for you. `session.groups` is keyed by group ID. ZeyOS has no iXML command that applies the platform's full record rule, which also considers assigned users, creators, public records, and module permissions. This example is deliberately stricter: it denies public records and records assigned to the user. Widen it only for cases you have reviewed, and keep the default as denial. The check's logic was run in a local copy of the iXML engine with stand-in values, not on an instance. ### Calling services from JavaScript A browser call to the application's `REMOTECALL` service is not authenticated by the session. The browser may send its cookie, but the service's auth step ignores it, so the service sees an anonymous caller and, depending on its checks, runs without a user or rejects the call. Connecting the two needs a [reviewed authentication design](https://www.zeyos.com/ixml/ixml-guide/development-environment/execution-contexts#calling-your-own-service-from-a-weblet). For most read-only panels, the weblet can answer its own requests instead. Those requests go through the weblet endpoint, which authenticates the session. `$SELFURL` has no query string, so pass the record `id` explicitly: ```xml ok
Content-Type: application/json

]]>
``` This example has had static review only. :::warning This pattern is for reading only. A cross-site page can trigger a request to a weblet URL, and the browser may attach the session cookie. The platform sets no `SameSite` attribute on that cookie, so the outcome depends on browser defaults. Weblet responses also carry no anti-framing header, so another site can frame them by default (both **source-confirmed**). - `$REQUEST` dispatch and `GET` handlers must never change data. - A write needs `POST` plus a verified anti-forgery control. The platform provides none for weblets, so that control is your own security design. It needs the same review as [a token bridge to a service](https://www.zeyos.com/ixml/ixml-guide/development-environment/execution-contexts#calling-your-own-service-from-a-weblet). ::: :::tip For complex custom UIs, keep the iXML weblet thin and put business logic in `resources/`, where the weblet and any service can share it. Never write settings values such as API keys into the page. ::: ## When to use which approach | Scenario | Approach | |----------|----------| | Data-entry form with standard fields | [Form namespace](https://www.zeyos.com/ixml/ixml-guide/user-interface/forms) | | Custom dashboard or visualization | Custom HTML weblet | | Interactive read-only UI with updates | Custom HTML that calls its own weblet URL | | Simple read-only data display | Either — forms with `readonly` fields or custom HTML | | Generating downloadable files | [Exports](https://www.zeyos.com/ixml/ixml-guide/user-interface/exports) | ## Related topics - [Execution Contexts](https://www.zeyos.com/ixml/ixml-guide/development-environment/execution-contexts) - [Minimal App](https://www.zeyos.com/ixml/ixml-guide/development-environment/minimal-app) - [Troubleshooting](https://www.zeyos.com/ixml/ixml-guide/development-environment/troubleshooting) --- Canonical: https://www.zeyos.com/sdk # ZeyOS SDK Documentation Welcome to the ZeyOS SDK documentation. ## Pages - [Application Activity Management](https://www.zeyos.com/sdk/activity-management) - [Activate Application](https://www.zeyos.com/sdk/activity-management/zeysdk-activate) - [Deactivate Application](https://www.zeyos.com/sdk/activity-management/zeysdk-deactivate) - [Pre-delete Application](https://www.zeyos.com/sdk/activity-management/zeysdk-predelete) - [Application Management](https://www.zeyos.com/sdk/application-management) - [Clone Application](https://www.zeyos.com/sdk/application-management/zeysdk-clone) - [Configure Application](https://www.zeyos.com/sdk/application-management/zeysdk-configure) - [Create Application](https://www.zeyos.com/sdk/application-management/zeysdk-create) - [Delete Application](https://www.zeyos.com/sdk/application-management/zeysdk-delete) - [Host Commands](https://www.zeyos.com/sdk/application-management/zeysdk-host) - [Migrate Application](https://www.zeyos.com/sdk/application-management/zeysdk-migrate) - [Rollout Application](https://www.zeyos.com/sdk/application-management/zeysdk-rollout) - [Asset Management](https://www.zeyos.com/sdk/asset-management) - [Add Asset](https://www.zeyos.com/sdk/asset-management/zeysdk-add) - [Remove Asset](https://www.zeyos.com/sdk/asset-management/zeysdk-remove) - [Rename Asset](https://www.zeyos.com/sdk/asset-management/zeysdk-rename) - [Update Asset](https://www.zeyos.com/sdk/asset-management/zeysdk-update) - [Authentication](https://www.zeyos.com/sdk/authentication) - [Bundle Management](https://www.zeyos.com/sdk/bundle-management) - [Create Bundle](https://www.zeyos.com/sdk/bundle-management/zeysdk-bundle-create) - [Install Bundle](https://www.zeyos.com/sdk/bundle-management/zeysdk-bundle-install) - [Configuration Files](https://www.zeyos.com/sdk/configuration-files) - [Debugging and Compilation](https://www.zeyos.com/sdk/debugging) - [ZeyOS Entities](https://www.zeyos.com/sdk/entities) - [Platform Management](https://www.zeyos.com/sdk/platform-management) - [Link Instance](https://www.zeyos.com/sdk/platform-management/zeysdk-link) - [Unlink Instance](https://www.zeyos.com/sdk/platform-management/zeysdk-unlink) - [Use Instance](https://www.zeyos.com/sdk/platform-management/zeysdk-use) - [Show Current Instance](https://www.zeyos.com/sdk/platform-management/zeysdk-whoami) - [Snapshot Management](https://www.zeyos.com/sdk/snapshot-management) - [Create Snapshot](https://www.zeyos.com/sdk/snapshot-management/zeysdk-snapshot-create) - [Delete Snapshot](https://www.zeyos.com/sdk/snapshot-management/zeysdk-snapshot-delete) - [List Snapshots](https://www.zeyos.com/sdk/snapshot-management/zeysdk-snapshot-list) - [Restore Snapshot](https://www.zeyos.com/sdk/snapshot-management/zeysdk-snapshot-restore) - [Synchronization](https://www.zeyos.com/sdk/synchronization) - [Compare Files (zeysdk compare)](https://www.zeyos.com/sdk/synchronization/zeysdk-compare) - [Pull Files](https://www.zeyos.com/sdk/synchronization/zeysdk-pull) - [Push Files](https://www.zeyos.com/sdk/synchronization/zeysdk-push) - [Recover Sync State](https://www.zeyos.com/sdk/synchronization/zeysdk-recover) - [Sync Files (zeysdk sync)](https://www.zeyos.com/sdk/synchronization/zeysdk-sync) - [Watch Files](https://www.zeyos.com/sdk/synchronization/zeysdk-watch) - [ZeyOS Views Reference](https://www.zeyos.com/sdk/views) --- Canonical: https://www.zeyos.com/sdk/activity-management # Application Activity Management The Activity Management commands allow you to control the operational state of your ZeyOS applications. Available Commands ------------------ | Command | Description | Usage | |---------|-------------|-------| | [`activate`](https://www.zeyos.com/sdk/activity-management/zeysdk-activate) | Enable application | `zeysdk activate` | | [`deactivate`](https://www.zeyos.com/sdk/activity-management/zeysdk-deactivate) | Disable application | `zeysdk deactivate` | | [`predelete`](https://www.zeyos.com/sdk/activity-management/zeysdk-predelete) | Mark application for deletion | `zeysdk predelete` | --- Canonical: https://www.zeyos.com/sdk/activity-management/zeysdk-activate # Activate Application The `activate` command enables your ZeyOS application in the current instance, making it accessible. ```bash zeysdk activate [options] ``` **Options** | Option | Description | |--------|-------------| | `--verbose` | Show detailed output | **Process** 1. **Validation** - Checks for sync.json - Validates configuration - Verifies authentication 2. **Activation** - Sets app activity to active - Updates platform state - Confirms activation **Error Handling** | Error | Description | Resolution | |-------|-------------|------------| | Missing sync.json | Configuration not found | Run `zeysdk link` | | Invalid config | Missing required fields | Check sync.json | | Auth failed | Invalid token | Re-link application | **Notes** - Activation is immediate - All users regain access - Previous settings restored - Use after maintenance or updates **Best Practices** - Confirm the active instance with `zeysdk whoami` before activation - Activate after validating recent changes in a staging instance - Notify users when re-enabling a previously unavailable app - Use `zeysdk compare` before activation if remote state may be stale --- Canonical: https://www.zeyos.com/sdk/activity-management/zeysdk-deactivate # Deactivate Application The `deactivate` command disables your ZeyOS application in the current instance while preserving it. ```bash zeysdk deactivate [options] ``` **Options** | Option | Description | |--------|-------------| | `--verbose` | Show detailed output | **Process** 1. **Validation** - Verifies app directory - Checks configuration - Validates token 2. **Deactivation** - Sets app activity to inactive - Updates platform state - Confirms deactivation **Error Handling** | Error | Description | Resolution | |-------|-------------|------------| | Not in app directory | Wrong location | Navigate to app root | | Config missing | No sync.json found | Run `zeysdk link` | | Network error | Platform unreachable | Check connectivity | **Notes** - App remains installed - Data is preserved - Users lose access - Can be reactivated - Ideal for maintenance **Best Practices** - Deactivate before planned maintenance that affects users - Confirm the active instance before deactivation - Communicate downtime before disabling production apps - Reactivate only after validating the app state --- Canonical: https://www.zeyos.com/sdk/activity-management/zeysdk-predelete # Pre-delete Application The `predelete` command marks your ZeyOS application for future deletion in the current instance, preparing it for removal. ```bash zeysdk predelete [options] ``` **Options** | Option | Description | |--------|-------------| | `--verbose` | Show detailed output | **Process** 1. **Validation** - Checks app context - Verifies configuration - Validates permissions 2. **Pre-deletion** - Sets app activity to pre-deleted - Updates platform state - Confirms status change 3. **Follow-up** - Leaves the application installed - Keeps application data available for recovery workflows - Allows reactivation when deletion is cancelled **Error Handling** | Error | Description | Resolution | |-------|-------------|------------| | Invalid context | Not in app directory | Navigate to app root | | Missing config | No sync.json | Run `zeysdk link` | | Permission denied | Insufficient rights | Check credentials | **Notes** - Reversible action - App remains installed - Users lose access - Data preserved until final deletion - Can be reactivated if needed **Best Practices** 1. Before Pre-deletion - Backup important data - Notify users - Document dependencies 2. After Pre-deletion - Verify status - Update documentation - Plan final removal --- Canonical: https://www.zeyos.com/sdk/application-management # Application Management The Application Management commands help you create, configure, and manage ZeyOS applications. Available Commands ------------------ | Command | Description | Usage | |---------|-------------|-------| | [`create`](https://www.zeyos.com/sdk/application-management/zeysdk-create) | Create new application | `zeysdk create --name=` | | [`configure`](https://www.zeyos.com/sdk/application-management/zeysdk-configure) | Configure CLI settings | `zeysdk configure` | | [`clone`](https://www.zeyos.com/sdk/application-management/zeysdk-clone) | Copy application from instance | `zeysdk clone [TARGET_DIRECTORY]` | | [`delete`](https://www.zeyos.com/sdk/application-management/zeysdk-delete) | Delete application | `zeysdk delete` | | [`rollout`](https://www.zeyos.com/sdk/application-management/zeysdk-rollout) | Roll out to instances | `zeysdk rollout` | | [`migrate`](https://www.zeyos.com/sdk/application-management/zeysdk-migrate) | Migrate application | `zeysdk migrate` | | [`host`](https://www.zeyos.com/sdk/application-management/zeysdk-host) | Manage instance hosts | `zeysdk host ` | Application Structure --------------------- ``` my-app/ ├── .zeysdk/ # SDK sync state and local backups │ └── sync.json # Empty until the app is linked ├── assets/ ├── resources/ ├── services/ ├── settings/ │ ├── defaultsettings.json │ ├── settingscode.ixml │ └── usersettingscode.ixml ├── weblets/ ├── README.md └── zeyos.app.json # App manifest ``` Linking fills the active `.zeysdk/sync.json`, keeps non-active links as `.zeysdk/sync..json`, and adds an instance-specific settings file under `settings`. --- Canonical: https://www.zeyos.com/sdk/application-management/zeysdk-clone # Clone Application The `clone` command copies an existing ZeyOS application from a remote instance. ```bash zeysdk clone [TARGET_DIRECTORY] [options] ``` **Parameters** | Parameter | Description | Required | Default | Example | |-----------|-------------|----------|---------|---------| | `INSTANCE_ID` | Source instance ID | Yes | - | `dev-instance` | | `APP_ID` | Application ID | Yes | - | `my-app` | | `TARGET_DIRECTORY` | Destination path | No | Same as APP_ID | `apps/my-app` | **Options** | Option | Description | |--------|-------------| | `--verbose` | Show detailed output | | `--token ` | SDK token for non-interactive linking | | `--url ` | Instance URL for non-interactive linking | **Process** 1. **Directory Setup** - If `TARGET_DIRECTORY` not provided, uses `APP_ID` as directory name - Requires the target's parent directory to exist - Refuses to overwrite any existing target path - Creates a uniquely named staging directory beside the final target 2. **App Creation** - Creates the local scaffold in the staging directory - Sets up initial scaffolding 3. **Instance Linking** - Links to specified instance - Validates credentials and connection - Requires `APP_ID` to already exist remotely; clone will not create it 4. **Data Sync** - Performs force pull to sync all app data - Retrieves complete application state - Renames the completed staging directory to `TARGET_DIRECTORY` - Removes staging data on any create, link, pull, or finalization failure **Error Handling** | Error | Description | Resolution | |-------|-------------|------------| | Directory exists | Target directory already exists | Choose different target directory | | Parent missing | Parent of the target path does not exist | Create the parent directory first | | Creation failed | App creation error | Check app identifier and permissions | | Link failed | Instance linking error | Verify instance ID and connectivity | | Remote app missing | `APP_ID` does not exist on the source instance | Verify the app ID and source instance | | Pull failed | Data sync error | Check network and try again | **Notes** - `TARGET_DIRECTORY` defaults to `APP_ID` - The target directory must not already exist - The target appears only after create, link, and pull have all succeeded - For a fully non-interactive clone, pass both `--token` and `--url` unless a matching stored host supplies the missing value - A failure after authentication removes the local staging directory, but a credential saved by the successful link step can remain in the global host store - The resulting project is linked to the source instance **Best Practices** - Clone into a clean workspace - Use a descriptive target directory when cloning multiple environments - Run `zeysdk compare` after cloning if you need to verify local and remote state - Avoid editing files during the initial clone and pull sequence --- Canonical: https://www.zeyos.com/sdk/application-management/zeysdk-configure # Configure Application The `configure` command configures global CLI settings and preferences interactively. ```bash zeysdk configure ``` **Available Settings** | Setting | Description | Default | |---------|-------------|---------| | `Default Diff Tool` | Choose comparison tool for file diffs | Platform dependent | | `List Height` | Number of items shown in selection lists | 10 | | `Run Output Style` | Default output format for `zeysdk run` (`Pretty` or `Simple`) | Pretty | | `Credential Storage` | Select credential backend (`Auto`, `OS Keyring`, `File Store`) | Auto | **Process** 1. **Settings Prompt** - Opens interactive prompts for global CLI preferences - Shows platform-specific diff tool choices - Validates numeric settings such as list height - Press Escape in a setting screen to return to the settings menu without saving that selection - Press Escape or choose Back in the settings menu to exit without saving - Returning from a setting keeps that row selected 2. **Credential Backend Selection** - Lets you choose automatic storage, OS keyring, or file store - Uses the selected backend for future saved credentials - Persists the preference for later commands 3. **Save Configuration** - Stores the updated settings - Applies preferences to subsequent CLI runs - Uses environment overrides when present **Diff Tools by Platform** 1. **Windows** - Visual Studio Code - Beyond Compare - Hunk - WinMerge - Araxis Merge - DiffMerge - TortoiseMerge - P4Merge - Other (prompts for a custom command when comparison starts) - None 2. **macOS** - Visual Studio Code - Beyond Compare - Hunk - FileMerge - Araxis Merge - DiffMerge - Kaleidoscope - P4Merge - Other (prompts for a custom command when comparison starts) - None 3. **Linux** - Visual Studio Code - Hunk - Meld - KDiff3 - Kompare - Diffuse - xxdiff - P4Merge - Other (prompts for a custom command when comparison starts) - None **Diff Tool Setup** - Install the selected tool separately and make its command available on `PATH`. - Beyond Compare uses `bcomp` on macOS and `BComp.com` on Windows. These launchers wait until the comparison closes, keeping the remote temporary file available. On macOS, use Beyond Compare's **Install Command Line Tools** menu item if `bcomp` is unavailable. See the [Beyond Compare command-line reference](https://www.scootersoftware.com/v5help/command_line_reference.html). - [Hunk](https://www.hunk.dev) is available on all three platforms. Once its `hunk` command is installed, select **Default Diff Tool → Hunk**. ZeySDK runs `hunk diff --files` in the current terminal and waits for it to exit. See [Hunk's file comparison guide](https://www.hunk.dev/docs/workflows/files-and-patches/). - Hunk provides a diff viewer. During a sync conflict, edit the local file in your editor before confirming that the merge is finished. - Custom commands should wait for the comparison to finish before exiting; add the tool's wait flag where available. **Configuration Storage** General CLI settings are saved through the credential backend. The credential-storage preference itself is saved in `~/.config/zeysdk/settings.json`. - `Auto`: Tries the OS keyring on supported desktop systems and falls back to the local file store; WSL and headless Linux select the file store directly - `OS Keyring`: Uses platform keychain when available - `File Store`: Stores credentials unencrypted in `~/.config/zeysdk/store.json` with owner-only file permissions Environment variables (`ZEYSDK_USE_KEYRING` or legacy `ZEYSDK_USE_KEYTAR`) override configured storage mode. **Error Handling** | Error | Description | Resolution | |-------|-------------|------------| | Storage access | Failed to access configured credential backend | Check keychain availability or switch to File Store | | Invalid saved configuration | Saved data is malformed JSON or is not a JSON object | Correct the saved value through the configured credential backend; the command leaves it unchanged | | Invalid input | Invalid list height value | Enter a positive number | | Save failed | Failed to save configuration | Verify write permissions | **Notes** - Configuration is global, not per application - Diff tool selection affects compare and merge workflows - `File Store` is useful in environments without OS keyring support - Selecting `OS Keyring` explicitly fails if keyring access is unavailable; `Auto` may fall back - Environment variables take precedence over saved credential storage settings **Best Practices** - Prefer `Auto` credential storage on developer workstations - Use a diff tool that can be launched from the command line - Keep list height high enough for projects with many instances or snapshots - Use file store only when keyring access is unavailable or unsuitable --- Canonical: https://www.zeyos.com/sdk/application-management/zeysdk-create # Create Application The `create` command creates a local ZeyOS application by setting up the application manifest and default directory structure. ```bash zeysdk create [options] ``` **Parameters** | Parameter | Description | Required | Example | |-----------|-------------|----------|---------| | `APP_ID` | Application ID | Yes | `my-app` | **Options** | Option | Description | Required | Example | |--------|-------------|----------|---------| | `--name ` | Display name; prompted when omitted | No | `--name="My App"` | | `--vendor ` | Vendor name | No | `--vendor="ACME Inc"` | | `--verbose` | Show detailed output | No | `--verbose` | **Process** 1. **Input Validation** - Requires an application ID - Uses the display name from `--name` or prompts for one - Uses the vendor name when provided 2. **Project Scaffolding** - Refuses to run inside an existing app tree - Refuses to overwrite any existing target path - Creates the application directory named by `APP_ID` - Creates standard asset and configuration folders - Writes the initial `zeyos.app.json` manifest 3. **Initial Files** - Creates default settings files - Creates an empty `.zeysdk/sync.json` - Creates an empty project `README.md` - Leaves the app ready for asset management and linking Creates a new ZeyOS application with the following structure: ``` my-app/ |-- .zeysdk/ | `-- sync.json |-- assets/ |-- resources/ |-- services/ |-- settings/ | |-- defaultsettings.json | |-- settingscode.ixml | `-- usersettingscode.ixml |-- weblets/ |-- README.md `-- zeyos.app.json ``` **Examples** ```bash zeysdk create my-app --name="My App" zeysdk create customer-tools --name="Customer Tools" --vendor="ACME Inc" zeysdk create reporting --name="Reporting" --verbose ``` **Error Handling** | Error | Description | Resolution | |-------|-------------|------------| | Missing app ID | Required application ID was not provided | Pass `APP_ID` | | Missing name | Display name was not provided | Pass `--name` or enter one when prompted | | Invalid app ID | ID has unsupported characters or punctuation at either end | Use letters, numbers, dots, underscores, and hyphens, beginning and ending with a letter or number | | Nested app | Command was run inside another app | Move to a parent workspace directory | | Directory exists | Target directory already exists | Choose another `APP_ID` or directory | | Write error | Files could not be created | Check local permissions | **Notes** - `create` only creates the local project structure - If scaffolding fails after the directory is created, the incomplete target is removed - Use `zeysdk link` to connect the app to an instance - Use `zeysdk add` to register new assets after creation - Keep `APP_ID` stable once the app is shared or deployed **Best Practices** - Use lowercase, descriptive application IDs - Set `--vendor` when the app belongs to a company or team - Commit the generated project before making large changes - Link and push only after reviewing the generated manifest --- Canonical: https://www.zeyos.com/sdk/application-management/zeysdk-delete # Delete Application The `delete` command permanently deletes your ZeyOS application from the current instance. ```bash zeysdk delete [options] ``` **Options** | Option | Description | |--------|-------------| | `-f, --force` | Skip deletion confirmation prompt | | `--verbose` | Show detailed output | **Process** 1. **Validation** - Checks that the command runs inside a linked app - Loads `.zeysdk/sync.json` - Verifies required instance and app identifiers 2. **Confirmation** - Prompts before deleting the remote application - Stops when deletion is not confirmed - Uses verbose output for additional progress details 3. **Deletion** - Sends the delete request to the current instance - Removes the application from the platform - Reports the final deletion result **Examples** ```bash zeysdk delete zeysdk delete --force zeysdk delete --verbose ``` **Error Handling** | Error | Description | Resolution | |-------|-------------|------------| | Missing sync.json | sync.json file not found | Ensure you are in an app directory | | Invalid sync.json | Missing required keys | Verify sync.json has id and url | | API error | Server communication failed | Check connection and permissions | **Warning** Delete permanently removes the application from the current instance. **Notes** - Delete acts on the currently linked instance - Local files are not a substitute for a remote backup - Use `zeysdk whoami` before deleting if you are unsure which instance is active - Consider `zeysdk predelete` when you need a reversible operational state first - `--force` skips confirmation and should be used carefully **Best Practices** - Create a snapshot before destructive changes - Confirm the active instance before deleting - Notify affected users before removing production apps - Keep a record of the app ID and instance for audit purposes --- Canonical: https://www.zeyos.com/sdk/application-management/zeysdk-host # Host Commands The `host` command manages saved ZeyOS instance hosts. ```bash zeysdk host [options] ``` **Available Commands** | Command | Description | |---------|-------------| | `list` | List all configured hosts | | `add [HOST_NAME]` | Add a new host | | `update [HOST_NAME]` | Update existing host | | `remove [HOST_NAME]` | Remove host configuration | | `link [HOST_NAME]` | Link current app to host | **Options for `add` and `update`** | Option | Description | |--------|-------------| | `-u, --url ` | URL for the host | | `-t, --token ` | Authentication token | **Process** 1. **Host Listing** - Reads saved host credentials - Displays configured host names and custom URLs; standard `cloud.zeyos.com` URLs are omitted from the compact listing - Hides stored tokens from output 2. **Host Creation and Updates** - Accepts host name, URL, and token from flags or prompts - Validates required values - Normalizes URLs by removing trailing slashes - Refuses to add a second entry with the same host name - Preserves the current URL or token when only the other field is supplied to `update` - Keeps the exact saved URL selected in interactive update, removal, and linking prompts - Rejects ambiguous positional host names before changes; omit the host name to choose the exact URL - Refuses to move an entry onto another saved credential for the same host and URL - Writes an updated URL-keyed credential before removing the old key; if old-key cleanup fails, it attempts to remove the replacement and keeps the known-good entry 3. **Host Removal and Linking** - Always asks for confirmation before removing saved credentials - Links the current app to a saved host when using `host link` - Requires app context for app-linking operations **Examples** ```bash # List all hosts zeysdk host list # Output: Configured hosts: # - instance1 # - custom https://zeyos.example.com/custom # Add new host with options zeysdk host add myinstance -u https://cloud.zeyos.com/app -t mytoken # Add new host interactively zeysdk host add # Output: Enter the host name: myinstance # Enter the URL: https://cloud.zeyos.com/app # Enter the token: **** # Update host with options zeysdk host update myinstance --url https://cloud.zeyos.com/newapp zeysdk host update myinstance --token replacement-token # Update host interactively zeysdk host update # Output: Select a host to update: # > instance1 # > instance2 # Remove host zeysdk host remove myinstance # Link the current app with a saved host zeysdk host link myinstance ``` **Error Handling** | Error | Description | Resolution | |-------|-------------|------------| | Host exists | Duplicate host | Update existing host | | Not in app | Outside app directory | Navigate to app directory for link command | | Write error | Cannot save config | Check permissions | | Host not found | Invalid host name | Check host name | | Multiple saved URLs | Host name matches more than one saved endpoint | Omit the host name and select the exact URL interactively | | Destination exists | Updating a URL would replace another saved credential | Select that destination entry to update its token | | Link/auth error | Saved URL or token cannot link the app | Update the host and retry `host link` | **Notes** - The `link` command is only available when inside a ZeyOS app directory - `host add` and `host update` save values without making a network request; `host link` performs connection validation - `host add` defaults a missing URL to `https://cloud.zeyos.com/`; `host link` follows normal link behavior and can create the remote app when it is missing - `host remove` has no force or `--yes` option - Storage follows the configured credential backend: the OS keyring when selected and available, or the unencrypted local file store **Best Practices** - Use clear host names such as `dev`, `staging`, or `prod` - Keep production tokens separate from development tokens - Rotate tokens when team access changes - Verify the host URL before linking an application --- Canonical: https://www.zeyos.com/sdk/application-management/zeysdk-migrate # Migrate Application The `migrate` command converts recognized legacy SDK metadata and instance sync files in the current directory to the current layout. ```bash zeysdk migrate [options] ``` **Options** | Option | Description | |--------|-------------| | `--link` | Link application after migration | | `--verbose` | Show detailed output | **Directory Structure Migration** The command recognizes these legacy files: - `.zeysdk.assets.json` - `.zeysdk.package.json` - `.zeysdk.sync..json`, where the instance ID is a safe SDK identifier It updates them toward this structure: ``` my-app/ |-- .zeysdk/ | |-- sync.json # Active link, created later by link/use | `-- sync..json # Migrated non-active instance state |-- assets/ |-- resources/ |-- services/ |-- settings/ |-- weblets/ `-- zeyos.app.json ``` **Process** 1. **Pre-migration Validation** - Requires at least one recognized legacy package, assets, or instance sync file - Requires each JSON metadata file, including an existing `zeyos.app.json`, to contain a JSON object - Requires the merged manifest to contain `identifier` and `name` - Refuses to overwrite an existing `.zeysdk/sync..json` destination 2. **Configuration Migration** - Merges existing `zeyos.app.json`, then `.zeysdk.assets.json`, then `.zeysdk.package.json` - Later sources win when the same property appears more than once - Writes the merged result atomically to `zeyos.app.json` 3. **Directory Migration** - Creates `.zeysdk` when needed - Moves `.zeysdk.sync..json` to `.zeysdk/sync..json` - Removes the legacy package and assets files after the new manifest succeeds - Preserves existing asset directories and files 4. **Rollback and Optional Linking** - Stages legacy metadata files and records moved sync files while migration is in progress - Restores moved files and the previous manifest if migration fails - Runs the normal interactive `zeysdk link` flow only after migration succeeds when `--link` is used - Does not roll the completed file migration back if that later link step fails **Examples** ```bash zeysdk migrate zeysdk migrate --link zeysdk migrate --verbose ``` **Error Handling** | Error | Description | Resolution | |-------|-------------|------------| | No legacy inputs | No recognized legacy files were found | Run from the legacy app root and verify filenames | | Invalid metadata | A JSON file is not an object or merged metadata lacks `identifier`/`name` | Repair the legacy metadata before retrying | | Sync conflict | A destination sync file already exists | Reconcile the old and current sync files manually | | Permission denied | Cannot modify files | Check file permissions | | Migration conflict | Incompatible changes | Resolve conflicts manually | | Link failed | Post-migration link failed | Check instance credentials | **Notes** - Migration does not move credentials or create an active `.zeysdk/sync.json`; use `--link`, `zeysdk link`, or `zeysdk use` as appropriate afterward - Successful migration removes the recognized legacy metadata files; a cleanup failure can leave a `.migrating-` file and emits a warning - `--link` validates and saves an instance connection after file migration - `--verbose` shows detailed progress information **Best Practices** - Commit or manually copy the legacy hidden files before migration; the normal snapshot glob does not include root files such as `.zeysdk.assets.json` - Review generated `zeyos.app.json` before pushing changes - Run `zeysdk compare` after migration to inspect local and remote differences - Commit the migrated structure separately from feature changes --- Canonical: https://www.zeyos.com/sdk/application-management/zeysdk-rollout # Rollout Application The `rollout` command pushes your ZeyOS application to one or more linked instances sequentially. ```bash zeysdk rollout [INSTANCE_ID...] [options] ``` **Parameters** | Parameter | Description | Required | Example | |-----------|-------------|----------|---------| | `INSTANCE_ID` | Target instance IDs | No | `dev-instance prod-instance` | **Options** | Option | Description | |--------|-------------| | `-a, --all` | Rollout to all linked instances | | `-f, --force` | Continue even if errors occur | | `-v, --verbose` | Show detailed output | | `--break-lock` | Remove an existing sync lock before acquiring the rollout lock | **Interactive Features** 1. **Instance Selection** - If no instances specified, shows interactive checkbox list - Currently active instance pre-selected - Must select at least one instance 2. **Backup Prompt** - Option to create server state backup before rollout - Defaults to creating a backup - Copies the project once per valid target, switches the copy to that instance, and force-pulls its server state - Creates `.zeysdk/backups/server-state_.zip` - Excludes `node_modules`, `.git`, `dist`, `build`, nested backups, and snapshots from copied backup state **Process** 1. **Validation** - Verifies `.zeysdk` directory exists - Validates instance tokens - Checks app identifier in `zeyos.app.json` 2. **Execution Order** - Active instance processed first (if included) - Remaining instances processed sequentially - Acquires one project sync lock for the rollout and passes that ownership through every internal force-push - Automatically switches between instances - Returns to original instance when complete 3. **Error Handling** - Stops on first error unless `--force` used - Creates backups before acquiring the rollout lock and before making remote changes when requested - Attempts to restore the original *active-instance selection* after success or failure; it does not roll back remote pushes that already succeeded - Continues with valid instances if token validation fails with `--force` - Records forced validation, backup, and rollout failures and exits nonzero after all eligible instances have been processed - Still aborts when the combined backup ZIP itself cannot be created **Error Handling** | Error | Description | Resolution | |-------|-------------|------------| | No instances | No valid target instances found | Specify instances or use `--all` | | Token invalid | Instance authentication failed | Check instance credentials | | Backup failed | Unable to create backup | Verify write permissions | | Rollout failed | Error during deployment | Check error message or use `--force` | | Switch failed | Unable to change instance | Manually switch using `use` command | | Lock busy | Another sync operation owns the project lock | Wait for it to finish; use `--break-lock` only after confirming the owner is stale | **Notes** - `rollout` can target explicit instance IDs, selected instances, or all linked instances - The active instance is pre-selected during interactive rollout - The command attempts to return to the original instance when rollout completes; failure to restore it makes the command fail - Pushes share one rollout-owned lock and run one at a time; rollout does not push to instances concurrently - `--force` continues across eligible instances when possible, but any recorded partial failure still makes the overall command fail with a nonzero exit - A target that fails credential validation is skipped under `--force` and included in the final failure summary - `--break-lock` is destructive to lock coordination and must not be used while another live SDK operation is working in the same project **Best Practices** - Run `zeysdk compare` before rolling out to important instances - Create backups before broad rollouts - Keep the generated server-state ZIP until the rollout has been verified - Roll out to staging before production - Prefer explicit instance IDs for release scripts - Review failures before retrying with `--force` --- Canonical: https://www.zeyos.com/sdk/asset-management # Asset Management The `add` command creates local files, while all Asset Management commands maintain their entries in `zeyos.app.json`. Supported classes are `services`, `resources`, `weblets`, and generic `assets`; only generic assets may use nested paths. Available Commands ------------------ | Command | Description | Usage | |---------|-------------|-------| | [`add`](https://www.zeyos.com/sdk/asset-management/zeysdk-add) | Add new asset | `zeysdk add ` | | [`update`](https://www.zeyos.com/sdk/asset-management/zeysdk-update) | Update asset metadata | `zeysdk update ` | | [`remove`](https://www.zeyos.com/sdk/asset-management/zeysdk-remove) | Remove asset | `zeysdk remove ` | | [`rename`](https://www.zeyos.com/sdk/asset-management/zeysdk-rename) | Rename asset | `zeysdk rename ` | --- Canonical: https://www.zeyos.com/sdk/asset-management/zeysdk-add # Add Asset The `add` command creates a local asset file and registers it in `zeyos.app.json`. ```bash zeysdk add [options] ``` **Parameters** | Parameter | Description | Required | Example | |-----------|-------------|----------| ------- | | `ASSET_PATH` | Asset path including class directory and filename | Yes | `services/my-service.ixml` | **Options** | Option | Description | |--------|-------------| | `--verbose` | Show detailed output | | `--name ` | Asset name | | `--type ` | Service or weblet type; required with non-interactive service/weblet usage | | `--view ` | Weblet view | | `--entity ` | Entity for an entity-triggered service | | `--schedule ` | `TIMING` start time as an integer from `0` to `1439` | | `--interval ` | `TIMING` interval as an integer of at least `1` | | `--public ` | Resource public flag (`0` or `1`) | | `--url ` | Empty or an `http://`/`https://` URL for a weblet or service | | `--accesskey ` | HTTP access key for `REMOTECALL` services | | `--width ` | Popup or embedded weblet width; non-negative integer | | `--height ` | Popup or embedded weblet height; non-negative integer | | `--svgpath ` | SVG path for an `INTEGRATED`, `STANDALONE`, or `DETACHED` icon | | `--color ` | Three or six uppercase hex digits without `#`, such as `FF0000` | | `--langaliases ` | Language aliases as valid JSON text | Any metadata flag selects non-interactive mode. In that mode, services and weblets must include `--type`; type names are case-insensitive and stored in uppercase. With no metadata flags, the command prompts for applicable fields. Generic `assets` never prompt for metadata. **Supported Types** - Services: `TIMING`, `REMOTECALL`, `AFTER_CREATION`, `BEFORE_MODIFICATION`, `AFTER_MODIFICATION`, `AFTER_CREATION_MODIFICATION`, `BEFORE_DELETION`, `AFTER_DELETION` - Weblets: `INTEGRATED`, `STANDALONE`, `DETACHED`, `POPUP_FRAMED`, `POPUP_PLAIN`, `EMBEDDED_FRAMED`, `EMBEDDED_COLLAPSED`, `EMBEDDED_PLAIN` Entity-triggered services require `--entity`. `TIMING` services use `--schedule` and `--interval`; interactive mode asks for the schedule as `HH:MM`. Popup and embedded weblets use width and height. Integrated, standalone, and detached weblets use SVG path and color. **Behavior** 1. Validates the app, asset class, filename, and manifest key. 2. Rejects absolute paths, traversal, symbolic-link path components, and an existing physical file. 3. Creates missing parent directories and the local file. New `.ixml` files receive the default iXML template; other files start empty. 4. Adds the manifest entry only after file creation succeeds. A later manifest failure removes the newly created file. `assets` supports nested paths such as `assets/images/logo.png`; services, resources, and weblets use a single filename below their class directory. **Error Handling** | Error | Description | Resolution | |-------|-------------|------------| | Invalid asset class | Unsupported asset type | Use correct asset class | | Missing app.json | Configuration file not found | Initialize app first | | Duplicate asset | Manifest key or physical file already exists | Use a different path or `update` | | Unsafe filename | Invalid characters, traversal, absolute path, or symbolic link | Keep the path inside its asset directory | | Missing type/entity | Required non-interactive metadata is absent | Supply the required flag or use interactive mode | | Invalid metadata | Value fails type, range, URL, color, or JSON validation | Correct the supplied value | **Notes** - Asset paths start with `services`, `resources`, `weblets`, or `assets`. - MIME type is inferred from the filename. - Use `zeysdk push` or `zeysdk sync` to send the new asset to an instance - Run the command from the application root. **Examples** ```bash # Public resource zeysdk add resources/logo.png --name "Company Logo" --public 1 # Daily service starting at 02:00 zeysdk add services/daily-backup.ixml \ --name "Daily Backup" --type TIMING --schedule 120 --interval 1440 # Entity hook zeysdk add services/contact-hook.ixml \ --name "Contact Hook" --type AFTER_CREATION --entity contacts # Popup weblet zeysdk add weblets/customer-dashboard.ixml \ --name "Customer Dashboard" --type POPUP_FRAMED \ --width 800 --height 600 --view customers.index # Integrated weblet with icon metadata zeysdk add weblets/quick-note.ixml \ --name "Quick Note" --type INTEGRATED --view notes.index \ --color FF0000 --svgpath 'M5 5h90v90h-90z' \ --langaliases '{"de_DE":"Schnellnotiz"}' # Nested generic asset (no metadata prompt) zeysdk add assets/images/icons/favicon.ico ``` --- Canonical: https://www.zeyos.com/sdk/asset-management/zeysdk-remove # Remove Asset The `remove` command removes an existing asset from your ZeyOS application. ```bash zeysdk remove [options] ``` **Parameters** | Parameter | Description | Required | Example | |-----------|-------------|----------| ------- | | `ASSET_PATH` | Asset path including class directory and filename | Yes | `services/my-service.ixml` | **Options** | Option | Description | |--------|-------------| | `--verbose` | Show detailed output | **Process** 1. **Validation** - Verifies `zeyos.app.json` exists - Validates the asset class from `ASSET_PATH` - Rejects traversal, absolute paths, symbolic-link path components, and invalid characters - Confirms the asset key exists and its extension matches the MIME type in the manifest 2. **Manifest Update** - Removes the asset entry from `zeyos.app.json` - Preserves unrelated asset definitions 3. **File Removal** - Deletes the local asset file - Removes empty parent directories below the asset-class directory - Treats an already-missing local file as a successful manifest cleanup - Reports progress when verbose output is enabled - Leaves remote deletion to `zeysdk push` or `zeysdk sync` Generic `assets` may use nested paths. Services, resources, and weblets use a single filename below their class directory. The operation is serialized with other local manifest mutations, and a filesystem error restores the manifest entry. **Error Handling** | Error | Description | Resolution | Example | |-------|-------------|------------|---------| | Invalid asset class | Wrong type specified | Use correct class | `Invalid asset class. Expected: services, resources, weblets, assets` | | Missing app.json | Config file not found | Initialize app | `requires the zeyos.app.json file to exist` | | Asset not found | Asset doesn't exist | Check asset name | `requires the asset key to exist in zeyos.app.json` | | Extension mismatch | Filename does not match the registered MIME type | Use the registered path | `requires the file extension to match` | | Unsafe path | Path escapes its asset directory or contains a symbolic link | Use a path inside the app | `requires the asset file path to stay inside` | | File delete error | Cannot remove file | Check permissions | `There was a problem deleting the file` | **Notes** - `remove` deletes the local file and its manifest entry - The command does not prompt for confirmation - The remote instance is not updated until you push or sync - Removing an asset can affect services, weblets, or references that depend on it - Use `zeysdk compare` to review removal before synchronizing **Best Practices** - Search for references before removing shared resources - Create a snapshot before large cleanup work - Remove unused manifest entries and files together - Push removals during a planned maintenance window when users may be affected **Examples** ```bash # Remove a service zeysdk remove services/backup.ixml --verbose # Output: # Loading assets info. # Updating assets info. # Deleting file: services/backup.ixml (...) # File deleted successfully. # Remove was completed successfully. # Remove a weblet zeysdk remove weblets/dashboard.ixml # Remove a resource zeysdk remove resources/logo.png # Remove a nested generic asset zeysdk remove assets/images/icons/favicon.ico ``` --- Canonical: https://www.zeyos.com/sdk/asset-management/zeysdk-rename # Rename Asset The `rename` command renames an existing asset in your ZeyOS application. ```bash zeysdk rename [options] ``` **Parameters** | Parameter | Description | Required | Example | |-----------|-------------|----------| ------- | | `OLD_ASSET_PATH` | Old asset path including class directory and filename | Yes | `services/old-service.ixml` | | `NEW_ASSET_PATH` | New asset path including class directory and filename | Yes | `services/new-service.ixml` | **Options** | Option | Description | |--------|-------------| | `--verbose` | Show detailed output | **Validation Rules** 1. **Source Validation** - Asset class must be valid (`services`, `resources`, `weblets`, `assets`) - Source key must exist in `zeyos.app.json` - Source extension must match the MIME type registered in the manifest - Source path must remain inside its asset directory and contain no symbolic-link component 2. **Destination Validation** - Asset classes must match between source and destination - A different destination key must not exist in the manifest - A different destination path must not exist in the filesystem, including as an untracked file or dangling symbolic link - New filename must be valid 3. **Configuration** - `zeyos.app.json` must exist - Asset must be properly configured **Process** 1. Renames the local file, creating a missing destination parent directory for nested generic assets. 2. Updates the manifest key and MIME type based on the new extension. 3. Records a key-changing rename in `.zeysdk/sync.json` for compare and sync when that file is available. 4. Restores the original file, manifest, and sync metadata if a later step fails. Generic `assets` may move between nested paths, but both paths must remain under `assets/`. Other asset classes use a single filename. Rename does not update references in source code or other applications. **Error Handling** | Error | Description | Resolution | Example Message | |-------|-------------|------------|-----------------| | Invalid arguments | Missing parameters | Provide both paths | `expects at least 2 arguments` | | Asset class mismatch | Different classes | Use same class | `expects the asset class directory to be the same` | | Invalid asset class | Wrong type | Use valid class | `should be a valid asset class: services, resources, weblets, assets` | | Missing config | No app.json | Initialize app | `requires the zeyos.app.json file to exist` | | Source missing | Source not found | Check source path | `requires the asset to exist` | | Source extension mismatch | Source extension differs from registered MIME type | Use the registered filename | `requires the source extension to match` | | Unsafe path | Path escapes its class directory or contains a symbolic link | Use an in-app path | `requires asset file paths to stay inside` | | Destination exists | Tracked or untracked target exists | Choose a different name | `requires the destination file to not exist` | | Rename failed | File system error | Check permissions | `There was a problem renaming the file` | **Automatic Updates** The following are automatically handled: 1. MIME type updates based on the new extension 2. Manifest key and file-path updates 3. In-process rollback on failure 4. Rename tracking in `.zeysdk/sync.json` when the manifest key changes **Notes** - Source and destination paths must use the same asset class - Rename updates metadata and the local file path - Existing destinations are never intentionally overwritten - Remote state is not updated until you push or sync - Rename tracking helps compare and sync display the change as a rename when possible **Best Practices** - Rename before pushing new references to the asset - Keep asset keys stable once they are used by other apps or integrations - Use `zeysdk compare` after renaming to verify the pending operation - Prefer one rename per asset over rename-plus-content edits in the same change **Examples** ```bash # Rename a service zeysdk rename services/old-backup.ixml services/new-backup.ixml --verbose # Output: # Loading assets info. # Renaming file. # Updating assets info. # Rename was completed successfully. # Rename a weblet (changing extension) zeysdk rename weblets/dashboard.ixml weblets/dashboard-test.ixml # Updates mimetype automatically # Rename a resource zeysdk rename resources/old-logo.png resources/new-logo.png # Move a nested generic asset within assets/ zeysdk rename assets/drafts/logo.svg assets/images/logo.svg ``` --- Canonical: https://www.zeyos.com/sdk/asset-management/zeysdk-update # Update Asset The `update` command changes an existing manifest entry without modifying the local asset file. ```bash zeysdk update [options] ``` **Parameters** | Parameter | Description | Required | Example | |-----------|-------------|----------|---------| | `ASSET_PATH` | Asset path including class directory and filename | Yes | `services/my-service.ixml` | **Options** | Option | Description | |--------|-------------| | `--verbose` | Show detailed output | | `--name ` | Asset name | | `--type ` | Service or weblet type; case-insensitive | | `--view ` | Weblet view | | `--entity ` | Entity for an entity-triggered service | | `--schedule ` | `TIMING` start time as an integer from `0` to `1439` | | `--interval ` | `TIMING` interval as an integer of at least `1` | | `--public ` | Resource public flag (`0` or `1`) | | `--url ` | Empty or an `http://`/`https://` URL for a weblet or service | | `--accesskey ` | HTTP access key for `REMOTECALL` services | | `--width ` | Popup or embedded weblet width; non-negative integer | | `--height ` | Popup or embedded weblet height; non-negative integer | | `--svgpath ` | SVG path for an `INTEGRATED`, `STANDALONE`, or `DETACHED` icon | | `--color ` | Three or six uppercase hex digits without `#` | | `--langaliases ` | Language aliases as valid JSON text; an empty value stores `null` | Any metadata flag selects non-interactive mode and merges applicable values over the existing entry. With no metadata flags, services, resources, and weblets use interactive prompts. Generic `assets` preserve their existing `content` and have no editable metadata prompts. **Validation and Behavior** 1. Requires `zeyos.app.json` and an existing asset key. 2. Rejects absolute paths, traversal, symbolic-link path components, and invalid characters. 3. For services, resources, and weblets, requires the supplied extension to match the MIME type registered in the manifest. Use `rename` to change an extension. 4. Validates type-specific metadata, then rewrites only the selected manifest entry. The local file contents are not read or changed. Service and weblet types are the same as for `add`. Type flags are normalized to uppercase. Entity-triggered services require an entity; `--schedule` and `--interval` enforce the documented `TIMING` ranges. Popup and embedded weblets use dimensions, while integrated, standalone, and detached weblets use SVG path and color. **Service Types** | Type | Description | Schedule | Interval | |------|-------------|----------|-----------| | `TIMING` | Scheduled task | Minutes from midnight (`0`-`1439`) | Integer of at least `1` | | `REMOTECALL` | External API call | N/A | N/A | | `AFTER_CREATION` | Post-create hook | N/A | N/A | | `BEFORE_MODIFICATION` | Pre-update hook | N/A | N/A | | `AFTER_MODIFICATION` | Post-update hook | N/A | N/A | | `AFTER_CREATION_MODIFICATION` | Combined hook | N/A | N/A | | `BEFORE_DELETION` | Pre-delete hook | N/A | N/A | | `AFTER_DELETION` | Post-delete hook | N/A | N/A | **Weblet Types** | Type | Description | Width/Height | |------|-------------|--------------| | `INTEGRATED` | In-app weblet | Not used | | `STANDALONE` | Independent window | Not used | | `DETACHED` | Floating window | Not used | | `POPUP_FRAMED` | Modal with frame | Non-negative integers | | `POPUP_PLAIN` | Modal without title | Non-negative integers | | `EMBEDDED_FRAMED` | Inline with frame | Non-negative integers | | `EMBEDDED_COLLAPSED` | Collapsible panel | Non-negative integers | | `EMBEDDED_PLAIN` | Inline without frame | Non-negative integers | **Examples** ```bash # Change a timing interval while preserving other service metadata zeysdk update services/daily-backup.ixml --interval 720 # Change a service type; entity hooks require an entity zeysdk update services/contact-hook.ixml \ --type AFTER_MODIFICATION --entity contacts # Change a weblet to an integrated type with icon metadata zeysdk update weblets/customer-view.ixml \ --type INTEGRATED --view customers.index \ --color 4287F5 --svgpath 'M10 10h100v100h-100z' # Update resource metadata zeysdk update resources/company-logo.png \ --name "Company Brand Logo" --public 1 # Clear optional values explicitly zeysdk update services/remote-call.ixml --url '' --accesskey '' zeysdk update weblets/customer-view.ixml --langaliases '' ``` **Error Handling** | Error | Description | Resolution | Example | |-------|-------------|------------|---------| | Invalid asset class | Unsupported type | Use correct class | `services/backup.ixml` not `service/backup.ixml` | | Missing app.json | Config file not found | Initialize app | Run `zeysdk create` first | | Asset not found | Asset doesn't exist | Create asset | Run `zeysdk add` first | | Invalid filename | Bad characters or unsafe path | Keep the path inside its asset directory | Use `services/backup-daily.ixml` | | Extension mismatch | Path extension differs from the registered MIME type | Use the registered filename or `rename` | Do not update a `.png` resource as `.jpg` | | Invalid type | Wrong service/weblet type | Use valid type | Use `TIMING` not `SCHEDULED` | | Invalid schedule | Not an integer from `0` to `1439` | Use minutes from midnight | Use `1410` for 23:30 | | Invalid interval | Not an integer of at least `1` | Use a positive minute count | Use `1440` for daily | | Invalid dimensions | Not a non-negative integer | Use pixels without units | Use `800` not `800px` | | Invalid color | Includes `#`, lowercase, or wrong length | Use 3 or 6 uppercase hex digits | Use `4287F5` | **Notes** - `update` changes metadata, not the asset file contents - Service and weblet prompts depend on the selected type. - Resource visibility is controlled by the `public` metadata field - Use `zeysdk compare` after updates to review pending metadata changes - Empty strings can clear optional URL, access-key, view, icon, and language-alias values where applicable. --- Canonical: https://www.zeyos.com/sdk/authentication # Authentication The SDK accepts credentials from environment variables or from its saved host store. A local app link records the active instance in `.zeysdk`, while saved host credentials are global to the user account. ## 1. Environment Variables (Recommended for CI/CD) Environment variables are the recommended method for CI/CD environments and automated workflows. ```bash # Set environment variables ZEYSDK_TOKEN=your_token ZEYSDK_SERVER=https://cloud.zeyos.com/your_instance # Run commands zeysdk [command] ``` `ZEYSDK_TOKEN` overrides token lookup. `ZEYSDK_SERVER` overrides the active sync URL for commands that load sync data, including `run` and `compile`, and is also consumed by `zeysdk link`. Set both for portable non-interactive execution; a server-only override still needs a matching saved token for the active instance and URL. ### Platform-Specific Commands **Windows CMD:** ```cmd set ZEYSDK_TOKEN=your_token && set ZEYSDK_SERVER=https://cloud.zeyos.com/your_instance && zeysdk [command] ``` **Windows PowerShell:** ```powershell $env:ZEYSDK_TOKEN="your_token"; $env:ZEYSDK_SERVER="https://cloud.zeyos.com/your_instance"; zeysdk [command] ``` **Linux/macOS:** ```bash export ZEYSDK_TOKEN=your_token && export ZEYSDK_SERVER=https://cloud.zeyos.com/your_instance && zeysdk [command] ``` ## 2. Stored Credentials (Default for local development) For local development, link an application once and let the SDK reuse the saved instance-and-URL credential. ```bash # Link your instance first zeysdk link # Then run commands zeysdk [command] ``` ## Authentication and URL Priority For `zeysdk link`, explicit `--token` and `--url` flags take precedence over `ZEYSDK_TOKEN` and `ZEYSDK_SERVER`. The resulting values then take precedence over a saved host or an interactive prompt. For commands operating on an existing link: 1. A non-empty `ZEYSDK_SERVER` replaces the URL from `.zeysdk/sync.json`. 2. A non-empty `ZEYSDK_TOKEN` replaces the saved token. 3. Without `ZEYSDK_TOKEN`, the SDK looks up a saved credential for the active instance and effective URL. Legacy instance-only entries are used only when their stored URL matches the effective URL; otherwise relink the instance or provide explicit environment credentials. `zeysdk bundle install` deliberately requires `--token` and `--url` together when either flag is used. ## Credential Storage Choose the backend interactively with `zeysdk configure` → `Credential Storage`: | Mode | Behavior | |------|----------| | `Auto` | Uses the OS keyring on supported desktop systems. WSL and headless Linux use the file store; an unavailable desktop keyring may also fall back to it. | | `OS Keyring` | Requires the platform keyring and reports an error when it cannot be used. | | `File Store` | Stores credentials unencrypted in `~/.config/zeysdk/store.json` with owner-only file permissions. | The storage preference is written to `~/.config/zeysdk/settings.json`. When the keyring is used, `~/.config/zeysdk/account-index.json` tracks account names so the CLI can enumerate configured hosts; tokens remain in the keyring. `ZEYSDK_USE_KEYRING` overrides the saved mode. Values `1`, `true`, or `yes` select the keyring; other values select the file store. `ZEYSDK_USE_KEYTAR` is accepted as a legacy alias when the newer variable is unset. ## Expired Tokens When most wrapped commands receive a `401`, the CLI can prompt once for a replacement token, validate the relink, update the saved credential, and retry the original command once. `link` reports its own authentication failure instead of recursively relinking, and `migrate` does not perform the automatic retry. ## Security Notes - Stored credentials can use the OS keyring or the unencrypted file store - Environment-variable lifetime depends on how the shell or CI system defines them - Stored credentials persist between sessions - Use environment variables for CI/CD to avoid storing credentials - Use stored credentials for local development convenience - Avoid putting tokens directly into shared scripts, command history, logs, or bundle files ## Error Handling | Error | Description | Resolution | |-------|-------------|------------| | Missing token | No authentication provided for the active instance and effective URL | Set both environment variables, add a matching host, or run `zeysdk link` | | Invalid token | Bad credentials | Check token validity | | Server error | Invalid server URL | Verify server URL format | | Keyring unavailable | Explicit keyring backend cannot be opened | Repair keyring access or select `Auto`/`File Store` | | Network error | Connection failed | Check connectivity | --- Canonical: https://www.zeyos.com/sdk/bundle-management # Bundle Management The Bundle Management commands help you create and install bundles of multiple ZeyOS applications. Available Commands ------------------ | Command | Description | Usage | |---------|-------------|-------| | [`create`](https://www.zeyos.com/sdk/bundle-management/zeysdk-bundle-create) | Create new bundle | `zeysdk bundle create [OUTPUT_FILE]` | | [`install`](https://www.zeyos.com/sdk/bundle-management/zeysdk-bundle-install) | Install bundle to instance | `zeysdk bundle install ` | **Process** 1. **Bundle Creation** - Scans a parent directory for ZeyOS applications - Lets you choose which applications belong in the bundle - Writes a bundle file containing the selected app directories 2. **Bundle Installation** - Validates unique, contained application directory names from the bundle file - Links each app to the target instance - Force-pushes each application to the target instance sequentially - Reports a nonzero result if any app fails, including when `--force` allowed later apps to run **Common Workflows** ```bash zeysdk bundle create release-bundle --all zeysdk bundle install release-bundle dev-instance --token "$ZEYSDK_TOKEN" --url https://cloud.zeyos.com/dev-instance ``` **Notes** - `bundle` is a meta command; use `zeysdk bundle create` or `zeysdk bundle install` - Bundle files store app directory names, not the full application contents - Run bundle commands from the parent directory that contains the app folders - Installation is not transactional across apps: an earlier successful remote push is not undone if a later app fails - Use snapshots when you need a local backup of a single project state --- Canonical: https://www.zeyos.com/sdk/bundle-management/zeysdk-bundle-create # Create Bundle The `bundle create` command creates a bundle file containing selected ZeyOS applications from the current directory. ```bash zeysdk bundle create [OUTPUT_FILE] [options] ``` **Parameters** | Parameter | Description | Required | Default | Example | |-----------|-------------|----------|---------|---------| | `OUTPUT_FILE` | Bundle filename | No | `bundle` | `my-bundle` | **Options** | Option | Description | |--------|-------------| | `-v, --verbose` | Show discovered, skipped, and selected directories | | `--apps ` | Comma-separated list of app directories to include | | `--all` | Include all ZeyOS app directories | **Process** 1. **Directory Scanning** - Scans the current directory for application folders - Skips hidden directories - Ignores `node_modules` and `vendor` - Validates each app by checking `zeyos.app.json` 2. **App Selection** - Prompts for apps when no selection option is provided - Includes all valid apps with `--all` - Includes listed apps with `--apps` - Sorts selected apps alphabetically in the bundle file 3. **Bundle Output** - Writes one selected app directory per line - Uses `bundle` as the default filename in non-interactive mode - Prompts for an output filename, defaulting to `bundle`, during a fully interactive run - Stores relative directory names from the current folder **Examples** ```bash zeysdk bundle create my-bundle zeysdk bundle create --all zeysdk bundle create my-bundle --apps app-one,app-two ``` **Error Handling** | Error | Description | Resolution | |-------|-------------|------------| | No directories | Current folder has no directories | Navigate to a folder with apps | | No valid apps | No ZeyOS app directories found | Verify app directory structure | | Invalid app | Requested app is missing or invalid | Check the `--apps` list | | Invalid selection | No apps selected | Select at least one app | | Write error | Bundle file could not be created | Check write permissions | **Notes** - A valid app directory must contain `zeyos.app.json` - The app manifest must include `identifier` and `name` - Hidden directories are skipped during discovery - `node_modules` and `vendor` are ignored during discovery - `--apps` expects directory names relative to the current folder - `--all` takes precedence when used together with `--apps` - An existing output file is replaced; choose the path carefully **Best Practices** - Create bundles from a stable parent folder that contains all target apps - Use descriptive bundle filenames for release or environment targets - Use `--all` for repeatable automation when every app in the folder should be included - Use `--apps` when a deployment bundle should contain only selected applications --- Canonical: https://www.zeyos.com/sdk/bundle-management/zeysdk-bundle-install # Install Bundle The `bundle install` command installs multiple applications from a bundle file to a ZeyOS instance. ```bash zeysdk bundle install [options] ``` **Parameters** | Parameter | Description | Required | Example | |-----------|-------------|----------|---------| | `BUNDLE_FILE` | Path to bundle file | Yes | `my-bundle` | | `INSTANCE_ID` | Target instance ID | Yes | `dev-instance` | **Options** | Option | Description | |--------|-------------| | `--token ` | Token for the target instance | | `--url ` | URL for the target instance | | `--yes` | Disable the missing-host configuration prompt; fail if a missing host has no token/URL pair | | `-v, --verbose` | Show detailed validation and installation output | | `-f, --force` | Continue to later apps after non-authentication failures | **Process** 1. **Validation** - Verifies the bundle file exists - Validates the target instance ID - Requires `--token` and `--url` together when either is supplied - Ignores blank lines but rejects duplicate app entries - Accepts only safe single-directory names and verifies both their lexical paths and resolved real paths remain below the current directory - Verifies every listed path is a directory before changing remote state 2. **Instance Configuration** - Prompts to configure missing instances - Uses `--token` and `--url` for non-interactive setup - With `--yes`, fails instead of prompting when neither a matching saved host nor a token/URL pair is available - Validates and stores newly supplied credentials through the first successful app link rather than saving them before authentication 3. **Installation** - Opens each application directory - Links the app to the target instance - Switches to the target instance - Pushes application content - Restores the original working directory before processing the next app 4. **Results** - Reports each successful installation - Reports failures with the application name - Stops immediately on authentication failures, even with `--force` - With `--force`, continues after other per-app failures where possible - Fails the overall command if any installation failed, including partial-success runs **Examples** ```bash zeysdk bundle install my-bundle dev-instance zeysdk bundle install my-bundle prod-instance --token abc123 --url https://cloud.zeyos.com/prod-instance zeysdk bundle install my-bundle prod-instance --yes --token abc123 --url https://cloud.zeyos.com/prod-instance zeysdk bundle install my-bundle staging --force --verbose ``` **Error Handling** | Error | Description | Resolution | |-------|-------------|------------| | Bundle not found | Bundle file is missing | Check file path | | Invalid bundle | Bundle path is not a file | Use a valid bundle file | | Empty bundle | Bundle contains no entries | Create a new bundle | | Duplicate entry | The same app directory appears more than once | Remove duplicate lines | | Unsafe app path | Entry is invalid, escapes the parent, or resolves outside it | Use a direct child directory name | | Invalid instance | Instance is not configured | Configure the instance or pass `--token` and `--url` | | Invalid token | Authentication failed | Check instance credentials | | Directory error | App directory is missing | Verify bundle contents | | Push failed | App installation failed | Check app configuration | **Notes** - Bundle installation expects each listed app directory to exist below the current directory - `--token` and `--url` must be provided together for non-interactive instance setup - `--yes` fails fast when the instance is missing and credentials are not provided - Existing credentials are reused when the target instance is already configured - Each app is pushed with force enabled during installation - `--force` controls whether bundle processing continues; it does not change the internal force-push behavior - Bundle installation is not an all-or-nothing transaction. Successfully pushed apps remain changed if a later app fails - A partial-success installation returns a nonzero exit so automation cannot mistake it for a complete deployment **Best Practices** - Run `zeysdk bundle install` from the same parent directory used to create the bundle - Verify the target instance credentials before installing production bundles - Inspect bundle files as untrusted input before installation; path traversal, duplicate entries, and out-of-tree symlinks are rejected - Use `zeysdk bundle create --apps` to keep installation scope explicit - Use a staging instance before installing the same bundle to production --- Canonical: https://www.zeyos.com/sdk/configuration-files # Configuration Files When using the SDK, you will notice that a couple of configuration files are being created: ### 1. Application Manifest (zeyos.app.json) Describes the application's properties and represents the properties of all included assets ```json { "identifier": "myapp", "name": "My Application", "version": 10000, "vendor": "Company Name", "services": { "reminder": { "type": "timing", "schedule": 600, "interval": 60 } }, "resources": { "logo": { "mimetype": "image/png", "public": 1 } }, "weblets": { "dashboard": { "type": "popup_framed", "width": 800, "height": 600 } } } ``` ### 2. The ZeyOS Sync File (sync.json) The `sync.json` file is created whenever you are syncing an application with a ZeyOS instance. The instance configuration stores the authentication information when executing `zeysdk link [INSTANCE_ID]`. By using `zeysdk use [INSTANCE_ID]` a user can switch between different instances. ```json Example: { "id": "instance-id", "url": "platform-url", "syncstates": { "settings": { "hash": "file-hash", "lastmodified": "timestamp" }, "defaultsettings": { "hash": "file-hash", "lastmodified": "timestamp" }, "services": {}, "resources": {}, "weblets": {} } } ``` The sync configuration consists of two core sections:
1. Connection Settings ```json { "id": "instance-id", "url": "https://platform-url" } ``` - Updated automatically when running `zeysdk link` - Contains platform URL - Manages instance connection details
2. Sync States ```json { "syncstates": { "settings": { "hash": "file-hash", "lastmodified": 1234567890 }, "services": {}, "resources": {}, "weblets": {} } } ``` - Tracks file states for settings, resources, services, weblets, and assets - Uses hashes for change detection - Stores the server-returned `lastmodified` timestamp for synced items - Manages synchronization between local and server files
### Sync Scenarios
New Files (Empty States) When no sync hash exists: | Scenario | Action | Resolution Options | |----------|--------|-------------------| | File doesn't exist on server | Auto-create | N/A | | File exists on server | Conflict | • `theirs`: Use server version• `mine`: Use local version |
Existing Files (With States) When a sync hash exists: | Scenario | Action | Resolution Options | |----------|--------|-------------------| | File missing on server | Conflict | • `theirs`: Remove locally• `mine`: Create on server | | States don't match | Conflict | • `theirs`: Use server version• `mine`: Use local version | | States match | Auto-sync | Copy to server |
**We highly recommend using the SDK's interface to edit those file and restrain from editing them directly.** However, in some cases you might want to automate certain tasks through additional scripts and task runners - in such cases it is certainly useful to have a better understanding about the underlying file structure and properties. **BE CAREFUL WHEN MANIPULATING THE SYNC STATES, YOU ARE PUTTING YOUR SYSTEM INTEGRITY AT RISK!** --- Canonical: https://www.zeyos.com/sdk/debugging # Debugging and Compilation Execute a local iXML or Zymba script on the active linked ZeyOS instance. Run this command from an app containing `zeyos.app.json` and `.zeysdk/sync.json`. **Execution Context** `run` is console execution with application globals. It sends the local source to the instance and executes it there. - Effects are real. Database writes, mail, and external calls happen, and there is no dry-run mode. - Your local file runs even if it was never pushed, but anything it includes resolves to assets deployed on the instance. - The app's namespace and settings are available. There is no user session, no service `AUTH` context, and no inbound HTTP request, so `REQUEST`, `SELFURL`, and `VIEW` are absent. - It does not simulate a weblet or a service route. Console requests use PUT, so `GET` and `POST` routes in a `rest:server` block do not match. Test weblets, services, schedules, and hooks through their real entry points. See [Execution Contexts](https://www.zeyos.com/ixml/ixml-guide/development-environment/execution-contexts). **Timeouts** Requests time out after 30 seconds by default; set `ZEYSDK_REQUEST_TIMEOUT_MS` to a positive integer to change this. The timeout ends the client's wait. It does not show that the server stopped, so the script may still be running or may have finished. `run` is not resent automatically. Check the script's effects before running it again. ```bash # Run a script asset zeysdk run [ASSET_PATH] [options] # Run inline iXML (auto-wrapped in default iXML document) zeysdk run --ixml 'hello' [options] # Run inline Zymba zeysdk run --zymba '/* your code */' [options] # Equivalent short alias zeysdk run --zy '/* your code */' [options] ``` **Parameters** | Parameter | Description | Required | Example | |-----------|-------------|----------|---------| | `ASSET_PATH` | Script path including class directory and filename | Yes, unless one inline option is used | `services/daily-backup.ixml` | **Options** | Option | Description | Default | |--------|-------------|---------| | `--verbose` | Show detailed execution progress and stack traces | `false` | | `--clean` | Print only console output (no metrics) | `false` | | `--unpretty` | Use simple metadata formatting for this run | `false` | | `--ixml ` | Run inline iXML (wrapped in default iXML document) | - | | `--zymba ` | Run inline Zymba code | - | | `--zy ` | Alias for `--zymba` | - | **Input Rules** - File paths must begin with `services`, `resources`, `weblets`, or `assets` and use `.ixml`, `.zy`, or `.zymba`. - Paths must remain inside their asset-class directory. Absolute paths, traversal, invalid characters, and symbolic-link components are rejected. - Supply either `ASSET_PATH` or exactly one of `--ixml`, `--zymba`, and `--zy`. - Inline code cannot be empty. iXML fragments are inserted into the default iXML wrapper; a complete iXML document is used unchanged. - A file does not need a manifest asset entry, but it must exist locally and be readable. **Execution Results** The command outputs detailed execution metrics: ```bash === Run Results === Context Platform | sdk-alpha (https://cloud.zeyos.com/sdk-alpha/) Engine | iXML Metrics Load Time | 1.2 ms Execution Time | 45.3 ms Memory Usage | 2.5 MB Output Size | 128 KB Response HTTP Status | 200 Output Operation completed successfully ``` | Metric | Description | Format | |--------|-------------|---------| | `Platform` | Active linked platform | String | | `Load Time` | Script loading duration | milliseconds/seconds | | `Execution Time` | Processing duration | milliseconds/seconds | | `Memory Usage` | Peak memory consumption | Bytes/KB/MB/GB | | `Output Size` | Result data size | Bytes/KB/MB/GB | | `Engine` | Execution engine version | String | | `Output` | Script execution result | String | | `Debug Output` | Debug information | String | Server-reported execution errors are written to standard error and return a nonzero exit status instead of rendering a results block. **Examples** ```bash # Run a service script zeysdk run services/backup.ixml --verbose # Output: # Input validation completed. # Loading sync info and package info. # Making REST call to run file on the server. # Run completed successfully. # === Run Results === # Context # Platform | sdk-alpha (https://cloud.zeyos.com/sdk-alpha/) # Engine | iXML # Metrics # Load Time | 0.8 ms # Execution Time | 123.4 ms # Memory Usage | 1.2 MB # Output # Backup completed successfully # Run a Zymba weblet script (`.zymba` is also accepted) zeysdk run weblets/dashboard.zy # Output: # === Run Results === # Context # Platform | sdk-alpha (https://cloud.zeyos.com/sdk-alpha/) # Metrics # Load Time | 1.1 ms # Execution Time | 67.2 ms # Memory Usage | 3.4 MB # Output # Dashboard rendered successfully # Run inline iXML zeysdk run --ixml 'Hello' # Run inline Zymba with the short alias zeysdk run --zy 'return "Hello";' # Print only script output zeysdk run services/backup.ixml --clean # Force simple metadata output zeysdk run services/backup.ixml --unpretty ``` **Error Handling** | Error | Description | Resolution | |-------|-------------|------------| | Invalid asset class | Wrong type specified | Use correct asset class | | Missing sync.json | App is not linked | Run `zeysdk link` first | | Invalid sync.json | Linked instance lacks `id` or `url` | Relink the instance | | Missing script | Local file is missing or unreadable | Check the path and permissions | | Unsafe path | Path escapes its asset directory or uses a symbolic link | Use an in-app path | | Invalid script type | Wrong extension | Use `.ixml`, `.zy`, or `.zymba` | | Invalid usage | Missing input, multiple inline flags, or mixed path and inline input | Supply exactly one input source | | Authentication error | Token expired | Auto-relinks | | Execution error | Server returned a runtime error | Check standard error; add `--verbose` for a stack trace | By default, stack traces from execution errors are hidden. Use `--verbose` to include them. `--clean` writes only script console output to standard output; errors still go to standard error and exit nonzero. When `--clean` is present, progress messages are suppressed. Default output style is pretty. Set a global default under `zeysdk configure` -> `Run Output Style`, or override per command with `--unpretty`. Compile Scripts (zeysdk compile) ================================ Compile a local or inline iXML/Zymba script on the linked instance without executing it. Compilation is not offline: the command transmits the source to the instance's authenticated compiler, so it needs a linked app, a valid token, and network access. A successful compile does not show that database identifiers exist, that `include` targets are deployed, or that the code behaves correctly at runtime. See [Verification Checkpoints](https://www.zeyos.com/ixml/ixml-guide/development-environment/verification-checkpoints). ```bash zeysdk compile [ASSET_PATH] [options] zeysdk compile --ixml 'Hello' zeysdk compile --zy 'return "Hello";' ``` `ASSET_PATH` and inline input follow the same extension, path-safety, and mutual-exclusion rules as `run`. Compile also requires both the app manifest and a valid linked sync file. | Option | Description | |--------|-------------| | `--verbose` | Show validation, API, and completion progress | | `--ixml ` | Compile inline iXML, wrapping a fragment in the default document | | `--zymba ` | Compile inline Zymba | | `--zy ` | Alias for `--zymba` | | `--phpout [OUTPUT_FILE]` | Print returned PHP to standard output, or write it to `OUTPUT_FILE` | | `--intermediate` | Request and print returned intermediate Z-code | ```bash # Show compilation summary zeysdk compile services/backup.ixml # Print only the returned PHP when available zeysdk compile services/backup.ixml --phpout # Write returned PHP to a file zeysdk compile services/backup.ixml --phpout compiled.php # Request and display intermediate Z-code zeysdk compile services/backup.ixml --intermediate ``` A server-reported compilation error is written to standard error and exits nonzero. It is checked before PHP output, so `--phpout OUTPUT_FILE` does not write code from an error response. A local output-file error also exits nonzero. Lint Project Scripts (zeysdk lint) ================================== Compile eligible project `.ixml`, `.zy`, and `.zymba` files and summarize each result. The file set comes from the app manifest plus standard local settings files. ```bash zeysdk lint [--all] [--verbose] ``` By default, `lint` fetches server state and checks locally present scripts that differ from the last synchronized state. `--all` checks every eligible locally present script without first fetching server state. The command requires a linked app and rejects unreadable or unsafe manifest paths rather than silently skipping them. Like `compile`, `lint` transmits source to the remote compiler and does not execute it. `--all` skips the server-state comparison, not the compile requests. Each script is reported as `OK` or `ERROR`. Compilation and network failures are reported per file; the command exits with status `1` if any file fails. Finding no eligible scripts is informational and exits successfully, but it is not a pass: nothing was checked. Scripts that are not registered in the manifest are not selected, so compile those by path. --- Canonical: https://www.zeyos.com/sdk/entities # ZeyOS Entities - `accounts` - `actionsteps` - `addresses` - `applications` - `appointments` - `associations` - `campaigns` - `categories` - `comments` - `components` - `contacts` - `contacts2contacts` - `contracts` - `couponcodes` - `coupons` - `davservers` - `dunning` - `dunning2transactions` - `events` - `feedservers` - `fields` - `files` - `follows` - `groups` - `groups2users` - `invitations` - `items` - `likes` - `links` - `mailinglists` - `mailservers` - `messagereads` - `messages` - `notes` - `numcounters` - `objects` - `opportunities` - `participants` - `payments` - `permissions` - `pricelists` - `pricelists2accounts` - `prices` - `projects` - `records` - `resources` - `services` - `stocktransactions` - `storages` - `suppliers` - `tasks` - `tickets` - `transactions` - `users` - `weblets` --- Canonical: https://www.zeyos.com/sdk/platform-management # Platform Management The Platform Management commands help you connect to and manage ZeyOS instances. Available Commands ------------------ | Command | Description | Usage | |---------|-------------|-------| | [`link`](https://www.zeyos.com/sdk/platform-management/zeysdk-link) | Connect to instance | `zeysdk link [INSTANCE_ID]` | | [`unlink`](https://www.zeyos.com/sdk/platform-management/zeysdk-unlink) | Remove instance connection | `zeysdk unlink [INSTANCE_ID]` | | [`use`](https://www.zeyos.com/sdk/platform-management/zeysdk-use) | Switch active instance | `zeysdk use [INSTANCE_ID]` | | [`whoami`](https://www.zeyos.com/sdk/platform-management/zeysdk-whoami) | Show current instance | `zeysdk whoami` | Links are local to the current application. Saved host credentials are global and are managed separately with `zeysdk host`. --- Canonical: https://www.zeyos.com/sdk/platform-management/zeysdk-link # Link Instance The `link` command connects your local application to a ZeyOS instance and sets up synchronization. ```bash zeysdk link [INSTANCE_ID] [options] ``` **Parameters** | Parameter | Description | Required | Example | |-----------|-------------|----------|---------| | `INSTANCE_ID` | ZeyOS instance identifier | No | `myinstance` | **Options** | Option | Description | |--------|-------------| | `--token ` | SDK authentication token | | `--url ` | Instance URL | | `--break-lock` | Replace the current sync lock after you verify its operation is no longer running | | `--verbose` | Show detailed output | **Process** 1. **Connection Setup** - Requires the current directory to contain `zeyos.app.json` with `identifier` and `name` - Resolves the instance ID, URL, and token from arguments, environment variables, saved hosts, or prompts - Defaults the URL to `https://cloud.zeyos.com/` - Can infer a missing instance ID from the server URL 2. **Authentication** - Validates the SDK token - Fetches the remote application, or creates it when it does not yet exist - Saves credentials only after the link operation succeeds - During an expired-token relink, validates the new token before replacing the saved credential; an obsolete URL-keyed entry is removed afterward 3. **Sync Setup** - Creates or updates `.zeysdk/sync.json` - Keeps the active link in `.zeysdk/sync.json` and other linked instances in `.zeysdk/sync..json` - Creates `settings/settings..json` when absent - Makes the newly linked instance active - Restores those local files if link setup fails - Holds the shared operation lock through remote validation, local state changes, and credential persistence - Makes the instance available for push, pull, sync, and activity commands **Examples** ```bash zeysdk link myinstance zeysdk link myinstance --url https://custom.domain/app zeysdk link myinstance --token "$ZEYSDK_TOKEN" --url https://cloud.zeyos.com/myinstance --verbose # The same values can be provided by the environment ZEYSDK_TOKEN=abc123 ZEYSDK_SERVER=https://cloud.zeyos.com/myinstance zeysdk link myinstance ``` **Error Handling** | Error | Description | Resolution | |-------|-------------|------------| | Missing token | No SDK token provided | Enter a token when prompted or use `--token` | | Invalid instance | Instance ID contains unsupported characters | Use letters, numbers, dots, underscores, and hyphens, beginning and ending with a letter or number | | Invalid URL | Instance URL cannot be reached | Check `--url` or instance ID | | Auth failed | Invalid token | Generate a new SDK token | | Save failed | Sync configuration could not be written | Check local permissions | **Notes** - `INSTANCE_ID` can be provided as an argument or resolved interactively - `--url` overrides the default cloud URL for the instance - Command-line flags take precedence over `ZEYSDK_TOKEN` and `ZEYSDK_SERVER` - Pass both token and URL for predictable non-interactive setup unless a matching saved host supplies the missing value - Unlike `zeysdk clone`, a normal link creates the remote application if it is missing - Linking does not automatically push local files - If the remote app is created and a later local write fails, local files are rolled back but the remote creation is not automatically undone **Best Practices** - Link development and production instances with clear IDs - Use `zeysdk whoami` after linking to verify the active instance - Avoid sharing SDK tokens in shell history when possible - Run `zeysdk compare` before the first push to an existing remote app --- Canonical: https://www.zeyos.com/sdk/platform-management/zeysdk-unlink # Unlink Instance The `unlink` command removes a local connection to a ZeyOS instance. ```bash zeysdk unlink [INSTANCE_ID] [options] ``` **Parameters** | Parameter | Description | Required | Example | |-----------|-------------|----------|---------| | `INSTANCE_ID` | Instance to unlink | No | `myinstance` | **Options** | Option | Description | |--------|-------------| | `--break-lock` | Replace the current sync lock after you verify its operation is no longer running | | `--verbose` | Show detailed output | **Process** 1. **Validation** - Loads linked instances - Uses the active instance automatically when `INSTANCE_ID` is omitted - Verifies that the selected instance exists 2. **Unlinking** - Removes the explicit non-active instance file, or removes `.zeysdk/sync.json` when unlinking the active instance - Prompts for a replacement before unlinking the active instance when multiple other links exist - Leaves no active instance when no linked instances remain - Restores affected sync files if the local unlink operation fails **Examples** ```bash zeysdk unlink myinstance zeysdk unlink ``` **Error Handling** | Error | Description | Resolution | |-------|-------------|------------| | Missing sync.json | No active instance configuration | Run `zeysdk link` | | Invalid instance | Instance is not linked | Choose a linked instance | | Delete failed | Sync files could not be removed | Check local permissions | **Notes** - `unlink` removes local connection state for the selected instance - Remote applications and files are not deleted - Globally saved host credentials are not deleted; use `zeysdk host remove` for that - If the active instance is unlinked, another linked instance can become active - When no linked instances remain, no active instance is set **Best Practices** - Use `zeysdk whoami` before unlinking the active instance - Unlink old development instances that are no longer used - Keep production instance names easy to recognize - `unlink` refuses to mutate instance state while another synchronization or instance-state operation owns the project lock --- Canonical: https://www.zeyos.com/sdk/platform-management/zeysdk-use # Use Instance The `use` command switches the active ZeyOS instance for the current application. ```bash zeysdk use [INSTANCE_ID] [options] ``` **Parameters** | Parameter | Description | Required | Example | |-----------|-------------|----------|---------| | `INSTANCE_ID` | Instance to switch to | No | `myinstance` | **Options** | Option | Description | |--------|-------------| | `--break-lock` | Replace the current sync lock after you verify its operation is no longer running | | `--verbose` | Show detailed output | **Process** 1. **Validation** - Loads linked instances - Prompts for an instance when none is provided - Verifies that the selected instance exists 2. **Activation** - Renames the current active sync file to its instance-specific filename - Makes the selected instance-specific sync file active as `.zeysdk/sync.json` - Restores the affected sync files if switching fails - Clears cached views for the new instance **Examples** ```bash zeysdk use myinstance zeysdk use ``` **Error Handling** | Error | Description | Resolution | |-------|-------------|------------| | Missing sync.json | No active instance configuration | Run `zeysdk link` | | Invalid instance | Instance is not linked | Choose a linked instance | | Switch failed | Sync files could not be updated | Check local permissions | **Notes** - `use` changes the active local instance for the current application - `use` refuses to switch while another synchronization or instance-state operation owns the project lock - The command does not modify remote application data - Cached views are cleared after switching instances - Commands such as `push`, `pull`, `sync`, and activity changes use the active instance **Best Practices** - Run `zeysdk whoami` after switching to confirm the target instance - Keep instance IDs descriptive enough to avoid confusing environments - Compare before pushing after switching to a different instance - Avoid switching instances while another CLI operation is running --- Canonical: https://www.zeyos.com/sdk/platform-management/zeysdk-whoami # Show Current Instance The `whoami` command displays the active ZeyOS instance for the current application. ```bash zeysdk whoami [options] ``` **Options** | Option | Description | |--------|-------------| | `--verbose` | Show detailed output | **Process** 1. **Validation** - Loads `.zeysdk/sync.json` - Verifies that an active instance is configured 2. **Output** - Displays the active instance as `URL: ...` - Displays the active instance as `ID: ...` **Examples** ```bash zeysdk whoami zeysdk whoami --verbose ``` **Error Handling** | Error | Description | Resolution | |-------|-------------|------------| | Missing sync.json | No active instance configuration | Run `zeysdk link` | | Invalid sync.json | Active instance configuration is invalid | Re-link the instance | **Notes** - `whoami` reads the active instance from `.zeysdk/sync.json` - It does not validate credentials or contact the remote instance - The command is useful before operations that modify remote state - Use verbose output when troubleshooting local sync configuration **Best Practices** - Run `zeysdk whoami` before destructive or production-facing commands - Confirm the instance before `push`, `sync`, `delete`, `deactivate`, or `predelete` - Re-link the instance if the displayed URL or ID is incorrect --- Canonical: https://www.zeyos.com/sdk/snapshot-management # Snapshot Management The Snapshot Management commands help you create, manage, and restore backups of your ZeyOS applications. Available Commands ------------------ | Command | Description | Usage | |---------|-------------|-------| | [`list`](https://www.zeyos.com/sdk/snapshot-management/zeysdk-snapshot-list) | List available snapshots | `zeysdk snapshot list` | | [`create`](https://www.zeyos.com/sdk/snapshot-management/zeysdk-snapshot-create) | Create new snapshot | `zeysdk snapshot create [COMMENT]` | | [`restore`](https://www.zeyos.com/sdk/snapshot-management/zeysdk-snapshot-restore) | Restore from snapshot | `zeysdk snapshot restore [SNAPSHOT_ID]` | | [`delete`](https://www.zeyos.com/sdk/snapshot-management/zeysdk-snapshot-delete) | Delete snapshot | `zeysdk snapshot delete ` | **Process** 1. **Snapshot Creation** - Archives non-hidden project entries plus the supported `.zeysdk` manifest files - Stores snapshot metadata with comment and timestamp - Saves the completed ZIP and metadata in `.zeysdk/snapshots` 2. **Snapshot Management** - Lists available snapshots with ID, comment, and date - Supports restore and delete actions from the interactive list - Lets you restore a selected snapshot by ID 3. **Snapshot Restore** - Confirms before replacing local files unless forced - Validates and extracts the archive into a sibling staging directory before touching the target - Preserves the target's root `.zeysdk` contents, then overlays `.zeysdk/assets.json` and `.zeysdk/package.json` when the snapshot contains them - Swaps the staged tree into place and rolls the original directory back if the commit step fails **Common Workflows** ```bash zeysdk snapshot create "Before payment changes" zeysdk snapshot list zeysdk snapshot restore 20231101-123456 zeysdk snapshot delete 20231101-123456 ``` **Notes** - `snapshot` is a meta command; use `zeysdk snapshot list`, `create`, `restore`, or `delete` - Snapshot IDs use timestamp-style names such as `20231101-123456`; same-second collisions add `-1`, `-2`, and so on - Snapshot IDs accepted by restore/delete are constrained to safe filenames and cannot contain directory separators - Create snapshots before major changes to a current-format project; separately preserve legacy hidden metadata before migration - Use bundles when you need to install multiple applications to an instance --- Canonical: https://www.zeyos.com/sdk/snapshot-management/zeysdk-snapshot-create # Create Snapshot The `snapshot create` command creates a backup of the current project state. ```bash zeysdk snapshot create [COMMENT] ``` **Parameters** | Parameter | Description | Required | Example | |-----------|-------------|----------|---------| | `COMMENT` | Description of the snapshot | No | `"Initial setup"` | **Process** 1. **Comment Collection** - Uses the provided `COMMENT` argument when present - Prompts for a comment when no argument is provided - Requires a non-empty comment - Press Escape at the comment prompt to cancel without creating an archive or metadata 2. **Archive Creation** - Creates the local snapshot directory when needed - Generates `YYYYMMDD-HHMMSS.zip`, adding a numeric suffix if that name already exists - Reserves the output filename exclusively instead of overwriting an existing snapshot - Creates a compressed `.zip` archive and waits for the output stream to close - Removes the partial ZIP if archive creation fails 3. **Metadata Update** - Updates metadata only after the ZIP has fully closed - Stores the unique snapshot ID - Stores the comment - Stores the creation timestamp **Examples** ```bash zeysdk snapshot create "Initial setup" zeysdk snapshot create zeysdk snapshot create "Release v2.1.0" zeysdk snapshot create "Pre-update: Payment API" ``` **Error Handling** | Error | Description | Resolution | |-------|-------------|------------| | Missing comment | No comment entered at prompt | Enter a non-empty comment | | Snapshot write error | Archive could not be created | Check write permissions | | Archive error | File compression failed | Check file access and disk space | | Metadata error | Snapshot metadata could not be updated | Check snapshot storage permissions | **Notes** - Snapshot IDs are generated from local date and time; collision suffixes preserve both same-second snapshots - The normal project glob includes dotfiles and excludes root `.git`, `node_modules`, and `.zeysdk/**` - Both `.git` directories and worktree `.git` pointer files are excluded - `.zeysdk/assets.json` and `.zeysdk/package.json` are added explicitly when present - `settings/defaultsettings.json` is included once through the normal project glob - Other hidden files and directories are included unless covered by the exclusions above - Generated directories such as `dist` and `build` are included; root `node_modules` is excluded - Restore preserves the destination's current root `.git` and `node_modules` by moving them into the restored tree without copying their contents. Git history, stashes, and worktree identity survive; dependencies may need updating for restored manifests - Restore rejects archives containing reserved root `.git` or `node_modules` entries, including older archives containing a worktree pointer. These entries must not replace destination operational state - Preservation and archive validation use existing dependencies only - Snapshot archives are compressed as ZIP files **Best Practices** - Create a snapshot before large refactors to a current-format project - Use comments that describe the reason for the snapshot - Include release, task, or change names in comments when helpful - Create a fresh snapshot after a stable milestone --- Canonical: https://www.zeyos.com/sdk/snapshot-management/zeysdk-snapshot-delete # Delete Snapshot The `snapshot delete` command deletes a specific snapshot. ```bash zeysdk snapshot delete ``` **Parameters** | Parameter | Description | Required | Example | |-----------|-------------|----------|---------| | `SNAPSHOT_ID` | ID of snapshot to remove | Yes | `20231101-123456` | **Process** 1. **Snapshot Lookup** - Accepts IDs with or without the `.zip` suffix - Accepts only a safe snapshot filename and rejects directory separators or traversal - Verifies the resolved archive remains within `.zeysdk/snapshots` - Stops when the snapshot does not exist 2. **Deletion** - Removes the snapshot archive - Removes the snapshot metadata entry - Reports successful removal **Examples** ```bash zeysdk snapshot delete 20231101-123456 ``` **Error Handling** | Error | Description | Resolution | |-------|-------------|------------| | Invalid ID | ID is empty, contains unsafe characters, or contains a path | Copy the ID from `snapshot list` | | Not found | Snapshot archive does not exist | Check snapshot list | | Access denied | Permission issue | Check file permissions | **Notes** - Delete removes the snapshot archive from local storage - Delete also removes the matching metadata entry - The archive is first renamed to a same-directory tombstone; if the atomic metadata update fails, the original archive name is restored - Invalid or unreadable snapshot metadata stops deletion before the archive is moved - Delete does not prompt for confirmation when called directly - Snapshot deletion cannot be undone - Use `zeysdk snapshot list` to find the exact snapshot ID **Best Practices** - Keep snapshots that mark stable milestones - Remove old snapshots after they are no longer needed - Verify a newer snapshot exists before deleting an older known-good one - Avoid deleting snapshots during active rollback work --- Canonical: https://www.zeyos.com/sdk/snapshot-management/zeysdk-snapshot-list # List Snapshots The `snapshot list` command displays existing snapshots and supports interactive snapshot management. ```bash zeysdk snapshot list ``` **Features** | Feature | Description | |---------|-------------| | List | View all snapshots with comments and dates | | Restore | Revert to a selected snapshot | | Delete | Remove selected snapshot | | Details | View snapshot metadata | **Process** 1. **Metadata Loading** - Reads local snapshot metadata - Exits when no snapshots are available - Formats snapshot rows for interactive display 2. **Snapshot Selection** - Displays snapshot ID, comment, and date - Uses configured list height when available - Prompts you to select a snapshot 3. **Action Selection** - Shows details for the selected snapshot - Offers restore or delete actions - Confirms deletion before removing a snapshot - Press Escape in a restore/delete confirmation to return to snapshot actions without making changes - Press Escape in snapshot actions to return to the snapshot list, or in the list to exit - Declining deletion returns to snapshot actions, with Delete still selected - Returning to either menu preserves its selected row **Display Format** | Column | Description | |--------|-------------| | ID | Snapshot identifier (`YYYYMMDD-HHMMSS`, optionally with a collision suffix) | | Comment | User-provided description | | Date | Creation timestamp | **Examples** ```bash zeysdk snapshot list ``` **Error Handling** | Error | Description | Resolution | |-------|-------------|------------| | No snapshots | Snapshot metadata is empty | Create a snapshot first | | Metadata error | Snapshot metadata cannot be read | Check snapshot metadata file | | Restore failed | Selected restore action failed | Check snapshot archive and permissions | | Delete failed | Selected delete action failed | Check file permissions | **Notes** - Snapshot IDs are displayed without the `.zip` suffix - Missing or invalid comments and dates display as `No comment` and `No date` - The list is driven by `.zeysdk/snapshots/snapshots.json`; an orphan ZIP without metadata is not displayed - The interactive list supports restore and delete actions - Delete actions ask for confirmation before removing the archive - Restore actions use the current working directory as the restore target **Best Practices** - Review comments before restoring a snapshot - Keep comments specific enough to identify the project state - Delete old snapshots that are no longer useful - Keep at least one known-good snapshot before risky local changes --- Canonical: https://www.zeyos.com/sdk/snapshot-management/zeysdk-snapshot-restore # Restore Snapshot The `snapshot restore` command reverts a project to a previous snapshot state. ```bash zeysdk snapshot restore [SNAPSHOT_ID] [options] ``` **Parameters** | Parameter | Description | Required | Example | |-----------|-------------|----------|---------| | `SNAPSHOT_ID` | ID of snapshot to restore | No | `20231101-123456` | **Options** | Option | Description | |--------|-------------| | `--dir ` | Target directory | | `-f, --force` | Skip confirmation | **Process** 1. **Snapshot Selection** - Uses `SNAPSHOT_ID` when provided - Shows an interactive snapshot list when no ID is provided - Stops when no snapshot is selected - Press Escape in the picker to exit - Accepts the ID with or without `.zip`, but rejects paths, traversal segments, and unsafe filename characters 2. **Confirmation** - Asks before restoring by default - Skips the prompt with `--force` - Warns that current changes will be overwritten - Press Escape to return to the picker with the same snapshot selected, or to exit when an ID was supplied directly 3. **Validation and Staging** - Opens the ZIP and rejects absolute, parent-traversal, and out-of-target entries - Rejects archived root `.git` and `node_modules` entries so snapshots cannot replace destination operational state - Extracts the complete archive to a uniquely named sibling staging directory - Leaves the target untouched if validation or extraction fails 4. **Root State Merge and Commit** - Copies the target's existing root `.zeysdk` into the staged tree - Overlays snapshot copies of `.zeysdk/assets.json` and `.zeysdk/package.json` - Does not specially preserve nested `.zeysdk` directories elsewhere in the old project - Renames the original target to a temporary backup and the staged tree to the target - Moves the target's root `.git` and `node_modules` into the staged tree during the guarded swap, without copying their contents; preserves Git directories, worktree pointer files, and dependency symlinks - Restores the original target and its operational state if the staged rename fails; retains and reports recovery paths if rollback itself fails **Examples** ```bash zeysdk snapshot restore 20231101-123456 zeysdk snapshot restore 20231101-123456 -f zeysdk snapshot restore 20231101-123456 --dir ./test-env zeysdk snapshot restore ``` **Error Handling** | Error | Description | Resolution | |-------|-------------|------------| | Not found | Snapshot ID does not exist | Check `zeysdk snapshot list` | | No selection | No snapshot selected interactively | Select a snapshot or pass `SNAPSHOT_ID` | | Access denied | Target files cannot be replaced | Check file permissions | | Extract failed | Snapshot archive could not be extracted | Verify the snapshot archive | | Unsafe archive | ZIP contains a path outside the restore root | Do not restore the archive; recreate it from a trusted project | | Unsafe target | Target is a filesystem root, symlink, or parent of the running CLI directory | Choose a normal project directory and run from a safe location | | Cancelled | Restore prompt was declined | Re-run with confirmation or `--force` | **Warning** Restore operation will: - Overwrite existing files - Remove additional files - Cannot be undone **Notes** - `--dir` defaults to the current directory - The snapshot archive is always read from `.zeysdk/snapshots` under the directory where the command was started, even when `--dir` points elsewhere - Restore preserves the target's root `.git`, `node_modules`, and `.zeysdk`; archived supported manifest files take precedence over their current versions - Git history, branches, index, and stashes remain current; snapshot restoration changes the working files, not the Git revision. Existing dependencies remain installed and may need updating if the restored dependency manifests differ - Additional old target files, including nested `.zeysdk` directories, are removed by the successful directory swap - Runtime extraction and commit errors are rollback-safe, but a completed restore intentionally replaces the old project tree - If the snapshot does not exist, the command reports the missing ID and stops **Best Practices** - Run `zeysdk snapshot list` before restoring if you are unsure which snapshot to use - Create a new snapshot before restoring when current work may still be needed - Use `--dir` to inspect a restore in a separate folder - Use `--force` only in scripted or fully verified workflows --- Canonical: https://www.zeyos.com/sdk/synchronization # Synchronization The Synchronization commands help you manage file states between your local development environment and remote ZeyOS instances. Available Commands ------------------ | Command | Description | Usage | |---------|-------------|-------| | [`compare`](https://www.zeyos.com/sdk/synchronization/zeysdk-compare) | Compare local and remote state | `zeysdk compare [ASSET_PATH...] [options]` | | [`pull`](https://www.zeyos.com/sdk/synchronization/zeysdk-pull) | Get latest from instance | `zeysdk pull [ASSET_PATH...] [options]` | | [`push`](https://www.zeyos.com/sdk/synchronization/zeysdk-push) | Upload local changes | `zeysdk push [ASSET_PATH...] [options]` | | [`sync`](https://www.zeyos.com/sdk/synchronization/zeysdk-sync) | Synchronize local and remote state | `zeysdk sync [ASSET_PATH...] [options]` | | [`watch`](https://www.zeyos.com/sdk/synchronization/zeysdk-watch) | Watch for changes | `zeysdk watch [options]` | | [`rollout`](https://www.zeyos.com/sdk/application-management/zeysdk-rollout) | Push the application to linked instances | `zeysdk rollout [INSTANCE_ID...] [options]` | | [`recover`](https://www.zeyos.com/sdk/synchronization/zeysdk-recover) | List, restore, or delete sync-state backups | `zeysdk recover [options]` | Shared Synchronization Safety ----------------------------- `pull`, `push`, `sync`, the instance-switching and push phase of `rollout`, and every `recover` operation use the project lock at `.zeysdk/.sync.lock`. The lock is created atomically and contains an owner token, so one operation cannot release a lock owned by another. Locks are released when an operation succeeds, fails, or is cancelled. If the lock belongs to a running process, wait for that operation to finish. Stale locks are reclaimed automatically. `pull`, `push`, `sync`, `rollout`, `link`, `unlink`, and `use` also expose `--break-lock`, but use it only after confirming that the recorded process is no longer performing work. `link`, `unlink`, and `use` acquire the same lock so the active instance cannot change during synchronization. `compare` does not acquire the lock. `watch` acquires it for each automatic push rather than for the lifetime of the watcher. Dry Runs -------- `pull --dry-run`, `push --dry-run`, and `sync --dry-run` scan local state and read server state, then report planned work without applying server mutations or changing project files, the application manifest, or `sync.json`. They also skip creation of a `sync.json` backup. If `--debug-log` is supplied, diagnostic output is still written to `.zeysdk/debug.log`. A sync dry-run may remove an SDK-owned merge temporary file older than one hour from the system temporary directory. There is no dry-run option for `watch` or `rollout`. `compare` is the read-only preview command. Backups and Recovery -------------------- Before a non-dry-run `pull`, `push`, or `sync`, the CLI rotates a small set of `.zeysdk/sync.json.backup*` files. On a reported failure it attempts to restore the newest one. These backups contain only `sync.json`; they do not undo local file changes, manifest changes, or server changes that happened before a later step failed. Use [`recover`](https://www.zeyos.com/sdk/synchronization/zeysdk-recover) to inspect or restore them manually. Rollout's optional timestamped ZIP under `.zeysdk/backups/` is separate. It contains per-instance project copies populated from server state, is not managed by `recover`, and is not applied automatically as rollback. Rollout Behavior ---------------- Targets can be supplied explicitly, selected interactively when no IDs are supplied, or discovered with `--all`. Automatic discovery accepts regular files named exactly `sync..json`, removes duplicate IDs, and processes the active instance first when it is included. `rollout` validates each target's stored credentials, optionally creates the ZIP described above, and pushes targets sequentially while switching linked instances. The originally active instance is restored in a `finally` path, including after a failed push. With `--force`, validation, backup, or push failures are recorded while eligible targets continue; the command still finishes with a non-zero status when any failure was recorded. Rollout has no dry-run mode or automatic server rollback. --- Canonical: https://www.zeyos.com/sdk/synchronization/zeysdk-compare # Compare Files (zeysdk compare) Compare Files (zeysdk compare) ============================== The `compare` command compares local files with their server versions to detect content and property differences. ```bash zeysdk compare [ASSET_PATH...] [options] ``` **Parameters** | Parameter | Description | Required | Example | |-----------|-------------|----------|---------| | `ASSET_PATH` | Specific asset paths to compare | No | `services/backup.ixml weblets/dashboard.ixml` | **Options** | Option | Description | |--------|-------------| | `--verbose` | Show detailed output | **Process** 1. **State Loading** - Reads local sync configuration - Loads local file metadata - Fetches matching server metadata 2. **Comparison** - Compares local file hashes with stored sync hashes - Compares server hashes with stored sync hashes - Checks metadata properties for differences 3. **Output** - Groups results by asset type - Marks local, server, and conflict states - Opens a diff tool when comparing selected files with content differences **Status Indicators** | Symbol | Meaning | |--------|---------| | `+` | Created locally or added on server | | `-` | Deleted locally or deleted on server | | `~` | Modified locally or modified on server | | `!` | Conflict | | `<>` | Property conflict | | `->` | Local changes to be pushed | | `<-` | Server changes to be pulled | **Comparison Categories** The output is organized by asset type: - Settings - Services - Assets - Resources - Weblets **Examples** ```bash zeysdk compare zeysdk compare services/backup.ixml weblets/dashboard.ixml zeysdk compare --verbose ``` **Error Handling** | Error | Description | Resolution | |-------|-------------|------------| | Missing sync.json | No sync configuration | Run `zeysdk link` first | | Invalid sync.json | Missing id/url | Re-link instance | | Diff tool error | Tool launch failed | Select alternative tool | | Server error | Cannot fetch remote | Check connection/auth | | Parse error | Invalid file content | Check file format | **Notes** - `compare` does not change local files or server state - `compare` reads the active instance and may fetch remote file contents to support a diff - `compare` does not acquire `.zeysdk/.sync.lock`; avoid running it during a modifying synchronization command if you need a consistent snapshot - Passing specific asset paths limits comparison and may open a diff workflow - Metadata differences are reported separately from content changes - Diff tool behavior depends on the global CLI configuration - Remote snapshots reuse the content fetched during comparison, including empty files, and are removed after the comparison tool exits - Beyond Compare uses its waiting launcher (`bcomp` on macOS, `BComp.com` on Windows), so an already-open window can read the remote snapshot before it is removed - Beyond Compare's normal result codes (`0`, `1`, `2`, `11`–`14`) finish the comparison without prompting for another tool; launch failures and error codes remain failures - To use [Hunk](https://www.hunk.dev), install its CLI separately, then select **Default Diff Tool → Hunk** in `zeysdk configure`; the viewer runs in the current terminal - Custom comparison commands must wait until the viewer is finished before exiting **Best Practices** - Run `zeysdk compare` before `push`, `pull`, or `sync` when remote state may have changed - Compare specific files when investigating a focused change - Configure a diff tool with `zeysdk configure` for easier conflict review - Resolve unexpected differences before running forced sync commands --- Canonical: https://www.zeyos.com/sdk/synchronization/zeysdk-pull # Pull Files The `pull` command downloads server files to your local environment. ```bash zeysdk pull [ASSET_PATH...] [options] ``` **Parameters** | Parameter | Description | Required | Example | |-----------|-------------|----------|---------| | `ASSET_PATH` | Specific asset paths to pull | No | `services/backup.ixml` | Instance-specific settings must match the active instance. For example, when linked to `dev`, `pull settings/settings.production.json` is rejected before requesting server data or changing project files. Select `settings/settings.dev.json` or switch the active instance first. The same rule applies to push, sync, and watch. **Options** | Option | Description | |--------|-------------| | `-f, --force` | Skip the confirmation before applying the pull | | `--verbose` | Show detailed output | | `--debug` | Show detailed debug information | | `--debug-log` | Write debug output to `.zeysdk/debug.log` | | `--delete-local-only` | Delete files that only exist locally | | `--break-lock` | Replace the current sync lock after you verify its operation is no longer running | | `--dry-run` | Read and report planned changes without applying them | | `--concurrency ` | Maximum concurrent file operations (default: `10`) | | `-q, --quiet` | Suppress progress indicators | **Process** 1. **State Loading** - Reads `.zeysdk/sync.json` - Scans local files and metadata - Fetches server state for the active instance 2. **Change Detection** - Identifies files that differ from server state - Computes hashes for local files - Detects files that only exist locally when requested 3. **Pull Execution** - Downloads selected or detected server changes - Writes files to the local project - Updates local sync state after successful changes 4. **Safety Controls** - Acquires the project sync lock before scanning and releases its owner token on success, failure, or cancellation - Creates a rotating `sync.json` backup for a non-dry-run pull - Uses `--dry-run` to preview changes and `--force` to skip the apply confirmation **Locking** Pull uses `.zeysdk/.sync.lock`, shared with push, sync, rollout, and recover. If another operation owns the lock, pull exits without applying changes. A stale lock is reclaimed automatically. Use `--break-lock` only after confirming that the recorded process is no longer performing synchronization work; breaking a live lock can allow two destructive operations to overlap. The lock is released from a `finally` path, including when the user declines the confirmation prompt. **Dry Run** `--dry-run` still scans local files, reads the active instance, and calculates the pull and deletion plan. It does not patch the server, write or delete project files, update the manifest or `sync.json`, or create a sync-state backup. When `--debug-log` is also supplied, the requested `.zeysdk/debug.log` is still written. Combining it with `--delete-local-only` reports which local-only files would be removed without deleting them. **Sync-State Backup Scope** A non-dry-run pull rotates `.zeysdk/sync.json.backup*` before applying its change set. If pull reports an error, it attempts to restore the newest backup. This recovery covers `sync.json` only: it cannot undo local files or manifest data already changed before a later failure. Use `zeysdk recover --list` to inspect retained backups. **File Hashing and Synchronization** The pull process determines which files need to be synchronized by comparing stored hashes with the server hashes returned by the API. Local files are hashed when their file objects are built; there is no timestamp or file-size pre-check before hashing. The `lastmodified` value returned by the server is stored in `sync.json` for the synced item. It is not used to decide whether a file needs to be pulled. Use `--verbose` to see detailed comparison and processing output. **Examples** ```bash zeysdk pull --verbose zeysdk pull services/backup.ixml weblets/dashboard.ixml zeysdk pull -f zeysdk pull --dry-run ``` **Error Handling** | Error | Description | Resolution | |-------|-------------|------------| | Missing sync.json | No sync configuration | Run `zeysdk link` first | | Invalid sync.json | Missing id/url | Re-link instance | | Network error | Server unreachable | Check connection | | Auth error | Invalid/expired token | Re-link application | | Lock error | Sync lock already exists | Wait for current sync or use `--break-lock` | | Write error | Cannot write local file | Check permissions | **Warning** Pull operations can overwrite local changes. Use `zeysdk compare` first to check for local modifications. **Notes** - Pull uses the currently active instance - `--delete-local-only` removes files that exist locally but not on the server - Local-only files are preserved unless `--delete-local-only` is present - `--dry-run` reports planned changes without writing files, sync state, or manifest metadata - Automatic recovery restores only `sync.json`, not project files already written or deleted - Debug output is useful when investigating sync state or server responses **Best Practices** - Run `zeysdk compare` before pulling over local work - Commit or snapshot important local changes before a broad pull - Pull specific files when you only need selected server updates - Use `--break-lock` only after confirming no other sync operation is still running --- Canonical: https://www.zeyos.com/sdk/synchronization/zeysdk-push # Push Files The `push` command uploads local files to the ZeyOS server. ```bash zeysdk push [ASSET_PATH...] [options] ``` **Parameters** | Parameter | Description | Required | Example | |-----------|-------------|----------|---------| | `ASSET_PATH` | Specific asset paths to push | No | `services/backup.ixml` | Instance-specific settings must match the active instance. For example, when linked to `dev`, `push settings/settings.production.json` is rejected before requesting server data or changing project files. Select `settings/settings.dev.json` or switch the active instance first. **Options** | Option | Description | |--------|-------------| | `-f, --force` | Skip the confirmation before applying the push. Does not change what the token is authorized to do | | `--verbose` | Show detailed output | | `--debug` | Show detailed debug information | | `--debug-log` | Write debug output to `.zeysdk/debug.log` | | `--break-lock` | Replace the current sync lock after you verify its operation is no longer running | | `--dry-run` | Read and report planned server changes without applying them | | `--keep-remote` | Preserve server-only assets that have no sync baseline in this checkout, instead of deleting them | | `--concurrency ` | Maximum concurrent file operations (default: `10`) | | `-q, --quiet` | Suppress progress indicators | **Process** 1. **State Loading** - Reads `.zeysdk/sync.json` - Scans local files and manifest metadata - Fetches server state for comparison 2. **Change Detection** - Identifies files that changed locally - Detects local creates, deletes, and metadata updates - Marks services, weblets, resources, and assets that exist only on the server for deletion - Builds the server update payload 3. **Push Execution** - Uploads selected or detected local changes - Applies remote asset and metadata updates - Deletes server-only assets unless `--keep-remote` is supplied - Updates local sync state for the entries the server confirmed 4. **Safety Controls** - Acquires the project sync lock and releases its owner token on success, failure, or cancellation - Creates a rotating `sync.json` backup for a non-dry-run push - Uses `--dry-run` to preview server changes and `--force` to skip the apply confirmation **Authoritative Push** A whole-project push makes the server match the working copy. Any service, weblet, resource, or asset that exists on the server but not locally is deleted, whether or not this checkout has ever synchronized it. The preview lists each one as `delete` before the confirmation prompt, and the prompt states how many files will be removed. Three things are never deleted this way: - Settings slots (`settingscode`, `usersettingscode`, `defaultsettings`, `settings.<instance>.json`, `readme`) when the local project has no copy and no sync baseline for them. These are fixed server-side slots, so a missing local file means the project was never scaffolded with it rather than that the file was removed. - Paths outside a targeted push (`zeysdk push services/backup.ixml`), which only touches the paths you name. A selected tracked file that has been removed locally can still be deleted on the server. - Server-only assets with no sync baseline in this checkout, when `--keep-remote` is supplied. `--keep-remote` does not protect every asset that is missing locally. If this checkout has a sync baseline for an asset and you remove it locally, a whole-project push still deletes it from the server, with or without `--keep-remote`. Use `--dry-run` or `zeysdk compare` first when pushing to a shared instance, and `--keep-remote` when another developer's work may live on the same instance. **Timeouts** Requests time out after 30 seconds by default; set `ZEYSDK_REQUEST_TIMEOUT_MS` to change this. The request layer does not resend a push update automatically. `zeysdk watch` is the exception: it keeps a failed batch and pushes it again with backoff. After a timeout the outcome is unknown, because the server may have applied the change. Stop any running watcher and run `zeysdk compare` before pushing again. See [Verification Checkpoints](https://www.zeyos.com/ixml/ixml-guide/development-environment/verification-checkpoints). **Locking** Push uses `.zeysdk/.sync.lock`, shared with pull, sync, rollout, and recover. If another operation owns the lock, push exits without applying changes. A stale lock is reclaimed automatically. Use `--break-lock` only after confirming that the recorded process is no longer performing synchronization work. The lock is released from a `finally` path, including when the user declines the confirmation prompt. Rollout holds this same project lock and passes its ownership into each nested push. **Dry Run** `--dry-run` still scans local files and reads the active instance. It reports uploads and server deletions without patching the server, cleaning missing entries out of `zeyos.app.json`, updating `sync.json`, or creating a sync-state backup. When `--debug-log` is also supplied, the requested `.zeysdk/debug.log` is still written. During a normal push, manifest cleanup removes missing resource, service, and weblet entries before the change set is built. This cleanup is intentionally skipped for a dry run. **Sync-State Backup Scope** A non-dry-run push rotates `.zeysdk/sync.json.backup*` before applying its server change set. If push reports an error, it restores the pre-push manifest snapshot and attempts to restore the newest sync backup. It cannot reverse a server patch that succeeded before a later local update failed. Use `zeysdk recover --list` to inspect retained sync backups. **File Hashing and Synchronization** The push process determines which files need to be synchronized by comparing stored hashes with hashes calculated from the local files. There is no timestamp or file-size pre-check before hashing. The `lastmodified` value returned by the server after a push is stored in `sync.json` for the synced item. It is not used to decide whether a file needs to be pushed. Use `--verbose` to see detailed comparison and processing output. **Examples** ```bash zeysdk push --verbose zeysdk push services/backup.ixml weblets/dashboard.ixml zeysdk push -f zeysdk push --dry-run zeysdk push --keep-remote ``` **Error Handling** | Error | Description | Resolution | |-------|-------------|------------| | Missing sync.json | No sync configuration | Run `zeysdk link` first | | Invalid sync.json | Missing id/url | Re-link instance | | Network error | Server unreachable | Check connection | | Auth error | Invalid/expired token | Re-link application | | Lock error | Sync lock already exists | Wait for current sync or use `--break-lock` | | File error | Cannot read local file | Check permissions | **Notes** - Push uses the currently active instance - Push favors local content and can overwrite server-side changes; use `sync` when conflicts need explicit resolution - A whole-project push deletes server-only assets; pass `--keep-remote` to preserve those with no sync baseline in this checkout - A baseline is advanced only for entries the server echoed back in its PATCH response - Successful server deletions are also removed from the corresponding local sync baseline - `--dry-run` reports planned changes without modifying the server, manifest, or sync state - Automatic recovery restores only `sync.json`; it is not a server rollback - Debug output can be written to `.zeysdk/debug.log` **Best Practices** - Run `zeysdk compare` before pushing to shared or production instances - Push specific files when only part of the app should change - Use `--force` only when you have already reviewed the change set - Confirm the active instance with `zeysdk whoami` before production pushes --- Canonical: https://www.zeyos.com/sdk/synchronization/zeysdk-recover # Recover Sync State The `recover` command lists, restores, or deletes the rotating backups of `.zeysdk/sync.json` created by pull, push, and sync operations. ```bash zeysdk recover [options] ``` **Options** | Option | Description | |--------|-------------| | `--list` | List available backups, their indexes, paths, and relative ages | | `--backup ` | Restore a backup index; `0` is the newest and is the default | | `--clean` | Delete all rotating `sync.json` backups | | `-f, --force` | Skip the restore or cleanup confirmation | **Backup Files** Before applying changes in a non-dry-run `pull`, `push`, or `sync`, the CLI rotates up to three sync-state backups: ```text .zeysdk/sync.json.backup # index 0, newest .zeysdk/sync.json.backup.1 # index 1 .zeysdk/sync.json.backup.2 # index 2, oldest retained ``` The newest backup is retained after a successful operation and is also used for the automatic restore attempted when that operation reports a failure. **Operations** 1. **List backups** ```bash zeysdk recover --list ``` Listing does not change `sync.json` or the backups. 2. **Restore a backup** ```bash zeysdk recover zeysdk recover --backup 1 zeysdk recover --backup 0 --force ``` Restore overwrites the current `.zeysdk/sync.json`. Without `--force`, the command asks for confirmation and defaults to proceeding. 3. **Delete all backups** ```bash zeysdk recover --clean zeysdk recover --clean --force ``` Without `--force`, cleanup asks for confirmation and defaults to cancellation. Use one operation mode at a time. If options are combined, `--list` takes precedence over `--clean` and restore, while `--clean` takes precedence over restore. **Locking** Every recover mode, including `--list`, acquires `.zeysdk/.sync.lock` and releases it from a `finally` path. This prevents `sync.json` backup files from being listed, restored, or deleted while another synchronization command is rotating or updating them. Recover does not expose `--break-lock`. Wait for the current operation to finish and retry. A stale lock is reclaimed automatically. **Important Recovery Scope** Recover restores only `.zeysdk/sync.json`. It does not restore: - project source or asset files; - `zeyos.app.json` manifest changes; - remote server state or a server patch that already succeeded; - rollout's timestamped ZIP archives under `.zeysdk/backups/`. After recovering an older sync baseline, run `zeysdk compare` before the next modifying command. The recovered baseline may intentionally differ from both current local files and current server state. **Error Handling** | Error | Description | Resolution | |-------|-------------|------------| | No backups | No rotating sync-state backup exists | Run `--list`; recover files through version control or another backup if necessary | | Invalid index | The complete value is not a non-negative decimal integer, or the index is unavailable | Use an index reported by `zeysdk recover --list` | | Lock error | Another synchronization operation owns the project lock | Wait for it to finish and retry | | Restore error | The selected backup cannot be copied over `sync.json` | Check file permissions and available disk space | **Best Practices** - Run `zeysdk recover --list` before restoring. - Preserve or commit current work before replacing the sync baseline. - Use `zeysdk compare` immediately after recovery. - Treat rollout ZIP archives as manual snapshots, not as inputs to `zeysdk recover`. --- Canonical: https://www.zeyos.com/sdk/synchronization/zeysdk-sync # Sync Files (zeysdk sync) Sync Files (zeysdk sync) ======================== The `sync` command synchronizes local files with the ZeyOS server and handles bidirectional changes. ```bash zeysdk sync [ASSET_PATH...] [options] ``` **Parameters** | Parameter | Description | Required | Example | |-----------|-------------|----------|---------| | `ASSET_PATH` | Specific asset paths to sync | No | `services/backup.ixml` | **Options** | Option | Description | |--------|-------------| | `-f, --force` | Skip the broad change-set confirmation; conflict choices may still prompt | | `--verbose` | Show detailed output | | `--debug` | Show detailed debug information | | `--debug-log` | Write debug output to `.zeysdk/debug.log` | | `--resolve ` | Conflict resolution strategy (`mine`, `theirs`, `merge`, or `skip`) | | `--resolve-all` | Apply the `--resolve` strategy to every conflict instead of only the first | | `--no-resolve` | Leave conflicts and their sync baselines untouched without prompting | | `--break-lock` | Replace the current sync lock after you verify its operation is no longer running | | `--dry-run` | Read and report planned changes without applying them | | `--concurrency ` | Maximum concurrent file operations (default: `10`) | | `-q, --quiet` | Suppress progress indicators | **Process** 1. **Preparation** - Validates `.zeysdk/sync.json` - Removes stale SDK-owned merge temporary files - Acquires the project sync lock - For a non-dry-run operation, cleans missing manifest entries and creates a rotating `sync.json` backup 2. **State Collection** - Scans local files and settings - Fetches server state from the active instance - Computes local, sync, and server hashes 3. **Change Planning** - Builds local-to-server and server-to-local changes - Detects deletes and conflicts - Displays the planned synchronization result 4. **Conflict Handling** - Prompts for conflict resolution when needed - Applies `--resolve` to the first conflict, or to every conflict when combined with `--resolve-all` - Leaves skipped and unresolved conflicts, including their common sync baseline, untouched 5. **Apply Changes** - Pushes local changes to the server - Pulls server changes to local files - Updates sync state and manifest metadata - Releases the sync lock **Locking** Sync uses `.zeysdk/.sync.lock`, shared with pull, push, rollout, and recover. Lock creation is atomic and release is tied to the acquiring owner's token. If another operation owns the lock, sync exits without applying changes. A stale lock is reclaimed automatically. Use `--break-lock` only after confirming that the recorded process is no longer performing synchronization work. The lock is released from a `finally` path when sync succeeds, fails, or the user cancels a prompt. **Dry Run** `--dry-run` scans local files, reads server state, computes changes, and reports conflicts. It returns before conflict resolution and before any apply step, even when `--resolve` is also supplied. It does not apply the planned server updates or deletions, change project files, clean or update `zeyos.app.json`, update `sync.json`, or create a sync-state backup. When `--debug-log` is also supplied, the requested `.zeysdk/debug.log` is still written. The command may still remove an SDK-owned `zeysdk-sync-*` merge temporary file older than one hour from the system temporary directory. **Status Indicators** | Symbol | Meaning | |--------|---------| | `+` | Created locally or added on server | | `-` | Deleted locally or deleted on server | | `~` | Modified locally or modified on server | | `!` | Conflict | | `<>` | Property conflict | | `->` | Local changes to be pushed | | `<-` | Server changes to be pulled | **Conflict Resolution** When conflicts are detected, sync displays content and property differences and offers these strategies: | Strategy | Result | |----------|--------| | `mine` | Use local content and metadata and send it to the server | | `theirs` | Request server content and write it locally | | `merge` | Open the configured comparison tool, then use the merged local file as the outgoing version | | `skip` | Do not apply the conflict and do not advance its sync baseline | `--resolve ` resolves only the first conflict and skips the remaining conflicts. Add `--resolve-all` to apply the same strategy to every conflict. `--resolve-all` has no effect by itself. `--no-resolve` suppresses conflict prompts and leaves every conflict and its baseline untouched; non-conflicting changes can still be applied. `--force` skips the broad change-set confirmation but does not select a conflict strategy, so conflicts still prompt unless `--resolve` or `--no-resolve` is present. **File Hashing and Synchronization** The sync process determines which files need to be synchronized by comparing stored hashes with hashes calculated from local files and hashes returned by the server. There is no timestamp or file-size pre-check before hashing. The `lastmodified` value returned by the server is stored in `sync.json` for the synced item. It is not used to decide whether a file needs to be synchronized. Use `--verbose` to see detailed comparison and processing output. **Examples** ```bash zeysdk sync zeysdk sync weblets/dashboard.ixml zeysdk sync -f zeysdk sync --dry-run zeysdk sync --resolve mine --resolve-all zeysdk sync --no-resolve ``` **Error Handling** | Error | Description | Resolution | |-------|-------------|------------| | Missing sync.json | No sync configuration | Run `zeysdk link` first | | Invalid sync.json | Missing id/url | Re-link instance | | Network error | Server unreachable | Check connection | | Auth error | Invalid/expired token | Re-link application | | Merge conflict | Unresolved changes | Choose a strategy, use `--no-resolve`, or rerun after manual reconciliation | | Lock error | Sync lock already exists | Wait for current sync or use `--break-lock` | | File system error | IO problems | Check permissions | **Notes** - `sync` is bidirectional and can change both local files and server state - `--dry-run` previews planned operations without changing project, manifest, server, or sync state - A non-dry-run sync creates a rotating backup of `sync.json` before applying changes - On failure, automatic restore covers only `sync.json`; it cannot undo local, manifest, or server changes already applied - Skipped and unresolved conflicts retain their previous sync baseline for a future reconciliation - `lastmodified` is stored for synced items but is not used to decide whether files changed **Best Practices** - Use `zeysdk compare` before syncing when you expect conflicts - Use `--dry-run` for production or scripted workflows - Keep conflict resolution explicit with `--resolve` and `--resolve-all` when running non-interactively - Use `zeysdk recover --list` to inspect retained sync-state backups after an interrupted operation - Avoid `--break-lock` unless a previous sync was interrupted and no sync process is active --- Canonical: https://www.zeyos.com/sdk/synchronization/zeysdk-watch # Watch Files The `watch` command monitors local files and automatically pushes updates to the ZeyOS server. ```bash zeysdk watch [options] ``` **Options** | Option | Description | |--------|-------------| | `--verbose` | Show detailed output | **Watched Directories** The following directories and files are monitored for changes: - `settings` - Configuration files - `services` - Service scripts - `weblets` - Web components - `resources` - Static resources - `assets` - Application assets - `zeyos.app.json` - Application manifest - `README.md` - Application readme **Process** 1. **Validation** - Validates active sync configuration - Checks each configured watch target before creating its watcher 2. **Watching** - Sets the interactive terminal title to `Watch: ` using the identifier from `zeyos.app.json` - Watches configured directories recursively - Skips missing targets at startup while a root watcher discovers recognized content directories created later - Ignores `.git`, `.zeysdk`, `node_modules`, editor temporary files, and unrelated project paths - Expands directory events to their relevant files and deduplicates changed paths during a 200 ms debounce window 3. **Auto Push** - Pushes one queued batch at a time - Queues changes received during an active push for a following batch - Uses force mode for automatic pushes and logs errors without stopping the watchers - Retains failed batches and retries them with bounded exponential backoff from 500 ms up to 30 seconds **Locking and Concurrency** Watch does not hold `.zeysdk/.sync.lock` continuously. Each automatic push acquires the same project lock used by pull, push, sync, rollout, and recover, then releases it when that batch finishes. This serializes batches within one watch process and prevents a batch from overlapping another lock-owning operation. Watch has no `--break-lock` option. If a batch cannot acquire the lock or its push fails, the error is logged, the batch remains queued, and watching retries it with backoff. New changes join the retained batch. Run `zeysdk push` manually if you need to resolve the error outside watch mode. **Automatic Push Scope** Every batch is equivalent to a forced push of its changed paths: there is no confirmation and no dry-run mode. The server may be updated immediately. A non-dry-run push also creates a rotating `sync.json` backup, but that backup cannot undo a server update. Removed files and directories are resolved against the manifest and sync baseline, and only those tracked paths are sent for deletion. Untracked removals are ignored; watch never escalates a removal into a whole-project push. Instance-specific settings files for another instance are rejected and excluded from the batch. **Examples** ```bash zeysdk watch zeysdk watch --verbose ``` **Error Handling** | Error | Description | Resolution | |-------|-------------|------------| | Missing sync.json | No sync configuration | Run `zeysdk link` first | | Invalid sync.json | Missing id/url | Re-link instance | | Watch error | Cannot monitor directory | Check permissions | | Push error | Failed to upload changes | Check connection | | Auth error | Invalid/expired token | Re-link application | | Lock error | Another synchronization operation owns the lock | Wait for it to finish, then save again or run `zeysdk push` | **Notes** - Changes are pushed automatically - Pushes run in force mode with a 200 ms debounce and are serialized within the watcher - Failed batches remain queued and retry with bounded backoff up to 30 seconds - Watch has no dry-run mode and does not hold the project lock between batches - Missing targets are skipped at startup; the root watcher can discover recognized content directories created later - Directory removals send only known descendants; unrelated remote-only files are preserved - Stop watch with the normal process interrupt (for example, `Ctrl+C`) **Best Practices** - Use `watch` only when you intend local changes to reach the active instance immediately - Confirm the active instance with `zeysdk whoami` before starting watch - Avoid watch mode for production instances unless the workflow is intentional - Stop and restart watch after changing linked instances or credentials --- Canonical: https://www.zeyos.com/sdk/views # ZeyOS Views Reference Complete list of available views for weblet configuration (`--view` parameter). ## Main Views - `main.index` - `main.index-user` - `main.index-group` - `main.monitor` - `main.monitor.export` - `main.files` - `main.files.export` ## Control Panel - `control.index` - `control.index.profile.form` - `control.import` - `control.servers` - `control.servers.mailservers` - `control.servers.mailservers.export` - `control.servers.davservers` - `control.servers.davservers.export` - `control.servers.feedservers` - `control.servers.feedservers.export` ### Mail Server Details - `control.details_mailserver` - `control.details_mailserver.head` - `control.details_mailserver.masterdata` - `control.details_mailserver.masterdata.form` - `control.details_mailserver.form` ### DAV Server Details - `control.details_davserver` - `control.details_davserver.head` - `control.details_davserver.masterdata` - `control.details_davserver.masterdata.form` - `control.details_davserver.form` ### Feed Server Details - `control.details_feedserver` - `control.details_feedserver.head` - `control.details_feedserver.masterdata` - `control.details_feedserver.masterdata.form` - `control.details_feedserver.form` ## Messages - `messages.index` - `messages.index.list` - `messages.index.table` - `messages.index.export` - `messages.details_message` - `messages.details_message.head` - `messages.details_message.masterdata` - `messages.details_message.masterdata.form` - `messages.details_message.newticket.form` - `messages.details_message.form` - `messages.details_message.attachments` ## Mailing Lists - `mailinglists.index` - `mailinglists.index.export` - `mailinglists.details_mailinglist` - `mailinglists.details_mailinglist.head` - `mailinglists.details_mailinglist.masterdata` - `mailinglists.details_mailinglist.masterdata.form` - `mailinglists.details_mailinglist.newcampaign.form` - `mailinglists.details_mailinglist.details` - `mailinglists.details_mailinglist.details.form` - `mailinglists.details_mailinglist.participants` - `mailinglists.details_mailinglist.participants.export` - `mailinglists.details_mailinglist.participants.addcontact.form` - `mailinglists.details_mailinglist.participants.addparticipant.form` - `mailinglists.participant.form` ## Contacts - `contacts.index` - `contacts.index.cards` - `contacts.index.table` - `contacts.index.export` - `contacts.index.new.form` - `contacts.details_contact` - `contacts.details_contact.head` - `contacts.details_contact.masterdata` - `contacts.details_contact.masterdata.form` - `contacts.details_contact.contactdata` - `contacts.details_contact.contactdata.form` - `contacts.details_contact.form` - `contacts.details_contact.contacts.addnew.form` ## Calendar - `calendar.index` - `calendar.index.agenda` - `calendar.index.day` - `calendar.index.week` - `calendar.index.month` - `calendar.index.year` - `calendar.appointments` - `calendar.appointments.export` - `calendar.appointments.new.form` - `calendar.details_appointment` - `calendar.details_appointment.head` - `calendar.details_appointment.masterdata` - `calendar.details_appointment.masterdata.form` - `calendar.details_appointment.form` ## Projects - `projects.index` - `projects.index.export` - `projects.index.new.form` - `projects.details_project` - `projects.details_project.head` - `projects.details_project.masterdata` - `projects.details_project.masterdata.form` - `projects.details_project.form` - `projects.details_project.tasks.addnew.form` - `projects.details_project.tickets.addnew.form` ## Tasks - `tasks.index` - `tasks.index.export` - `tasks.index.new.form` - `tasks.details_task` - `tasks.details_task.head` - `tasks.details_task.masterdata` - `tasks.details_task.masterdata.form` - `tasks.details_task.form` - `tasks.details_task.actionsteps.addnew.form` ## Action Steps - `actionsteps.index` - `actionsteps.index.export` - `actionsteps.index.new.form` - `actionsteps.details_actionstep` - `actionsteps.details_actionstep.head` - `actionsteps.details_actionstep.masterdata` - `actionsteps.details_actionstep.masterdata.form` - `actionsteps.details_actionstep.form` ## Notes - `notes.index` - `notes.index.export` - `notes.index.new.form` - `notes.details_note` - `notes.details_note.head` - `notes.details_note.masterdata` - `notes.details_note.masterdata.form` - `notes.details_note.form` - `notes.details_note.attachments` ## Links - `links.index` - `links.index.export` - `links.index.new.form` - `links.details_link` - `links.details_link.head` - `links.details_link.masterdata` - `links.details_link.masterdata.form` - `links.details_link.form` ## Accounts - `accounts.index` - `accounts.index.cards` - `accounts.index.table` - `accounts.index.export` - `accounts.details_account` - `accounts.details_account.head` - `accounts.details_account.masterdata` - `accounts.details_account.masterdata.form` - `accounts.details_account.newcontact.form` - `accounts.details_account.contactdata` - `accounts.details_account.details` - `accounts.details_account.details.form` - `accounts.details_account.contacts.addnew.form` - `accounts.details_account.addresses` - `accounts.details_account.addresses.export` - `accounts.details_account.addresses.addnew.form` - `accounts.details_account.items` - `accounts.details_account.items.export` - `accounts.details_account.prices` - `accounts.details_account.prices.export` - `accounts.details_account.cron` ## Billing - `billing.index` - `billing.index.export` - `billing.index.quotes` - `billing.index.quotes.export` - `billing.index.orders` - `billing.index.orders.export` - `billing.index.deliveries` - `billing.index.deliveries.export` - `billing.index.invoices` - `billing.index.invoices.export` - `billing.index.credits` - `billing.index.credits.export` - `billing.details_transaction` - `billing.details_transaction.head` - `billing.details_transaction.masterdata` - `billing.details_transaction.masterdata.form` - `billing.details_transaction.addressdata` - `billing.details_transaction.addressdata.form` - `billing.details_transaction.form` - `billing.details_transaction.items` - `billing.details_transaction.payments.addnew.form` ## Procurement - `procurement.index` - `procurement.index.export` - `procurement.index.orders` - `procurement.index.orders.export` - `procurement.index.deliveries` - `procurement.index.deliveries.export` - `procurement.index.invoices` - `procurement.index.invoices.export` - `procurement.index.credits` - `procurement.index.credits.export` - `procurement.details_transaction` - `procurement.details_transaction.head` - `procurement.details_transaction.masterdata` - `procurement.details_transaction.masterdata.form` - `procurement.details_transaction.addressdata` - `procurement.details_transaction.addressdata.form` - `procurement.details_transaction.form` - `procurement.details_transaction.items` - `procurement.details_transaction.payments.addnew.form` ## Collection - `collection.index` - `collection.index.receivables` - `collection.index.receivables.export` - `collection.index.payables` - `collection.index.payables.export` - `collection.index.dunning` - `collection.index.dunning.export` - `collection.details_dunning` - `collection.details_dunning.head` - `collection.details_dunning.masterdata` - `collection.details_dunning.masterdata.form` - `collection.details_dunning.addressdata` - `collection.details_dunning.addressdata.form` - `collection.details_dunning.form` - `collection.details_dunning.transactions` ## Payments - `payments.index` - `payments.index.export` - `payments.index.new.form` - `payments.ledgers` - `payments.ledgers.export` - `payments.ledgers.new.form` - `payments.details_payment` - `payments.details_payment.head` - `payments.details_payment.masterdata` - `payments.details_payment.masterdata.form` - `payments.details_payment.form` - `payments.details_ledger` - `payments.details_ledger.head` - `payments.details_ledger.masterdata` - `payments.details_ledger.masterdata.form` - `payments.details_ledger.form` ## Contracts - `contracts.index` - `contracts.index.export` - `contracts.index.new.form` - `contracts.details_contract` - `contracts.details_contract.head` - `contracts.details_contract.masterdata` - `contracts.details_contract.masterdata.form` - `contracts.details_contract.details` - `contracts.details_contract.details.form` - `contracts.details_contract.billingitems` - `contracts.details_contract.procurementitems` ## Tickets - `tickets.index` - `tickets.index.export` - `tickets.index.new.form` - `tickets.details_ticket` - `tickets.details_ticket.head` - `tickets.details_ticket.masterdata` - `tickets.details_ticket.masterdata.form` - `tickets.details_ticket.details` - `tickets.details_ticket.details.form` - `tickets.details_ticket.tasks.addnew.form` - `tickets.details_ticket.actionsteps.addnew.form` - `tickets.details_ticket.billingitems` - `tickets.details_ticket.procurementitems` ## Opportunities - `opportunities.index` - `opportunities.index.export` - `opportunities.index.new.form` - `opportunities.details_opportunity` - `opportunities.details_opportunity.head` - `opportunities.details_opportunity.masterdata` - `opportunities.details_opportunity.masterdata.form` - `opportunities.details_opportunity.form` ## Campaigns - `campaigns.index` - `campaigns.index.export` - `campaigns.index.new.form` - `campaigns.details_campaign` - `campaigns.details_campaign.head` - `campaigns.details_campaign.masterdata` - `campaigns.details_campaign.masterdata.form` - `campaigns.details_campaign.details` - `campaigns.details_campaign.details.form` - `campaigns.details_campaign.participants` - `campaigns.details_campaign.participants.export` - `campaigns.details_campaign.participants.addcontact.form` - `campaigns.details_campaign.participants.addparticipant.form` - `campaigns.participant.form` ## Inventory - `inventory.index` - `inventory.index.export` - `inventory.index.new.form` - `inventory.storages` - `inventory.storages.export` - `inventory.storages.new.form` - `inventory.devices` - `inventory.devices.export` - `inventory.transactions` - `inventory.transactions.export` - `inventory.details_item` - `inventory.details_item.head` - `inventory.details_item.masterdata` - `inventory.details_item.masterdata.form` - `inventory.details_item.details` - `inventory.details_item.details.form` - `inventory.details_item.inventory` - `inventory.details_item.suppliers` - `inventory.details_item.prices` - `inventory.details_item.prices.export` - `inventory.details_item.components` - `inventory.details_item.components.export` - `inventory.details_item.transactions` - `inventory.details_item.transactions.export` - `inventory.details_item.transactions.addtransaction.form` - `inventory.details_item.transactions.addreservation.form` - `inventory.details_item.transactions.transfer.form` - `inventory.details_item.locations` - `inventory.details_item.locations.export` - `inventory.details_item.serials` - `inventory.details_item.serials.export` - `inventory.details_item.charges` - `inventory.details_item.charges.export` - `inventory.details_item.variants` - `inventory.details_item.variants.export` - `inventory.details_storage` - `inventory.details_storage.head` - `inventory.details_storage.masterdata` - `inventory.details_storage.masterdata.form` - `inventory.details_storage.details` - `inventory.details_storage.details.form` - `inventory.details_storage.locations` - `inventory.details_storage.locations.export` - `inventory.details_device` - `inventory.details_device.head` - `inventory.details_device.masterdata` - `inventory.details_device.masterdata.form` - `inventory.details_device.details` - `inventory.details_device.details.form` - `inventory.details_device.transactions` - `inventory.details_device.transactions.export` - `inventory.transaction.form` ## Price Lists - `pricelists.index` - `pricelists.index.pricelists` - `pricelists.index.pricelists.export` - `pricelists.index.pricelists.new.form` - `pricelists.index.coupons` - `pricelists.index.coupons.export` - `pricelists.index.coupons.new.form` - `pricelists.details_pricelist` - `pricelists.details_pricelist.head` - `pricelists.details_pricelist.masterdata` - `pricelists.details_pricelist.masterdata.form` - `pricelists.details_pricelist.details` - `pricelists.details_pricelist.details.form` - `pricelists.details_pricelist.prices` - `pricelists.details_pricelist.prices.export` - `pricelists.details_pricelist.accounts` - `pricelists.details_pricelist.accounts.export` - `pricelists.details_coupon` - `pricelists.details_coupon.head` - `pricelists.details_coupon.masterdata` - `pricelists.details_coupon.masterdata.form` - `pricelists.details_coupon.details` - `pricelists.details_coupon.details.form` - `pricelists.details_coupon.codes` - `pricelists.details_coupon.codes.export` - `pricelists.details_coupon.codes.add.form` - `pricelists.details_coupon.code.form` ## Production - `production.index` - `production.index.export` - `production.index.fabrications` - `production.index.fabrications.export` - `production.index.disassemblies` - `production.index.disassemblies.export` - `production.details_transaction` - `production.details_transaction.head` - `production.details_transaction.masterdata` - `production.details_transaction.masterdata.form` - `production.details_transaction.form` - `production.details_transaction.items` - `production.details_transaction.process.form` ## Administration - `admin.index` - `admin.index.users` - `admin.index.users.export` - `admin.index.users.new.form` - `admin.index.groups` - `admin.index.groups.export` - `admin.index.groups.new.form` - `admin.subscription` - `admin.fields` - `admin.fields.extdatafields` - `admin.fields.tagfields` - `admin.fields.taggroupfields` - `admin.config` - `admin.logs` - `admin.details_user` - `admin.details_user.head` - `admin.details_user.masterdata` - `admin.details_user.masterdata.form` - `admin.details_user.newcontact.form` - `admin.details_user.contactdata` - `admin.details_user.form` - `admin.details_group` - `admin.details_group.head` - `admin.details_group.masterdata` - `admin.details_group.masterdata.form` - `admin.details_group.form` - `admin.details_application` - `admin.details_application.head` - `admin.details_application.masterdata` - `admin.details_application.masterdata.form` - `admin.details_application.form` - `admin.details_service` - `admin.details_service.head` - `admin.details_service.masterdata` - `admin.details_service.masterdata.form` - `admin.details_service.form` - `admin.details_resource` - `admin.details_resource.head` - `admin.details_resource.masterdata` - `admin.details_resource.masterdata.form` - `admin.details_resource.form` - `admin.details_weblet` - `admin.details_weblet.head` - `admin.details_weblet.masterdata` - `admin.details_weblet.masterdata.form` - `admin.details_weblet.form` - `admin.details_object` - `admin.details_object.head` - `admin.details_object.masterdata` - `admin.details_object.masterdata.form` - `admin.details_object.form` ## Development - `dev.index` - `dev.index.applications` - `dev.index.applications.export` - `dev.index.applications.new.form` - `dev.index.services` - `dev.index.services.export` - `dev.index.resources` - `dev.index.resources.export` - `dev.index.weblets` - `dev.index.weblets.export` - `dev.objects` - `dev.objects.export` - `dev.objects.new.form` - `dev.db` - `dev.db.tables` - `dev.db.tables.export` - `dev.db.indexes` - `dev.db.indexes.export` - `dev.db.schemas` - `dev.db.schemas.export` - `dev.db.backends` - `dev.db.backends.export` - `dev.sql` - `dev.sql.export` - `dev.extdata` - `dev.extdata.export` - `dev.tags` - `dev.tags.export`