---
id: rest-1
slug: /apis/rest-1
title: 'REST API (rest-1.v1)'
description: 'Query, create, update and operate on Agility assets over HTTP, including full historical access through the Hist namespace.'
sidebar_label: 'Overview'
sidebar_position: 1
tags: ['api', 'rest', 'create', 'update', 'crud', 'history']
---

## Overview

`rest-1.v1` is the general-purpose data API. Every asset, every attribute and every
past version of both is addressable by URL. `GET` reads, `POST` writes.

It is a data API, not a task API: rather than a fixed catalogue of endpoints, it
exposes one URL shape that is parameterised by asset type, so it grows
automatically as the schema does. Ask the [Meta API](/apis/meta-v1) what asset
types and attributes exist, then address them here.

## Prerequisites

- A running Agility instance and its application path, for example
  `http://localhost/VersionOne`.
- Credentials. Unlike [`meta.v1`](/apis/meta-v1), `rest-1.v1` **requires
  authentication**, and every request is subject to the calling member's
  permissions. See [Security & errors](/apis/rest-1/security-and-errors).
- A working understanding of [assets, attributes and
  moments](/apis/rest-1/information-model).

## The URL Shape

All requests are made against a single template:

```text
~/rest-1.v1/{QueryType}/{AssetType}/{AssetID}/{Moment}/{AttributeName}?{QueryString}
```

| Segment         | Required | Meaning                                                           |
| --------------- | -------- | ----------------------------------------------------------------- |
| `QueryType`     | yes      | `Data`, `Hist` or `New` — see below.                              |
| `AssetType`     | yes      | The asset type token, for example `Story`, `Defect`, `Member`.     |
| `AssetID`       | no       | Integer identifying one asset. Omit it to address the whole list.  |
| `Moment`        | no       | Integer identifying one past version of that asset.                |
| `AttributeName` | no       | An attribute token, to address a single attribute rather than the whole asset. |
| `QueryString`   | no       | Selection, filtering, sorting, paging, search and operations.      |

Each segment narrows the result. `/Data/Story` is every Story you can see;
`/Data/Story/1364` is one Story; `/Data/Story/1364/903` is that Story as it was at
moment 903; `/Data/Story/1364/903/Name` is a single attribute of that version.

### The Three Namespaces

| `QueryType` | Purpose                                                              | Response root |
| ----------- | -------------------------------------------------------------------- | ------------- |
| `Data`      | Current data. Query with `GET`, create and update with `POST`.        | `Assets`      |
| `Hist`      | Historical data. Every version of every matching asset, read-only.    | `History`     |
| `New`       | A template for a new asset, pre-populated with defaults and rules.    | `Asset`       |

`New` is worth knowing about: rather than guessing which attributes a new asset
needs, `GET /rest-1.v1/New/Story?ctx=Scope:1000` returns a skeleton asset already
filled in for that context. See [New asset
templates](/apis/rest-1/new-asset-templates).

### Query String Parameters

