Reference
This is the syntax and helper reference for writing ONEScript scripts. For task-based guidance, start with Get started and Guides; for ready-to-adapt recipes, see the Cookbook.
Helpers are the methods you call on the script objects — issue, api, event, selection, openapi — to read and change data (for example issue.update(...), api.reject(...), issue.status.changedTo(...), selection.options.from(...)). Accessors such as issue.field("Priority").value and selection.request.relations are properties you read, not methods you call.
Triggers
A script's trigger decides when it runs and what it's allowed to do. ONEScript provides eight trigger types:
| Trigger | Level | When it runs | Configure under |
|---|---|---|---|
console | L1 (read-only) | Run a Dry Run from the Script editor | — |
taskPreAction | L0 | Before a change is committed; can reject it | Automation → Trigger Bindings |
taskActionDone | L1 | After a change is committed | Automation → Trigger Bindings |
event | L1 | On an issue event (created / updated) | Automation → Trigger Bindings |
timer | L1 | On a schedule | Automation → Timer Rules |
scriptedField | L0 | To compute a Float field | Resources → Scripted Fields |
scriptedFieldOptions | L1 (read-only) | To provide options for a native Selection field configured as single choice or multiple choices | Resources → Scripted Fields |
httpEndpoint | L1 | On an authenticated inbound HTTP request (POST /hooks/<slug>) | REST Endpoints |
Trigger policy matrix
L0 triggers are synchronous and high-frequency, so they get no network and no storage. L1 triggers may use controlled adapters, subject to read/write rules.
| Trigger | Level | Network | Writes | Storage | Typical use |
|---|---|---|---|---|---|
console | L1 | yes | no | get only | one-off read checks, metadata lookup, safe queries |
taskPreAction | L0 | no | issue field / reject only | no | pre-submit validators and guards |
taskActionDone | L1 | yes | yes | get/set/delete | post-action updates, comments, sync |
event | L1 | yes | yes | get/set/delete | async listeners, idempotent automations |
timer | L1 | yes | yes | get/set/delete | scheduled query/batch work |
scriptedField | L0 | no | no | no | Float field calculation (On-Premises) |
scriptedFieldOptions | L1 | yes | no | get only | native Selection options (On-Premises) |
httpEndpoint | L1 | yes | yes | get/set/delete | authenticated inbound REST endpoints |
Helpers that aren't allowed for the current trigger are flagged by the editor's inline diagnostics before you publish.
Run now is not a configurable trigger. It is an editor action that performs one live execution of an eligible latest published version.
skip(...), api.reject(...), and a runtime timeout are terminal control flow. Catching their signal in script code does not re-enable host helpers, network, storage, logging, responses, or recorded effects.
For taskPreAction, Trigger Bindings may be scoped by Project and Issue Type. ONEScript resolves missing scope from the current trusted issue before selecting bindings. A proven non-match continues normally. Only when a potentially applicable scoped guard remains unresolved does ONEScript block the action with an availability reason and run zero scripts. Successful guard create, enable, disable, edit, and delete operations are effective for the next workflow action; they don't wait for cache expiry.
Workbench access
The ONEScript Workbench is protected by the onescript_workbench_access permission point. An explicit denial returns HTTP 403. If the permission service is unavailable, Workbench API requests return HTTP 503.
Script context objects
Inside a script you have these objects:
| Object | What it is |
|---|---|
issue | The issue in context — read and change its fields, status, assignee; add comments |
event | The triggering event (e.g. event.eventID, event.changedField(...)) |
context | Lower-level trigger context — context.taskUUID, context.action, raw changed-field snapshots |
api | Helpers: api.log, api.reject, api.queryIssues, api.storage, api.fetchExternal |
vars | Reusable values and secrets defined under Resources → Variables (vars.get("NAME")) |
selection | Native Selection option helpers and read-only request context (selection.options.*, selection.request.*) |
openapi | Catalog-backed ONES OpenAPI helpers (openapi.issue.*, openapi.workflow.transit, …) |
Run now
Run now executes only the latest published version after the server confirms it again. The script must be Published and must not depend on a current issue, event or field changes, workflow action, timer scheduling context, or an inbound HTTP request, caller, or response.
Run now supplies the current team and executing administrator, Variables and Secrets, and the Storage, OpenAPI, external HTTP, and logging capabilities allowed by the manual-execution policy. A batch script should select explicit targets with issue.search(...) or explicit issue identities.
The confirmation identifies the script and published version and warns about live data changes and external calls. One request executes at most once. Status, duration, operations, output, and errors are written to the Execute Log. Result Unknown means the script may have run but the final recorded state could not be confirmed; inspect the Execute Log and business data before retrying.
Timer Rule intervals
| Item | Rule |
|---|---|
| Units | Seconds, Minutes, Hours, Days |
| Value | Positive integer |
| Range | 30 seconds through 366 days |
| Meaning of a day | One day is always 24 hours |
| Create, re-enable, or change interval | Server save time plus one full interval |
| Rename only | Preserve the existing Next run |
| Current schedule type | Fixed interval |
Last run and Next run on the rule card come from the server. Decimals, Cron, natural-language schedules, “every day at,” and timezone calendar schedules are not supported.
Native scripted fields
Native Float and Selection scripted fields are available in the private-full On-Premises package and are configured under Resources → Scripted Fields after their provider script is Published. ONEScript manages the native field UUID internally.
Float
A scriptedField script returns one synchronous finite number:
return 42.5
The runtime does not allow network or storage in this L0 trigger. Enter the field name when enabling the rule; never ask an author to find the native field UUID. One app installation owns one manifest-provisioned Float field, so editing the rule renames or rebinds that field instead of creating arbitrary additional Float fields.
Selection option protocol
A scriptedFieldOptions script supplies options for a native Issue, Project, or Product Selection field configured as single choice or multiple choices. It is read-only L1 with a five-second limit: ONES/OpenAPI reads are allowed, while issue, field, storage, variable, comment, and notification writes are blocked.
The runtime receives search text, pagination, selected IDs, relations, user, field, and context from ONES through selection.request. Authors normally do not parse those values directly. The selection.options helpers own keyword filtering, pagination, deduplication, exact selected-ID resolution, and the platform { options, page_result } response.
selection.options.from(items, config?)
Use an in-memory array. Strings need no separate ID:
return selection.options.from(["High", "Medium", "Low"])
Each string is both identity and display. If its label changes, its saved identity also changes. For a renamable option, use a stable business/source key:
return selection.options.from([
{ id: "level-high", value: "High" },
{ id: "level-medium", value: "Medium" }
])
The canonical author shape is { id, value }:
idis a stable business/source identity, not an ONES field UUID. Keep it unchanged when the display text changes.uuidandidare accepted aliases for the same identity.value,name,label, andtitleare accepted aliases for the same display text.- These aliases have no behavioral difference. ONEScript normalizes them to the platform
{ uuid, value }shape. - Objects without a stable identity fail explicitly. ONEScript does not invent array-position, display-text, hash, or random IDs.
For custom object properties, configure the paths once:
return selection.options.from(products, {
id: "productCode",
display: "{productCode} - {productName}",
searchBy: ["productCode", "productName"]
})
display can be a property path or a {token} template. searchBy defaults to the rendered display. Duplicate IDs with the same display collapse; conflicting displays for one ID fail.
selection.options.fromIssues(config)
Use the semantic issue search adapter for dynamic issue choices:
return await selection.options.fromIssues({
where: {
issueType: "Requirement",
fields: { Priority: "Highest" }
}
})
Provide exactly one of where or matches. The same semantic names accepted by issue.search are resolved in the current team. Defaults are:
- ID: issue UUID.
- Display:
{key} - {title}. - Search: key, title, and rendered display.
- Paging: automatically search and return the requested page from ONES.
- Selected-ID resolution: exact current-team UUID lookup, without reapplying
where/matches, keyword, or page filters. maxItems: an optional positive-integer business guard; exceeding it fails explicitly.- Execution time: an options script runs for at most five seconds; timeout or an incomplete source fails explicitly.
Optional members are select, maxItems, display, and searchBy. display supports {uuid}, {key}, {title}, ordinary object paths, and {field:Field Name}. Add any named fields used by the template or search to select.
selection.options.fromProvider(config)
Use a paged external REST, ONES OpenAPI, or ONESQL source:
return await selection.options.fromProvider({
search: async ({ keyword, offset, limit, relations, scope }) => {
const result = await loadCatalogPage({ keyword, offset, limit, relations, scope })
return { items: result.items, total: result.total }
},
resolve: async ({ uuids, relations, scope }) => {
return await loadCatalogItemsByIds(uuids, { relations, scope })
},
id: "id",
display: "{code} - {name}"
})
search returns an array or { items, total }; resolve must return every ID requested by ONES. Both callbacks are required because ONEScript cannot infer a source-specific exact-ID lookup. scope contains the current field UUID, user UUID, and context. The helper owns the ONES protocol; provider code owns only source querying and stable source identity.
Worklog aggregate
Inside a taskPreAction script, issue.worklog exposes an immutable snapshot of the current issue's time aggregate:
| Member | Meaning |
|---|---|
spentHours | Registered work on the current issue |
estimatedHours | Estimate on the current issue |
remainingHours | Remaining estimate on the current issue |
hasSpent() | true when spentHours > 0 |
hasEstimate() | true when estimatedHours > 0 |
isOverEstimate() | true when spent time exceeds the estimate |
varianceHours | spentHours - estimatedHours |
progressPercent | Spent time as a percentage of the estimate; null when the estimate is zero |
totalEstimatedHours | Platform-provided estimate roll-up |
totalRemainingHours | Platform-provided remaining-time roll-up |
There is no totalSpentHours. Do not derive one by listing and summing paginated worklog records.
if (
issue.status.changedFromTo("In Progress", "Done") &&
!issue.worklog.hasSpent()
) {
api.reject("Please register work before completing this issue.")
}
For Dry Run, provide a visible key such as "issueKey": "PROJECT-123" together with the simulated field change. ONEScript resolves the key in the current team; users do not need to find context.taskUUID.
Semantic field syntax
The readable syntax resolves fields, statuses, projects, issue types, and options by display name. Names and values are case-sensitive and exact — User Story and User story are different values, and the runtime never lowercases or fuzzy-matches them.
Current-value checks
issue.project.is("Project Name")
issue.issueType.is("Requirement")
issue.status.is("In Review")
issue.statusCategory.is("in_progress")
issue.field("Priority").is("Highest")
issue.statusCategory matches one of the three fixed category codes — to_do, in_progress, done — which are identical on every instance regardless of display language. (The localized display name, e.g. Done, also matches, but is instance-specific.) issue.status, by contrast, matches a specific workflow status by its display name ("In Review", "Done").
Field identity
issue.field("Field Name")resolves against the issue's field metadata. An exact field display name or field UUID is valid; guessed, translated, or fuzzy names are not.- If two fields share the same display name, the reference is ambiguous — supply a unique name or the field UUID instead of guessing.
Field values
.value and .newValue are accessors — properties you read on issue.field(...), not methods you call:
issue.field("Priority").value— the field's current stored value.issue.field("Planned end date").newValue— in ataskPreActionguard, the incoming (pending) value. There.valuedeliberately stays the pre-change state, because the change has not been applied yet; use.newValue(orchangedTo(...)) to read what the edit is about to set. OutsidetaskPreAction,.newValueis not meaningful.
Type-aware values
Values are not treated as plain strings — ONEScript interprets and encodes every value according to the field's type. This applies wherever a value is read, compared, or written: .is(...), .contains*(...) / .isExactly(...), the change helpers (.changedTo(...), .changedFrom(...)), and writes via issue.update({...}) all use the same type rules. You supply the value in its natural, human form (a display name, a business number, a date string, a boolean) and the runtime converts it to the stored representation — and back when comparing.
| Field type | How you supply the value (read, compare, or write) |
|---|---|
| Text | raw string — "Test" |
| Integer / float | business number — 42 (the runtime applies ONES scaling) |
| Date / time | seconds timestamp or a parseable date/time string — "2026-06-06" (normalized to seconds) |
| Boolean | true / false |
| Single reference (option, sprint, user, project, task, issue type, status) | exact display name or ID, resolved to the stored ID |
| Multi-value (user list, multi-option, version, project list, department) | display names or IDs, resolved to an ID array |
Because resolution is type-aware, the same value works whether you're matching or writing — e.g. issue.field("Sprint").is("Sprint 12") and issue.update({ Sprint: "Sprint 12" }) both resolve "Sprint 12" to its stored ID. Display values remain case-sensitive and exact.
Multi-value matching
For multi-value fields, choose the helper that matches your intent:
issue.field("Tags").is("A") // contains this one value
issue.field("Tags").is(["A", "B"]) // exact set equality (order-independent)
issue.field("Tags").containsAll(["A", "B"]) // contains all of these
issue.field("Tags").containsAny(["A", "B"]) // contains at least one of these
issue.field("Tags").isExactly(["A", "B"]) // exactly this set
Prefer containsAll / containsAny / isExactly for multi-value intent; they read clearly and are the canonical forms.
Change helpers
Available only in event, taskActionDone, and taskPreAction (triggers that carry changed-field information).
issue.anyFieldChanged()
issue.anyStatusChanged()
issue.status.changedTo("Done")
issue.statusCategory.changedTo("in_progress")
issue.statusCategory.changedFromTo("to_do", "in_progress")
issue.assignee.changedTo("Bob")
issue.assignee.changedFromTo("Alice", "Bob")
issue.field("Found in Production").changedTo("Yes")
issue.field("Priority").changedFrom("Low")
event.changedField("Status").isChanged()
For status-related rules on updated events, prefer issue.anyStatusChanged() plus issue.status.changed* or issue.statusCategory.changed*. In a taskPreAction guard, remember that field reads report the pre-change state — see Field values for .value vs .newValue.
Related issues and hierarchy
Read the state of issues linked to the current one — by a link/relation field, or by a named parent/child hierarchy. Reference the field by its display name, then use a quantifier (all / any / none) and a predicate.
issue.linkedIssues("Prerequisites").none.statusCategory.is("done")
issue.linkedIssues("Prerequisites").any.field("Priority").is("High")
issue.hierarchy("Upstream").any.field("Planned end date").isBefore("2026-08-01")
- Quantifiers:
all(every linked issue),any(at least one),none(zero). With no linked issues,all/noneare true andanyis false. - Predicates:
status/statusCategoryplus the field helpers (is,contains,containsAll,containsAny,isExactly), and day-granularity date orderingisAfter(value)/isBefore(value). Ordering compares at day granularity across every date shape ONES produces; a missing or non-date value makes the predicate false, so an unset date never rejects. issue.hierarchy("Name")is the same surface aslinkedIssues, named for the product concept and validated: pointing it at a non-hierarchy field, or a display name that does not resolve, throws at the call site instead of silently matching nothing.- Set size:
.countis the number of linked issues;.isEmptyistrueonly when the set is genuinely empty..isEmptystaysfalsewhen the data can't be read or was truncated — it never claims "empty" it can't confirm. Use these to tell "there are none" apart from "none match":none.<predicate>istruein both cases, so guard "no prerequisites at all" with.isEmpty(or.count === 0), notnone. .list()(escape hatch) — returns a read-only array of per-issue handles for iteration the quantifiers can't express. Each handle exposes the samestatus/statusCategory/field(name)predicates, and you can use native array methods (.some,.filter,.length). Prefer the quantifiers for readability; reach for.list()only when you need custom iteration.
// Distinguish "no prerequisites" from "prerequisites not done"
if (issue.linkedIssues("Prerequisites").isEmpty) {
api.log("No prerequisites linked")
}
// Count the ones still open, using .list() + native array methods
const open = issue.linkedIssues("Prerequisites")
.list()
.filter(p => !p.statusCategory.is("done")).length
api.log(open + " prerequisite(s) still open")
The canonical hierarchy date guard — a child's planned end date must not be later than its parent's, in both directions:
if (context.action === "update" && issue.field("Planned end date").changed()) {
const next = issue.field("Planned end date").newValue
if (issue.hierarchy("Upstream").any.field("Planned end date").isBefore(next)) {
api.reject("The planned end date must not be later than the upstream (parent) issue.")
}
if (issue.hierarchy("Downstream").any.field("Planned end date").isAfter(next)) {
api.reject("A downstream (child) issue has a later planned end date; adjust it first.")
}
}
If the host cannot read the linked issues (a transient outage, or more than the fan-out cap), the predicate throws and the run is recorded as a failed execution — not an automatic rejection. To fail closed, wrap the rule in
try { ... } catch (e) { api.reject("Cannot verify prerequisites.") }.
Write helpers
Available only in write-capable triggers (taskActionDone, event, timer). api.reject(...) is the exception — it belongs to taskPreAction.
issue.update({ Priority: "Highest" }) // canonical batch field update
issue.transitTo("Done") // workflow-based status change
issue.addComment("Handled by ONEScript.") // add a plain-text comment
api.reject("Reason shown to the user.") // taskPreAction only
Never place a Secret in an api.reject(...) reason. If an exact resolved Secret value is used accidentally, the platform reject_reason and Host decision logs show [REDACTED]. Encoded, hashed, split, partially extracted, or otherwise transformed values are not covered.
Status transitions
Status is not an ordinary writable field. To move the current issue to an exact target status, prefer:
issue.transitTo("Done")
The target is an exact, case-sensitive status display name or status ID. ONEScript queries the current issue's executable workflows and executes the single action whose target matches. It does not use partial or case-insensitive matching and does not choose arbitrarily when multiple actions reach the same status.
The optional second argument supplies workflow form data. It does not identify the issue or choose a workflow:
issue.transitTo("Done", {
fields: {
Resolution: "Fixed"
},
comment: [
{ contentType: 1, text: "Completed by ONEScript." }
],
resources: ["attachment-id"],
timeEstimated: { hours: 4 },
timeSpent: [
{
owner: "user-uuid",
startTime: 1785340800,
hours: 1,
description: "Final verification"
}
]
})
fieldsuses the same display-name and type-aware value resolution asissue.update(...).commentis an array whosecontentTypeis a positive integer and whosetextis a string.resourcescontains existing attachment IDs.timeEstimated.hours,timeSpent.startTime, andtimeSpent.hoursare integers. Every spent-time row also requires an owner UUID.- Do not pass
issueID,taskUUID,workflowID, or rawfieldValues. ONEScript owns the current issue and workflow discovery. - If the issue is already at the target status, the transition is a no-op.
No executable action is an error; multiple executable actions reaching the
target produce
AMBIGUOUS_WORKFLOW. - The executable-workflow read may retry safely. The workflow execution request is sent at most once and is never retried after an unknown transport outcome.
taskPreActioncannot start a nested workflow. Useapi.reject(...)to block the action being submitted, or move status from aneventor post-action script.- Dry Run can read executable workflows to preview the result, but never sends the workflow execution request.
Existing semantic updates remain compatible. When Status appears in
issue.update(...), ONEScript routes it through the same workflow path and sends
the other fields with that action:
issue.update({
Status: "Done",
Resolution: "Fixed"
})
Use issue.transitTo(...) for an explicit status move and issue.update(...)
when status and fields belong together. Status Category is derived and cannot
be written. Raw openapi.issue.update payloads cannot write field005; advanced
scripts that intentionally select a known workflow ID can use
openapi.workflow.transit only after proving that action is currently
executable.
Email notifications
Send email through the official ONES notification channel. Write-capable async triggers only (timer, event, taskActionDone), and On-Premises deployments only — the SaaS-compatible build logs an explicit unsupported outcome instead of sending.
api.notify({
channel: "email", // required; only "email" is supported
recipients: ["<userUUID>"], // ONES user UUIDs (never emails or names), max 100
title: "Overdue issues need attention",
message: "This issue has stayed in the current stage for over 3 days.",
issue: context.taskUUID // optional; the host builds the email link from it
})
Delivery is not retried automatically — check Execute Log for the per-notification outcome. For rich mail, add columns (table headers) and items (one row per issue, ≤20 entries); the host builds each link, and raw URL values are rejected.
Comments and mentions
issue.addComment({
text: "The status changed, please check",
mentions: [issue.assignee.mention()]
})
issue.addComment(...) is supported. Mentions (.mention() / .mentions()) are documented but pending re-verification — validate with a Dry Run / Execute Log before relying on them.
For a custom member field, use issue.field("Reviewer").mention() (single-user) or issue.field("Reviewers").mentions() (multi-user). Keep comment text in text; don't read raw user UUIDs when the field helper can supply the mention.
Blocking a transition: from a guard script, only
api.reject(...)blocks a change. A script that throws, times out, or hits a policy limit is logged for diagnosis but does not block the transition — so write guards that either callapi.reject(...)intentionally or do nothing. Guard selection separately fails closed without running a script when trusted scope is unavailable and an enabled scoped guard could apply.
Querying data
issue.search(...) is the canonical way to find issues by their business meaning. Available where network is allowed (L1 triggers) — there is no dedicated Query UI; querying lives inside scripts.
const issues = await issue.search({
where: {
issueType: "Requirement",
fields: { Priority: "Highest" }
},
select: ["Estimated Hours"], // read named fields onto each row (unknown names throw)
limit: 100
})
api.log("Matched issues: " + issues.length)
limit defaults to 50 and must be an integer from 1 through 100. It controls the maximum number of issues returned by that call. Use offset to skip a specific number of earlier results:
const issues = await issue.search({
where: { issueType: "Requirement" },
offset: 100,
limit: 50
})
For continuous multi-page processing, use the read-only pageInfo on the result. Pass the current page's pageInfo.endCursor as cursor in the next call:
let cursor = ""
do {
const issues = await issue.search({
where: { issueType: "Requirement" },
limit: 100,
cursor
})
for (const item of issues) {
api.log(item.key + " - " + item.title)
}
cursor = issues.pageInfo.hasNextPage
? issues.pageInfo.endCursor
: ""
} while (cursor)
Do not combine cursor with a non-zero offset. The query fails explicitly when paging returns duplicates, too many rows, incomplete metadata, or a cursor that cannot advance.
issue.search({ where, select, offset, limit, cursor }) returns row-bound semantic issues, so each result supports current-value helpers, issue.assignee.mention(), issue.update(...), issue.addComment(...), and issue.transitTo(...) — locked to that result's issue. The condition object supports project, issueType (or type), status, statusCategory, assignee, and named fields; provide exactly one of where or its matches spelling.
Scheduled queries. A Timer Rule can run an
issue.searchon a schedule — for example, email each owner about their overdue issues every morning. Seeapi.notifyunder Write helpers.
Use raw ONESQL only as an advanced escape hatch for conditions issue.search cannot express (such as date ranges). It uses the singular issue table and field UUIDs, and returns plain data rows without semantic issue methods:
const rows = await api.queryIssues({
query: "SELECT uuid, field001 FROM issue WHERE v$cursor > '' LIMIT 1000, 20",
variables: []
})
Inline values as quoted literals; do not rely on $1-style positional variables (some deployments ignore the parameter, matching the literal text $1 and returning zero rows with no error).
Runtime state and variables
const token = vars.get("WEBHOOK_TOKEN") // Resources → Variables
const cursor = await api.storage.get("lastCursor") // get: read-only L1 OK
await api.storage.set("lastCursor", { issue: context.taskUUID }) // set/delete: write-capable L1
api.log("Cursor saved")
api.storage is ONEScript's own runtime state. (It is not Hosted-App object/entity storage, which is not exposed in 1.1.)
For a Variable of type Secret, ONEScript masks the exact plaintext in execution logs, script output, and recorded effect payloads. This includes Secrets created or first resolved during the same execution. Exact-value masking does not cover an encoded, hashed, split, partially extracted, or otherwise transformed value, so authors who can read Secrets must be treated as trusted credential users. Never log, return, comment, notify, or persist a Secret.
External HTTP and OpenAPI
External HTTP
const response = await api.fetchExternal({
url: "https://example.com/hooks/onescript",
method: "POST",
headers: { Authorization: "Bearer " + vars.get("WEBHOOK_TOKEN") },
body: { issue: context.taskUUID },
timeoutMs: 5000
})
api.log("Webhook status: " + response.statusCode)
Rules:
- Only
httpsURLs are accepted. Plain HTTP and URL user information are rejected before DNS. - L1 network-capable triggers only (not
taskPreActionorscriptedField). - If
methodis omitted, a request withbodyusesPOST; a request withoutbodyusesGET. GET,HEAD, andOPTIONSuse read policy. Every other method requires a write-capable trigger.- Request and response bodies are limited to 512 KB. Request headers are limited to 32 KB.
- If supplied,
timeoutMsmust be a finite number. Strings,NaN, and infinite values are rejected before DNS. - The runtime blocks localhost, private, link-local/cloud-metadata, CGNAT, documentation/benchmark, multicast/reserved IPv4 ranges, every IPv6 destination, and local-only hostnames. A dual-stack hostname is pinned to a validated public IPv4 address; an IPv6-only target is unsupported. Your deployment may further restrict hosts with an allowlist.
- Don't set transport-controlled headers such as
Host,Content-Length,Transfer-Encoding,Connection,Keep-Alive,TE,Trailer,Upgrade, or proxy authorization headers. Invalid header names and values containing CRLF or NUL are rejected. - Read tokens via
vars.get(...); never log tokens, auth headers, or secret payloads. - Use the adapter — never raw
fetch.
OpenAPI helpers (with limits)
Catalog-backed helpers call the ONES OpenAPI by name. Namespaces include openapi.issue.*, openapi.workflow.transit, openapi.issueComment.*, openapi.worklog.*, openapi.wiki.*, openapi.account.*, openapi.project.*, and openapi.testcaseLibrary.*.
const response = await openapi.issue.create({
query: { teamID: context.teamUUID },
body: {
projectUUID: context.projectUUID,
issueTypeUUID: context.issueTypeUUID,
summary: "Follow-up from ONEScript"
}
})
api.log("Created issue status: " + response.statusCode)
openapi.request(...) / api.fetchOpenAPI(...) are escape hatches that still obey trigger policy and operation checks. Don't invent OpenAPI paths or helper names.
Raw OpenAPI paths must stay inside /openapi. Before policy checks, the runtime canonicalizes percent encoding, slashes, backslashes, trailing separators, and dot segments. Known blocked operations and every unknown mutating path fail closed; unknown safe reads are allowed only inside the canonical /openapi namespace.
Current limits to be aware of:
- Copilot helper — blocked at the service level in the verified profile.
- Summary worklog helpers — gated on a matching team worklog profile.
- Hosted-App object/entity storage — not exposed; use
api.storage.
Inbound REST endpoints
An inbound endpoint lets an external system call the exact latest version of any Published script over HTTP — the ScriptRunner "Custom REST Endpoint" equivalent. The stored script kind does not need to be httpEndpoint; endpoint execution supplies the request, authenticated caller, and response surfaces. Create it under the REST Endpoints tab (pick a Published script, a slug, an auth mode, and a runAs mode). Reached at POST /hooks/<slug> (POST-only).
Read the request via request (also available as context.request):
| Field | What it is |
|---|---|
slug | the endpoint slug that matched |
method / path | HTTP method and path |
query | query-string parameters |
headers | business headers only — auth/secret headers are stripped before the script sees them |
body | parsed JSON when the body is JSON, otherwise the raw string |
rawBody | a normalized string reconstructed from the platform-delivered body; for JSON, it isn't the caller's original protocol bytes |
Send the response with api.response.send(...); otherwise the script's plain output becomes a 200 JSON body:
const order = context.request.body
api.response.send({
status: 201,
headers: { "Content-Type": "application/json" },
body: { received: order.id }
})
Failure states (timeout, policy-denied, rejected) map to safe status codes and never leak script output.
Authentication (configured per endpoint):
| Mode | How it authenticates |
|---|---|
ones | a verified ONES OAuth2 token; supports required scopes and user/org/client allowlists; the caller must be a member of the endpoint's team |
token | a generated shared bearer token stored in the endpoint's reserved Secret |
hmac | a signature over the request using a generated endpoint-reserved Secret, with a timestamp tolerance |
Token/HMAC credentials are shown once when generated and can't be replaced with an ordinary script variable. Same-mode edits reuse the credential; changing authentication rotates it; switching to ones or deleting the endpoint removes it. A script can never read its own endpoint credential. HMAC signature and timestamp are accepted only in their dedicated headers.
For a new HMAC JSON client, send:
X-ONEScript-Signature-Version: onescript-json-v1
X-ONEScript-Timestamp: <timestamp>
X-ONEScript-Signature: sha256=<lowercase HMAC-SHA256 hex>
Content-Type: application/json
The signing input is the UTF-8 encoding of:
onescript-json-v1
<timestamp>
<RFC 8785 canonical JSON>
The timestamp in the signing input must exactly match the timestamp header. JSON object properties are recursively sorted by UTF-16 code units, arrays keep their order, and primitives use ECMAScript JSON serialization. Send I-JSON: avoid duplicate object keys, non-finite numbers, and lone Unicode surrogates. Changing the business value, timestamp, or signature version makes authentication fail before the script runs.
Requests without X-ONEScript-Signature-Version retain the historical reconstructed-body algorithm for existing clients only. Don't use that algorithm for a new integration. A third-party provider that can sign only its original raw JSON bytes, or can't send the ONEScript signature-version header, isn't compatible with HMAC mode.
REST endpoints don't support Project or Issue Type scope. Non-empty scope configuration is rejected instead of being silently ignored.
ones authentication defaults to Run as caller. Run as admin requires at least one explicit caller scope, user, organization, or client constraint. The app identity supplies network, writes, and storage. Deferred issue, comment, and notification effects are committed only after a successful, non-rejected run and before a success response is returned. A reservation is released only when zero commit is proven and no direct Host I/O occurred; partial or unknown commit outcomes keep the key reserved and the same idempotency key returns 409.
Run as caller. This mode is available only with ones authentication. Raw OpenAPI requests and aliases, generated openapi.* helpers, metadata, ONESQL, issue.search(...), issue.update(...), issue.set(...), issue.addComment(...), and issue.transitTo(...) run with the current requester's ONES identity and permissions. Missing identity or permission fails explicitly, with no admin fallback. The caller credential remains in the Host and is never exposed to script code or logs.
Hosted API, team-variable administration, and api.notify(...) are not caller OpenAPI operations and remain unavailable.
External HTTP and api.storage / api.scriptStorage remain ordinary endpoint L1 adapters in caller mode. They follow network, write, team, and script-isolation policy; they don't run as or impersonate the ONES caller.
Dependency and commit deadlines. Endpoint configuration, Secret, published-script, and runtime-configuration reads have a 3-second deadline; ONES token introspection and team-membership checks have a 5-second deadline; idempotency reads and reservations have a 1.5-second deadline. A pre-execution timeout returns a safe 503, runs no script, and releases both ingress and authenticated request capacity. A reservation timeout is treated as an unknown outcome: ONEScript never blindly releases the key, so a late reservation may make the same key return 409, while other keys can continue. Best-effort execution-log, delivery-log, and safe reservation-release writes have a 1.5-second deadline and cannot change the business response or keep request capacity occupied.
After a successful script run, each deferred-effect transport call has a 5-second deadline within a 10-second total effect-commit budget. The absolute budget covers authorization refreshes and retries as well as the original call. After any hop times out, ONEScript starts no later hop, retry, fallback, or subsequent effect. Because the commit outcome is unknown, the endpoint returns a generic 500, retains the idempotency key, and releases request capacity. In particular, a timed-out email send never falls back to a second notification channel because the first call may already have delivered.
Request and response limits. The inbound URL cap is 4096 bytes, request headers are limited to 100 entries and 32 KB total, and the request body cap is 512 KB. The execution timeout is 10 seconds and the response body cap is 256 KB; oversized responses return a safe 500 and are never truncated. In api.response.send(...), status must be an integer from 200 through 599. A response can contain at most 64 headers and 32 KB of total header data. Header names must be valid and values can't contain CRLF or NUL. Set-Cookie, Content-Length, hop-by-hop headers, and other transport-controlled headers are rejected. Invalid response metadata fails the endpoint instead of being forwarded.
Disallowed in the sandbox
Script validation rejects dynamic escape hatches and host access:
eval, Function, require, dynamic import, module loaders, process, globalThis, Buffer, filesystem (fs), and child-process access — plus raw fetch. It also flags likely typos of sandbox globals and unsupported helper signatures before you can publish or dry-run.
Execution capacity
Script executions run on a shared, bounded pool. Each ONES instance runs a limited number of scripts at once and holds a limited number of waiting runs, per instance and per team.
- When the pool has room, extra runs wait briefly and then execute normally.
- When the waiting queue is full, a new run fails immediately instead of waiting indefinitely. It produces no output and applies no effects, and the failure is recorded in the Execute Log.
- Waiting is shared fairly between teams, so one team's backlog cannot starve another's.
A capacity failure is not a defect in your script. If you see them, reduce how often the trigger fires, or split long-running work across scheduled runs.
Unsupported capabilities
ONEScript 1.1 does not support:
- Hiding native ONES transition/action buttons before the form opens (
taskPreActioncan reject on submit; it can't control early button visibility). - Full Jira Behaviours (arbitrary native field hide/show/rename/description changes during form entry).
- SaaS native scripted fields (private-deployment scoped today).
- Field-group value linkage (
groupFieldOnChange) and field-group submit validation (groupFieldValidate). - Custom JQL / native ONESQL function injection.
- Arbitrary Node.js, filesystem, process, package imports, raw HTTP, or unrestricted runtime access.
- Network or storage in L0 triggers (
taskPreAction,scriptedField), including OpenAPI reads; Selection option providers use the separate read-only L1scriptedFieldOptionstrigger. - Hosted-App object/entity storage writes; OAuth token or app-authorization admin endpoints.
- User impersonation / run-as-another-user.
For how these map from Jira/ScriptRunner, see Migrating from Atlassian.
Versioning
This reference targets ONEScript 1.1 (build 1.1.3). When you upgrade, re-check:
- the trigger list and policy matrix,
- helper availability (especially OpenAPI namespaces and mentions),
- the unsupported list.
A changelog is maintained from the early preview onward (see Resources → Changelog).