---
id: typescript
title: 'TypeScript Examples'
description: 'Runnable TypeScript examples for the Digital.ai Agility API'
sidebar_label: 'TypeScript'
sidebar_position: 3
tags: ['examples', 'typescript']
---

The examples below form one sequence — the same operations appear in
[cURL](/examples/curl), [Python](/examples/python) and
[TypeScript](/examples/typescript) — so you can switch languages without
switching mental models. Set up
[authentication](/getting-started/authentication) first.

## Setup

```typescript
const BASE_URL = process.env.AGILITY_BASE_URL!;
const HEADERS = {
  Authorization: `Bearer ${process.env.AGILITY_ACCESS_TOKEN!}`,
  'Content-Type': 'application/json',
  Accept: 'application/json',
};

type Asset = { _oid: string } & Record<string, unknown>;

async function query(payload: unknown): Promise<Asset[]> {
  const response = await fetch(`${BASE_URL}/query.v1`, {
    method: 'POST',
    headers: HEADERS,
    body: JSON.stringify(payload),
  });
  if (!response.ok) throw new Error(`query.v1 failed: ${response.status}`);
  const [results] = (await response.json()) as Asset[][];
  return results;
}
```

## Who Am I?

The best connection test — it proves the URL *and* the credentials, and
tells you which member the token acts as:

```typescript
const [me] = await query({
  from: 'Member',
  where: { IsSelf: true },
  select: ['Name', 'Nickname', 'Email'],
});
console.log(`Authenticated as ${me.Name} (${me._oid})`);
```

:::note The response envelope
`query.v1` returns an **array of result arrays** — one inner array per query
in the request. There is no `results` or `query` wrapper object:

```json
[
  [
    { "_oid": "Story:1207", "Number": "S-01001", "Name": "Logon" }
  ]
]
```

:::

## Get One Story by Number

```typescript
const [story] = await query({
  from: 'Story',
  where: { Number: 'S-01001' },
  select: ['Number', 'Name', 'Description', 'Status.Name'],
});
```

## List a Backlog, Paged Correctly

Page until a response comes back shorter than the page size — no total
needed. `Scope.ParentMeAndUp` means "in this project or any project beneath
it":

```typescript
async function* backlog(scopeOid: string, size = 50): AsyncGenerator<Asset> {
  for (let start = 0; ; start += size) {
    const page = await query({
      from: 'PrimaryWorkitem',
      filter: [`Scope.ParentMeAndUp='${scopeOid}'`],
      select: ['Number', 'Name', 'Status.Name'],
      sort: ['Number'],
      page: { size, start },
    });
    yield* page;
    if (page.length < size) return;
  }
}

for await (const item of backlog('Scope:0')) {
  console.log(item.Number, item.Name);
}
```

## Filter Precisely

Quote filter values, compare relations by OID, and prefer `RollupState`
and UTC dates for queries that survive renames and timezones:

```typescript
const active = await query({
  from: 'Story',
  where: { Scope: 'Scope:0' },
  filter: ["Estimate>'2'", "Status.RollupState='Active'"],
  select: ['Number', 'Name', 'Estimate', 'Status.Name'],
  sort: ['-ChangeDateUTC'],
  page: { size: 50, start: 0 },
});
```

## Traverse Relations

Dotted paths flatten into their own keys; a nested subquery groups child
attributes into objects:

```typescript
const [detail] = await query({
  from: 'Story',
  where: { Number: 'S-01001' },
  select: [
    'Name', 'Scope.Name', 'Owners.Name',
    { from: 'Children:Task', select: ['Number', 'Name', 'Status.Name'] },
  ],
});
console.log(detail['Scope.Name']);                    // flattened key
for (const task of detail['Children:Task'] as Asset[]) {
  console.log(' ', task.Number, task.Name);           // grouped objects
}
```

## Create and Update with the Bulk API

One surface for writes; the response lists what was created, modified and
what failed:

```typescript
async function bulk(commands: unknown[]): Promise<any> {
  const response = await fetch(`${BASE_URL}/api/asset`, {
    method: 'POST',
    headers: HEADERS,
    body: JSON.stringify(commands),
  });
  if (!response.ok) throw new Error(`api/asset failed: ${response.status}`);
  return response.json();
}

const created = await bulk([{ AssetType: 'Story', Name: 'My new story', Scope: 'Scope:0' }]);
const storyOid = created.assetsCreated.oidTokens[0].split(':', 2).join(':'); // strip the moment

await bulk([{ from: storyOid, update: { Estimate: 3, Owners: { add: ['Member:20'] } } }]);
```

Several commands can share one request, and later commands can reference
earlier ones by alias:

```typescript
await bulk([
  { '@epic':  { AssetType: 'Epic',  Name: 'New initiative', Scope: 'Scope:0' } },
  { '@story': { AssetType: 'Story', Name: 'First slice', Scope: 'Scope:0', Super: '@epic' } },
]);
```

## Run an Operation

Lifecycle changes are operations on `rest-1.v1` — query parameter, empty
body:

```typescript
await fetch(`${BASE_URL}/rest-1.v1/Data/Story/3884?op=Inactivate`, {
  method: 'POST',
  headers: { Authorization: HEADERS.Authorization },
});
```

## Handle Failures Properly

A Bulk API request answers `200` even when commands fail — always check
`commandFailures`, and resolve violation tags to human-readable text
through the localizer (unmatched tags echo back unchanged):

```typescript
const result = await bulk([{ from: 'Story:3884', update: { Name: 'renamed' } }]);

if (result.commandFailures.count > 0) {
  const tag: string = result.commandFailures.commands[0].error.message;
  const response = await fetch(`${BASE_URL}/loc-2.v1?[${encodeURIComponent(tag)}]`, {
    headers: { Authorization: HEADERS.Authorization },
  });
  const readable = await response.json();
  console.error(readable[tag] ?? tag);
}
```

## Related Topics

- [Query size and performance](/guides/query-size-and-performance) — paging and projection discipline
- [Bulk API](/apis/asset-bulk) — the full command reference
- [Error conventions](/apis/conventions/errors) — including localizable violation tags
