Troubleshooting
Start from what you are seeing, not from which API you are using.
The Agility API has a particular failure signature worth understanding up front: many of its
traps return 200 OK. A mistyped parameter, an unencoded character or a missing flag will
frequently produce a well-formed, successful-looking response that is quietly wrong, rather
than an error you can catch. Most of this page is about those.
For more information, see Error handling.
My Request Succeeded but the Data Is Wrong
The largest and most dangerous category.
| Symptom | Cause | Where to look |
|---|---|---|
| History query returns current values, not values at that moment | # was not percent-encoded. Everything after an unencoded # is treated as a URL fragment and never leaves your client — the server answers a different question than you asked, successfully | Attribute paths |
where, sort, page or find appear to be ignored | On a single-asset Data URL such as /Data/Story/1234 these are accepted and silently dropped. On /Hist/... they apply. with applies everywhere | Query grammar |
| Wrong page of results, or pages that overlap | page=<size>,<start> — size comes first, the opposite of most APIs. page=0,50 is a zero-size page, not "the first 50" | Sort and paging |
| Paging repeats or skips assets between pages | Paging is applied after sorting, so an unsorted list has no stable order to page through | Sort and paging |
total is missing from a paged response | A paged response only carries total when the server computed it. Whether it does depends on the per-instance ApiDefaultNeedTotal setting — send needTotal=true explicitly | Sort and paging |
| You get XML when you expected JSON | Omitting Accept gets you XML. Accept is also honoured as a query-string parameter, not only as a header | HTTP headers |
A relation update returned 200 but nothing changed | act was probably on the Relation element; it belongs on the Asset value element inside it. The misplaced form is accepted silently | REST-1 writing data |
An operation returned 200 but the asset did not transition | Some redundant operations succeed and write a new moment without changing state — 200 means accepted, not transitioned | REST-1 operations |
| Response contains attributes you never selected | Selecting any relation attribute additionally returns up to three conditional attributes — commonly just its Name. This is implicit and cannot be turned off | Query grammar |
| Response is far larger than expected | Omitting sel does not return "nothing" — it returns a per-type basic attribute set. Combined with no default page size, an unbounded request can try to return your whole instance | Query grammar |
| Removing a tag removes nothing | The payload shape for remove is removeFrom plus field. A previously published example showed the merge shape instead | Remove a tag |
| History looks like it is missing versions | Moment numbers are instance-wide, not per-asset — they are not a contiguous per-asset sequence | Moments and history |
| Localized strings come back untranslated | An unmatched tag echoes back as a trimmed literal of the tag itself, so a typo is indistinguishable from an untranslated term | Tag resolution |
?noloc looks like it worked but changed nothing | ?noloc does not work against Agility. It is a sample-application feature; appended to a loc-2.v1 URL it is parsed as a tag and echoed straight back | noloc |
I Got a Success Response but Nothing Happened
| Symptom | Cause | Where to look |
|---|---|---|
Bulk request returns 200 but assets are unchanged | A 200 means the request was processed, not that every command succeeded. Always check commandFailures.count | Bulk errors |
| A bulk command matches nothing, silently | IsDeleted: true is required to match deleted assets even when from is an explicit OID token. Without it the command matches nothing and reports no error | Bulk queries |
| Only one asset is affected when several matched | Subquery cardinality differs by position: in a relation update every match is used; in an operation's args only the first is | Relation updates · Operations |
Update fails with ClosedAssetCannotEditExistingValues | Closed assets accept only backfill: attributes with no value can be filled in, existing values cannot be changed. Reactivate the asset to edit it | Operations |
Status update fails with PrimaryWorkitemCannotHaveInvalidStatus | The asset's team uses a Team Process, which restricts which StoryStatus values its primary workitems may enter. Use a status from the team's set (statuses whose Team relation points at that team) | Workflows and status |
| Webhook created successfully but never authenticates | The authorizationHeader key was misspelled. Unknown keys are ignored, so the subscription is created, delivers with no Authorization header, the receiver 401s, that counts as a delivery failure, and the subscription is eventually auto-disabled. Tell-tale: hasAuthorizationHeader comes back false | Create a subscription |
| Webhook update lost other fields | Update replaces the whole subscription; send the complete object, not just the changed field | Update a subscription |
| Terminology change is not visible to clients | The loc-2.v1 ETag can go stale and serve 304 with outdated text until the application restarts | Hot reload |
My Request Fails
| Symptom | Cause | Where to look |
|---|---|---|
Invalid SEL parameter, Invalid WHERE parameter, Invalid PAGE parameter | Malformed query-string grammar. These are the literal wire strings — useful for diagnosis, but do not match on them in code | Query grammar · Status codes |
| Querying a relation attribute fails on an abstract type | A downcast is required: Workitems:Story.Category, not Workitems.Category. Applies to Workitem, BaseAsset and Scope relations | Attribute paths |
filter= is rejected on rest-1.v1 | There is no filter query parameter on rest-1.v1 — where takes the full filter language. On query.v1 the same two names mean different things | Filter expressions |
| Activity stream request 400s on a valid-looking verb | Verb parsing is case-sensitive, and one wrong-cased verb fails the whole request. direction is case-insensitive | Activity stream |
Bulk request returns a hard 500 | A missing request body produces a 500 rather than a 400 | Request format |
| A large bulk request times out on the client | The server enforces no processing time limit and keeps running after your client gives up. Each command is its own transaction, so committed commands stay committed — there is no rollback or cancel. Re-query to find the actual state; never blindly resubmit the original payload | Processing model |
| Requests 404 after a URL change | Do not drop version suffixes — the -2 in loc-2.v1 is significant | Base URL |
| A malformed query string produces no error at all | Not every bad query string is rejected; some are accepted and ignored | Status codes |
It Behaves Differently on Another Instance
Behaviour that depends on deployment or configuration rather than on the API. Code that works against one Agility instance can fail against another with no change on your side.
needTotaldefaults per instance, from theApiDefaultNeedTotalsetting — so a client that drives a pager offtotalcan work in one place and break in another. See Sort and paging.stackTraceappears in error responses only on development builds, which are internal to Digital.ai. Customer deployments — hosted and on-premise alike — run release builds, which never include stack traces or exception detail in API responses, and no setting can enable them. An example payload captured on an internal development instance may show the field; code must not expect it. See Bulk errors.- Asset type and attribute names vary with the methodology chosen at installation, and can
be overridden by localization. Discover them from
meta.v1rather than hardcoding them. See Meta API and Terminology. - Custom fields are never in the default attribute set and must be selected explicitly.
Something Changed That I Did Not Expect
POST api/webhook/deletewith no id deletes every subscription in the instance, with no filtering by member. See Delete a subscription.- Tag merge and tag remove have no undo. See Merge and Remove.
- Bulk failures cascade. A failure on a parent asset fails its dependents in the same request. Use preview mode to validate before writing, and see Bulk errors for how to reissue only the failed subset.
Still Stuck
- Error handling guide — retries, backoff and failure classification
- HTTP status codes — what each code means on this API
- FAQ
- Sources and provenance — how far to trust any given page here, and which source wins when two disagree
If you hit something that is not here, it is worth writing down. Undocumented behaviour that costs someone an afternoon is exactly what this page is for.