Update an existing project’s name, description, URL, accent color, and tech stack. Requires a valid JWT access token. The project must belong to the authenticated user’s tenant and must not be in ARCHIVED status.
- This is a full replace operation — all fields must be sent in the request body.
nameis required (not blank); optional fields (description,url,accent,techStack) can be set tonullto clear their value. - If the URL is changed, the new URL must be globally unique (same rules as during creation).
- The project status cannot be changed through this endpoint — use dedicated status-change endpoints (Archive, Restore).
- Projects in ARCHIVED status cannot be updated — the user must restore the project first.
sequenceDiagram
actor User
User->>+FE: edits project fields
FE->>FE: validate form
alt form is invalid
FE-->>User: show error messages <br> disable submit button
end
User->>+FE: submits update form
FE->>+BE: PUT /api/v1/projects/{id} <br> Authorization: Bearer {accessToken} <br> UpdateProjectRequest
BE->>BE: validate JWT access token
alt access token invalid or missing
BE-->>FE: 401 Unauthorized <br> ErrorResponse
end
BE->>BE: validate request
alt request is invalid
BE-->>FE: 400 Bad Request <br> ErrorResponse
end
BE->>DB: find project by id and tenant_id
alt project not found
BE-->>FE: 404 Not Found <br> ErrorResponse
end
BE->>BE: check project status
alt project is ARCHIVED
BE-->>FE: 409 Conflict <br> ErrorResponse
end
BE->>DB: check if new URL is already taken (if URL changed)
alt URL already taken
BE-->>FE: 409 Conflict <br> ErrorResponse
end
BE->>DB: update project record
BE->>-FE: 200 OK <br> ProjectResponse
FE->>-User: show updated project
FE->>-User: show success notification
PUT /api/v1/projects/{id} UpdateProjectRequest:
{
"name": "Wildwood Bakery & Cafe",
"description": "Online ordering for a neighborhood bakery and cafe",
"url": "wildwood-cafe.talkide.app",
"accent": "oklch(0.80 0.14 65)",
"techStack": "Vue + Spring Boot"
}
200 OK ProjectResponse:
{
"data": {
"id": 1,
"name": "Wildwood Bakery & Cafe",
"description": "Online ordering for a neighborhood bakery and cafe",
"status": "DRAFT",
"url": "wildwood-cafe.talkide.app",
"accent": "oklch(0.80 0.14 65)",
"techStack": "Vue + Spring Boot",
"progress": null,
"createdAt": "2026-04-20T10:00:00Z",
"updatedAt": "2026-04-29T15:00:00Z"
}
}
400 Bad Request (validation) ErrorResponse:
{
"status": 400,
"code": "VALIDATION_ERROR",
"message": "Validation failed",
"errors": [
{ "field": "name", "message": "must not be blank" }
]
}
401 Unauthorized (missing or invalid access token) ErrorResponse:
{
"status": 401,
"code": "AUTHENTICATION_FAILED",
"message": "Access token is missing or invalid"
}
404 Not Found (project does not exist or does not belong to user’s tenant) ErrorResponse:
{
"status": 404,
"code": "NOT_FOUND",
"message": "Project not found"
}
409 Conflict (project is ARCHIVED) ErrorResponse:
{
"status": 409,
"code": "CONFLICT_PROJECT",
"message": "Cannot update an archived project"
}
409 Conflict (URL already taken) ErrorResponse:
{
"status": 409,
"code": "CONFLICT_PROJECT",
"message": "URL is already taken by another project"
}
Frontend
Validations
| Field | Constraints | Size | Pattern | Note |
|---|---|---|---|---|
| name | not_blank | 1 - 100 | ||
| description | (optional) | 0 - 500 | ||
| url | (optional) | 0 - 253 | ^[a-z0-9-]+\.talkide\.app$ | |
| accent | (optional) | 0 - 100 | ||
| techStack | (optional) | 0 - 100 |
Backend
Validations
| Field | Constraints | Size | Pattern | Note |
|---|---|---|---|---|
| name | not_blank | 1 - 100 | ||
| description | (optional) | 0 - 500 | ||
| url | (optional) | 0 - 253 | ^[a-z0-9-]+\.talkide\.app$ | Must be globally unique if changed |
| accent | (optional) | 0 - 100 | ||
| techStack | (optional) | 0 - 100 |
Test Cases
| GIVEN | WHEN | THEN |
|---|---|---|
| authenticated user, project in DRAFT status, valid request | PUT /projects/{id} is called | 200 OK with updated project returned |
| authenticated user, project in LIVE status, valid request | PUT /projects/{id} is called | 200 OK with updated project returned |
| authenticated user, project in UPDATED status, valid request | PUT /projects/{id} is called | 200 OK with updated project returned |
| authenticated user, project in BUILDING status, valid request | PUT /projects/{id} is called | 200 OK with updated project returned |
| authenticated user, project in PAUSED status, valid request | PUT /projects/{id} is called | 200 OK with updated project returned |
| authenticated user, project in ARCHIVED status | PUT /projects/{id} is called | 409 CONFLICT_PROJECT error response is returned |
| authenticated user, URL changed to an already taken URL | PUT /projects/{id} is called | 409 CONFLICT_PROJECT error response is returned |
| authenticated user, URL unchanged (same as current) | PUT /projects/{id} is called | 200 OK with updated project returned |
| project belongs to another tenant | PUT /projects/{id} is called | 404 NOT_FOUND error response is returned |
| project ID does not exist | PUT /projects/{id} is called | 404 NOT_FOUND error response is returned |
| name is blank | PUT /projects/{id} is called | 400 VALIDATION_ERROR error response is returned |
| name longer than 100 characters | PUT /projects/{id} is called | 400 VALIDATION_ERROR error response is returned |
| URL does not end with .talkide.app | PUT /projects/{id} is called | 400 VALIDATION_ERROR error response is returned |
| invalid request (empty body) | PUT /projects/{id} is called | 400 VALIDATION_ERROR error response is returned |
| no Authorization header | PUT /projects/{id} is called | 401 AUTHENTICATION_FAILED error response is returned |
Was this page helpful?
Thanks for the feedback.