Skip to main content

Creating Assets

A create command is any command object that carries an AssetType property. Everything else in the object is either an attribute value, a relation value, or a nested hierarchy of further create commands.

A Single Asset​

{
"AssetType": "Story",
"Scope": "Scope:0",
"Name": "First Story"
}
Response
{
"assetsCreated": { "oidTokens": ["Story:1585"], "count": 1 },
"assetsModified": { "oidTokens": [], "count": 0 },
"assetsOperatedOn": { "oidTokens": [], "count": 0 },
"commandFailures": { "commands": [], "count": 0 },
"queryResult": { "results": [], "count": -1 }
}

Many Assets​

Send an array (JSON) or a multi-document stream (YAML). Asset types may be mixed freely.

[
{ "AssetType": "Story", "Scope": "Scope:0", "Name": "Second Story" },
{ "AssetType": "Story", "Scope": "Scope:0", "Name": "Third Story" },
{ "AssetType": "Defect", "Scope": "Scope:0", "Name": "First Defect" }
]

Referencing a Relation by OID Token or by Name​

A single-value relation can be given either an OID token or the target asset's name. Both forms resolve to the same asset.

By name
AssetType: Story
Name: My Story
Scope: System (All Projects)
By OID token
AssetType: Story
Name: My Story
Scope: Scope:0

A name is matched against the target type's naming attributes — Name for most types, Name or Number for types that carry a display number, and Name, Nickname, Username, or Email for a Member — and deleted assets never match.

note
Role.Name'Project Admin is a name, not a syntax

Some built-in assets store a localization tag as their Name; every Role is named Role.Name'<name>, for example (see roles and permissions). So this is an ordinary lookup by name:

AssetType: Member
Name: User1
Username: User1
Password: User1
Nickname: User1
DefaultRole: Role.Name'Project Admin

There is no <AssetType>.<Attribute>'<Value> form for relation values. A value of Scope.Name'Optimize looks for a Scope literally named Scope.Name'Optimize, and fails.

Qualifying a Name with a Path​

A bare name only works when it is unambiguous. When it is not, qualify it with a backslash-separated path through the asset's naming context:

AssetType: Story
Name: My Story
Scope: Offerings\Agility 25.2\Optimize

The path needs to be only deep enough to resolve the name uniquely — it does not have to start at the root. Everything after the last backslash is the target asset's name; everything before it is context.

Backslashes in a name must be doubled

The last unescaped backslash is what separates context from name, so a backslash that is part of an asset's own name must be written \\.

An asset genuinely called Q1\Q2 Planning inside Offerings is therefore written Offerings\Q1\\Q2 Planning. Get this wrong and the name splits in the wrong place, resolving to a different asset or to none.

The context may itself be an OID token. Each segment is resolved by trying it as an OID token first and falling back to a name lookup, so Scope:1003\Optimize is as valid as spelling out the parent by name — useful when you know the container's OID but not the child's.

Which types accept a path​

A path walks a single relation, the target type's naming context, and only the types below (and the types derived from them) have one. Any other type reads the whole value, backslashes and all, as one literal name.

Target typeA segment matches onNaming context
ScopeNameParent, another Scope
Workitem and every type derived from it — Story, Defect, Epic, Task, Test, TestSet, ThemeName or NumberScope
Issue, Request, Goal, StrategicTheme, Environment, RegressionTest, RegressionPlanName or NumberScope
RoomNameScope
RegressionSuiteName or NumberRegressionPlan
TimeboxNameSchedule
StoryStatusNameTeam

Two consequences follow:

  • A segment can be a Number where the table allows it. Parent: S-01001 resolves a Task's parent by display number, and Acme\S-01001 narrows the same lookup to one project.
  • A path can cross types. Each context segment is resolved by the context type's own rule, so Offerings\Agility\Production as an Environment value finds Production inside the Scope that Offerings\Agility resolves to, which is in turn the Agility child of the Offerings Scope. The chain ends at the first type with no naming context: Acme\Platform as a Team value is a Team literally named Acme\Platform.

Find a type's naming context​

meta.v1 does not report it. The rule is an attribute of the AssetType metadata asset, which any query endpoint can read with any member's token, whatever its role:

