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"
}
{
"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.
AssetType: Story
Name: My Story
Scope: System (All Projects)
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.
Role.Name'Project Admin is a name, not a syntaxSome 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.
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 type | A segment matches on | Naming context |
|---|---|---|
Scope | Name | Parent, another Scope |
Workitem and every type derived from it — Story, Defect, Epic, Task, Test, TestSet, Theme | Name or Number | Scope |
Issue, Request, Goal, StrategicTheme, Environment, RegressionTest, RegressionPlan | Name or Number | Scope |
Room | Name | Scope |
RegressionSuite | Name or Number | RegressionPlan |
Timebox | Name | Schedule |
StoryStatus | Name | Team |
Two consequences follow:
- A segment can be a Number where the table allows it.
Parent: S-01001resolves a Task's parent by display number, andAcme\S-01001narrows 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\Productionas anEnvironmentvalue findsProductioninside the Scope thatOfferings\Agilityresolves to, which is in turn theAgilitychild of theOfferingsScope. The chain ends at the first type with no naming context:Acme\Platformas aTeamvalue is a Team literally namedAcme\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:
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"
{
"from": "AssetType",
"select": ["Name", "Base.Name", "NameResolutionAttributes", "Resolver"],
"where": { "Name": "Scope" },
"page": { "size": 5, "start": 0 }
}
{
"_oid": "AssetType:-5034",
"Name": "Scope",
"Base.Name": "BaseAsset",
"NameResolutionAttributes": "Name,Parent",
"Resolver": "VersionOne.Meta.RelatedAssetResolver,VersionOne"
}
Read it like this:
Resolverset: the last entry ofNameResolutionAttributesis the naming context, and the entries before it are what a segment matches on.Resolverempty: the type has no naming context. The whole value is matched as a name against each listed attribute in turn, which is why aMember(Name,Nickname,Username,Email) can be referenced by any of the four.- Both empty: the type inherits its base type's rule.
Storyreports nothing of its own; followBasetoPrimaryWorkitemand then toWorkitem.
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:
- The value is tried as an OID token.
- 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:
| Matches | Outcome | error.message |
|---|---|---|
| Exactly one | The relation is set | — |
| None | The command fails | Resolution'Unresolved'<Type>.<Attribute>'<TargetType> |
| More than one | The command fails | Resolution'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.
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.
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" }
]
}
]
}
]
}
{
"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'"]
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.