REST API (rest-1.v1)
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 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,rest-1.v1requires authentication, and every request is subject to the calling member's permissions. See Security & errors. - A working understanding of assets, attributes and moments.
The URL Shape
All requests are made against a single template:
~/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.
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. |
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 publishes the full applicability matrix along with the syntax of each parameter.
needTotal and pagingIf 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:
curl -u "$V1_USER:$V1_PASSWORD" \
"~/rest-1.v1/Data/Story?sel=Name,ToDo&where=ToDo='0';Owners='Member:20'"
<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).
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:
{
"_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 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. |
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"
act goes on the value element, and the wrong placement is silentOn a multi-value relation, act belongs on the Asset element inside the
Relation, not on the Relation element itself:
<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:
<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 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:
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.
Operations
Some state changes are not attribute edits. Closing, reopening, deleting and
similar transitions are operations, invoked with op on a POST:
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. See Operations.
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:
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 explains the model in full.
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:
{
"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:
{
"Attributes": {
"Scope": { "value": "Scope:0", "act": "set" },
"Members": {
"value": [
{ "idref": "Member:1000", "act": "add" },
{ "idref": "Member:1001", "act": "remove" }
]
}
}
}
api/asset JSON is not accepted hererest-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. A payload written for one does not work on the
other.
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 — asset types, attribute definitions, assets, attributes and moments.
- Query examples — thirteen worked
GETqueries. - Update examples — worked
POSTpayloads. - New asset templates — the
Newnamespace. - Operations — invoking operations with
op. - XML responses — the response document structure.
- Security & errors — authentication and status codes.
- Meta API — discover the asset types and attributes to address here.
- Query grammar —
sel,where,sort,page,find,findinandasofsyntax. - Query size and performance — there is no default page size; send
pageon every list request. - Bulk API — batch many creates, updates and operations into a single request.