Projects: the Five Project Routes
A Studio project is the record that binds a stack, a composition content type, an environment, and a locale together. Every composition route resolves its target through a project, which is why these routes come first. Same envelope as the rest of the chapter: /v1 prefix, organization_uid header, the { error_message, error_code, errors } envelope. See Endpoint reference for the shared conventions.
Preview: not yet GA. Paths and response shapes may still change, and the service may not be enabled on your region or tenant yet.
Do It With a Skill
provision-studio-project uses these routes to stand a project up headlessly.
curl -fsSL https://studio-documentation.contentstackapps.com/install.sh | sh
Why Projects Matter to Every Other Route
Composition routes take a projectUid and nothing else about your stack. That works because the project record carries the rest:
| The project holds | What reads it |
|---|---|
| connectedStackApiKey | Every access check, and the CMA calls that compositions make |
| contentTypeUid | Where compositions are stored as entries |
| settings.configuration.locale | The default locale for composition reads |
| settings.configuration.environment | The environment for published reads |
| canvasUrl | Which app route Studio loads to preview sections |
So a composition call failing with an access error is often a project problem, not a composition problem: check the project’s connected stack first.
The Project Record
| Field | Type | Notes |
|---|---|---|
| uid | string | Assigned by the service. |
| name | string | Required on create. Max 256 characters. |
| description | string | Optional. Max 1024 characters. Defaults to "". |
| canvasUrl | string | Optional. Max 100 characters, shorter than it looks, and the most common validation surprise here. Defaults to "". Empty means the project uses Playground Canvas. |
| connectedStackApiKey | string | Required on create. The stack this project authors against. |
| contentTypeUid | string | Required on create. The content type compositions are stored in. |
| settings | object | { configuration: { environment, locale } }. Defaults to both empty strings. |
| organizationUid | string | Set from the organization_uid header, not from the body. |
| createdAt / updatedAt | ISO date | Maintained by the service. |
| createdBy / updatedBy | string | User UID, taken from the caller’s identity. |
| deletedAt | false | ISO date | false while live. Deletion sets a timestamp, see § 5. |
| deletedBy | string | Set on delete. |
A note on the published schema. The service’s Swagger output describes the string length limits with maximum rather than maxLength. That’s an annotation slip in the source, not a different rule: the constraints enforced at runtime are the character counts in the table above.
Only six fields are writable: name, description, canvasUrl, connectedStackApiKey, contentTypeUid, settings. Everything else is service-maintained, and sending it is silently ignored.
1. List Projects
GET /v1/projects
Returns the projects in the organization that you can see. The scope is not simply “all projects in the org”. It is the union of two sets:
- projects whose connectedStackApiKey is a stack you have access to, and
- projects you created, whether or not you still have access to their stack.
The second half is what stops a project from vanishing from its owner’s list after a stack permission change.
Response: 200 OK
{
"projects": [
{
"uid": "blt9f2c…",
"name": "Marketing site",
"description": "",
"canvasUrl": "/studio",
"connectedStackApiKey": "blt1234567890",
"contentTypeUid": "compositions",
"settings": { "configuration": { "environment": "production", "locale": "en-us" } },
"createdAt": "2026-01-01T00:00:00.000Z",
"deletedAt": false
}
]
}
An empty organization returns { "projects": [] }, not a 404.
curl 'https://<host>/v1/projects' \ -H "$CS_AUTH" -H 'organization_uid: <org-uid>'
2. Fetch a Project
GET /v1/projects/{uid}
| Param | In | Required | Notes |
|---|---|---|---|
| uid | path | yes | UID of the project. |
Scoped to your organization and to live projects only (deletedAt: false). Access is re-checked after the lookup: if you can neither reach the project’s stack nor own the project, the response is 404 project_not_found (1) rather than a 403: the service doesn’t confirm that a project you can’t see exists.
Response: 200 OK
{ "project": { "uid": "blt9f2c…", "name": "Marketing site", … } }
The missing-deletedAt trap. The lookup filters on deletedAt: false, and that does not match a document where the field is absent: an older record written before the field existed returns 404 here while still appearing in the list route. A project that lists but won’t fetch has this shape.
curl 'https://<host>/v1/projects/<uid>' \ -H "$CS_AUTH" -H 'organization_uid: <org-uid>'
3. Create a Project
POST /v1/projects
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
| name | string | Yes | Max 256 characters. |
| connectedStackApiKey | string | Yes | You need write access to this stack, checked before anything is written. |
| contentTypeUid | string | Yes | The content type compositions are stored in. |
| description | string | - | Max 1024 characters. |
| canvasUrl | string | - | Max 100 characters. Omit to use Playground Canvas. |
| settings | object | - | { configuration: { environment, locale } }. |
organizationUid, createdBy, and updatedBy are set from your identity and the organization_uid header, sending them has no effect.
Response: 201 Created
{
"notice": "Project created successfully",
"project": { "uid": "blt9f2c…", "name": "Marketing site", "deletedAt": false, … }
}
Notable errors
422 stack_not_found (21) when you lack write access to connectedStackApiKey: this is the access failure, not a claim that the stack doesn’t exist. 400 for DTO-shape problems, in the framework’s { statusCode, message, error } shape. 422 project_create_failed (2) if the authorization SDK fails during the call.
curl -X POST 'https://<host>/v1/projects' \
-H "$CS_AUTH" -H 'organization_uid: <org-uid>' \
-H 'Content-Type: application/json' \
-d '{ "name": "Marketing site", "connectedStackApiKey": "blt1234567890",
"contentTypeUid": "compositions",
"settings": { "configuration": { "environment": "production", "locale": "en-us" } } }'
4. Update a Project
PUT /v1/projects/{uid}
A partial update: every writable field is optional, and omitted fields keep their stored values.
Write access is checked against the project’s stored connectedStackApiKey before the update runs. Repointing a project at a different stack therefore checks your access to the current stack, not the new one.
Response: 200 OK
{ "notice": "Project updated successfully", "project": { /* the updated record */ } }
404 project_not_found (1) if the uid isn’t a live project in your organization. 422 stack_not_found (21) on access denial. 422 project_update_failed (3) on an authorization-SDK failure.
curl -X PUT 'https://<host>/v1/projects/<uid>' \
-H "$CS_AUTH" -H 'organization_uid: <org-uid>' \
-H 'Content-Type: application/json' \
-d '{ "canvasUrl": "/studio" }'
5. Delete a Project
DELETE /v1/projects/{uid}
A soft delete: the record stays and deletedAt becomes a timestamp, which removes it from every lookup in this chapter. There is no undelete route, and no force parameter.
Unlike deleting a composition, this is not reference-guarded: the project’s compositions are not checked, counted, or removed. They remain as entries in the connected stack, orphaned from Studio’s point of view.
Response: 200 OK
{ "notice": "Project deleted successfully" }
404 project_not_found (1) if it isn’t a live project in your organization. 422 stack_not_found (21) on access denial. 422 project_delete_failed (4) on an authorization-SDK failure.
Send the DELETE without a Content-Type header. A body-less DELETE carrying Content-Type: application/json is rejected with a 400 before the handler runs.
curl -X DELETE 'https://<host>/v1/projects/<uid>' \ -H "$CS_AUTH" -H 'organization_uid: <org-uid>'
See Also
- Endpoint reference: every route in the service, and the shared envelope.
- Compositions: the routes that resolve their target through a project.
- Errors and validation: the full error_code catalog.
- Create a Studio project: the same thing through the Studio UI.