@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,572 @@
1
+ ---
2
+ sidebar_label: Making Requests
3
+ ---
4
+
5
+ # Making API Requests
6
+
7
+ 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.
8
+
9
+ ## Generated Methods
10
+
11
+ All standard API operations are available as `client.api.<operationId>(input, options?)`. The `operationId` corresponds directly to the operation name in the ZeyOS OpenAPI specification.
12
+
13
+ ```js
14
+ // Examples of generated methods
15
+ client.api.listTickets(...)
16
+ client.api.getTicket(...)
17
+ client.api.createTicket(...)
18
+ client.api.updateTicket(...)
19
+ client.api.deleteTicket(...)
20
+ client.api.listAccounts(...)
21
+ client.api.createTask(...)
22
+ ```
23
+
24
+ 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.
25
+
26
+ ## CRUD Operations
27
+
28
+ ### List Records
29
+
30
+ Retrieve collections of records with optional filters, sorting, and pagination:
31
+
32
+ ```js
33
+ const tickets = await client.api.listTickets({
34
+ fields: ['ID', 'name', 'status', 'priority', 'duedate'],
35
+ filters: { status: 1, visibility: 0 },
36
+ sort: ['-lastmodified'],
37
+ limit: 50,
38
+ });
39
+ ```
40
+
41
+ ### Get a Single Record
42
+
43
+ Fetch a specific record by ID. Additional flags control which related data is included:
44
+
45
+ ```js
46
+ const ticket = await client.api.getTicket({
47
+ ID: 42,
48
+ extdata: 1,
49
+ tags: 1,
50
+ });
51
+ ```
52
+
53
+ ### Create a Record
54
+
55
+ 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:
56
+
57
+ ```js
58
+ const newTicket = await client.api.createTicket({
59
+ name: 'Fix login bug',
60
+ status: 0,
61
+ priority: 3,
62
+ description: 'Users cannot log in with SSO',
63
+ visibility: 0,
64
+ });
65
+ ```
66
+
67
+ :::caution Required fields the spec does not declare
68
+ 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.
69
+
70
+ ```js
71
+ await client.api.createAccount({ lastname: 'Acme Corp', type: 1, currency: 'EUR' });
72
+ ```
73
+ :::
74
+
75
+ ### Update a Record
76
+
77
+ Update an existing record with a PATCH request. Pass the `ID` and changed fields in one object:
78
+
79
+ ```js
80
+ await client.api.updateTicket({
81
+ ID: 42,
82
+ status: 4,
83
+ priority: 4,
84
+ });
85
+ ```
86
+
87
+ :::tip Explicit body is also supported
88
+ Use `body` or `data` when you want to separate URL parameters from payload fields manually:
89
+
90
+ ```js
91
+ await client.api.updateTicket({ ID: 42, body: { status: 4, priority: 4 } });
92
+ ```
93
+ :::
94
+
95
+ The PATCH response body contains the full updated record. Use it to confirm the server applied your changes:
96
+
97
+ ```js
98
+ const updated = await client.api.updateTicket({
99
+ ID: 42,
100
+ status: 4,
101
+ });
102
+ console.log(updated.status); // 4 -- confirmed by the server
103
+ ```
104
+
105
+ ### Delete a Record
106
+
107
+ Delete a record by ID:
108
+
109
+ ```js
110
+ await client.api.deleteTicket({ ID: 42 });
111
+ ```
112
+
113
+ ### Check Existence
114
+
115
+ Use a HEAD request to check whether a record exists without downloading the full response body:
116
+
117
+ ```js
118
+ const exists = await client.api.existsTicket({ ID: 42 });
119
+ // Returns true if the record exists (2xx/3xx), throws ZeyosApiError on 404
120
+ ```
121
+
122
+ ## Field Selection
123
+
124
+ Control which fields are returned in list responses. This reduces payload size and improves performance.
125
+
126
+ ### Array Form
127
+
128
+ Pass an array of field names to return only those fields:
129
+
130
+ ```js
131
+ const result = await client.api.listAccounts({
132
+ fields: ['ID', 'lastname', 'contact.city'],
133
+ filters: { visibility: 0 },
134
+ });
135
+ ```
136
+
137
+ ### Object Form (with Aliases)
138
+
139
+ Pass an object to rename fields in the response. Keys become the output names, values are the source field paths:
140
+
141
+ ```js
142
+ const result = await client.api.listAccounts({
143
+ fields: {
144
+ 'Id': 'ID',
145
+ 'Name': 'lastname',
146
+ 'City': 'contact.city',
147
+ 'Agent': 'assigneduser.name',
148
+ },
149
+ filters: { visibility: 0 },
150
+ });
151
+ ```
152
+
153
+ :::note
154
+ Dot-notation field paths (e.g. `contact.city`, `assigneduser.name`) allow you to select fields from related or nested objects.
155
+ :::
156
+
157
+ ## Filtering
158
+
159
+ ZeyOS provides two filter parameters. Use `filters` (plural) for the broadest compatibility -- it works with both scalar fields and GIN-indexed foreign key fields:
160
+
161
+ ```js
162
+ // Standard filtering -- works for all field types
163
+ const active = await client.api.listTickets({
164
+ filters: { status: 4, visibility: 0 },
165
+ });
166
+
167
+ // Filter by foreign key field (e.g. project, account)
168
+ const projectTickets = await client.api.listTickets({
169
+ filters: { visibility: 0, project: projectId },
170
+ });
171
+ ```
172
+
173
+ ### `filter` vs `filters`
174
+
175
+ | Parameter | Supports | Notes |
176
+ |-----------|----------|-------|
177
+ | `filter` | Scalar fields (status, visibility, priority) | Defined in the OpenAPI spec. May not work for all FK fields. |
178
+ | `filters` | All field types including GIN-indexed foreign keys (project, account, ticket) | Recommended for general use -- handles both scalar and FK fields. |
179
+
180
+ :::tip
181
+ When in doubt, use `filters` (plural). Using `filter` (singular) with a foreign-key field like `project` silently returns unfiltered results rather than throwing an error, which makes problems hard to spot.
182
+ :::
183
+
184
+ ### Full-Text Search
185
+
186
+ Use the `query` parameter to search across a resource's indexed text fields:
187
+
188
+ ```js
189
+ const results = await client.api.listAccounts({
190
+ fields: ['ID', 'lastname', 'contact.email'],
191
+ filters: { visibility: 0 },
192
+ query: 'acme',
193
+ limit: 20,
194
+ });
195
+ ```
196
+
197
+ ## Distinct Results
198
+
199
+ Pass `distinct: true` to deduplicate result rows. This is useful when using dot-notation joins that may produce multiple rows per record:
200
+
201
+ ```js
202
+ const result = await client.api.listAccounts({
203
+ distinct: true,
204
+ fields: ['ID', 'lastname', 'contact.country'],
205
+ filters: { visibility: 0 },
206
+ });
207
+ ```
208
+
209
+ ## Sorting
210
+
211
+ Pass an array of field names prefixed with `+` (ascending) or `-` (descending):
212
+
213
+ ```js
214
+ // Sort by last modified, newest first
215
+ const tickets = await client.api.listTickets({
216
+ sort: ['-lastmodified'],
217
+ });
218
+
219
+ // Multi-field sort: priority descending, then name ascending
220
+ const tickets = await client.api.listTickets({
221
+ sort: ['-priority', '+name'],
222
+ });
223
+ ```
224
+
225
+ ## Pagination
226
+
227
+ Use `limit` and `offset` to page through large result sets:
228
+
229
+ ```js
230
+ // Get total count first
231
+ const countResult = await client.api.listTickets({
232
+ count: true,
233
+ filters: { status: 1, visibility: 0 },
234
+ });
235
+ // countResult contains the total number of matching records
236
+
237
+ // Fetch the first page
238
+ const page1 = await client.api.listTickets({
239
+ limit: 50,
240
+ offset: 0,
241
+ filters: { status: 1, visibility: 0 },
242
+ });
243
+
244
+ // Fetch the second page
245
+ const page2 = await client.api.listTickets({
246
+ limit: 50,
247
+ offset: 50,
248
+ filters: { status: 1, visibility: 0 },
249
+ });
250
+ ```
251
+
252
+ :::tip
253
+ Use `count: true` to get the total number of matching records without fetching the full dataset. This is useful for building pagination controls.
254
+ :::
255
+
256
+ ## Normalising List Responses
257
+
258
+ List endpoints are not uniform across the full surface area. Depending on the endpoint and response mode, you may see:
259
+
260
+ - a plain array
261
+ - an object wrapper with `data`
262
+ - count metadata alongside the payload when `count: true` is used
263
+
264
+ The normalization helpers are useful when you want call sites to share one response-shape contract:
265
+
266
+ ```js
267
+ import { normalizeCountResult, normalizeListResult } from '@zeyos/client';
268
+
269
+ // Without count -- result is a plain array
270
+ const raw = await client.api.listTickets({ filters: { visibility: 0 } });
271
+ const { data } = normalizeListResult(raw);
272
+ // data is always an array
273
+
274
+ // With count metadata -- result may include both data and count
275
+ const raw2 = await client.api.listTickets({ filters: { visibility: 0 }, count: true });
276
+ const { data: tickets, count } = normalizeListResult(raw2);
277
+ // tickets: array, count: number
278
+
279
+ // Count-only request -- result may be a number or an object with count metadata
280
+ const countOnly = await client.api.listTickets({ filters: { visibility: 0 }, count: true });
281
+ const total = normalizeCountResult(countOnly);
282
+ ```
283
+
284
+ ## Extended Data
285
+
286
+ 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.
287
+
288
+ ### Including Extended Data in List Requests
289
+
290
+ For list operations, `extdata` is sent as a body parameter:
291
+
292
+ ```js
293
+ const tickets = await client.api.listTickets({
294
+ fields: ['ID', 'name', 'status', 'priority', 'duedate'],
295
+ filters: { status: 1, visibility: 0 },
296
+ sort: ['-lastmodified'],
297
+ limit: 50,
298
+ extdata: 1,
299
+ });
300
+ ```
301
+
302
+ ### Including Extended Data in GET Requests
303
+
304
+ For single-record GET operations, `extdata` is sent as a query parameter:
305
+
306
+ ```js
307
+ const ticket = await client.api.getTicket({
308
+ ID: 42,
309
+ extdata: 1,
310
+ tags: 1,
311
+ });
312
+ ```
313
+
314
+ ### Selecting Specific Extended Data Fields
315
+
316
+ 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:
317
+
318
+ ```js
319
+ const tickets = await client.api.listTickets({
320
+ fields: {
321
+ 'Id': 'ID',
322
+ 'Name': 'name',
323
+ 'Region': 'extdata.region',
324
+ 'CustomerType': 'extdata.customer_type',
325
+ },
326
+ filters: { status: 1, visibility: 0 },
327
+ limit: 50,
328
+ });
329
+ ```
330
+
331
+ :::note
332
+ 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.
333
+ :::
334
+
335
+ ## Expanding JSON and Binary Columns
336
+
337
+ 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.
338
+
339
+ ```js
340
+ // Expand the binary file content of a message
341
+ const message = await client.api.getMessage({
342
+ ID: 123,
343
+ expand: ['binfile'],
344
+ });
345
+
346
+ // Expand the items array on a transaction
347
+ const transaction = await client.api.getTransaction({
348
+ ID: 456,
349
+ expand: ['items'],
350
+ });
351
+ ```
352
+
353
+ :::caution
354
+ 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.
355
+ :::
356
+
357
+ ## Schema Introspection and Validation
358
+
359
+ 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.
360
+
361
+ ```js
362
+ client.schema.resources(); // all resource names
363
+ client.schema.describe('tickets'); // { name, type, fields: { status: { type, enum }, account: { fk }, … } }
364
+ client.schema.fields('accounts'); // ['ID', 'lastname', 'firstname', 'type', …]
365
+ client.schema.operations('tickets'); // ['listTickets', 'getTicket', 'createTicket', …]
366
+ client.schema.resourceForOperation('listDunningNotices'); // 'dunning'
367
+ client.schema.suggestOperation('listDunning'); // 'listDunningNotices'
368
+ ```
369
+
370
+ ### Validating a Call
371
+
372
+ `client.schema.validate(operationId, input)` returns structured, self-correcting hints. It never throws and is lenient about dot-notation joins (`contact.city`) and extended fields (`extdata.*`):
373
+
374
+ ```js
375
+ const result = client.schema.validate('createAccount', { name: 'Acme' });
376
+ // {
377
+ // valid: false,
378
+ // errors: [{ field: 'name', message: 'Unknown field "name". Did you mean "lastname"?', suggestion: 'lastname' }]
379
+ // }
380
+ ```
381
+
382
+ 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`:
383
+
384
+ ```js
385
+ client.schema.validate('createAccount', { lastname: 'Acme' });
386
+ // { valid: false, errors: [{ field: 'currency', message: 'Missing required field "currency" for accounts …', suggestion: 'currency' }] }
387
+ ```
388
+
389
+ ### Pre-flight Validation
390
+
391
+ Set `validate: true` on the client to validate every request before it is sent and throw a `ZeyosValidationError` (rather than letting the server reject it):
392
+
393
+ ```js
394
+ import { ZeyosValidationError } from '@zeyos/client';
395
+
396
+ const client = createZeyosClient({ platform: 'live', instance: 'demo', validate: true });
397
+
398
+ try {
399
+ await client.api.createAccount({ name: 'Acme' }); // wrong field
400
+ } catch (err) {
401
+ if (err instanceof ZeyosValidationError) {
402
+ console.log(err.operationId); // 'createAccount'
403
+ console.log(err.errors); // structured hints (see above)
404
+ }
405
+ }
406
+ ```
407
+
408
+ Validation is **off by default** so that custom and extended fields are never blocked; enable it in agent or development workflows where fast, descriptive feedback is more valuable.
409
+
410
+ ## Retries and Rate Limiting
411
+
412
+ The client automatically retries transient failures. By default it retries `429 Too Many Requests` and `503 Service Unavailable` up to twice, using exponential backoff with jitter and honoring a `Retry-After` header when present. Retries are abort-aware (an `AbortSignal` cancels pending waits).
413
+
414
+ ```js
415
+ // Customize the policy
416
+ const client = createZeyosClient({
417
+ platform: 'live',
418
+ instance: 'demo',
419
+ retry: {
420
+ maxRetries: 3,
421
+ retryOn: [429, 503], // statuses to retry
422
+ baseDelayMs: 300, // backoff base
423
+ maxDelayMs: 10000 // cap per wait
424
+ }
425
+ });
426
+
427
+ // Disable retries entirely
428
+ const noRetry = createZeyosClient({ platform: 'live', instance: 'demo', retry: false });
429
+ ```
430
+
431
+ :::note
432
+ Only `429`/`503` are retried by default -- statuses that clearly mean "try again later". `5xx` codes such as `500`/`502` are **not** retried automatically, to avoid re-applying a non-idempotent write that may have partially succeeded. Add them to `retryOn` only for read-heavy workloads.
433
+ :::
434
+
435
+ ## Error Handling
436
+
437
+ All API errors are thrown as `ZeyosApiError` instances. This class extends `Error` and includes structured information about the failed request.
438
+
439
+ ```js
440
+ import { ZeyosApiError } from '@zeyos/client';
441
+
442
+ try {
443
+ await client.api.getTicket({ ID: 999 });
444
+ } catch (err) {
445
+ if (err instanceof ZeyosApiError) {
446
+ console.log(err.status); // 404
447
+ console.log(err.statusText); // 'Not Found'
448
+ console.log(err.operationId); // 'getTicket'
449
+ console.log(err.service); // 'api'
450
+ console.log(err.method); // 'GET'
451
+ console.log(err.url); // Full request URL
452
+ console.log(err.body); // Error response body (parsed JSON or text)
453
+ }
454
+ }
455
+ ```
456
+
457
+ The `ZeyosApiError` properties:
458
+
459
+ `JsonValue` means a parsed JSON scalar, array, or plain JSON object. Non-JSON error responses are returned as strings.
460
+
461
+ | Property | Type | Description |
462
+ |----------|------|-------------|
463
+ | `status` | `number` | HTTP status code (e.g. `404`, `401`, `500`) |
464
+ | `statusText` | `string` | HTTP status text (e.g. `'Not Found'`) |
465
+ | `operationId` | `string` | The operation that failed (e.g. `'getTicket'`) |
466
+ | `service` | `string` | The service key (e.g. `'api'`, `'oauth2'`) |
467
+ | `method` | `string` | HTTP method used (e.g. `'GET'`, `'POST'`) |
468
+ | `url` | `string` | The full request URL |
469
+ | `body` | `JsonValue` | The parsed JSON response body, plain-text error body, or `null` |
470
+ | `headers` | `Record<string,string>` | Response headers as a plain object |
471
+
472
+ ### Unknown Operations
473
+
474
+ 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:
475
+
476
+ ```js
477
+ await client.api.listDunning({});
478
+ // ZeyosApiError: Unknown operation 'api.listDunning'. Did you mean 'listDunningNotices'?
479
+ ```
480
+
481
+ ### Validation Errors
482
+
483
+ 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.
484
+
485
+ ## Low-Level Requests
486
+
487
+ For endpoints not covered by the generated methods, or when you need full control over the request, use `client.request()`.
488
+
489
+ ### By Operation ID
490
+
491
+ Reference a known operation by its service and operation ID:
492
+
493
+ ```js
494
+ const result = await client.request({
495
+ service: 'api',
496
+ operationId: 'listTickets',
497
+ body: { filters: { status: 1, visibility: 0 }, limit: 10 },
498
+ });
499
+ ```
500
+
501
+ ### By Path and Method
502
+
503
+ Specify the HTTP method and path directly for custom or undocumented endpoints:
504
+
505
+ ```js
506
+ const result = await client.request({
507
+ service: 'api',
508
+ method: 'POST',
509
+ path: '/tickets/',
510
+ body: { filters: { status: 1, visibility: 0 } },
511
+ });
512
+ ```
513
+
514
+ ### Raw Responses
515
+
516
+ Pass `raw: true` to receive the full response envelope instead of just the parsed body:
517
+
518
+ ```js
519
+ const response = await client.request({
520
+ service: 'api',
521
+ operationId: 'listTickets',
522
+ body: { limit: 10 },
523
+ raw: true,
524
+ });
525
+
526
+ console.log(response.status); // 200
527
+ console.log(response.headers); // Response headers
528
+ console.log(response.data); // Parsed body
529
+ ```
530
+
531
+ ## Request Options
532
+
533
+ All generated methods and `client.request()` accept an optional second argument with request-level options:
534
+
535
+ | Option | Type | Description |
536
+ |--------|------|-------------|
537
+ | `signal` | `AbortSignal` | An `AbortController` signal to cancel the request |
538
+ | `raw` | `boolean` | Return the full response envelope instead of just the data |
539
+ | `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 |
540
+ | `baseUrl` | `string` | Override the base URL for this request |
541
+ | `bodyType` | `'json' \| 'form'` | Force a body encoding |
542
+
543
+ Example with an abort controller:
544
+
545
+ ```js
546
+ const controller = new AbortController();
547
+
548
+ // Cancel after 5 seconds
549
+ setTimeout(() => controller.abort(), 5000);
550
+
551
+ const tickets = await client.api.listTickets(
552
+ { limit: 100 },
553
+ { signal: controller.signal }
554
+ );
555
+ ```
556
+
557
+ :::tip Persisting refreshed tokens
558
+ 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:
559
+
560
+ ```js
561
+ async function syncTokens() {
562
+ const tokenSet = await client.auth.getTokenSet();
563
+ if (tokenSet?.accessToken) {
564
+ localStorage.setItem('zeyos_tokens', JSON.stringify(tokenSet));
565
+ }
566
+ }
567
+
568
+ // Call after important API operations
569
+ const tickets = await client.api.listTickets({ limit: 50 });
570
+ await syncTokens();
571
+ ```
572
+ :::