@zeyos/client 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/LICENSE +21 -0
  3. package/README.md +458 -0
  4. package/agents/README.md +66 -0
  5. package/agents/shared/business-app-benchmarks.md +111 -0
  6. package/agents/shared/zeyos-entity-map.md +142 -0
  7. package/agents/shared/zeyos-entity-reference.md +570 -0
  8. package/agents/shared/zeyos-query-patterns.md +89 -0
  9. package/agents/zeyos-account-intelligence/SKILL.md +34 -0
  10. package/agents/zeyos-account-intelligence/agents/openai.yaml +4 -0
  11. package/agents/zeyos-account-intelligence/references/workflows.md +84 -0
  12. package/agents/zeyos-billing-insights/SKILL.md +41 -0
  13. package/agents/zeyos-billing-insights/agents/openai.yaml +4 -0
  14. package/agents/zeyos-billing-insights/references/workflows.md +106 -0
  15. package/agents/zeyos-campaign-and-outreach/SKILL.md +44 -0
  16. package/agents/zeyos-campaign-and-outreach/agents/openai.yaml +4 -0
  17. package/agents/zeyos-campaign-and-outreach/references/workflows.md +100 -0
  18. package/agents/zeyos-collaboration-and-activity/SKILL.md +37 -0
  19. package/agents/zeyos-collaboration-and-activity/agents/openai.yaml +4 -0
  20. package/agents/zeyos-collaboration-and-activity/references/workflows.md +104 -0
  21. package/agents/zeyos-collections-and-dunning/SKILL.md +46 -0
  22. package/agents/zeyos-collections-and-dunning/agents/openai.yaml +4 -0
  23. package/agents/zeyos-collections-and-dunning/references/workflows.md +132 -0
  24. package/agents/zeyos-commerce-and-inventory/SKILL.md +38 -0
  25. package/agents/zeyos-commerce-and-inventory/agents/openai.yaml +4 -0
  26. package/agents/zeyos-commerce-and-inventory/references/workflows.md +101 -0
  27. package/agents/zeyos-mail-operations/SKILL.md +35 -0
  28. package/agents/zeyos-mail-operations/agents/openai.yaml +4 -0
  29. package/agents/zeyos-mail-operations/references/workflows.md +110 -0
  30. package/agents/zeyos-notes-and-sops/SKILL.md +31 -0
  31. package/agents/zeyos-notes-and-sops/agents/openai.yaml +4 -0
  32. package/agents/zeyos-notes-and-sops/references/workflows.md +85 -0
  33. package/agents/zeyos-platform-and-schema/SKILL.md +37 -0
  34. package/agents/zeyos-platform-and-schema/agents/openai.yaml +4 -0
  35. package/agents/zeyos-platform-and-schema/references/workflows.md +97 -0
  36. package/agents/zeyos-work-management/SKILL.md +45 -0
  37. package/agents/zeyos-work-management/agents/openai.yaml +4 -0
  38. package/agents/zeyos-work-management/references/workflows.md +148 -0
  39. package/docs/01-api-reference/01-data-retrieval.md +601 -0
  40. package/docs/01-api-reference/02-authentication.md +288 -0
  41. package/docs/01-api-reference/03-resources.md +270 -0
  42. package/docs/01-api-reference/04-schema.md +539 -0
  43. package/docs/01-api-reference/_category_.json +9 -0
  44. package/docs/02-javascript-client/01-getting-started.md +146 -0
  45. package/docs/02-javascript-client/02-authentication.md +287 -0
  46. package/docs/02-javascript-client/03-making-requests.md +572 -0
  47. package/docs/02-javascript-client/04-practical-guide.md +348 -0
  48. package/docs/02-javascript-client/_category_.json +9 -0
  49. package/docs/03-cli/01-getting-started.md +219 -0
  50. package/docs/03-cli/02-commands.md +407 -0
  51. package/docs/03-cli/03-configuration.md +220 -0
  52. package/docs/03-cli/_category_.json +9 -0
  53. package/docs/04-agent-workflows/00-coding-agents.md +35 -0
  54. package/docs/04-agent-workflows/01-agent-quickstart.md +147 -0
  55. package/docs/04-agent-workflows/02-agent-recipes.md +109 -0
  56. package/docs/04-agent-workflows/03-cli-coverage-and-escalation.md +65 -0
  57. package/docs/04-agent-workflows/_category_.json +9 -0
  58. package/docs/04-sample-apps/01-kanban.md +89 -0
  59. package/docs/04-sample-apps/02-crm.md +81 -0
  60. package/docs/04-sample-apps/03-dashboard.md +80 -0
  61. package/docs/04-sample-apps/_category_.json +9 -0
  62. package/docs/05-tutorials/00-application-developers.md +43 -0
  63. package/docs/05-tutorials/01-integration-architecture.md +60 -0
  64. package/docs/05-tutorials/02-build-your-own-zeyos-frontend.md +517 -0
  65. package/docs/05-tutorials/03-server-side-integrations.md +185 -0
  66. package/docs/05-tutorials/_category_.json +9 -0
  67. package/docs/intro.md +197 -0
  68. package/openapi/api.json +24308 -0
  69. package/openapi/auth.json +415 -0
  70. package/openapi/dbref.json +56223 -0
  71. package/openapi/oauth2.json +781 -0
  72. package/openapi/sdk.json +949 -0
  73. package/openapi/views.txt +642 -0
  74. package/package.json +49 -0
  75. package/samples/crm/README.md +28 -0
  76. package/samples/crm/index.html +327 -0
  77. package/samples/crm/js/api.js +208 -0
  78. package/samples/crm/js/auth.js +61 -0
  79. package/samples/crm/js/main.js +545 -0
  80. package/samples/crm/js/state.js +90 -0
  81. package/samples/crm/js/ui.js +51 -0
  82. package/samples/dashboard/README.md +28 -0
  83. package/samples/dashboard/index.html +280 -0
  84. package/samples/dashboard/js/api.js +197 -0
  85. package/samples/dashboard/js/auth.js +59 -0
  86. package/samples/dashboard/js/main.js +382 -0
  87. package/samples/dashboard/js/state.js +81 -0
  88. package/samples/dashboard/js/ui.js +48 -0
  89. package/samples/kanban/README.md +28 -0
  90. package/samples/kanban/index.html +263 -0
  91. package/samples/kanban/js/api.js +152 -0
  92. package/samples/kanban/js/auth.js +59 -0
  93. package/samples/kanban/js/constants.js +40 -0
  94. package/samples/kanban/js/kanban.js +246 -0
  95. package/samples/kanban/js/main.js +362 -0
  96. package/samples/kanban/js/modals.js +474 -0
  97. package/samples/kanban/js/settings.js +82 -0
  98. package/samples/kanban/js/state.js +118 -0
  99. package/samples/kanban/js/ui.js +49 -0
  100. package/scripts/generate-client.mjs +344 -0
  101. package/src/generated/operations.js +9772 -0
  102. package/src/generated/schema.js +8982 -0
  103. package/src/index.js +85 -0
  104. package/src/runtime/client.js +1208 -0
  105. package/src/runtime/error.js +29 -0
  106. package/src/runtime/http.js +174 -0
  107. package/src/runtime/request-shape.js +35 -0
  108. package/src/runtime/schema.js +206 -0
  109. package/src/runtime/suggest.js +74 -0
  110. package/src/runtime/token-store.js +105 -0