rest-1.v1
curl -H "Authorization: Bearer <access-token>" \
"${AGILITY_BASE_URL}/rest-1.v1/Data/AssetType?sel=Name,Base.Name,NameResolutionAttributes,Resolver&where=Name='Scope'&page=5,0"
The same read as a Bulk API query
{
"from": "AssetType",
"select": ["Name", "Base.Name", "NameResolutionAttributes", "Resolver"],
"where": { "Name": "Scope" },
"page": { "size": 5, "start": 0 }
}
Result (one row)
{
"_oid": "AssetType:-5034",
"Name": "Scope",
"Base.Name": "BaseAsset",
"NameResolutionAttributes": "Name,Parent",
"Resolver": "VersionOne.Meta.RelatedAssetResolver,VersionOne"
}

Read it like this:

  • Resolver set: the last entry of NameResolutionAttributes is the naming context, and the entries before it are what a segment matches on.
  • Resolver empty: the type has no naming context. The whole value is matched as a name against each listed attribute in turn, which is why a Member (Name,Nickname,Username,Email) can be referenced by any of the four.
  • Both empty: the type inherits its base type's rule. Story reports nothing of its own; follow Base to PrimaryWorkitem and then to Workitem.

How a Relation Value Is Interpreted​

The rule is the same for every relation attribute, and it is worth knowing because it explains why names work at all:

  1. The value is tried as an OID token.
  2. If that fails, it is treated as a moniker and resolved by name, path and all.

There is no flag and no syntax to choose between them — the fallback is automatic. A consequence worth noting: a mistyped OID token does not fail as a bad token, it becomes a name lookup, and the error you get back is about an unresolvable name rather than a malformed identifier.

The same resolution applies when setting a relation in an update command, not just a create. See Updating assets and multi-value relations.

A moniker must resolve to exactly one asset​

Name resolution has three outcomes, and only one of them sets the relation:

MatchesOutcomeerror.message
Exactly oneThe relation is set—
NoneThe command failsResolution'Unresolved'<Type>.<Attribute>'<TargetType>
More than oneThe command failsResolution'Ambiguous'<Type>.<Attribute>'<TargetType>

Resolution'Ambiguous'Story.Scope'Scope names the attribute being set and the type it searched; the value you sent is not echoed back. The failure is per command: the rest of the request still runs, and the failed command comes back in commandFailures ready to correct and resubmit (see errors and partial failures).

A path does not change the rule, it narrows the candidates. Acme\Optimize that still matches two child Scopes of Acme named Optimize is still Resolution'Ambiguous'; the only unambiguous reference to one of them is its OID token.

Resolution searches every asset of the type, not just the ones you can see. A member with access to only one of two Scopes named Moniker Test sees one match when querying for that name, yet Scope: Moniker Test still fails with Resolution'Ambiguous'. When a query returns exactly one asset and the name still fails as ambiguous, the duplicate is one you cannot see: use the OID token from your query result.

The same rule applies outside the API

Agility's Excel import resolves relation columns the same way and reports the same two outcomes in words — "Unable to resolve attribute value for asset type …" and "Multiple … assets matching provided name". An import that fails on a duplicate name is the ambiguity a Bulk request reports as Resolution'Ambiguous', with the same remedies: qualify the value with a path, or use the OID token.

Creating List Values​

The values behind a dropdown — statuses, priorities, defect types, and any custom list an administrator has added — are ordinary assets, not a special kind of configuration. Each list is an asset type deriving from the abstract List type, and each value in it is one asset of that type.

So populating a list is just creating assets:

[
{ "AssetType": "Custom_RiskTier", "Name": "Low" },
{ "AssetType": "Custom_RiskTier", "Name": "Medium" },
{ "AssetType": "Custom_RiskTier", "Name": "High" }
]

Custom list types carry the Custom_ prefix, matching the convention for custom attributes. Built-in lists use their own names — StoryStatus, WorkitemPriority, DefectType and so on. Confirm the type name against meta.v1 rather than guessing it; list type names are not localized the way role names are, but they do vary with the methodology the instance was installed with.

