---
id: aliases-and-projection
title: 'Projection, Processing and Aliases'
description: 'The two-phase execution model behind the Bulk API, auto-aliasing, user-aliasing, request hierarchy flattening, and deferred commands.'
sidebar_label: 'Projection and Aliases'
sidebar_position: 9
tags: ['api', 'bulk', 'asset', 'aliases', 'concepts']
---

Every `api/asset` request is fulfilled in two phases. Almost everything that
looks surprising about the Bulk API follows from this split.

| Phase | What happens |
| --- | --- |
| **Projection** | The request is normalised into a flat, ordered list of single-purpose commands, each labelled with an alias. Hierarchies are flattened, shorthand syntaxes are expanded, and queries are left in place. Nothing is written. |
| **Processing** | Each projected command is executed in order. Aliases are resolved to real OID tokens as the assets they name come into existence. |

The result of the projection phase is a mapping of aliases to commands. You can
inspect it directly with [preview mode](/apis/asset-bulk/preview-mode).

## Why the Split Matters

- **Aliases**: a command can refer to an asset that does not exist yet, because
  resolution happens in the second phase.
- **Flattening**: nested creates become sibling commands linked by
  `#ContextOid`, which is why a partial failure returns a flat list you can
  edit and resubmit.
- **Deferral**: user-aliasing a command postpones its projection until
  processing, so it can see assets created earlier in the same request.
- **Preview**: stopping after projection tells you exactly what will happen
  without writing anything.

## Auto-Aliasing

If you do not name a command, the server names it for you.

```json title="Request"
{ "AssetType": "Story", "Scope": "Scope:0", "Name": "My Story" }
```

```json title="Projection"
[
  {
    "@e630d6f8-5b95-44b7-8d30-3632549a0121$auto-aliased": {
      "AssetType": "Story",
      "Scope": "Scope:0",
      "Name": "My Story"
    }
  }
]
```

Details worth noticing:

- A single object is normalised into a one-element array. Each element is a
  wrapper object with exactly one property, whose value is the command.
- The property name is a GUID. The `$auto-aliased` suffix marks it as
  server-generated rather than user-supplied.
- The suffix appears **only on auto-aliased asset create commands**. An
  auto-aliased query or update gets a bare GUID with no suffix.


## User-Aliasing

Wrap a command in a property whose name starts with `@` to name it yourself.

```json title="Request"
{
  "@story": { "AssetType": "Story", "Scope": "Scope:0", "Name": "My Story" }
}
```

```json title="Projection"
[
  {
    "@story": { "AssetType": "Story", "Scope": "Scope:0", "Name": "My Story" }
  }
]
```

The alias is then usable anywhere a reference is accepted:

| Position | Example |
| --- | --- |
| Relation value | `{ "AssetType": "Test", "Parent": "@story", "Name": "Test Time" }` |
| Query `from` | `{ "from": "@story", "select": ["Number"] }` |
| Query `where` | `{ "from": "Test", "where": { "Parent": "@story" } }` |
| Update / execute `from` | `{ "@inactivate": { "from": "@test1", "execute": "Inactivate" } }` |
| Operation argument | `{ "Member": "@memberAlias1", "Role": "Role:3" }` |


:::tip The most common reason to user-alias
Server-generated values — a Story `Number`, a Task `Number`, a default
`Status` — are only knowable after creation. Alias the create, then query the
alias in the same request:

```json
[
  { "@story": { "AssetType": "Story", "Scope": "Scope:0", "Name": "My Story" } },
  { "from": "@story", "select": ["Number"] }
]
```

:::

In YAML, quote the alias key so the `@` is not misread:

```yaml
"@story":
 AssetType: Story
 Name: New Story
 Scope: Scope:0
---
from: "@story"
select:
- Name
- Number
```

## Request Hierarchy Flattening

A nested create request is projected into a flat list. Each descendant gains a
system-generated `#ContextOid` property pointing at its container's alias.

`#ContextOid` is about **new-asset initialization**: the server seeds the new
asset with the defaults it would receive if created from that context in the
application, then applies your attributes on top. You can also supply it
yourself on a flat create command — worth doing when the type has required
relations that must be defaulted from a container rather than spelled out.

```json title="Request"
{
  "AssetType": "Story",
  "Scope": "Scope:0",
  "Name": "My Story with Children",
  "Children": [
    { "AssetType": "Task", "Name": "Task 1" },
    { "AssetType": "Task", "Name": "Task 2" }
  ]
}
```

```json title="Projection"
[
  {
    "@0f4bcf21-0f0f-4668-a7ee-a4db84f593bb$auto-aliased": {
      "AssetType": "Story", "Scope": "Scope:0", "Name": "My Story with Children"
    }
  },
  {
    "@17afb273-11ae-4942-bf46-6fd14eda3472$auto-aliased": {
      "AssetType": "Task", "Name": "Task 1",
      "#ContextOid": "@0f4bcf21-0f0f-4668-a7ee-a4db84f593bb$auto-aliased"
    }
  },
  {
    "@9d182825-e5d4-4bbc-99d4-591e8133e087$auto-aliased": {
      "AssetType": "Task", "Name": "Task 2",
      "#ContextOid": "@0f4bcf21-0f0f-4668-a7ee-a4db84f593bb$auto-aliased"
    }
  }
]
```