@@ -0,0 +1,60 @@
1
+ ---
2
+ sidebar_position: 2
3
+ sidebar_label: Integration Architecture
4
+ ---
5
+
6
+ # Integration Architecture
7
+
8
+ 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.
9
+
10
+ ## Comparison
11
+
12
+ | Model | Where code runs | Auth model | Best for | Main constraint |
13
+ |-------|-----------------|------------|----------|-----------------|
14
+ | 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 |
15
+ | 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 |
16
+ | 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 |
17
+
18
+ ## Recommended Choices
19
+
20
+ ### Use browser session mode when:
21
+
22
+ - the user is already logged into ZeyOS
23
+ - the UI runs on the same origin or an allowed credentialed origin
24
+ - you want the simplest browser auth flow with no token storage in the page
25
+
26
+ ### Use browser token mode when:
27
+
28
+ - you already have tokens from a trusted flow
29
+ - you are building a local demo or internal prototype
30
+ - you can avoid embedding client secrets in shipped browser code
31
+
32
+ ### Use server token mode when:
33
+
34
+ - the application runs without a live browser session
35
+ - you need retries, scheduling, background work, or webhook handling
36
+ - you want one place to centralize token refresh and request logging
37
+
38
+ 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`.
39
+
40
+ ## Interface Selection
41
+
42
+ | Need | Interface |
43
+ |------|-----------|
44
+ | JavaScript application code with full API coverage | `@zeyos/client` |
45
+ | Shell-driven operational workflows | `zeyos` CLI |
46
+ | Another language or custom SDK | REST/OpenAPI |
47
+
48
+ ## Cross-Cutting Rules
49
+
50
+ - List operations are `POST` requests in ZeyOS, even though they behave like queries.
51
+ - Prefer `filters` in client code for consistent handling of scalar and foreign-key fields.
52
+ - Include `visibility: 0` in normal list queries.
53
+ - Use `body: { ... }` for updates that also pass `ID`.
54
+ - Treat count-enabled responses defensively. Different endpoints or client layers may return either a count wrapper or a list wrapper with count metadata.
55
+
56
+ ## Recommended Reading Order
57
+
58
+ 1. [Browser UI Playbook](./02-build-your-own-zeyos-frontend.md) for user-facing frontends
59
+ 2. [Server-Side Integrations](./03-server-side-integrations.md) for services, workers, and scheduled jobs
60
+ 3. [Making Requests](../02-javascript-client/03-making-requests.md) for the full request model
@@ -0,0 +1,517 @@
1
+ ---
2
+ sidebar_position: 3
3
+ sidebar_label: Browser UI Playbook
4
+ ---
5
+
6
+ # Browser UI Playbook
7
+
8
+ 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.
9
+
10
+ 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.
11
+
12
+ ---
13
+
14
+ ## Prerequisites
15
+
16
+ - A **ZeyOS instance** with some ticket data (e.g. `https://cloud.zeyos.com/demo/`)
17
+ - An **access token** (obtain one via the [CLI](../03-cli/01-getting-started.md) or the ZeyOS OAuth2 flow)
18
+ - A **local HTTP server** to serve static files (e.g. `python3 -m http.server 8080` or `npx serve .`)
19
+
20
+ ---
21
+
22
+ ## Step 1: Project Setup
23
+
24
+ Create a project folder with this structure:
25
+
26
+ ```
27
+ my-zeyos-app/
28
+ index.html
29
+ app.js
30
+ zeyos-client/ # symlink or copy of the @zeyos/client src/ directory
31
+ ```
32
+
33
+ Link the ZeyOS client source so your browser can import it:
34
+
35
+ ```bash
36
+ # Symlink (Linux/macOS)
37
+ ln -s /path/to/zeyos/client/src ./zeyos-client
38
+
39
+ # Or copy
40
+ cp -r /path/to/zeyos/client/src ./zeyos-client
41
+ ```
42
+
43
+ Create a minimal `index.html`:
44
+
45
+ ```html
46
+ <!DOCTYPE html>
47
+ <html lang="en">
48
+ <head>
49
+ <meta charset="UTF-8">
50
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
51
+ <title>My ZeyOS App</title>
52
+ <script src="https://cdn.tailwindcss.com"></script>
53
+ </head>
54
+ <body class="bg-gray-50 min-h-screen font-sans p-8">
55
+
56
+ <div id="app">
57
+ <h1 class="text-2xl font-bold mb-6">My ZeyOS Ticket Dashboard</h1>
58
+ <div id="content">Loading...</div>
59
+ </div>
60
+
61
+ <script type="module" src="./app.js"></script>
62
+ </body>
63
+ </html>
64
+ ```
65
+
66
+ ---
67
+
68
+ ## Step 2: Initialize the Client
69
+
70
+ Create `app.js` and set up the ZeyOS client. You have two authentication options:
71
+
72
+ ### Option A: Token Mode
73
+
74
+ Use this when you already have an OAuth access token. This is most useful for development and controlled demos:
75
+
76
+ ```js
77
+ import { createZeyosClient, MemoryTokenStore, normalizeListResult } from './zeyos-client/index.js';
78
+
79
+ const client = createZeyosClient({
80
+ platform: 'https://cloud.zeyos.com/demo/',
81
+ auth: {
82
+ mode: 'oauth',
83
+ oauth: {
84
+ tokenStore: new MemoryTokenStore({
85
+ accessToken: 'YOUR_ACCESS_TOKEN',
86
+ }),
87
+ },
88
+ },
89
+ });
90
+ ```
91
+
92
+ 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.
93
+
94
+ ### Option B: Session Mode
95
+
96
+ Use this when you are already logged into ZeyOS in the same browser:
97
+
98
+ ```js
99
+ import { createZeyosClient, normalizeListResult } from './zeyos-client/index.js';
100
+
101
+ const client = createZeyosClient({
102
+ platform: 'https://cloud.zeyos.com/demo/',
103
+ auth: {
104
+ mode: 'session',
105
+ session: { enabled: true, credentials: 'include' },
106
+ },
107
+ });
108
+ ```
109
+
110
+ :::tip
111
+ 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](../04-sample-apps/01-kanban.md) for a reusable config pattern.
112
+ :::
113
+
114
+ ---
115
+
116
+ ## Step 3: Fetch and Display Data
117
+
118
+ Add a function to load tickets and render them as a table:
119
+
120
+ ```js
121
+ async function loadTickets() {
122
+ const result = await client.api.listTickets({
123
+ fields: ['ID', 'ticketnum', 'name', 'status', 'priority', 'duedate', 'assigneduser'],
124
+ filters: { visibility: 0 },
125
+ sort: ['-lastmodified'],
126
+ limit: 50,
127
+ });
128
+
129
+ const { data: tickets } = normalizeListResult(result);
130
+ return tickets;
131
+ }
132
+ ```
133
+
134
+ Key things to note:
135
+
136
+ - **List operations are POST requests.** The client handles this; you just pass an object.
137
+ - **Always include `visibility: 0`** to exclude archived/deleted records.
138
+ - **Use `filters` (plural)** for best compatibility across all field types. This handles both simple equality filters and GIN-indexed foreign key fields.
139
+ - **Always specify `fields`** to keep payloads small. Without it, every field on every record is returned.
140
+ - **Normalise the response.** Use `normalizeListResult()` so list calls share one array/object-wrapper handling path. Use `normalizeCountResult()` for count-only requests.
141
+
142
+ Now render it:
143
+
144
+ ```js
145
+ const STATUS_LABELS = {
146
+ 0: 'Not Started', 1: 'Awaiting Acceptance', 2: 'Accepted',
147
+ 3: 'Rejected', 4: 'Active', 5: 'Inactive',
148
+ 6: 'Feedback Required', 7: 'Testing', 8: 'Cancelled',
149
+ 9: 'Completed', 10: 'Failed', 11: 'Booked',
150
+ };
151
+
152
+ const PRIORITY_LABELS = {
153
+ 0: 'Lowest', 1: 'Low', 2: 'Medium', 3: 'High', 4: 'Highest',
154
+ };
155
+
156
+ function formatDate(unix) {
157
+ if (!unix) return '';
158
+ return new Date(unix * 1000).toLocaleDateString();
159
+ }
160
+
161
+ function renderTickets(tickets) {
162
+ if (tickets.length === 0) {
163
+ return '<p class="text-gray-500">No tickets found.</p>';
164
+ }
165
+
166
+ const rows = tickets.map(t => `
167
+ <tr class="border-t hover:bg-gray-50">
168
+ <td class="py-2 px-3 font-mono text-sm text-gray-400">${t.ticketnum ?? t.ID}</td>
169
+ <td class="py-2 px-3 font-medium">${esc(t.name ?? '')}</td>
170
+ <td class="py-2 px-3 text-sm">${STATUS_LABELS[t.status] ?? t.status}</td>
171
+ <td class="py-2 px-3 text-sm">${PRIORITY_LABELS[t.priority] ?? t.priority}</td>
172
+ <td class="py-2 px-3 text-sm">${formatDate(t.duedate)}</td>
173
+ <td class="py-2 px-3 text-sm">${esc(t.assigneduser ?? '')}</td>
174
+ </tr>
175
+ `).join('');
176
+
177
+ return `
178
+ <table class="w-full bg-white rounded-lg shadow text-left">
179
+ <thead>
180
+ <tr class="text-xs uppercase text-gray-500 border-b">
181
+ <th class="py-2 px-3">#</th>
182
+ <th class="py-2 px-3">Name</th>
183
+ <th class="py-2 px-3">Status</th>
184
+ <th class="py-2 px-3">Priority</th>
185
+ <th class="py-2 px-3">Due</th>
186
+ <th class="py-2 px-3">Assigned</th>
187
+ </tr>
188
+ </thead>
189
+ <tbody>${rows}</tbody>
190
+ </table>`;
191
+ }
192
+
193
+ function esc(str) {
194
+ return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;')
195
+ .replace(/>/g, '&gt;').replace(/"/g, '&quot;');
196
+ }
197
+ ```
198
+
199
+ Wire it up:
200
+
201
+ ```js
202
+ async function boot() {
203
+ try {
204
+ const tickets = await loadTickets();
205
+ document.getElementById('content').innerHTML = renderTickets(tickets);
206
+ } catch (err) {
207
+ document.getElementById('content').innerHTML =
208
+ `<p class="text-red-500">Error: ${esc(err.message)}</p>`;
209
+ }
210
+ }
211
+
212
+ boot();
213
+ ```
214
+
215
+ Start your server and open the page -- you should see a table of tickets.
216
+
217
+ ---
218
+
219
+ ## Step 4: Filter by Project
220
+
221
+ Add a project selector above the table. First, load the available projects:
222
+
223
+ ```js
224
+ async function loadProjects() {
225
+ try {
226
+ const result = await client.api.listProjects({
227
+ fields: ['ID', 'name'],
228
+ filters: { visibility: 0 },
229
+ sort: ['+name'],
230
+ limit: 500,
231
+ });
232
+ return normalizeListResult(result).data;
233
+ } catch {
234
+ return [];
235
+ }
236
+ }
237
+ ```
238
+
239
+ Then build a `<select>` and reload tickets when the selection changes:
240
+
241
+ ```js
242
+ function renderProjectFilter(projects, onSelect) {
243
+ const options = projects.map(p =>
244
+ `<option value="${p.ID}">${esc(p.name)}</option>`
245
+ ).join('');
246
+
247
+ return `
248
+ <select id="project-filter"
249
+ class="border rounded-lg px-3 py-2 text-sm mb-4">
250
+ <option value="">All Projects</option>
251
+ ${options}
252
+ </select>`;
253
+ }
254
+
255
+ async function boot() {
256
+ const projects = await loadProjects();
257
+ const app = document.getElementById('content');
258
+
259
+ app.innerHTML = renderProjectFilter(projects) + '<div id="tickets">Loading...</div>';
260
+
261
+ const loadAndRender = async (projectId) => {
262
+ const filters = { visibility: 0 };
263
+ if (projectId) filters.project = Number(projectId);
264
+
265
+ const result = await client.api.listTickets({
266
+ fields: ['ID', 'ticketnum', 'name', 'status', 'priority', 'duedate', 'assigneduser'],
267
+ filters,
268
+ sort: ['-lastmodified'],
269
+ limit: 50,
270
+ });
271
+ const { data: tickets } = normalizeListResult(result);
272
+ document.getElementById('tickets').innerHTML = renderTickets(tickets);
273
+ };
274
+
275
+ document.getElementById('project-filter').addEventListener('change', e => {
276
+ loadAndRender(e.target.value);
277
+ });
278
+
279
+ await loadAndRender('');
280
+ }
281
+ ```
282
+
283
+ ---
284
+
285
+ ## Step 5: Create a New Ticket
286
+
287
+ Add a simple form and use the `createTicket` API. Note: **create operations use PUT**, not POST.
288
+
289
+ ```js
290
+ function renderCreateForm() {
291
+ return `
292
+ <form id="create-form" class="bg-white rounded-lg shadow p-4 mb-6 flex gap-3 items-end">
293
+ <div class="flex-1">
294
+ <label class="block text-xs font-medium text-gray-600 mb-1">Ticket Name</label>
295
+ <input id="f-name" type="text" placeholder="What needs to be done?"
296
+ class="w-full border rounded-lg px-3 py-2 text-sm" required>
297
+ </div>
298
+ <div>
299
+ <label class="block text-xs font-medium text-gray-600 mb-1">Priority</label>
300
+ <select id="f-priority" class="border rounded-lg px-3 py-2 text-sm">
301
+ <option value="0">Lowest</option>
302
+ <option value="1">Low</option>
303
+ <option value="2" selected>Medium</option>
304
+ <option value="3">High</option>
305
+ <option value="4">Highest</option>
306
+ </select>
307
+ </div>
308
+ <button type="submit"
309
+ class="px-4 py-2 bg-blue-600 text-white rounded-lg text-sm font-medium hover:bg-blue-700">
310
+ Create
311
+ </button>
312
+ </form>`;
313
+ }
314
+ ```
315
+
316
+ Handle form submission:
317
+
318
+ ```js
319
+ document.getElementById('create-form').addEventListener('submit', async e => {
320
+ e.preventDefault();
321
+ const name = document.getElementById('f-name').value.trim();
322
+ if (!name) return;
323
+
324
+ try {
325
+ await client.api.createTicket({
326
+ name,
327
+ priority: Number(document.getElementById('f-priority').value),
328
+ status: 0,
329
+ visibility: 0,
330
+ });
331
+
332
+ document.getElementById('f-name').value = '';
333
+ await loadAndRender(''); // Refresh the list
334
+ } catch (err) {
335
+ alert(`Create failed: ${err.message}`);
336
+ }
337
+ });
338
+ ```
339
+
340
+ For generated create and update operations, the flat input style works for normal record fields.
341
+
342
+ ---
343
+
344
+ ## Step 6: Update a Ticket
345
+
346
+ Update a record by passing the record ID and the changed fields:
347
+
348
+ ```js
349
+ async function updateTicketStatus(ticketId, newStatus) {
350
+ const updated = await client.api.updateTicket({
351
+ ID: ticketId,
352
+ status: newStatus,
353
+ });
354
+
355
+ // The response contains the full updated record -- use it to
356
+ // confirm the change was applied
357
+ return updated;
358
+ }
359
+ ```
360
+
361
+ If you prefer explicit separation, `body` and `data` are also supported:
362
+
363
+ ```js
364
+ await client.api.updateTicket({ ID: ticketId, body: { status: newStatus } });
365
+ ```
366
+
367
+ ---
368
+
369
+ ## Step 7: Delete a Ticket
370
+
371
+ Deletion is straightforward:
372
+
373
+ ```js
374
+ async function removeTicket(ticketId) {
375
+ await client.api.deleteTicket({ ID: ticketId });
376
+ }
377
+ ```
378
+
379
+ Always confirm with the user before deleting -- there is no undo.
380
+
381
+ ---
382
+
383
+ ## Step 8: Fetch Related Data
384
+
385
+ Most ZeyOS entities are related to each other. For example, tickets can have tasks. To load tasks for a specific ticket:
386
+
387
+ ```js
388
+ async function loadTasks(ticketId) {
389
+ const result = await client.api.listTasks({
390
+ fields: ['ID', 'tasknum', 'name', 'status', 'duedate', 'assigneduser'],
391
+ filters: { ticket: ticketId, visibility: 0 },
392
+ sort: ['+name'],
393
+ limit: 200,
394
+ });
395
+ return normalizeListResult(result).data;
396
+ }
397
+ ```
398
+
399
+ You can also use **dot-notation joins** to pull fields from related records in a single request:
400
+
401
+ ```js
402
+ const tickets = await client.api.listTickets({
403
+ fields: {
404
+ Id: 'ID',
405
+ Name: 'name',
406
+ ProjectName: 'project.name',
407
+ AssignedTo: 'assigneduser.name',
408
+ ContactCity: 'contact.city',
409
+ },
410
+ filters: { visibility: 0 },
411
+ limit: 50,
412
+ });
413
+ ```
414
+
415
+ This returns flattened objects like `{ Id: 42, Name: '...', ProjectName: 'Acme', AssignedTo: 'Jane', ContactCity: 'Berlin' }` -- no extra API calls needed.
416
+
417
+ ---
418
+
419
+ ## Step 9: Handle Errors Gracefully
420
+
421
+ Every API error throws a `ZeyosApiError` with rich context:
422
+
423
+ ```js
424
+ import { ZeyosApiError } from './zeyos-client/index.js';
425
+
426
+ try {
427
+ await client.api.getTicket({ ID: 999999 });
428
+ } catch (err) {
429
+ if (err instanceof ZeyosApiError) {
430
+ switch (err.status) {
431
+ case 401:
432
+ // Token expired and auto-refresh failed
433
+ showLoginScreen();
434
+ break;
435
+ case 403:
436
+ showMessage('You do not have permission to view this record.');
437
+ break;
438
+ case 404:
439
+ showMessage('Record not found.');
440
+ break;
441
+ default:
442
+ showMessage(`Error ${err.status}: ${err.body ?? err.message}`);
443
+ }
444
+ } else {
445
+ // Network error, timeout, etc.
446
+ showMessage('Network error. Please check your connection.');
447
+ }
448
+ }
449
+ ```
450
+
451
+ ---
452
+
453
+ ## Step 10: Working with Dates
454
+
455
+ All ZeyOS date fields are **Unix timestamps in seconds** (not milliseconds). This catches most JavaScript developers off guard since `Date.now()` returns milliseconds.
456
+
457
+ ```js
458
+ // Reading: multiply by 1000
459
+ const jsDate = new Date(ticket.duedate * 1000);
460
+
461
+ // Writing: divide by 1000
462
+ const duedate = Math.floor(new Date('2026-06-15').getTime() / 1000);
463
+ await client.api.updateTicket({ ID: id, body: { duedate } });
464
+
465
+ // Checking overdue
466
+ const isOverdue = ticket.duedate * 1000 < Date.now();
467
+ ```
468
+
469
+ ---
470
+
471
+ ## Summary: Quick Reference Card
472
+
473
+ | Task | Code |
474
+ |------|------|
475
+ | List records | `client.api.listTickets({ fields: [...], filters: {...}, sort: [...], limit: N })` |
476
+ | Get one record | `client.api.getTicket({ ID: 42 })` |
477
+ | Create | `client.api.createTicket({ name: '...', status: 0, visibility: 0 })` |
478
+ | Update | `client.api.updateTicket({ ID: 42, body: { status: 4 } })` |
479
+ | Delete | `client.api.deleteTicket({ ID: 42 })` |
480
+ | Related data | `client.api.listTasks({ filters: { ticket: 42 } })` |
481
+ | Dot-notation join | `fields: { City: 'contact.city', Agent: 'assigneduser.name' }` |
482
+ | Date read | `new Date(record.duedate * 1000)` |
483
+ | Date write | `Math.floor(date.getTime() / 1000)` |
484
+
485
+ ---
486
+
487
+ ## Where to Go Next
488
+
489
+ - **[Practical Guide](../02-javascript-client/04-practical-guide.md)** -- deeper coverage of `filter` vs `filters`, token persistence, and optimistic UI patterns
490
+ - **[Making Requests](../02-javascript-client/03-making-requests.md)** -- full reference for field selection, sorting, pagination, extended data, and error handling
491
+ - **[Data Retrieval](../01-api-reference/01-data-retrieval.md)** -- the complete REST query language with advanced filter operators, full-text search, and composite expressions
492
+ - **[Kanban Sample](../04-sample-apps/01-kanban.md)** -- a reusable sample browser app with drag-and-drop, modals, and session detection
493
+ - **[Authentication](../02-javascript-client/02-authentication.md)** -- OAuth 2.0 flows, session mode, legacy auth, and token management
494
+
495
+ ---
496
+
497
+ ## Available Resources
498
+
499
+ The ZeyOS API provides access to over 50 resource types. Here are the ones most commonly used when building custom frontends:
500
+
501
+ | Resource | Operations | Use case |
502
+ |----------|-----------|----------|
503
+ | `Tickets` | list, get, create, update, delete | Issue tracking, support, project work |
504
+ | `Tasks` | list, get, create, update, delete | Task management within tickets/projects |
505
+ | `Accounts` | list, get, create, update, delete | CRM contacts and companies |
506
+ | `Projects` | list, get, create, update, delete | Project organisation |
507
+ | `Contacts` | list, get, create, update, delete | Contact details (addresses, phone, email) |
508
+ | `Appointments` | list, get, create, update, delete | Calendar and scheduling |
509
+ | `Transactions` | list, get, create, update, delete | Invoices, quotes, orders |
510
+ | `Items` | list, get, create, update, delete | Products and services |
511
+ | `Documents` | list, get, create, update, delete | File management |
512
+ | `Notes` | list, get, create, update, delete | Notes attached to any entity |
513
+ | `Messages` | list, get, create, update, delete | Email and messaging |
514
+ | `Opportunities` | list, get, create, update, delete | Sales pipeline |
515
+ | `Users` | list, get | Team members and permissions |
516
+
517
+ 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.