Operations
Routes, scopes, idempotency and purpose of all 23 HTTP API operations, with request examples.
Call every operation with POST /api/v1/<operation> and a JSON object body. The operation name in the tables is the exact suffix after /api/v1/. On MCP, the same operation is the tool autonomy_<operation> with dots replaced by underscores.
In the tables, Required means the request must carry an idempotencyKey (see Idempotent mutations).
Projects
| Operation | Scope | Idempotency | Purpose |
|---|---|---|---|
project.list | autonomy:read | None | List the projects you can reach, by name. Start here to find the projectId other operations take. |
project.get | autonomy:read | None | Fetch one project by projectId, including its product knowledge. |
Test cases
| Operation | Scope | Idempotency | Purpose |
|---|---|---|---|
test_case.list | autonomy:read | None | List test cases, newest first. Filter by projectId, platform or a name query. Check here before creating a case so existing coverage is reused. |
test_case.get | autonomy:read | None | Fetch one test case by testCaseId, with its outcome steps and their attachments. Secret values are never returned. |
test_case.create | autonomy:test_cases:write | Required | Create a test case in a project from outcome steps. Takes projectId, name, platforms, steps, and optional description and tags. |
test_case.update_steps | autonomy:test_cases:write | Required | Replace a test case's whole step list. Always needs confirm: true. |
test_case.archive | autonomy:test_cases:write | Required | Archive a test case: it leaves lists and can't be run. Its runs and their results are kept. Archiving it again changes nothing. |
Test plans
| Operation | Scope | Idempotency | Purpose |
|---|---|---|---|
test_plan.list | autonomy:read | None | List test plans. Filter by projectId or a name query. |
test_plan.get | autonomy:read | None | Fetch one test plan with its ordered test cases. |
test_plan.create | autonomy:test_plans:write | Required | Create a test plan in a project, optionally with its first test cases (testCaseIds, all from that project). |
test_plan.set_members | autonomy:test_plans:write | Required | Replace a test plan's ordered test cases. Needs confirm: true only when cases would be removed. |
Runs
| Operation | Scope | Idempotency | Purpose |
|---|---|---|---|
run.trigger | autonomy:runs:write | Required | Queue runs. Give exactly one of testCaseId or testPlanId (a plan queues one run per test case). Target a saved environment of the same project with environmentSlug, or pass targets for a fresh deployment or build. Returns right away. |
run.list | autonomy:read | None | List recent runs, newest first. Filter by projectId, testCaseId, status, environmentSlug or branch. Check here before triggering duplicate work. |
run.get | autonomy:read | None | Fetch a run's result, each step's result, and what the run created. Long-polls with cursor and waitMs. |
run.step_actions | autonomy:read | None | List the run's Step Actions: everything the agent did to reach each outcome, including the Final check. Values you saved as secrets are masked. Filter with stepIndex. |
run.evidence | autonomy:read | None | Get the evidence for each step that ran: the outcome, result, explanation, Final check and short-lived media links. Filter with step. |
See Runs and evidence for how to follow a run and read these results.
Environments
| Operation | Scope | Idempotency | Purpose |
|---|---|---|---|
environment.list | autonomy:read | None | List environments with their platform targets. Filter by projectId. Value names are shown; values never are. |
environment.get | autonomy:read | None | Fetch one environment by environmentSlug. |
environment.upsert | autonomy:environments:write | Required | Create an environment (projectId is required to create one), or update one by slug. Updating an existing environment needs confirm: true. runtimeValues merge by name and are used as {{env.NAME}} in any step. Secret values are write-only. |
A web or API target needs a url, and an Android target an artifactUrl for its build (iOS targets can't be saved until iOS testing is available). packageName, bundleId and versionLabel are optional: when you leave them out they're read from the build, and a value you send wins. Saving works while the build is still being read; if its app ID can't be read, runs on that target can't start.
Issues
| Operation | Scope | Idempotency | Purpose |
|---|---|---|---|
issue.list | autonomy:read | None | List issues, newest first. Filter by projectId, environmentSlug, status, kind, or runId for the issues one run found or showed. |
issue.create | autonomy:issues:write | Required | File an issue in the environment where it was seen. kind is bug or risk. Takes title, a plain-text body, optional expected and actual, and runIds to cite runs. |
issue.comment | autonomy:issues:write | Required | Add a plain-text comment to an issue. |
issue.set_status | autonomy:issues:write | Required | Set an issue to open, resolved or ignored. Resolve it after a run of the same test case shows the fix. |
An issue never changes a run's or a step's result. Issues filed before v0.5 may have the kind note; new issues are bug or risk. See Issues.
Outcome steps
A test case is a list of steps. Each step is an outcome in plain words, with optional attachments:
1{2 "id": "sign-up",3 "text": "A new account exists for a fresh email address",4 "attachments": [5 {6 "id": "inbox",7 "kind": "inbox",8 "exact": false9 }10 ]11}id: stable within the test case. Letters, digits,_and-, up to 64 characters.text: the outcome, up to 2,000 characters. It can use{{env.NAME}}.attachments: up to 12. Each has its ownid, akindandexact.
kind | Fields | What it gives the agent |
|---|---|---|
account | role | A saved sign-in of the run's environment, by role, such as "Grower". |
inbox | label?, address? | A fresh inbox for the run, or an existing address. |
phone | label?, number? | A phone number that receives SMS, or an existing number. |
file | label?, file | A file to upload. Copy the file object from a test case that already uses the file. |
screenshot | label? | A screenshot of the outcome, kept as evidence. |
data | label, value?, intent?, secret? | A value to use (value), or an intent for the agent to choose one, such as "A strong password". |
Attachments are test data unless exact is true. If the product refuses a test-data value only because it's taken, the agent uses a fresh value of the same kind and says so. An Exact value is used as written, and a refusal of it is what the step is judged on. An Exact data, inbox or phone attachment needs its value, address or number.
address, number and value can be {{env.NAME}}, filled in from the run's environment. A data attachment with secret: true is write-only: its value is stored hidden and never returned. If test_case.update_steps resends a secret attachment without its value, the stored value is kept.
The API accepts up to 50 steps per test case. The dashboard editor shows up to 12, so keep cases you also edit in the dashboard short. See Authoring for how to write good outcomes.
Create a test case
This test case signs up with a fresh inbox and a generated password, picks a country that must be exactly "Kenya", confirms the account from the welcome email and keeps a screenshot of the profile.
curl --request POST "https://api.autonomyqa.com/api/v1/test_case.create" \
--header "Authorization: Bearer $AUTONOMY_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"idempotencyKey": "ci-create-grower-sign-up-v1",
"projectId": "jd7exampleprojectid",
"name": "Grower sign-up",
"description": "A new grower can sign up, confirm their email and see their profile.",
"platforms": ["web"],
"tags": ["sign-up", "smoke"],
"steps": [
{
"id": "sign-up",
"text": "A new grower account exists for a fresh email address, with Kenya as the country",
"attachments": [
{ "id": "inbox", "kind": "inbox", "label": "Sign-up inbox", "exact": false },
{
"id": "password",
"kind": "data",
"label": "Password",
"intent": "A strong password the sign-up form accepts",
"exact": false
},
{ "id": "country", "kind": "data", "label": "Country", "value": "Kenya", "exact": true }
]
},
{
"id": "confirm-email",
"text": "The account is confirmed with the link in the welcome email",
"attachments": []
},
{
"id": "profile",
"text": "The profile page shows Kenya as the country",
"attachments": [
{ "id": "profile-shot", "kind": "screenshot", "label": "Profile", "exact": false }
]
}
]
}'Store the aut_ key in AUTONOMY_API_KEY. The response is the new test case, with its id.
Trigger a run
Give exactly one of testCaseId or testPlanId, and either environmentSlug or targets, not both. This example targets a fresh web preview:
curl --request POST "https://api.autonomyqa.com/api/v1/run.trigger" \
--header "Authorization: Bearer $AUTONOMY_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"idempotencyKey": "ci-run-pr-128-grower-sign-up-9f3a2c1",
"testCaseId": "jh7exampletestcaseid",
"platforms": ["web"],
"targets": {
"web": { "baseUrl": "https://preview-128.example.com" }
},
"branch": "feature/sign-up-copy",
"pr": "128",
"deployment": { "commitSha": "9f3a2c1d8e7f6a5b4c3d2e1f0a9b8c7d6e5f4a3b" }
}'| Target | Fields |
|---|---|
web | baseUrl |
api | baseUrl, optional runtimeValues ([{ "name", "value", "isSecret" }]) |
android | packageName, plus artifactUrl or a scanned storageId |
ios | Coming soon: iOS runs can't start yet. bundleId, plus artifactUrl or a scanned storageId |
platforms limits the run to some of the test case's platforms. branch, pr and deployment link the runs to a branch, pull request or commit. On the Team plan and above, scope: "changed" with changedFiles focuses the run on the changed files.
The response has runIds, the environment used, and planGroupRunId for a test plan. Follow each run with run.get.
run.trigger refuses an imported test case that hasn't been confirmed, a test case that still has only steps from before v0.5 ("Rewrite them as outcome steps with test_case.update_steps, then run it."), and a test plan with no test cases.
Idempotent mutations
Every mutation marked Required needs an idempotencyKey string in its JSON body. It's a body field, not a header. Without it, the API returns 400 INVALID_ARGUMENT with the message "idempotencyKey is required for this operation."
- Retrying with the same key and the same arguments returns the stored result instead of doing the work again.
- Reusing a key with different arguments returns
409 CONFLICT: "idempotencyKey was already used with different arguments." - Results are kept for 24 hours. Keys are separate for each caller.
Use one key per intended change, and reuse it only to retry that change. Read operations don't take a key.
Confirmation before replacement
Creates and run.trigger never need confirmation. Three operations need confirm: true when they would replace something:
| Operation | When | details.wouldChange |
|---|---|---|
test_case.update_steps | Always: the whole step list is replaced. | stepCountBefore, stepCountAfter |
test_plan.set_members | Only when test cases would be removed. | removed, added (each [{ testCaseId, name }]) |
environment.upsert | When the slug exists and fields or targets are given. | fields, platforms |
Without confirmation, nothing is written. The API returns 400 INVALID_ARGUMENT with details.confirmRequired: true and the details.wouldChange preview. Read the preview, then send the same request again with confirm: true. Don't add confirm: true automatically. See Errors for the full response.
Cursor pagination
Every *.list operation, and run.step_actions, accepts an optional limit and an opaque cursor:
1{2 "projectId": "jd7exampleprojectid",3 "limit": 20,4 "cursor": "<nextCursor from the previous response>"5}limit defaults to 20 and must be an integer from 1 to 100. Each response is { items, nextCursor }. Pass nextCursor unchanged to get the next page; null means there are no more pages. Keep the filters the same across pages. A top-level null, such as "cursor": null, counts as not sent.
run.list and issue.list apply some filters within each page, so a page can be short or empty while more results remain. Keep paging while nextCursor is set.
Didn’t find it?