---
title: Create a sub-project
sidebar_label: Create a sub-project
sidebar_position: 3
description: "Create a sub-project under your current project: what is created, who owns it, and what the response contains for a user or a client application."
---

# Create a sub-project

| Method | Path |
| --- | --- |
| `POST` | `/v1/sub-project` |

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

Creates a new project below your current project. Use it from admin tooling, or from a headless integration that holds an admin client token.

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

**Auth:** Bearer token of a project admin, meaning the membership of the caller in the token's project is an admin one. Sending `parentProjectId` also requires a super admin.
**Scope:** The parent is the caller's project (from the token). Only a super admin can name another parent.

## Request

### Body

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | `string` | Yes | Name of the new project. At least 1 character. Stored as sent. |
| `extension` | `any` | No | Copied onto the new project as given. The FHIR server must accept it as the project's `extension`, otherwise the request answers `400`. |
| `parentProjectId` | `string (uuid)` | No | Create the project under this parent instead of your own. Super admins only. Any other caller gets `403`. |

Fields that are not listed here are ignored.

## Behaviour

- The create is all or nothing. It writes the project with its own Signals tenant, a tenant code derived from the name, the parent's link to the project, a Practitioner in the new project, an admin membership in it, and the parent-child record. If a step fails, the steps already done are undone and the request answers the failure.
- The new project has the standard Ovok features: `bots`, `cron`, `email`, `transaction-bundles` and `websocket-subscriptions`. See [Settings and features](/settings-and-features).
- No project setting is written and no access policy is copied from the parent. Every setting starts unset, so its default applies. Later changes to the parent's access policies made through the Slim access-policy routes, such as [`POST /v1/slim/policy`](/slim-apis/policy/create-access-policy), are copied to its direct sub-projects by name.
- **User session:** the new project is owned by your user. A copy of your Practitioner profile is created in it, and your user gets an admin membership in it. Your profile must be a Practitioner. The response is `{ "success": true }`, without the new project's id. Find the project with [`GET /v1/projects`](/projects/list-projects), which also returns its `tenantCode`, or with [`GET /v1/sub-project`](/projects/list-sub-projects).
- **Client application session** (no user behind the token): the new project is owned by the parent project, and no user becomes its admin. The response is `{ "success": true, "projectId": "..." }`.
- With `parentProjectId`, the parent is that project and the new project is linked below it. The parent must exist, otherwise the request answers `404` and nothing is written.
- Sub-projects can be nested: a project that is itself a sub-project can create sub-projects of its own.
- A project name is not unique. A request with the same name after an earlier one finished creates a second project. Only a concurrent request for the same name, compared without regard to case or surrounding spaces, under the same parent is refused with `409` and writes nothing.
- The route that creates a sub-project through the Slim API does more. See [Projects](/projects) for the differences.

## Example

```bash
curl -X POST 'https://api.sandbox.ovok.com/v1/sub-project' \
  -H "Authorization: Bearer ${OVOK_TOKEN}" \
  -H 'Content-Type: application/json' \
  -d '{ "name": "Example Clinic North" }'
```

## Successful response

`201` — The sub-project was created. With a user token the body is:

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

With a client application token the body also carries the new project's id:

```json
{
  "success": true,
  "projectId": "8c5e7a21-4b9d-4f36-a1c8-0d2e6b9f3a54"
}
```

| Field | Type | Description |
| --- | --- | --- |
| `success` | `boolean` | Always `true`. |
| `projectId` | `string (uuid)` | Id of the new project. Present only when the caller is a client application. Left out for a user session. |

## Errors

| Status | Meaning |
| --- | --- |
| `400` | The FHIR server refused the new project, for example because `extension` is not valid. Nothing is left behind. |
| `401` | Bearer token is missing, invalid, revoked or expired, or the session carries no project or no profile. |
| `403` | The caller is not an admin of the project in the token, or `parentProjectId` was sent by a caller who is not a super admin. |
| `404` | `parentProjectId` names no project, or the caller's Practitioner profile cannot be read. |
| `409` | Another create of a project with this name under the same parent is running, or another write to the parent's project links holds the lock. Nothing was written. Retry. |
| `422` | The body fails validation: `name` is missing, not a string or empty, or `parentProjectId` is not a UUID. |
| `429` | Too many requests. Retry after a short wait. |
| `500` | A step failed and an earlier step could not be undone. The body names the failing step and lists the steps that could not be undone in `compensationGaps`. Some of the new project's records can remain. |
| `503` | The new project's Signals tenant cannot be set up. Nothing was created. Retry shortly. |
