Skip to main content

Error Handling Guide

The most important fact about Agility API errors: success codes are not automatic indicators of success. The Bulk API answers 200 OK with failures itemised in the body, and several classic traps return 200 with wrong data rather than any error at all. Build your handling around inspecting responses, not around status codes — and when something looks wrong despite a 200, start at the troubleshooting hub.

StatusWhat it means
200The request was understood. On api/asset, commands may still have failed — always check commandFailures.count
400A violation or bad value: the body carries a tag like Violation'AssetCannotDeleteClosed or a message like Wrong OID AssetType
401Missing/invalid credentials — or access tokens disabled instance-wide; see authentication
403Authenticated, but the member's role lacks the right
404No such route or asset
405Wrong HTTP method — notoriously, api/customFields is a POST
500On api/asset, usually your unparseable body, returned opaquely with only a reference GUID — see errors and partial failures

Turn Violation Tags into Readable Messages​

Many 400s carry a tag, not prose. Some tags resolve through the localization API into human-readable, caller-language text — and unmatched tags echo back unchanged, so resolving every error costs at worst one wasted request, never a wrong message:

GET ~/loc-2.v1?[Violation'PrimaryWorkitemCannotHaveInvalidStatus]
→ { "…": "The chosen Status is not valid for the Backlog with the assigned Team" }

Handle Bulk API Partial Failure​

api/asset is not transactional: successes apply, failures itemise. Each failure's sourceCommandIndex maps back to your request, so the correct retry unit is the failed commands only:

result = bulk(commands)
failures = result["commandFailures"]["commands"]
if failures:
retryable = [commands[f["error"]["sourceCommandIndex"]] for f in failures]
# fix or re-send just these — the successes are already applied

See errors and partial failures for the full failure model, including why a missing body is a hard 500.

Retry Policy​

  • Retry: network errors, and 5xx responses that are not the known unparseable-body 500 — with exponential backoff and jitter.
  • Do not retry: 400 violations (the request will fail identically), 401/403 (fix credentials or rights), or Bulk API command failures without fixing the command.
  • Handle 429. An exceeded quota returns 429 with a JSON body identifying the limit and how long to wait; honor RetryAfter and back off rather than retrying immediately. See rate limiting.
  • Separately, a slow response is a signal to narrow your query, not to hammer with retries — the underlying constraint is shared database load.

Rate Limiting​

Digital.ai-hosted instances enforce rate limits; on-premises instances do so only once an administrator enables them. Quota policies cap API usage by request time, size of data returned, number of requests, and number of errors, each over a set duration. A request that exceeds a limit gets a 429 response identifying the limit and how long to wait before retrying. See rate limiting for the full quota-policy and usage-metrics endpoints.

Timeouts​

The server's SQL timeout applies per query, not per request — a bulk request carrying several large queries can legitimately run far longer than any single query. Size client timeouts for the whole request, and treat a query that brushes the timeout as a design problem: narrow the projection, tighten the filter, shrink the page.

What to Log​

  • The failing command payload and its violation tag (the tag is stable; localized text is not)
  • For opaque 500s: the reference GUID — on a hosted instance it is the only handle support has; on-premise it keys into the host's exception log
  • The page/offset in long extractions, so a retry can resume rather than restart