Projects API Reference
Create, retrieve, update, delete, and manage membership for your Axe Developer Hub projects programmatically using the REST API
The Projects API lets you manage Axe Developer Hub projects and their membership programmatically, using the same Axe Account API key you already use for the rest of Axe Developer Hub's REST APIs. There is no separate credential for project management. For a step-by-step introduction, see Get Started with Project Management Automation.
If you only need to discover a project's ID from its name so you can call the Sessions and Results API, see List Projects below. If you already know your project ID, you can find it in Axe Developer Hub and call the Sessions and Results API directly.
Authentication
All requests require an API key. Provide it using the X-API-Key header:
X-API-Key: <DEQUE_API_KEY>Find your API key in the Axe Account Portal. Choose an Axe Developer Hub API key for web or CI/CD projects. Choose an Axe DevTools Mobile API key for mobile projects.
Treat your API key as a secret: store it in an environment variable or your CI/CD platform's secret store rather than hardcoding it in your requests.
Access Control and Permissions
- Creating a project requires an active subscription (license) for that project's type. Whoever creates a project automatically becomes its project admin.
- Project roles are admin and member. A project admin can update or delete the project and manage its membership. A project member can access the project but not manage it or its membership.
- Org Admins can see and manage every project of their organization's licensed types, regardless of their own project membership. They do not need a personal license to view, update, delete, or manage the members of an existing project. Creating a project is the one exception: it always requires an active license, whether the caller is an org admin or not.
- Adding a member to a project does not require that member to hold a license. The API records them as a project member immediately, but they cannot access the project until they hold a license. The target user must already belong to the organization. You cannot add someone who is not already an organization member.
- Deleting a project is final. There is no self-serve restore for a deleted project.
- In every case, the response is scoped to the API key's product. An Axe Developer Hub API key returns and manages only Axe Developer Hub (web) projects. An Axe DevTools Mobile API key returns and manages only Axe DevTools Mobile projects.
An inactive subscription returns 401 Unauthorized.
Project Type Configuration Values
Every project has a selected_project_type, and each type accepts only a specific, fixed set of test_language / test_platform values:
selected_project_type |
Product family | Valid test_language |
Valid test_platform |
|---|---|---|---|
axe-devtools-watcher |
Web | javascript, typescript |
Cypress, Cypress Component, WebDriverJS, Playwright, Playwright Test, Puppeteer, WebdriverIO, WebdriverIO Testrunner |
axe-devtools-watcher |
Web | java |
Selenium WebDriver, Playwright |
axe-devtools-html |
Web | null (required) |
null (required) |
axe-devtools-mobile |
Mobile | null (required) |
Appium, Espresso, UI Automator, XCUITest |
A project's selected_project_type is fixed when the project is created. To use a different type, create a new project.
List Projects
Returns the list of projects the authenticated API key can access.
Request
- Endpoint:
GET https://axe.deque.com/api-pub/v1/results/projects - Headers (required):
X-API-Key: <DEQUE_API_KEY>Accept: application/json
Query Parameters
All query parameters are optional.
| Parameter | Description |
|---|---|
project_types |
Comma-separated list of project types to return, for example axe-devtools-watcher,axe-devtools-html. Also accepts the aliases web and mobile, which each expand to the concrete types for that product: web returns axe-devtools-watcher and axe-devtools-html projects, and mobile returns axe-devtools-mobile projects (Axe DevTools Mobile SDK and Appium projects). If you omit this parameter, the response returns every project type your API key can access. |
page_size |
Number of projects to return per page. Default: 30. Maximum: 100. Values above the maximum are clamped to 100. See Cursor Pagination. |
after |
Cursor value from the previous response, used to retrieve the next page. See Cursor Pagination. |
Response Body
A successful response returns a JSON array of project objects. Each project object includes the following fields:
| Field | Type | Description |
|---|---|---|
project_id |
String | Unique identifier for the project. When you call this API or the Sessions endpoint, use this value as {project_id}. |
name |
String | The project's display name. |
selected_project_type |
String | The project's type, for example axe-devtools-watcher or axe-devtools-html. See Project Type Configuration Values. |
test_language |
String | The project's test language. For project types that don't use a test language, this is null. See Project Type Configuration Values. |
test_platform |
String | The project's test platform. For project types that don't use a test platform, this is null. See Project Type Configuration Values. Read test_language and test_platform before you update a project: the update validates the pair after merging your changes with these stored values. |
role |
String | Your role on the project: admin, or contributor for a project member. If you are an org admin who is not a member of the project, this is null. |
user_id |
String | Your user ID, when role is set. If role is null, this is null. |
enterprise_id |
String | ID of the organization that owns the project. If the project doesn't belong to an organization, this is null. |
is_enterprise_project |
Boolean | Whether the project belongs to an organization, that is, whether enterprise_id is set. |
created_at |
String | ISO 8601 UTC timestamp for the project's creation time. |
updated_at |
String | ISO 8601 UTC timestamp for the project's last update. If the project has never been updated, this is null. |
run_by |
String | Name of the user whose API key ran the project's most recent session. If the project has no sessions, or the user can't be resolved (for example, the API key was deleted), this is null. |
last_session_created_at |
String | ISO 8601 UTC timestamp of the project's most recent session. If the project has no sessions, this is null. |
has_git_information |
Boolean | Whether the project has Git metadata attached. |
git_url |
String | Git repository URL associated with the project. If the project has no Git information, this is null. |
latest_session |
Object | Details of the project's most recent session, including its ID (id), creation time (created_at), and Git branch (git_branch). If the project has no sessions, this is null. This field holds only one session. To list every session, use the Sessions endpoint instead. |
Responses can include additional fields that aren't listed here, and new fields may be added over time. Ignore any field you don't recognize, and don't rely on undocumented fields.
Example Response Body
[
{
"project_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "webapp-ci",
"selected_project_type": "axe-devtools-watcher",
"test_language": "typescript",
"test_platform": "Playwright",
"role": "admin",
"user_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"enterprise_id": "3f2b8e1a-6c4d-4f7e-9a0b-1d2c3e4f5a6b",
"is_enterprise_project": true,
"created_at": "2026-06-01T14:23:00.000Z",
"updated_at": "2026-06-10T16:45:00.000Z",
"run_by": "Jane Developer",
"last_session_created_at": "2026-06-14T09:12:00.000Z",
"has_git_information": true,
"git_url": "https://github.com/example/webapp",
"latest_session": {
"id": "0d4a85a2-9e6f-44ef-b814-fee8412abeb0",
"created_at": "2026-06-14T09:12:00.000Z",
"git_branch": "main"
}
},
{
"project_id": "f0e1d2c3-b4a5-6789-0123-456789abcdef",
"name": "design-system",
"selected_project_type": "axe-devtools-html",
"test_language": null,
"test_platform": null,
"role": "admin",
"user_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"enterprise_id": "3f2b8e1a-6c4d-4f7e-9a0b-1d2c3e4f5a6b",
"is_enterprise_project": true,
"created_at": "2026-05-20T08:00:00.000Z",
"updated_at": null,
"run_by": null,
"last_session_created_at": null,
"has_git_information": false,
"git_url": null,
"latest_session": null
},
{
"project_id": "c4d5e6f7-a8b9-4c0d-9e1f-2a3b4c5d6e7f",
"name": "ios-app-ci",
"selected_project_type": "axe-devtools-mobile",
"test_language": null,
"test_platform": "XCUITest",
"role": "admin",
"user_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"enterprise_id": "3f2b8e1a-6c4d-4f7e-9a0b-1d2c3e4f5a6b",
"is_enterprise_project": true,
"created_at": "2026-06-03T10:15:00.000Z",
"updated_at": null,
"run_by": "Jane Developer",
"last_session_created_at": "2026-06-15T11:05:00.000Z",
"has_git_information": false,
"git_url": null,
"latest_session": {
"id": "5b6c7d8e-9f0a-4b1c-8d2e-3f4a5b6c7d8e",
"created_at": "2026-06-15T11:05:00.000Z",
"git_branch": null
}
}
]To keep the example short, each latest_session object shows only three of its fields. The API returns more.
Cursor Pagination
Pagination is opt-in. Send neither page_size nor after, and the response returns your complete project list in a single response, with no cursor header.
To page through results instead, the Projects API uses the same cursor-based pagination as the Sessions endpoint. When there are more results beyond the current page, the response returns an opaque cursor value in the x-pagination-cursor header. Pass this value as the after query parameter in your next request to retrieve the next page.
When the response has no x-pagination-cursor header, you are at the last page.
Look Up a Project ID by Name
This example uses curl and jq to find a project's ID from its name:
curl -s \
-H "Accept: application/json" \
-H "X-API-Key: $API_KEY" \
"https://axe.deque.com/api-pub/v1/results/projects" \
| jq -r '.[] | select(.name == "webapp-ci") | .project_id'Pass the returned ID to the Sessions endpoint to list that project's sessions.
List Projects Error Responses
| Status | Cause |
|---|---|
400 Bad Request |
The project_types value contains an invalid project type, the after cursor is malformed, or page_size/after was repeated in the query string. |
401 Unauthorized |
The API key is invalid, missing, or the associated subscription is inactive. |
Retrieve a Project
Returns a single project by its ID, in the same shape as List Projects.
Request
- Endpoint:
GET https://axe.deque.com/api-pub/v1/results/projects/{project_id} - Headers (required):
X-API-Key: <DEQUE_API_KEY>Accept: application/json
Retrieve a Project Error Responses
| Status | Cause |
|---|---|
400 Bad Request |
project_id is not a valid UUID. |
401 Unauthorized |
The API key is invalid, missing, or the associated subscription is inactive. |
403 Forbidden |
The project exists but is outside your API key's organization or product type, or you are not a member of it. |
404 Not Found |
No project exists with that ID, or the project was deleted. |
Create a Project
Creating a project requires the full configurable set. name, selected_project_type, test_language, and test_platform are all required. An omitted field is rejected with a 400 error.
Request
- Endpoint:
POST https://axe.deque.com/api-pub/v1/results/projects - Headers (required):
X-API-Key: <DEQUE_API_KEY>Content-Type: application/jsonAccept: application/json
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name |
String | Yes | The project's display name. 1 to 512 characters. |
selected_project_type |
String | Yes | The project's type. See Project Type Configuration Values. |
test_language |
String or null |
Yes | Must be a valid value for selected_project_type. See Project Type Configuration Values. |
test_platform |
String or null |
Yes | Must be a valid value for selected_project_type and test_language. See Project Type Configuration Values. |
Example Request Body
{
"name": "webapp-ci",
"selected_project_type": "axe-devtools-watcher",
"test_language": "typescript",
"test_platform": "Playwright"
}Response Body
Returns the created project, in the same shape as List Projects. Whoever creates a project becomes its project admin automatically, so no separate membership call is required. A successful create returns 201 Created.
Create a Project Error Responses
| Status | Cause |
|---|---|
400 Bad Request |
The request body is missing a required field, or selected_project_type is not a valid project type. It can also mean test_language/test_platform are not a valid combination for selected_project_type, or that name is empty or over 512 characters. |
401 Unauthorized |
The API key is invalid, missing, or the associated subscription is inactive. |
403 Forbidden |
Your API key's product does not match selected_project_type, or your subscription does not cover this project type. |
Update a Project
A PATCH changes only the fields you send. Although name, selected_project_type, test_language, and test_platform are each optional, the request needs at least one. An omitted field keeps its stored value. After the update, test_language and test_platform must still be a valid pair for the project's selected_project_type, so if you send only one of them, check the other's current value first.
Request
- Endpoint:
PATCH https://axe.deque.com/api-pub/v1/results/projects/{project_id} - Headers (required):
X-API-Key: <DEQUE_API_KEY>Content-Type: application/jsonAccept: application/json
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name |
String | No | The project's display name. 1 to 512 characters. |
selected_project_type |
String | No | The project's type can't change after creation. Sending the current value is accepted, and any other value returns a 400 error. |
test_language |
String or null |
No | Must be a valid value for the project's selected_project_type. See Project Type Configuration Values. |
test_platform |
String or null |
No | Must be a valid value for the project's selected_project_type and test_language. See Project Type Configuration Values. |
Example Request Body
{
"name": "webapp-ci-renamed"
}Response Body
Returns the updated project, in the same shape as List Projects. A successful update returns 200 OK.
Update a Project Error Responses
| Status | Cause |
|---|---|
400 Bad Request |
The request body has none of the updatable fields, or selected_project_type differs from the project's current type. It can also mean test_language/test_platform are not valid for the project's type or that name is empty or over 512 characters. |
401 Unauthorized |
The API key is invalid, missing, or the associated subscription is inactive. |
403 Forbidden |
Your API key's product does not match the project's selected_project_type, or your subscription does not cover this project type. It also covers the case where you are not a project admin. |
404 Not Found |
No project exists with that ID, or the project was deleted. |
Delete a Project
Deleting a project is final. There is no self-serve restore.
Request
- Endpoint:
DELETE https://axe.deque.com/api-pub/v1/results/projects/{project_id} - Headers (required):
X-API-Key: <DEQUE_API_KEY>Accept: application/json
Response Body
{ "deleted": true }Delete a Project Error Responses
| Status | Cause |
|---|---|
400 Bad Request |
project_id is not a valid UUID. |
401 Unauthorized |
The API key is invalid, missing, or the associated subscription is inactive. |
403 Forbidden |
You are not a project admin or an org admin of the organization that owns the project, or the project's type is outside your access. |
404 Not Found |
No project exists with that ID, or the project was already deleted. |
Project Membership
A project's membership is a sub-resource of the project. A project admin can manage the project and its membership. A project member (the member role in the membership operations below) can access the project but not manage it. The membership operations identify users by their email address. Adding a member does not require that member to hold a license. The API records them immediately, but they cannot access the project until they hold a license. The target user must already belong to your organization. You cannot add someone who is not already an organization member.
Use the batch operations below wherever possible. All three write operations accept multiple users in a single call. This reduces the number of requests you need to make. If you manage membership for a large organization, apply backoff and retry when you receive a 429 response. See 429 Too Many Requests for the general pattern.
List Project Members
- Endpoint:
GET https://axe.deque.com/api-pub/v1/results/projects/{project_id}/memberships - Headers (required):
X-API-Key: <DEQUE_API_KEY>,Accept: application/json
Response Body
{
"members": [
{
"user_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"role": "admin",
"email": "jane@example.com",
"first_name": "Jane",
"last_name": "Doe",
"active_license": true,
"created_at": "2026-06-01T14:23:00.000Z"
}
]
}| Field | Type | Description |
|---|---|---|
user_id |
String | The member's unique user ID. |
role |
String | admin or member. |
email, first_name, last_name |
String or null |
The member's email address is the value the add, change role, and remove operations use to identify them. If the member's Axe Account record no longer exists, these are null. The response still lists the member instead of dropping the row, so an existing admin membership row always shows an admin on the project. |
active_license |
Boolean | Whether the member currently holds an active license for this project's product type. |
created_at |
String | ISO 8601 UTC timestamp for the membership's creation time. |
Add Project Members
- Endpoint:
POST https://axe.deque.com/api-pub/v1/results/projects/{project_id}/memberships - Headers (required):
X-API-Key: <DEQUE_API_KEY>,Content-Type: application/json,Accept: application/json
Request Body
{
"user_email_role_pairs": [
{ "user_email": "sam@example.com", "role": "member" },
{ "user_email": "alex@example.com", "role": "admin" }
]
}user_email is the user's Axe Account email address. The match ignores letter case and surrounding spaces. role accepts admin or member (contributor is also accepted as a synonym for member). Each user can appear only once in a request. Adding a user who already belongs to the project is a no-op for that entry: it does not change their existing role. This is a batch operation. An invalid entry rejects the entire request, and the API writes nothing. The error message lists every entry that failed, so you can fix a batch in one pass.
A successful request returns 201 Created with { "created": true }, whether or not the request actually added any new memberships.
Change Member Roles
- Endpoint:
PATCH https://axe.deque.com/api-pub/v1/results/projects/{project_id}/memberships - Headers (required):
X-API-Key: <DEQUE_API_KEY>,Content-Type: application/json,Accept: application/json - Request body: same
user_email_role_pairsshape as Add Project Members
Each user_email must belong to a current member of the project. A project must always retain at least one admin. The API rejects a request that leaves the project with no admin. A sole remaining member who is the project's only admin cannot change their own role. This is a batch operation. An invalid entry rejects the entire request, and the API writes nothing.
A successful request returns 200 OK with { "ok": true }.
Remove Project Members
- Endpoint:
DELETE https://axe.deque.com/api-pub/v1/results/projects/{project_id}/memberships?user_emails={comma-separated,url-encoded,emails} - Headers (required):
X-API-Key: <DEQUE_API_KEY>,Accept: application/json
Pass the email addresses of the users to remove as a comma-separated list in a single user_emails query parameter, and URL-encode the value. For example, a literal + in an address that is not encoded arrives as a space and is rejected as malformed. There is no request body. Removing a user who is not a member succeeds and removes nothing, so a retried request is not an error. A project must always retain at least one admin. The API rejects a request that removes the project's last admin. A sole remaining member who is the project's only admin cannot remove themselves.
A successful request returns 200 OK with the addresses it removed and the ones that matched no member, so you can tell a typo from a completed removal:
{
"deleted": true,
"removed": ["sam@example.com"],
"unmatched": ["alx@example.com"]
}Project Membership Error Responses
| Status | Cause |
|---|---|
400 Bad Request |
project_id is not a valid UUID, user_email_role_pairs is missing or malformed, a user_email is not a valid email address, or the same user appears more than once. It also covers a role that is not admin or member, or a user_emails value that is missing, repeated as separate parameters, or malformed on the remove request. On the add request, it covers an email address that does not belong to your organization. On the change role request, it covers an email address that does not belong to a project member. On any write request, it covers an email address that matches more than one user; the API rejects it instead of risking a change to the wrong person. |
401 Unauthorized |
The API key is invalid, missing, or the associated subscription is inactive. |
403 Forbidden |
You are not a project admin or org admin. It also covers a change that leaves the project with no admin, or a sole remaining member trying to change or remove their own membership. |
404 Not Found |
No project exists with that ID, or the project was deleted. |