Observations:

- The descendants keep their original relative order, immediately following
  their container.
- During processing, `#ContextOid` is replaced with the container's real OID
  token, and becomes the context the contained asset is created in.
- `assetsCreated.oidTokens` comes back in exactly this flattened order.


### Hierarchical User-Aliases

User aliases can be supplied at any depth. The container may be auto-aliased
while its children are user-aliased, or the reverse.

```json title="Request"
{
  "AssetType": "Story",
  "Scope": "Scope:0",
  "Name": "My Story with Children",
  "Children": [
    { "@task1": { "AssetType": "Task", "Name": "Task 1" } },
    { "@task2": { "AssetType": "Task", "Name": "Task 2" } }
  ]
}
```

```json title="Projection"
[
  {
    "@8f49972e-e9ce-4465-8d6e-4dc91671d0fb$auto-aliased": {
      "AssetType": "Story", "Scope": "Scope:0", "Name": "My Story with Children"
    }
  },
  {
    "@task1": {
      "AssetType": "Task", "Name": "Task 1",
      "#ContextOid": "@8f49972e-e9ce-4465-8d6e-4dc91671d0fb$auto-aliased"
    }
  },
  {
    "@task2": {
      "AssetType": "Task", "Name": "Task 2",
      "#ContextOid": "@8f49972e-e9ce-4465-8d6e-4dc91671d0fb$auto-aliased"
    }
  }
]
```

You can then query each Task by its alias. But note there is usually a simpler
route — alias only the parent and use a nested select:

```json
[
  {
    "@story": {
      "AssetType": "Story", "Scope": "Scope:0", "Name": "My Story with Children",
      "Children": [
        { "AssetType": "Task", "Name": "Task 1" },
        { "AssetType": "Task", "Name": "Task 2" }
      ]
    }
  },
  {
    "from": "@story",
    "select": [{ "from": "Children:Task", "select": ["Number"] }]
  }
]
```

Two aliased queries return two result sets; one nested query returns one result
set containing both Tasks.


## Deferring Commands Until the Processing Phase

Projection normally happens up front, which is a problem for a command whose
target does not exist yet. **User-aliasing an update or execute command defers
its projection until the processing phase**, by which time earlier aliases have
resolved to real assets.

```json
[
  {
    "@story": {
      "AssetType": "Story", "Scope": "Scope:0", "Name": "Story Test",
      "Children": [
        { "@test1": { "AssetType": "Test", "Name": "My Test 1 (checked off immediately)" } },
        { "AssetType": "Test", "Name": "My Test 2 (still undone)" }
      ]
    }
  },
  { "@inactivate": { "from": "@test1", "execute": "Inactivate" } },
  {
    "from": "Test",
    "where": { "Parent": "@story" },
    "select": ["Name", "AssetState"]
  }
]
```

```json title="Projection"
[
  {
    "@story": {
      "AssetType": "Story", "Scope": "Scope:0", "Name": "Story Test",
      "Children": [
        { "@test1": { "AssetType": "Test", "Name": "My Test 1 (checked off immediately)" } },
        { "AssetType": "Test", "Name": "My Test 2 (still undone)" }
      ]
    }
  },
  { "@inactivate": { "from": "@test1", "execute": "Inactivate" } },
  {
    "@147491a4-ce7f-4f1b-b238-626d6858d25b": {
      "from": "Test", "where": { "Parent": "@story" },
      "select": ["Name", "AssetState"]
    }
  }
]
```

Observations:

- The user-aliased Story is **not** flattened. Deferring it also defers the
  flattening of everything inside it — which is why `@test1` remains nested.
- The only thing auto-aliased in this projection is the query.

```json title="Processing response"
{
  "assetsCreated": { "oidTokens": ["Story:1582", "Test:1583", "Test:1584"], "count": 3 },
  "assetsOperatedOn": { "oidTokens": ["Test:1583"], "count": 1 },
  "queryResult": {
    "results": [
      [
        { "_oid": "Test:1583", "Name": "My Test 1 (checked off immediately)", "AssetState": "Closed" },
        { "_oid": "Test:1584", "Name": "My Test 2 (still undone)", "AssetState": "Active" }
      ]
    ],
    "count": 1
  }
}
```


### Queries Do Not Need Deferral

Queries are always evaluated in place after the commands preceding them, so a
user alias buys nothing. The following request contains the same query twice —
once unaliased, once user-aliased — and produces two identical result sets:

```json
[
  { "@story": { "AssetType": "Story", "Scope": "Scope:0", "Name": "My Story" } },
  { "from": "@story", "select": ["Name", "Number"] },
  { "@query": { "from": "@story", "select": ["Name", "Number"] } }
]
```

Do not reflexively wrap queries in aliases.


