---
title: Delete a sub-project
sidebar_label: Delete a sub-project
sidebar_position: 4
description: "Delete an empty direct sub-project of your project together with the setup Ovok created for it, and how to retry a delete that ended in a 502."
---

# Delete a sub-project

| Method | Path |
| --- | --- |
| `DELETE` | `/v1/sub-project/:subProjectId` |

[Authentication](/authentication) · [Access policies](/access-policies) · [Projects](/projects)

Deletes a direct sub-project of your current project, together with the setup Ovok created for it. Use it to remove a project you no longer need. The project must be empty.

The same route also answers at `/sub-project/:subProjectId`, without `/v1`.

**Auth:** Bearer token of a project admin, meaning the membership of the caller in the token's project is an admin one.
**Scope:** `subProjectId` must be a direct child of the caller's project (from the token). You cannot delete your own project, a grandchild, or a project that is not below yours.

## Request

### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `subProjectId` | `string` | Yes | Id of the sub-project to delete. It is not checked as a UUID: an id that is not a direct child of your project answers `404`. |

## Behaviour

- **The project must be empty.** It is refused with `409` and the error `project_not_empty`, and nothing is changed, when the project still holds a `Patient`, a `Device` or a `Location`, or is the parent of another project. The `409` body gives the number of each type in `counts` and the ids of the sub-projects in `childProjectIds`. Move or delete those first. Delete the children of a project before the project itself.
- When the project is empty, Ovok unlinks it from your project and then deletes, in one all-or-nothing write, its project memberships, Practitioners, AccessPolicies, PlanDefinitions, ClientApplications, Organizations and tenant code. It then deletes the project, revokes its Signals tenant, and finally deletes the parent-child record.
- The project's own access policies are deleted with it. Nothing is copied back to your project.
- User accounts are never deleted: a user can belong to other projects. Audit records (`Provenance` and `AuditEvent`) are kept.
- Only `Patient`, `Device` and `Location` are counted. The route does not check for other kinds of data.
- The delete is safe to retry. If the Signals tenant cannot be revoked, the project is already deleted and the route answers `502` with the error `signals_tenant_revoke_failed`. Send the same request again to finish the revoke. After a `200`, the same request answers `404`.
- If the project's Signals credentials cannot be read, the route answers `503` and nothing is changed. Retry shortly.
- Two deletes of the same project cannot run at once. The second answers `409` until the first ends.
- The result of [`GET /v1/sub-project`](/projects/list-sub-projects) is refreshed at once, instead of waiting for its 5 minute limit.
- [`DELETE /v1/slim/project/child/:subProjectId`](/slim-apis/project/delete-a-child-project) runs the same delete.
- No access policy permission is checked beyond the admin role.

## Example

```bash
curl -X DELETE 'https://api.sandbox.ovok.com/v1/sub-project/8c5e7a21-4b9d-4f36-a1c8-0d2e6b9f3a54' \
  -H "Authorization: Bearer ${OVOK_TOKEN}"
```

## Successful response

`200` — The sub-project was deleted.

```json
{ "success": true }
```

| Field | Type | Description |
| --- | --- | --- |
| `success` | `boolean` | Always `true`. |

## Errors

| Status | Meaning |
| --- | --- |
| `401` | Bearer token is missing, invalid, revoked or expired, or the session carries no project. |
| `403` | The caller is not an admin of the project in the token. |
| `404` | No project with this id is a direct child of your project. It also answers after a delete that already finished. |
| `409` | The project still holds a `Patient`, `Device` or `Location`, or has sub-projects (`project_not_empty`). Or another delete of it, or another write to the parent's project links, holds the lock. Nothing was changed. |
| `429` | Too many requests. Retry after a short wait. |
| `502` | The project was deleted, but its Signals tenant could not be revoked (`signals_tenant_revoke_failed`). Send the same request again to finish. |
| `503` | The project's Signals credentials cannot be read. Nothing was changed. Retry shortly. |

A `409` for a project that is not empty has this shape. The text in `message` lists only the types that are present.

```json
{
  "statusCode": 409,
  "error": "project_not_empty",
  "timestamp": "2026-10-09T08:53:20.000Z",
  "path": "/v1/sub-project/8c5e7a21-4b9d-4f36-a1c8-0d2e6b9f3a54",
  "message": "2 Patient, 1 Device still in this project",
  "requestId": "5f0e3a6b-2c1d-4e87-9a3b-7d6c5b4a3f21",
  "subProjectId": "8c5e7a21-4b9d-4f36-a1c8-0d2e6b9f3a54",
  "counts": { "Patient": 2, "Device": 1, "Location": 0 },
  "childProjectIds": []
}
```
