Skip to main content

Assets in Agility

Assets are the fundamental building blocks of Digital.ai Agility. Every work item, project, sprint and configuration element is an asset with a unique identifier, a type, and a set of attributes.

What Is an Asset?​

An asset is any entity stored in the Agility system:

  • Work items: Stories, Defects, Test Sets, Tasks, Tests
  • Portfolio items: Epics (displayed as Portfolio Item), Themes (displayed as Backlog Group)
  • Organization and planning: Scopes (Projects), Timeboxes (Iterations/Sprints), Teams, Members
  • Tracking: Issues, Requests, Goals

Each asset has:

The Asset Type Hierarchy​

Asset types inherit from one another. The work-item corner of the hierarchy:

Two things about this tree surprise people:

  • Issue is not a work item. It descends directly from BaseAsset, so a Workitem query never returns Issues.
  • Epic and Theme are. Both descend from Workitem, so a Workitem query returns portfolio items alongside stories and tasks.

Discover any type's parent with the Meta API: GET /meta.v1/Story reports Base: PrimaryWorkitem.

Even the schema is assets

The model describes itself: asset types and their attribute definitions are assets too. AttributeDefinition is a queryable type, and filtering it on its AssetType relation returns a type's attribute definitions through query.v1, rest-1.v1 or api/asset. It works, but it is not the recommended path — meta.v1 serves the same information already shaped for the purpose: inheritance resolved, operations included, no authentication required.

Querying Abstract Types Is Allowed — and Useful​

Workitem and PrimaryWorkitem are abstract: no asset is ever just a Workitem, and you cannot create one. But you can absolutely query them, and it is the natural way to work across subtypes:

{
"from": "PrimaryWorkitem",
"select": ["Number", "Name"],
"page": { "size": 50, "start": 0 }
}

That one query returns Stories, Defects and Test Sets together — the classic "whole backlog" shape. Remember the tree when interpreting results: a Workitem query includes Epics and Themes too.

Object IDs (OIDs)​

Every asset has a unique Object ID: the asset type and a numeric id, joined by a colon.

Story:1207
Defect:5678
Scope:0
Member:20

An OID with a third part (Story:1207:5837) identifies a past version — a moment — of the asset. See Moments and history.

OID Vs Number​

Work items also have a human-readable Number:

PropertyExampleDescription
OIDStory:1207Permanent identifier — use in API calls
NumberS-01001Display number — use with humans
NameLogonTitle

Find an asset's OID from its Number (every result carries _oid):

{
"from": "Story",
"where": { "Number": "S-01001" },
"select": ["Number", "Name"]
}

Fetch a specific asset when you already hold its OID — filter on ID, the attribute every asset has that refers to itself:

{
"from": "Story",
"where": { "ID": "Story:1207" },
"select": ["Number", "Name"]
}

Asset State and Status — Two Different Things​

Two mechanisms track "where an asset is", and conflating them causes real bugs:

  • AssetState is the system lifecycle: Future (0), Active (64), Closed/Inactive (128), and the dead states (192 and above, including Deleted at 255). Every asset has it, it is not customizable, and it is what Inactivate/Reactivate operations change. See the state table in the quickstart.
  • Status is the workflow position — a list value like Future, In Progress, Done — customizable per instance and per project. See Workflows and status.

A story with Status = Done is usually still AssetState = Active; it becomes Closed when someone closes it.

Lifecycle transitions are performed with operations, invoked as a query-string parameter with an empty POST body:

curl -X POST -H "Authorization: Bearer ${AGILITY_ACCESS_TOKEN}" \
"${AGILITY_BASE_URL}/rest-1.v1/Data/Story/1207?op=Inactivate"

See Operations for the catalogue and their quirks.

Relations Between Assets​

Relations are attributes whose values are other assets. They come in two shapes:

  • Single-value: Scope, Timebox, Status, Parent, Super
  • Multi-value: Owners, Children, Dependencies, Dependants

Frequently used work-item relations:

RelationCardinalityDescription
ScopesingleThe project that contains the asset
TimeboxsingleSprint/iteration assignment
OwnersmultiAssigned members — note the plural
ChildrenmultiA primary workitem's Tasks and Tests
ParentsingleA Task's or Test's primary workitem
SupersingleA primary workitem's Epic (portfolio parent)
DependenciesmultiUpstream work this item depends on

Traverse relations in a selection with dotted attribute paths:

{
"from": "Story",
"select": ["Name", "Scope.Name", "Timebox.Name", "Owners.Name"],
"page": { "size": 50, "start": 0 }
}

See Relations for traversal depth, filtering through relations, and downcasting.

Querying Assets​

The response to a query.v1 request is an array of result arrays — one per query in the request, each containing the matching assets:

[
[
{ "_oid": "Story:1207", "Number": "S-01001", "Name": "Logon" },
{ "_oid": "Story:1208", "Number": "S-01002", "Name": "Setup" }
]
]

Filter with where (exact match) and filter (full comparison grammar, including OR-lists):

{
"from": "Story",
"where": { "Scope": "Scope:0" },
"filter": ["Status.Name='Future','Done'"],
"select": ["Number", "Name", "Status.Name"],
"page": { "size": 50, "start": 0 }
}

See the Query API and the query grammar for the full syntax.

Best Practices​

  1. Use OIDs in code, Numbers with people.
  2. Query abstract types (PrimaryWorkitem) to work across subtypes; remember you cannot create them.
  3. Always page list queries — Agility has no default page size. See query size and performance.
  4. Select only what you need; every relation you project costs a join.
  5. Check meta.v1 when an attribute or type surprises you — the schema, including custom attributes, differs per instance.

Code Examples​

TypeScript: Fetch One Asset by OID​

async function fetchAsset(client: AgilityClient, oid: string) {
const [results] = await client.query({
from: oid.split(':')[0],
where: { ID: oid },
select: ['Number', 'Name'],
});

if (results.length === 0) {
throw new Error(`Asset ${oid} not found`);
}
return results[0];
}

const story = await fetchAsset(client, 'Story:1207');
console.log(`${story.Number}: ${story.Name}`);

Python: OID Helpers​

def parse_oid(oid: str) -> tuple[str, int]:
"""Split an OID into asset type and numeric id."""
asset_type, _, num = oid.partition(':')
return asset_type, int(num)

def build_oid(asset_type: str, asset_id: int) -> str:
return f"{asset_type}:{asset_id}"

asset_type, asset_id = parse_oid("Story:1207")