### Give Every Aliased Command Its Own Object

Each aliased command must be **its own element in the array**. Aliases are not
siblings inside one object, and getting this wrong fails in a way that depends on
how many you have.

```json title="Correct — one object per command"
[
  { "@newEpic": { "AssetType": "Epic", "Name": "Parent", "Scope": "Scope:0" } },
  { "@epic0": { "AssetType": "Epic", "Name": "Child A", "Super": "@newEpic", "Scope": "Scope:0" } },
  { "@epic1": { "AssetType": "Epic", "Name": "Child B", "Super": "@newEpic", "Scope": "Scope:0" } }
]
```

```json title="Wrong — aliases as siblings in one object"
[
  {
    "@newEpic": { "AssetType": "Epic", "Name": "Parent", "Scope": "Scope:0" },
    "@epic0": { "AssetType": "Epic", "Name": "Child A", "Super": "@newEpic", "Scope": "Scope:0" },
    "@epic1": { "AssetType": "Epic", "Name": "Child B", "Super": "@newEpic", "Scope": "Scope:0" }
  }
]
```

:::danger The failure mode changes with the number of siblings

A command object is only recognised as aliased when it has **fewer than three
children**. That threshold produces three different behaviours from the same
mistake:

| Aliases in one object | What happens |
| ---: | --- |
| 1 | Correct. This is the intended shape. |
| 2 | **Accepted, and both are created.** It works — but only by falling under the threshold, so it is one added sibling away from failing. |
| 3 or more | `200` with a `commandFailure` — `Invalid key at node(s): root->@a | root->@b | root->@c` — and **nothing is created**. |

The middle row is the dangerous one. Two siblings work — the request succeeds
and both assets are created — so the shape gets copied and scaled up. It fails
only when a third command is added, and the failure then reads like a query
syntax error rather than a payload-shape problem: with three or more children
the object is no longer recognised as aliased, so it is read as a **query
command**, and the query parser rejects the `@` keys it finds at the root.


:::

**Nesting is usually the better answer anyway.** If the children belong to the
parent, express that with a relation rather than three aliased commands:

```json
[
  {
    "AssetType": "Epic", "Name": "Parent", "Scope": "Scope:0",
    "Subs": [
      { "AssetType": "Epic", "Name": "Child A", "Scope": "Scope:0" },
      { "AssetType": "Epic", "Name": "Child B", "Scope": "Scope:0" }
    ]
  }
]
```

That avoids aliases entirely, and the hierarchy is flattened for you — see
[request hierarchy flattening](#request-hierarchy-flattening).

## What Aliases Cannot Do \{#limitations\}

Aliases are more capable than they first appear — a user alias can be referenced
from a query's `from`, a `where` clause, an update or execute `from`, a relation
value and an operation argument. That breadth makes the
gaps surprising when you hit them, because the natural next step often turns out
not to be supported.

### An Operation's Output Cannot Be Referenced

**This is the limitation people hit most.** An alias names a *command*, and for a
create command it resolves to the asset that command created. An operation that
*produces* a new asset gives you nothing to name.

The canonical case is copying an asset and then amending the copy:

```json title="Does not work"
[
  { "from": "Story:455628", "execute": "Copy" },
  {
    "from": "<the Story the Copy just produced>",
    "update": { "Name": "New Updated Story" }
  }
]
```

There is no expression for the second `from`. Aliasing the execute command names
the *command*, not the Story it produced, so it does not help either.

Split it into two requests: run the `Copy`, read the new OID token from the
response, then send the update.

### A Create Command Cannot Project Its Own Result

A create returns OID tokens in `assetsCreated` and nothing else. To see any
attribute of the asset you just made — a system-generated `Number`, a defaulted
`Status` — add a **separate query command** referencing the create's alias:

```json
[
  { "@story": { "AssetType": "Story", "Scope": "Scope:0", "Name": "My Story" } },
  { "from": "@story", "select": ["Number", "Status.Name"] }
]
```

That works. What is not available is asking the create
command itself to return a projection, which would save a round of commands.

### A Subquery Cannot Supply a Single-Value Relation

[Multi-value relations](/apis/asset-bulk/relation-updates#referencing-by-subquery)
accept a subquery in place of a literal list, in both `add` and `remove`. A
**single-value** relation does not — you cannot write a subquery as the value of
`Scope`, `Parent` or `Status` in a create or an update.

The practical workaround is a
[moniker](/apis/asset-bulk/creating-assets#qualifying-a-name-with-a-path), which
resolves a relation by name rather than requiring you to know its OID token:

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

That covers the common case of "the asset I mean is identified by its name". It
does not cover choosing a single-value relation by an arbitrary filter.

No test asserts a *negative*, so treat these limitations as accurate
descriptions of current behaviour rather than as guarantees about future
releases.

## Related Topics

- [Preview mode](/apis/asset-bulk/preview-mode)
- [Errors and partial failures](/apis/asset-bulk/errors)
- [Compound requests](/apis/asset-bulk/compound-requests)
- [Queries](/apis/asset-bulk/queries)
