awork_action
ChatGPTRun awork API actions from synchronous JavaScript. Use first-class awork.get/list/post/put/delete calls only. CRITICAL: Do NOT use async/await, Promise, fetch, imports, absolute URLs, or wrap code in
(async () => ...). Use relative awork API routes discovered with find_capability. Do not use absolute URLs. Response shape: - all calls: { ok: boolean, statusCode: number, headers: Record<string, string>, body: any } - awork.list: array body - awork.get: object or primitive body - awork.post/put: usually object body - awork.delete/NoContent: body may be empty - use response.body for data Data rules: JSON request and response properties use camelCase. Date/time fields use UTC datetime strings such as 2026-05-26T12:34:56Z with second precision. Duration fields are usually seconds. UUID 01010001-0000-0000-0000-111111111111 represents actions taken by awork. Display rule: return human-readable names, links, counts, and short summaries for users. Include stable awork IDs only when they help identify records unambiguously or when another tool call needs them. AVOID unbounded large collection reads when not necessary. Do not pull huge datasets like all projects/tasks/users/documents just to filter in JS. Prefer the most specific endpoint plus server-side filterby/orderby/page/pageSize first. BUDGET RULE: runtime call budget is limited. Before .map()/.forEach() writes, estimate operation count and split into small chunks when needed instead of one giant run. Iteration: use .map() or .forEach() on response.body from GET/list results to create operations per item: const tasksResponse = awork.list('/projects/{id}/projecttasks'); tasksResponse.body.map(t => // PUT replaces the whole resource: include all existing first-level properties, // then override only the properties that should change. awork.put('/tasks/' + t.id, { ...t, // CRITICAL: always map existing properties to not lose data on PUT dueOn: '2026-04-01' }) ); Filtering: ALWAYS use server-side filterBy over in-memory filtering. Avoid n+1: f.e do not issue one delete call per task if batch endpoint exists. Before issuing a destructive action such as delete or update, run a normal read with the same final filter to confirm ids. POST /tasks/delete accepts task ID batches, and each request should contain at most 1000 IDs: let deletedCount = 0; while (true) { // GOOD: Use pagination to iterate over all items, not just first page const tasksResponse = awork.list('/projects/{id}/projecttasks', { filterby: "taskStatus/type eq 'done'", // GOOD: filter on api level page: 1, pageSize: 1000 }); const taskIds = tasksResponse.body.map(t => t.id); if (taskIds.length === 0) { break; } awork.post('/tasks/delete', { taskIds // GOOD: use batch endpoints where possible }); deletedCount += taskIds.length; } return { deletedCount }; Multiple .filter() calls can still be chained for local value shaping: tasksResponse.body.filter(a).filter(b).map(fn). Should be used with api pre-filtering if the filter is very complex or the api request performance gets significantly slower because of the filterBy. FILTER RULE: never invent filterby properties, always use the properties of the model. There might be properties which are filter only and not returned in the final response model. Nested models like project inside a task or timeentry can differ, so always check the exact shape of the model. Call find_guidance with skillIds: ['filter-syntax'] and includeContent: true for filter/order syntax and use find_capability to confirm supported fields for the exact endpoint before executing. Endpoint filterby is deterministic and not fuzzy; use GET /search for fuzzy workspace search or narrow reads for stable identifiers. Example: Filtered batch update + export: filter on the API, batch the write, and return projected rows. If the result should be used outside the immediate response, set forceDataRef=true and use continue_reading to retrieve or slice the stored result; the MCP client or agent can then write the returned rows to JSON, CSV, XLSX, or another local artifact format with its own file/artifact tools: const exportedRows = []; while (true) { // Get not billed times of a user on a project for a client, export them and set them to billed. const timeEntriesResponse = awork.list('/projects/{projectId}/timeentries', { filterby: "userId eq guid'{userId}' and startDateUtc ge datetime'2026-01-01T00:00:00Z' and startDateUtc lt datetime'2026-02-01T00:00:00Z' and isBillable eq true and isBilled eq false", orderby: 'startDateUtc asc', page: 1, pageSize: 1000 }); if (timeEntriesResponse.body.length === 0) { break; } const timeEntryIds = timeEntriesResponse.body.map(t => t.id); awork.post('/timeentries/batch/setIsBilled', { timeEntryIds, isBilled: true // Important: cannot be undone, only by admin }); timeEntriesResponse.body .map(t => ({ id: t.id, startDateUtc: t.startDateUtc, duration: t.duration, userId: t.userId, projectId: t.projectId, isBillable: t.isBillable, isBilled: true })) .forEach(row => exportedRows.push(row)); } return exportedRows; Dependencies: use property access for cross-operation refs (projectResponse.body.id resolves at execution time). DEPENDENCY RULE: verify dependent ids are non-empty before subsequent calls; do not execute routes with undefined ids. Result format: returns your final JS expression/object. If nothing is returned, a compact summary is returned. Large payloads are truncated with dataRef for continue_reading. Set forceDataRef=true when another MCP client step needs the full result. On partial failure, the result includes succeededOperationRefs/failedOperationRefs with operation indexes and extracted resourceId where available; use these IDs before retrying to avoid duplicate creates. Use find_capability first (schema-first flow) to confirm route, method and body shape. Call find_guidance with skillIds: ['tasks-execution'] before task mutations, skillIds: ['projects-lifecycle'] before project mutations, skillIds: ['search-and-entity-resolution'] before resolving ambiguous user-provided entity names, and skillIds: ['filter-syntax'] before non-trivial filters. Query parameters are passed as the second argument, e.g. { filterby, orderby, page, pageSize }. For exact count-only checks, add count: true and read response.headers['aw-totalitems']; count responses always have empty body, so do not read response.body from count requests. Keep allowNonSuccessStatusCodes=false unless you intentionally handle non-success HTTP status codes. Use continue_reading for JMESPath filtering/slicing after execution. HTML/comment rule: project and task descriptions and comments can contain HTML. Comment text can contain escaped sequences such as \u003C and \u003E; decode them when displaying formatted text. PUT rule: read first, then always send all first-level fields in body and change only intended fields. Missing first-level fields can be set to null by the API. Destructive rule: before delete/trash/remove/bulk-overwrite, run a normal read with the same final filter to confirm ids, and run a separate count-only request when an exact total is needed. Legacy array payloads like [{ method: 'POST', route: '/tasks' }] are rejected. Do not emit them. Example with allowNonSuccessStatusCodes=true: const ids = ['00000000-0000-0000-0000-000000000001', '00000000-0000-0000-0000-000000000002']; const found = []; for (let i = 0; i < ids.length; i += 1) { const response = awork.get('/projects/' + ids[i]); if (response.ok) { found.push(response.body); continue; } if (response.statusCode === 404) { continue; } return { error: 'Unexpected non-success status while reading project', id: ids[i], statusCode: response.statusCode, body: response.body }; } return { found }; Route discovery: always use find_capability to confirm routes before execution. Do not guess endpoint paths.




