---
id: agent-primer
title: 'Agent Primer'
description: 'A dense, imperative briefing for AI agents calling the Agility API - decision procedures, contracts, and anti-patterns, with links to the owning pages'
sidebar_label: 'Agent Primer'
sidebar_position: 91
tags: ['reference', 'ai', 'llms']
---

This page briefs an AI agent on calling the Agility API. It is written for
models, not people: dense, imperative, and exhaustive about failure modes.
Every rule links the page that owns the full explanation — when this page
and an owning page disagree, the owning page wins.

## Choosing a Surface

| Task                                      | Use                                                                    |
| ----------------------------------------- | ---------------------------------------------------------------------- |
| Read one asset, or write attributes       | `rest-1.v1/Data/<Type>` — [rest-1](/apis/rest-1)                       |
| Read with shaped/nested JSON projections  | `query.v1` (read-only) — [query.v1](/apis/query-v1)                    |
| Create/update/execute in bulk, aliases    | `api/asset` — [Bulk API](/apis/asset-bulk)                             |
| Discover types, attributes, operations    | `meta.v1` (no auth) — [meta.v1](/apis/meta-v1)                         |
| Resolve display names, localize tags      | `loc-2.v1` (no auth) — [loc-2.v1](/apis/loc-2-v1)                      |
| Instance settings: attachment size limit, time zone, build version | `config.v1` (no auth, XML only) — [config.v1](/apis/config-v1) |
| Read past values / change history         | `rest-1.v1/Hist/<Type>` and `asof` — [moments](/concepts/moments-and-history) |
| Read member-scope role assignments        | `api/ProjectRoles` — [guide](/guides/members-roles-scopes)             |
| Link a **human** to an asset              | `assetdetail.v1?oid=…` or `?Number=…` — [permalinks](/apis/asset-permalinks); never emit these for programs |

## Non-Negotiable Query Rules

- **Every list query carries an explicit selection and page.** Omitting
  them returns every default attribute of every matching asset.
  `rest-1.v1`: `sel=Name,Number&page=50,0` (positional, **size first**).
  `query.v1`: `"select": [...]` and `"page": {"size": 50, "start": 0}`
  (named, **both keys required** — omitting `start` is a server error).
  Never port the positional form into `query.v1` or vice versa.
  [Owner](/guides/query-size-and-performance).
- **Do not alias.** `"Name as Title"` and `"from": "Story as s"` parse
  and are silently discarded — response keys are always the attribute
  tokens. [Owner](/apis/query-v1).
- **A paged response reports no total unless you ask**: `rest-1.v1` omits
  the `total` attribute, `api/asset` reports `null`; send
  `needTotal=true` when paging off the count, and treat its absence as
  "unknown", not zero. `query.v1` never returns a total at all.
  [Owner](/reference/query-grammar/sort-and-paging).
- **Never invent attribute names.** Fetch `meta.v1/<Type>` and use the
  tokens it returns. Custom fields (`Custom_*`) vary per instance.
  [Owner](/concepts/attributes).
- **Resolve a UI word to a system name in this order**: the
  [display-names table](/reference/asset-type-display-names) → its
  informal-names and false-friends sections → assume an instance
  terminology override and enumerate via `meta.v1` + `loc-2.v1`
  ([procedure](/guides/display-names)). Beware: "Planning Level" usually
  means `Scope`, not `PlanningLevel`; "Message" is an inbox notification,
  not a comment.
- **`query.v1` responses are double-wrapped** — one array per query in the
  request document: `[[...results...]]`. Dotted select tokens come back as
  flattened keys (`"Scope.Name"`); bare relation selects return
  `{"_oid": "..."}` objects. [Owner](/apis/query-v1).
- **Fetch by OID** with `"where": {"ID": "Story:1207"}` (query.v1) or the
  URL path (rest-1). Match on OIDs, never on display names — display
  names are localized, instance-configurable, and not unique.

## Non-Negotiable Write Rules