| Parameter    | Purpose                                                                 |
| ------------ | ----------------------------------------------------------------------- |
| `sel`        | Which attributes to return.                                              |
| `select`     | Undocumented alias for `sel`. **Takes precedence when both are sent.**   |
| `where`      | Filter a list of assets.                                                 |
| `with`       | Bind variables referenced by `where`.                                    |
| `sort`       | Order a list of assets.                                                  |
| `page`       | Page size and start position, in that order — `page=<size>,<start>`.     |
| `find`       | Free-text search across attributes.                                      |
| `findin`     | Restrict which attributes `find` searches.                               |
| `asof`       | Return data as it existed at a given moment or date and time.            |
| `deleted`    | Boolean. Include deleted assets in the result.                           |
| `needTotal`  | Boolean. Compute the `total` count. The default comes from the `ApiDefaultNeedTotal` instance setting. |
| `op`         | Execute an operation. `POST` only, with an `AssetType` and `AssetID`.    |
| `Accept`     | Override content negotiation when you cannot set headers. See [Content types](#content-types). |

:::warning[Not every parameter applies to every request shape]

On a single-asset request in the `Data` namespace such as `/Data/Story/1364`, the
parameters that only make sense for a list — `where`, `sort`, `page` and `find` —
are **accepted and silently ignored**. No error is raised, so a query that looks
like it filtered may simply have returned the asset unchanged. `sel`, `with`,
`asof`, `deleted` and `needTotal` _are_ applied on that same URL, so this is not
a case of "list parameters don't work here" — some work and some are dropped.

This is a property of the `Data` namespace, **not of single-asset URLs in
general**: `/Hist/Story/1364` is also a single-asset request, and there `where`,
`sort`, `page` and `find` all take effect. History requests also gain an implicit
ascending `Moment` sort when `sort` is absent.

The [query grammar reference](/reference/query-grammar) publishes the full
applicability matrix along with the syntax of each parameter.

:::

:::note[`needTotal` and paging]

If you drive a pager off the `total` attribute of the response, set
`needTotal=true` explicitly rather than relying on the instance default. When
`needTotal` is off, a paged response **omits `total` entirely** — it is not
written as `-1` or any other sentinel. (Unpaged responses always carry a real
`total`.) Because the default comes from the
instance-configurable `ApiDefaultNeedTotal` setting, a pager that works against
one instance can silently break against another.

:::

## Reading Data

Ask for a list, choose your attributes, and filter:

```bash title="Stories with no remaining effort, owned by Member 20"
curl -u "$V1_USER:$V1_PASSWORD" \
  "~/rest-1.v1/Data/Story?sel=Name,ToDo&where=ToDo='0';Owners='Member:20'"
```

```xml
<Assets total="1" pageSize="2147483647" pageStart="0">
	<Asset href="/VersionOne/rest-1.v1/Data/Story/1364" id="Story:1364">
		<Attribute name="Name">Company History</Attribute>
		<Attribute name="ToDo">0</Attribute>
	</Asset>
</Assets>
```

Every `Asset` element carries an `href` you can follow and an `id` that is the
asset's OID.

The same read as JSON (`Accept: application/json`) wraps every value in an
`Attributes` map, renders a relation as a `Relation` whose value is one
`{ "idref": … }` or a list of them, and renders `AssetState` as a number
(see [AssetState values](/reference/attribute-types#assetstate-values)).
Selecting a relation also brings its display attributes along uninvited -
`Owners.Name` and `Owners.Nickname` beside `Owners`, `Status.Name` beside
`Status` - as ordinary attributes, whether or not you asked for them:

```json title="…&sel=Name,Owners,AssetState — what comes back"
{
  "_type": "Assets",
  "total": 1,
  "pageSize": 1,
  "pageStart": 0,
  "Assets": [
    {
      "_type": "Asset",
      "href": "/VersionOne/rest-1.v1/Data/Story/1364",
      "id": "Story:1364",
      "Attributes": {
        "Name": { "_type": "Attribute", "name": "Name", "value": "Company History" },
        "Owners": {
          "_type": "Relation",
          "name": "Owners",
          "value": [{ "_type": "Asset", "href": "…/Member/20", "idref": "Member:20" }]
        },
        "Owners.Name": { "_type": "Attribute", "name": "Owners.Name", "value": ["Alfred Smith"] },
        "Owners.Nickname": { "_type": "Attribute", "name": "Owners.Nickname", "value": ["alfred"] },
        "AssetState": { "_type": "Attribute", "name": "AssetState", "value": 128 }
      }
    }
  ]
}
```

An unset single relation renders `"value": null`; an empty multi-value one
`"value": []`. `total` is present only when `needTotal=true`. [Query examples](/apis/rest-1/query-examples) works through thirteen
progressively more involved queries, from a single asset to text search and
history.

## Writing Data

Updates are a `POST` to the URL of the asset, with a body describing only what
should change. Each change is marked with an `act`:

| `act`    | Applies to                | Effect                                     |
| -------- | ------------------------- | ------------------------------------------ |
| `set`    | Scalars, single relations | Replace the value.                          |
| `add`    | Multi-value relations     | Add a value to the collection.              |
| `remove` | Multi-value relations     | Remove a value from the collection.         |

```bash title="Update one attribute"
curl -u "$V1_USER:$V1_PASSWORD" \
  -H "Content-Type: text/xml; charset=utf-8" \
  --data-binary '<Asset><Attribute name="Phone" act="set">555-555-1212</Attribute></Asset>' \
  "~/rest-1.v1/Data/Member/20"
```

:::warning `act` goes on the value element, and the wrong placement is silent
On a multi-value relation, `act` belongs on the **`Asset` element inside the
`Relation`**, not on the `Relation` element itself:

```xml
<Asset><Relation name="Owners"><Asset idref="Member:20" act="add"/></Relation></Asset>
```

Putting it on the `Relation` — `<Relation name="Owners" act="add">` — returns
`200`, consumes a moment, and **adds nothing**. The response looks like
success, so read the relation back when a relation update matters.
:::

The response echoes the saved asset at a URL that includes the moment the change
created, giving you a permanent address for exactly this version:

```xml
<Asset href="/VersionOne/rest-1.v1/Data/Member/20/173" id="Member:20:173">
	<Attribute name="Phone">555-555-1212</Attribute>
</Asset>
```

Creating an asset is the same request without an `AssetID` — `POST` to
`/rest-1.v1/Data/Story` — and all required attributes must be supplied or the save
fails. [Update examples](/apis/rest-1/update-examples) covers scalars,
single-value relations, multi-value relations and creation.

### Recording Why a Change Was Made

Add a `comment` query-string parameter to any write to attach an audit comment
to that change — the same field the UI records when a user edits an asset:

```bash
curl -u "$V1_USER:$V1_PASSWORD" -X POST   -H "Content-Type: text/xml; charset=utf-8"   --data-binary '<Asset><Attribute name="Estimate" act="set">5</Attribute></Asset>'   "${AGILITY_BASE_URL}/rest-1.v1/Data/Story/1024?comment=Re-estimated%20after%20grooming"
```

It works on operations too — `?op=Inactivate&comment=…`. The comment is not an
attribute you set; it is read back from history as
[`ChangeComment`](/concepts/moments-and-history#reading-history-as-a-relation).

### Operations

Some state changes are not attribute edits. Closing, reopening, deleting and
similar transitions are **operations**, invoked with `op` on a `POST`:

```bash
curl -u "$V1_USER:$V1_PASSWORD" -X POST \
  "~/rest-1.v1/Data/Story/1021?op=Delete"
```

Which operations an asset type supports, and when each is valid, is described by
[`meta.v1`](/apis/meta-v1). See [Operations](/apis/rest-1/operations).

:::note A redundant operation is not reliably rejected
Some redundant invocations fail a validation rule — `Inactivate` on an already
inactive asset is a `400` `Violation'AssetCannotInactivateClosed` — but others
succeed: `Reactivate` on an **Active** asset returns `200` and writes a **new
moment** every time, changing nothing but the history. A `200` from an
operation proves the request was accepted, not that a transition happened;
check the asset's state when it matters.
:::

## History

Swap `Data` for `Hist` and you get every version of every matching asset instead
of just the current one, with each version addressable by moment. Add `asof` to
ask what things looked like at a point in time:

```bash
curl -u "$V1_USER:$V1_PASSWORD" \
  "~/rest-1.v1/Hist/Story/1364?asof=2005-08-03T17:30:00.00&sel=Name,ToDo,ChangeDate"
```

Moments are global to the instance and therefore sparse per asset, which has real
consequences for how you walk history. [Moments &
history](/concepts/moments-and-history) explains the model in full.

## Content Types {#content-types}

XML is the canonical format: responses default to `text/xml`, and that is the
format the in-product reference and every example in these pages uses.

The handler also negotiates other representations. Set an `Accept` header — or, if
you cannot set headers, an `Accept` query string parameter — to one of:

| `Accept`            | Response                            |
| ------------------- | ----------------------------------- |
| `text/xml`, `application/xml`, `*/*` | XML (the default). |
| `application/json`, `text/json` | JSON.                    |
| `application/jsonx` | JSON with a more explicit, XML-shaped structure. |

An unrecognised `Accept` value is rejected with `406 Not Acceptable`.

`POST` bodies are read as XML unless the request's `Content-Type` begins with
`application/json`, in which case a JSON payload of this shape is accepted:

```json title="JSON equivalent of the update above"
{
  "Attributes": {
    "Phone": { "value": "555-555-1212", "act": "set" }
  }
}
```

Relations take an OID token as their value, and multi-value relations take an array
whose entries each carry their own `act`:

```json
{
  "Attributes": {
    "Scope": { "value": "Scope:0", "act": "set" },
    "Members": {
      "value": [
        { "idref": "Member:1000", "act": "add" },
        { "idref": "Member:1001", "act": "remove" }
      ]
    }
  }
}
```

:::warning The concise `api/asset` JSON is not accepted here
`rest-1.v1` requires the `Attributes` wrapper shown above. The concise Bulk API
shape — `{ "from": "Story:1207", "update": { … } }` — is **rejected** with
`400 Invalid request payload: missing Attributes`. The two surfaces take
different JSON: use `Attributes` on `rest-1.v1`, and the `from`/`update` form on
[`api/asset`](/apis/asset-bulk). A payload written for one does not work on the
other.
:::

:::note[XML is better documented]

The JSON request and response formats are implemented and supported, but the
in-product API reference documents only XML, so XML is the safer choice when you
need behaviour to be predictable across versions. Prefer XML unless you have a
specific reason not to.

:::

## Related Topics

- [Information model](/apis/rest-1/information-model) — asset types, attribute
  definitions, assets, attributes and moments.
- [Query examples](/apis/rest-1/query-examples) — thirteen worked `GET` queries.
- [Update examples](/apis/rest-1/update-examples) — worked `POST` payloads.
- [New asset templates](/apis/rest-1/new-asset-templates) — the `New` namespace.
- [Operations](/apis/rest-1/operations) — invoking operations with `op`.
- [XML responses](/apis/rest-1/xml-responses) — the response document structure.
- [Security & errors](/apis/rest-1/security-and-errors) — authentication and status codes.
- [Meta API](/apis/meta-v1) — discover the asset types and attributes to address here.
- [Query grammar](/reference/query-grammar) — `sel`, `where`, `sort`, `page`, `find`, `findin` and `asof` syntax.
- [Query size and performance](/guides/query-size-and-performance) — there is no default page size; send `page` on every list request.
- [Bulk API](/apis/asset-bulk) — batch many creates, updates and operations into a single request.
