ScriptForge Canarys HAPI - User Guide & Test Reference
This guide covers all 52 implemented Canarys HAPI functions available in the ScriptForge Execution Console. Each section includes a one-liner description, a ready-to-run code example, and a plain-English explanation of what to expect.
How to use this guide
Open the ScriptForge Execution Console in your Jira instance, paste any code block below, press Run, and observe the output. Replace placeholder values (project keys, account IDs, etc.) with ones from your own Jira instance.
Table of Contents
- WorkItems - Static Methods
- HapiWorkItem - Instance Methods
- HapiSearchResults - Methods
- HapiEntityProperties - Methods
- Users - Static Methods
- HapiUser - Instance Methods
- Groups - Static Methods
- HapiGroup - Instance Methods
- Spaces - Static Methods
- HapiSpace - Instance Methods
- Platform Limitations
- Quick Reference Table
- Common Patterns
Before You Begin
Every script in this guide runs inside the Execution Console. The following objects are automatically available - you do not need to import anything:
| Object | What it does |
|---|---|
WorkItems |
Create, search, and fetch Jira issues |
Spaces |
Create and manage Jira projects |
Users |
Look up Jira users |
Groups |
Manage Jira groups |
All operations are async, so always use await. Wrap code in try/catch when testing destructive operations.
1. WorkItems - Static Methods
WorkItems.getByKey(key)
Fetch a single issue by its Jira key (e.g.
PROJ-1).
const wi = await WorkItems.getByKey('PROJ-1');
return {
key: wi.key,
summary: wi.summary,
status: wi.status,
type: wi.issueType
};
What to expect: Returns an object with the issue key, summary, current status, and issue type. Throws if the key does not exist.
WorkItems.getById(id)
Fetch a single issue by its internal numeric Jira ID.
const wi = await WorkItems.getById('10001');
return { key: wi.key, summary: wi.summary };
What to expect: Same result as getByKey. Useful when you only have the numeric ID stored (e.g. from a webhook payload). Replace '10001' with a real issue ID from your instance.
WorkItems.create(projectKey, issueType, fn)
Create a new Jira issue using a fluent builder.
const wi = await WorkItems.create('PROJ', 'Task', b => {
b.setSummary('My first Canarys HAPI issue');
b.setDescription('Created via the Execution Console');
b.setPriority('High');
b.setLabels(['Canarys HAPI-test', 'automation']);
});
return { key: wi.key, summary: wi.summary };
What to expect: A new issue is created in the PROJ project and its key (e.g. PROJ-42) is returned. The builder (b) supports:
setSummary(text)- requiredsetDescription(text)setPriority('High' | 'Medium' | 'Low')setAssignee(accountId)setLabels(['label1', 'label2'])setComponents(['Backend'])setFixVersions(['1.0'])setDueDate('2026-12-31')setCustomFieldValue(nameOrId, value)setComment(text)- adds a comment right after creation
WorkItems.search(jql)
Execute a JQL query and get back a lazy
HapiSearchResultsobject.
const results = WorkItems.search('project = PROJ ORDER BY created DESC');
const first5 = await results.take(5);
return first5.map(wi => ({ key: wi.key, summary: wi.summary }));
What to expect: Returns up to 5 of the most recently created issues in your project. The result is paginated internally - you only fetch what you ask for.
WorkItems.count(jql)
Count how many issues match a JQL query without fetching them all.
const total = await WorkItems.count('project = PROJ');
return { totalIssues: total };
What to expect: A single number. Fast - only one lightweight API call is made regardless of result size.
2. HapiWorkItem - Instance Methods
All methods below are called on an issue object returned by WorkItems.getByKey, WorkItems.create, etc.
workItem.update(fn)
Modify one or more fields on an existing issue.
const wi = await WorkItems.getByKey('PROJ-1');
await wi.update(b => {
b.setSummary('Updated via Canarys HAPI');
b.setPriority('Low');
b.setComment('Updated by script');
});
const refreshed = await wi.refresh();
return { summary: refreshed.summary, priority: refreshed.priority };
What to expect: The issue is updated in Jira. The update builder supports the same methods as WorkItems.create, plus setResolution and clearCustomField.
workItem.refresh()
Reload the latest field values for an issue from Jira.
const wi = await WorkItems.getByKey('PROJ-1');
const fresh = await wi.refresh();
return { latestStatus: fresh.status, latestSummary: fresh.summary };
What to expect: Returns a new HapiWorkItem with up-to-date values. The original object is not mutated - use the returned value.
workItem.addComment(text)
Add a plain-text comment to an issue.
const wi = await WorkItems.getByKey('PROJ-1');
const comment = await wi.addComment('Hello from the Execution Console!');
return { commentId: comment.id, body: comment.body };
What to expect: The comment is posted and the new comment object (including its id and body) is returned.
workItem.getComments()
Retrieve all comments on an issue.
const wi = await WorkItems.getByKey('PROJ-1');
const comments = await wi.getComments();
return { count: comments.length, latest: comments.at(-1)?.body };
What to expect: An array of comment objects. Each has id, body, authorDisplayName, created, and updated. Returns an empty array if the issue has no comments.
workItem.getCreator()
Get the user who created the issue (synchronous - no
awaitneeded).
const wi = await WorkItems.getByKey('PROJ-1');
const creator = wi.getCreator();
return { name: creator.displayName, accountId: creator.accountId };
What to expect: Returns { displayName, accountId }. Falls back to the reporter if the creator field is not populated.
workItem.getCustomFieldValue(fieldNameOrId)
Read the value of a custom field by name or Jira field ID.
const wi = await WorkItems.getByKey('PROJ-1');
const val = wi.getCustomFieldValue('Story Points');
// or by ID: wi.getCustomFieldValue('customfield_10016')
return { storyPoints: val };
What to expect: Returns the raw field value (string, number, object, or undefined if the field is not set on this issue).
workItem.delete()
Permanently delete an issue.
const wi = await WorkItems.create('PROJ', 'Task', b => {
b.setSummary('[TEST] Delete me');
});
const key = wi.key;
await wi.delete();
return { deleted: key };
What to expect: The issue is deleted with no confirmation and no undo - use with care.
workItem.transition(statusName)
Move an issue to a new workflow status.
const wi = await WorkItems.getByKey('PROJ-1');
const updated = await wi.transition('In Progress');
return { newStatus: updated.status };
What to expect: The issue moves to the named status. The name must match an available transition for the current workflow step (case-insensitive). Throws with a list of valid transitions if the name is not found.
You can also update fields during the transition:
const wi = await WorkItems.getByKey('PROJ-1');
await wi.transition('Done', b => {
b.setResolution('Fixed');
b.setComment('Resolved via script');
});
workItem.createSubTask(issueType, fn)
Create a sub-task under this issue.
const parent = await WorkItems.getByKey('PROJ-1');
const sub = await parent.createSubTask('Sub-task', b => {
b.setSummary('My sub-task');
b.setPriority('Medium');
});
return { parent: parent.key, subTask: sub.key };
What to expect: A new issue is created as a child of this issue. The sub-task type name depends on your Jira configuration (common values: 'Sub-task' or 'Subtask').
workItem.getSubTaskObjects()
Get all sub-tasks of this issue as full
HapiWorkItemobjects.
const parent = await WorkItems.getByKey('PROJ-1');
const subs = await parent.getSubTaskObjects();
return { count: subs.length, keys: subs.map(s => s.key) };
What to expect: An array of HapiWorkItem objects. Returns an empty array if the issue has no sub-tasks.
workItem.getEntityProperties()
Get the
HapiEntityPropertiesinterface for this issue's key-value store.
const wi = await WorkItems.getByKey('PROJ-1');
const props = wi.getEntityProperties();
await props.setString('myTag', 'reviewed');
const val = await props.getString('myTag');
return { stored: val };
What to expect: Returns an HapiEntityProperties object. See Section 4 for all available methods.
workItem.link(linkType, targetKey)
Create a named link between two issues.
const wi = await WorkItems.getByKey('PROJ-1');
await wi.link('Relates', 'PROJ-2');
return { linked: true };
What to expect: A link of type 'Relates' is created. The link type must match one configured in your Jira instance (e.g. 'Blocks', 'Clones', 'Relates').
workItem.unlink(linkType, targetKey)
Remove a named link between two issues.
const wi = await WorkItems.getByKey('PROJ-1');
await wi.unlink('Relates', 'PROJ-2');
return { unlinked: true };
What to expect: The matching link is removed. Throws if no such link exists between the two issues with the specified type.
3. HapiSearchResults - Methods
WorkItems.search() returns a HapiSearchResults object. It is lazy - results are only fetched from Jira when you call one of these methods.
results.take(n)
Fetch the first
nresults from the search.
const results = WorkItems.search('project = PROJ ORDER BY created DESC');
const top = await results.take(10);
return { count: top.length, keys: top.map(wi => wi.key) };
What to expect: An array of up to n HapiWorkItem objects. More efficient than toArray() when you only need a subset.
results.toArray()
Fetch all results from the search into a single array.
const results = WorkItems.search('project = PROJ AND status = "To Do"');
const all = await results.toArray();
return { total: all.length };
What to expect: All matching issues are returned. Multiple API calls are made internally for large result sets. Use take() or forEach() for better performance on big data sets.
results.forEach(fn)
Iterate over every result, calling a function for each one.
const results = WorkItems.search('project = PROJ');
let highPriority = 0;
await results.forEach(wi => {
if (wi.priority === 'High') highPriority++;
});
return { highPriorityCount: highPriority };
What to expect: Your callback is called once per issue. Issues are fetched in pages of 50 in the background - no pagination code required. More memory-efficient than toArray() for large result sets.
4. HapiEntityProperties - Methods
Entity properties are a Jira-native key-value store attached to any issue, project, user, or comment. They persist between script runs and are invisible in the standard Jira UI.
Get the properties object via workItem.getEntityProperties(), space.getEntityProperties(), or user.getEntityProperties().
props.setString(key, value)
Store a string value under the given key.
const wi = await WorkItems.getByKey('PROJ-1');
await wi.getEntityProperties().setString('reviewStatus', 'approved');
return { saved: true };
props.getString(key)
Read back a previously stored string.
const val = await (await WorkItems.getByKey('PROJ-1')).getEntityProperties().getString('reviewStatus');
return { reviewStatus: val };
What to expect: Returns the stored string, or null if the key does not exist.
props.setInteger(key, value)
Store a whole number under the given key.
const wi = await WorkItems.getByKey('PROJ-1');
await wi.getEntityProperties().setInteger('retryCount', 3);
return { saved: true };
props.getInteger(key)
Read back a stored integer.
const val = await (await WorkItems.getByKey('PROJ-1')).getEntityProperties().getInteger('retryCount');
return { retryCount: val };
props.setBoolean(key, value)
Store a
trueorfalseflag.
const wi = await WorkItems.getByKey('PROJ-1');
await wi.getEntityProperties().setBoolean('notified', true);
return { saved: true };
props.getBoolean(key)
Read back a stored boolean.
const flag = await (await WorkItems.getByKey('PROJ-1')).getEntityProperties().getBoolean('notified');
return { notified: flag };
props.setJson(key, value)
Store any JSON-serialisable object or array.
const wi = await WorkItems.getByKey('PROJ-1');
await wi.getEntityProperties().setJson('metadata', { source: 'api', version: 2 });
return { saved: true };
props.getJson(key)
Read back a stored JSON object or array.
const data = await (await WorkItems.getByKey('PROJ-1')).getEntityProperties().getJson('metadata');
return { metadata: data };
What to expect: Returns the original object/array exactly as it was stored, or null if the key does not exist.
props.propertyExists(key)
Check whether a key is currently set on this entity.
const exists = await (await WorkItems.getByKey('PROJ-1')).getEntityProperties().propertyExists('reviewStatus');
return { exists };
What to expect: true if the key exists, false otherwise. Does not return the value.
props.getKeys()
List all property keys currently set on this entity.
const keys = await (await WorkItems.getByKey('PROJ-1')).getEntityProperties().getKeys();
return { keys };
What to expect: An array of key name strings. Returns [] if no properties have been set.
props.delete(key)
Remove a stored property entirely.
const wi = await WorkItems.getByKey('PROJ-1');
await wi.getEntityProperties().delete('reviewStatus');
return { deleted: true };
What to expect: The key is removed. Subsequent get* calls for this key return null. Silently succeeds if the key did not exist.
5. Users - Static Methods
Users.getLoggedInUser()
Get the Jira user identity running the current script (the app service account).
const me = await Users.getLoggedInUser();
return {
displayName: me.displayName,
accountId: me.accountId,
active: me.active
};
What to expect: In ScriptForge the executing user is the Forge app service account. This is the easiest way to get your own accountId for use in other calls.
Users.getByAccountId(accountId)
Fetch a specific user by their Atlassian account ID.
const user = await Users.getByAccountId('YOUR_ACCOUNT_ID_HERE');
return {
name: user.displayName,
email: user.emailAddress,
type: user.accountType
};
What to expect: Returns a user object with displayName, emailAddress, accountId, active, accountType, and avatarUrl. Find account IDs via Users.getLoggedInUser() or from Jira's user management page.
Users.search(query)
Search users by display name or email address.
const users = await Users.search('jane');
return { count: users.length, names: users.map(u => u.displayName) };
What to expect: An array of matching users (max 50 by default). Returns an empty array if nobody matches the query.
6. HapiUser - Instance Methods
user.getEmailAddress()
Fetch the user's email address (makes a live API call).
const me = await Users.getLoggedInUser();
const email = await me.getEmailAddress();
return { email };
What to expect: Returns the email string, or undefined if the user's profile hides their email or the account is a service account.
user.getGroups()
Get all Jira groups this user belongs to.
const me = await Users.getLoggedInUser();
const groups = await me.getGroups();
return { count: groups.length, names: groups.map(g => g.name) };
What to expect: An array of group objects, each with name and groupId. Returns an empty array if the user is in no groups.
user.getEntityProperties()
Get the entity properties store for this user account.
const me = await Users.getLoggedInUser();
const props = me.getEntityProperties();
await props.setString('theme', 'dark');
const theme = await props.getString('theme');
return { theme };
What to expect: Returns an HapiEntityProperties object scoped to this user's account ID. All 11 property methods from Section 4 are available.
7. Groups - Static Methods
Groups.getByName(name)
Fetch a Jira group by its exact name.
const group = await Groups.getByName('jira-users-yoursite');
return { name: group.name, id: group.getGroupId() };
What to expect: Returns an HapiGroup object. On Jira Cloud, group names are typically jira-users-<sitename>. You can find exact names in Jira's group management settings (Admin → User management → Groups).
Groups.create(name)
Create a new Jira group.
const group = await Groups.create('my-automation-group');
return { name: group.name, id: group.getGroupId() };
What to expect: A new empty group is created and its object returned. Group names must be unique. On Jira Cloud with identity provider sync, creation may have a short propagation delay.
8. HapiGroup - Instance Methods
group.getGroupId()
Get the internal unique ID of the group (synchronous - no
awaitneeded).
const group = await Groups.getByName('jira-users-yoursite');
return { groupId: group.getGroupId() };
What to expect: Returns the UUID string for the group, or undefined for older groups that predate Jira Cloud's group ID system.
group.getMembers()
List all users who are members of the group.
const group = await Groups.getByName('jira-users-yoursite');
const members = await group.getMembers();
return { count: members.length, first: members[0]?.displayName };
What to expect: An array of user objects, each with accountId, displayName, and emailAddress. All members are returned across multiple API calls for large groups.
group.contains(accountId)
Check whether a specific user is a member of the group.
const group = await Groups.getByName('jira-users-yoursite');
const me = await Users.getLoggedInUser();
const isMember = await group.contains(me.accountId);
return { isMember };
What to expect: true or false. You can pass either an account ID string or a HapiUser object directly.
group.add(accountId) âš ï¸
Add a user to the group by account ID.
const group = await Groups.getByName('my-automation-group');
await group.add('5ff5815e34847e0069fd1234');
return { added: true };
What to expect: Adds the user to the group. On Jira Cloud with managed accounts or external identity providers, the Forge app service account may receive a 400 error. See Platform Limitations.
group.remove(accountId) âš ï¸
Remove a user from the group by account ID.
const group = await Groups.getByName('my-automation-group');
await group.remove('5ff5815e34847e0069fd1234');
return { removed: true };
What to expect: Removes the user from the group. Subject to the same platform restriction as add(). See Platform Limitations.
9. Spaces - Static Methods
In ScriptForge, Spaces map directly to Jira Projects. A space key is the same as a Jira project key.
Spaces.getByKey(key)
Fetch a Jira project by its key.
const space = await Spaces.getByKey('PROJ');
return {
key: space.key,
name: space.name,
lead: space.lead,
type: space.projectTypeKey
};
What to expect: Returns the project name, lead display name, project type (software, business, service_desk), description, and URL.
Spaces.getById(id)
Fetch a Jira project by its numeric ID.
const space = await Spaces.getById('10000');
return { key: space.key, name: space.name };
What to expect: Same result as getByKey. Useful when you have the ID rather than the key. Find project IDs from Spaces.list() or in the project settings URL.
Spaces.list()
List all projects visible to the current user.
const projects = await Spaces.list();
return { count: projects.length, keys: projects.slice(0, 5).map(s => s.key) };
What to expect: An array of HapiSpace objects. Only projects the service account has browse access to are returned.
Spaces.create(key, name, fn)
Create a new Jira project with optional configuration via a fluent builder.
const suffix = Date.now().toString().slice(-4);
const me = await Users.getLoggedInUser();
const space = await Spaces.create('TST' + suffix, 'Test Project ' + suffix, b => {
b.setLeadAccountId(me.accountId);
b.setSpaceTypeKey('software');
b.setDescription('Created by the Execution Console');
});
return { key: space.key, name: space.name };
What to expect: A new Jira project is created. The project key must be unique, all uppercase, and 2-10 characters. The builder (b) supports:
setLeadAccountId(accountId)- the project lead's account ID (required for most Jira Cloud instances)setSpaceTypeKey('software' | 'business' | 'service_desk')setDescription(text)setUrl(url)setDefaultAssigneeToProjectLead()/setDefaultAssigneeToUnassigned()setAvatarId(numericId)setSpaceTemplateKey(templateKey)
10. HapiSpace - Instance Methods
space.update(fn)
Modify one or more properties of an existing project.
const space = await Spaces.getByKey('PROJ');
const updated = await space.update(b => {
b.setName('Renamed Project');
b.setDescription('Updated description');
});
return { key: updated.key, name: updated.name };
What to expect: The project is updated and the refreshed HapiSpace object is returned. The builder (b) supports:
setKey(newKey)- rename the project keysetName(name)setDescription(text)setUrl(url)setLeadAccountId(accountId)setDefaultAssigneeToProjectLead()/setDefaultAssigneeToUnassigned()setSpaceCategory(categoryId)
space.delete()
Permanently delete a Jira project and all its issues.
// Create a throwaway project first to test deletion safely
const me = await Users.getLoggedInUser();
const suffix = Date.now().toString().slice(-4);
const space = await Spaces.create('DEL' + suffix, 'Delete Test', b => {
b.setLeadAccountId(me.accountId);
});
await space.delete();
return { deleted: space.key };
What to expect: The project and all its issues are deleted with no undo. Requires project admin permission.
space.getEntityProperties()
Get the entity properties store for this project.
const space = await Spaces.getByKey('PROJ');
const props = space.getEntityProperties();
await props.setString('deployEnv', 'production');
const env = await props.getString('deployEnv');
return { deployEnv: env };
What to expect: Returns an HapiEntityProperties object scoped to the project key. All 11 property methods from Section 4 are available.
11. Platform Limitations
Two functions have inherent Jira Cloud platform restrictions when running as a Forge app service account:
| Function | Limitation |
|---|---|
HapiGroup.add(accountId) |
Jira Cloud returns HTTP 400 when a Forge app service account tries to add users to managed groups. This is a Jira permission restriction, not a Canarys HAPI bug. Works correctly when the executing account has group admin rights. |
HapiGroup.remove(accountId) |
Same restriction as add(). |
These functions are correctly implemented in Canarys HAPI and will work as expected in environments where the executing account has sufficient Jira group admin permissions.
12. Quick Reference Table
| # | Function | Module | One-liner |
|---|---|---|---|
| 1 | WorkItems.getByKey(key) |
WorkItems | Fetch an issue by its Jira key |
| 2 | WorkItems.getById(id) |
WorkItems | Fetch an issue by its numeric ID |
| 3 | WorkItems.create(proj, type, fn) |
WorkItems | Create a new issue with a builder |
| 4 | WorkItems.search(jql) |
WorkItems | Run a JQL query, get lazy results |
| 5 | WorkItems.count(jql) |
WorkItems | Count issues matching a JQL query |
| 6 | workItem.update(fn) |
HapiWorkItem | Update fields on an existing issue |
| 7 | workItem.refresh() |
HapiWorkItem | Reload latest field values from Jira |
| 8 | workItem.addComment(text) |
HapiWorkItem | Post a comment to the issue |
| 9 | workItem.getComments() |
HapiWorkItem | Retrieve all comments on the issue |
| 10 | workItem.getCreator() |
HapiWorkItem | Get the issue creator's info (sync) |
| 11 | workItem.getCustomFieldValue(field) |
HapiWorkItem | Read a custom field value |
| 12 | workItem.delete() |
HapiWorkItem | Permanently delete the issue |
| 13 | workItem.transition(status) |
HapiWorkItem | Move the issue to a new workflow status |
| 14 | workItem.createSubTask(type, fn) |
HapiWorkItem | Create a sub-task under this issue |
| 15 | workItem.getSubTaskObjects() |
HapiWorkItem | Get sub-tasks as full HapiWorkItem objects |
| 16 | workItem.getEntityProperties() |
HapiWorkItem | Get the entity properties store |
| 17 | workItem.link(type, target) |
HapiWorkItem | Create a named link to another issue |
| 18 | workItem.unlink(type, target) |
HapiWorkItem | Remove a named link to another issue |
| 19 | results.take(n) |
HapiSearchResults | Get the first N results from a search |
| 20 | results.toArray() |
HapiSearchResults | Fetch all results into an array |
| 21 | results.forEach(fn) |
HapiSearchResults | Iterate every result with a callback |
| 22 | props.setString(key, val) |
HapiEntityProperties | Store a string property |
| 23 | props.getString(key) |
HapiEntityProperties | Read a stored string |
| 24 | props.setInteger(key, val) |
HapiEntityProperties | Store an integer property |
| 25 | props.getInteger(key) |
HapiEntityProperties | Read a stored integer |
| 26 | props.setBoolean(key, val) |
HapiEntityProperties | Store a boolean flag |
| 27 | props.getBoolean(key) |
HapiEntityProperties | Read a stored boolean |
| 28 | props.setJson(key, obj) |
HapiEntityProperties | Store any JSON object or array |
| 29 | props.getJson(key) |
HapiEntityProperties | Read a stored JSON object or array |
| 30 | props.propertyExists(key) |
HapiEntityProperties | Check if a property key is set |
| 31 | props.getKeys() |
HapiEntityProperties | List all property keys on this entity |
| 32 | props.delete(key) |
HapiEntityProperties | Remove a stored property |
| 33 | Users.getLoggedInUser() |
Users | Get the currently executing user |
| 34 | Users.getByAccountId(id) |
Users | Fetch a user by Atlassian account ID |
| 35 | Users.search(query) |
Users | Search users by name or email |
| 36 | user.getEmailAddress() |
HapiUser | Fetch the user's email address |
| 37 | user.getGroups() |
HapiUser | Get all groups the user belongs to |
| 38 | user.getEntityProperties() |
HapiUser | Get the entity properties store for the user |
| 39 | Groups.getByName(name) |
Groups | Fetch a group by its exact name |
| 40 | Groups.create(name) |
Groups | Create a new Jira group |
| 41 | group.getGroupId() |
HapiGroup | Get the group's unique UUID (sync) |
| 42 | group.getMembers() |
HapiGroup | List all members of the group |
| 43 | group.contains(accountId) |
HapiGroup | Check if a user is in the group |
| 44 | group.add(accountId) âš ï¸ |
HapiGroup | Add a user to the group |
| 45 | group.remove(accountId) âš ï¸ |
HapiGroup | Remove a user from the group |
| 46 | Spaces.getByKey(key) |
Spaces | Fetch a project by its key |
| 47 | Spaces.getById(id) |
Spaces | Fetch a project by its numeric ID |
| 48 | Spaces.list() |
Spaces | List all visible projects |
| 49 | Spaces.create(key, name, fn) |
Spaces | Create a new Jira project |
| 50 | space.update(fn) |
HapiSpace | Modify project properties |
| 51 | space.delete() |
HapiSpace | Permanently delete a project and its issues |
| 52 | space.getEntityProperties() |
HapiSpace | Get the entity properties store for the project |
âš ï¸ = Platform limitation on Jira Cloud Forge app service accounts. See Section 11.
13. Common Patterns
Find your own account ID
const me = await Users.getLoggedInUser();
return { accountId: me.accountId };
List all your project keys
const projects = await Spaces.list();
return projects.map(s => ({ key: s.key, name: s.name }));
Full issue lifecycle (safe test)
const wi = await WorkItems.create('PROJ', 'Task', b => {
b.setSummary('[TEST] Canarys HAPI lifecycle');
});
await wi.addComment('Created by Canarys HAPI test');
await wi.update(b => b.setPriority('Low'));
await wi.transition('In Progress');
await wi.delete();
return { completed: true };
Full project create → update → delete cycle
const me = await Users.getLoggedInUser();
const suffix = Date.now().toString().slice(-4);
const space = await Spaces.create('TS' + suffix, 'Temp Space', b => {
b.setLeadAccountId(me.accountId);
});
const updated = await space.update(b => b.setName('Temp Space (renamed)'));
await space.delete();
return { key: space.key, renamed: updated.name, deleted: true };
Bulk-update all open issues
const results = WorkItems.search('project = PROJ AND status = "To Do"');
await results.forEach(async wi => {
await wi.addComment('Auto-tagged by Canarys HAPI script');
});
return { done: true };
Store metadata on an issue and read it back
const wi = await WorkItems.getByKey('PROJ-1');
const props = wi.getEntityProperties();
// Write
await props.setJson('auditInfo', { taggedBy: 'Canarys HAPI-test', taggedAt: new Date().toISOString() });
// Read
const info = await props.getJson('auditInfo');
return { auditInfo: info };