- `rest-1.v1` JSON writes wrap every attribute:
  `{"Attributes": {"Name": {"value": "...", "act": "set"}}}`. Multi-value
  relations use `"act": "add"` / `"remove"`. [Owner](/apis/rest-1).
- `api/asset` documents are named commands, **one aliased command per
  array element**:
  `[{"@alias": {"AssetType": "Story", ...}}, {"@next": {"from": "@alias", "update": {...}}}]`.
  Aliases resolve in order. Never put two `@` keys in one object: two
  siblings happen to work, three or more fail with
  `Invalid key at node(s)` and nothing is created.
  [Owner](/apis/asset-bulk/aliases-and-projection).
- Operations execute as `"execute": "Delete"` (no arguments) or
  `"execute": {"op": "AssignToScopeWithRole", "args": {...}}`
  (parameterized). A `with` block does **not** pass operation arguments.
  [Owner](/apis/asset-bulk/operations).
- **Never write a Rank attribute** (`Order`, AttributeType `Rank`) with a
  numeric set — the server rejects it (`Violation'NotSupported`). Rank
  moves are relative, and rank changes leave **no history**.
- **Closed assets accept backfill only**: writing an attribute that
  already has a value fails with
  `Violation'ClosedAssetCannotEditExistingValues`.
  [Owner](/concepts/workflows).
- `Delete` is soft and recoverable (`Undelete`); `Inactivate` closes
  (AssetState Closed). A closed asset cannot be deleted
  (`Violation'AssetCannotDeleteClosed`). [Owner](/concepts/assets).
- **Status writes must be in the team's allowed set** when the team
  enforces a process (`Violation'PrimaryWorkitemCannotHaveInvalidStatus`).
  Ask [`api/ValidValues`](/apis/valid-values) before writing, or inspect
  `Team.HasProcess` / `Team.SelectedValues`. [Owner](/concepts/workflows).
- **A 200 from an operation means accepted, not transitioned.** Some
  redundant operations no-op with a new history moment (`Reactivate` on
  an Active asset); others fail a rule. Read the asset's state back when
  the transition matters. [Owner](/apis/rest-1).

## Error Handling

- Violations return structured tags (`Violation'...`). The tags are
  localizable through `loc-2.v1`; unresolved tags echo back unchanged, so
  blanket resolution is always safe. [Owner](/apis/loc-2-v1/tag-resolution).
- An unparseable `api/asset` document returns an opaque 500, not a 400 —
  do not retry unchanged; fix the document shape.
- A 400 `Wrong OID AssetType` means the relation's declared type rejects
  the target. Check the attribute's `RelatedAsset` type in `meta.v1` —
  and do not generalize between sibling types (`Task.Parent` and
  `Test.Parent` are typed differently). [Owner](/concepts/relations).

## Common Recipes

- **Comment on a workitem (with a mention)**: create a `Conversation`,
  an `Expression` with `BelongsTo` (or reply with `InReplyTo`), and
  anchor via `Mentions`. One-request payload:
  [working with comments](/guides/working-with-comments).
- **Scope-tree filtering**:
  `Scope.ParentMeAndUp[Schedule=$S;ScopeLabels=$L]=$Scope` with `with`
  variables; arrays in `with` are in-list matches. For large trees,
  precompute the scope set first. [Owner](/guides/scope-context).
- **Who did what, when**: `Hist` queries with `History[...]` projections;
  `DoneDate` is stamped when EffectiveAssetState reaches Closed. Not
  every attribute is tracked. [Owner](/concepts/moments-and-history).
- **Grant access**: default role via `Member.Scopes` add; a specific role
  via the `AssignToScopeWithRole` / `AssignMemberWithRole` operations;
  read back via `api/ProjectRoles`, never via asset queries.
  [Owner](/guides/members-roles-scopes).

## Authentication

Access tokens never expire and carry the member's full rights — send
`Authorization: Bearer <token>`. Basic auth is a dev/demo configuration
only; production instances almost always use SSO. OAuth2 has never been
supported despite legacy naming. [Owner](/getting-started/authentication).