Some list types carry more than a Name. StoryStatus, for example, takes a RollupState and can be scoped to a Team:

{ "AssetType": "StoryStatus", "Name": "In Progress", "RollupState": "64", "Team": "@team" }

Check the type in meta.v1 for what it requires.

This is where the Bulk API earns its keep

Creating list values one at a time through rest-1.v1 is a request per value. The Bulk API takes the whole set in a single call, which turns populating a several-thousand-entry list from a long-running script into one request.

The same size considerations apply as for any large request — a very large list is still better split into a few batches than sent as one enormous body.

Creating a Hierarchy​

Nest child create commands inside a relation property on the parent. Any relation that accepts child assets works — Children, Subs, Workitems.

{
"AssetType": "Epic",
"Scope": "Scope:0",
"Name": "Epic with Subs",
"Category": "Epic",
"Subs": [
{
"AssetType": "Epic",
"Name": "Feature Epic",
"Category": "Feature",
"Subs": [
{
"AssetType": "Story",
"Name": "Feature Story 1",
"Children": [
{ "AssetType": "Task", "Name": "Code the feature" },
{ "AssetType": "Test", "Name": "Test the feature" }
]
}
]
}
]
}
Response
{
"assetsCreated": {
"oidTokens": ["Epic:1589", "Epic:1590", "Story:1591", "Task:1592", "Test:1593"],
"count": 5
},
"assetsModified": { "oidTokens": [], "count": 0 },
"assetsOperatedOn": { "oidTokens": [], "count": 0 },
"commandFailures": { "commands": [], "count": 0 },
"queryResult": { "results": [], "count": -1 }
}

Nesting is not limited in depth, and the child does not repeat the Scope — it inherits its context from the containing asset. Internally the server flattens the hierarchy into a linear command list before processing it; see Request hierarchy flattening.

Omitted Attributes Are Inherited from the Containing Asset​

A nested create command that omits Name and Description inherits them from its parent.

AssetType: Epic
Name: Epic name
Description: Epic description
Subs:
- AssetType: Story
Name: My own name
Description: My own description
- AssetType: Story # inherits "Epic name" / "Epic description"
- AssetType: Story
Name: Story with Children
Description: Story with Children that has its own Description

Setting a Multi-Value Relation from a Subquery​

A relation value in a create command can be a subquery instead of a literal reference. The subquery runs during processing and its matches become the relation's members.

AssetType: Story
Name: I need to drop one of these Owners!
Scope: Remove one owner
Owners:
from: Member
filter: ["Name='Administrator','User2'"]
tip

Subquery-valued relations are the cleanest way to write portable setup scripts: they let you reference assets by a stable business value rather than by an OID token that differs per instance.

Recording a Change Comment​

Add ChangeComment at the command root, alongside AssetType, to attach an audit comment to the creation:

{ "AssetType": "Story", "Scope": "Scope:0", "Name": "New Story", "ChangeComment": "Imported from tracker" }

It is recorded as the change's comment, not as an attribute of the new asset, and reads back from history as ChangeComment. (On an update command it goes inside the update node instead — see updating assets.)

When you do not supply one, the Bulk API stamps a default: a create's history entry reads Created through api/asset. This marks Bulk API writes in the audit trail on its own — rest-1.v1 writes no comment at all unless one is supplied. Supply your own ChangeComment when the integration has something more specific to say.

Creating Assets Through an Update Command​

Assets can also be created as a side effect of an update command, by nesting create commands inside a relation being updated:

from: Scope:0
update:
Workitems:
- AssetType: Story
Name: Story 1
- AssetType: Story
Name: Story 2
- AssetType: Epic
Name: Epic that will get new Subs

The response reports these in assetsCreated, and the updated container in assetsModified. See Updating multi-value relations.

Naming a Create Command with an Alias​

Wrapping a create command in a user alias lets later commands in the same request refer to the asset before it exists:

[
{
"@story": { "AssetType": "Story", "Scope": "Scope:0", "Name": "Story Time" }
},
{
"AssetType": "Test", "Parent": "@story", "Name": "Test Time"
}
]

See Projection, processing and aliases.