Skip to content

Projects API

Projects are the top-level container for issues, sprints, epics, and board lanes.

Permissions

ActionRequired PermissionScope
ListProjects: ReadOnly projects accessible via team membership, accessAllProjects role flag, or ownership
ViewProjects: ReadMust have project access
CreateProjects: Create
UpdateProjects: Update (Own/All)Must have project access
Archive / restoreProjects: Update (Own/All)Must have project access
DeleteProjects: Delete (Own/All)Must have project access
Transfer ownershipOwner or AdminMust be project owner or admin
Upload / remove avatarProjects: Update (Own/All)Must have project access

Admins bypass all permission checks. Project owners have full access to their own projects.

Endpoints

MethodEndpointDescription
GET/api/projectsList projects — active only unless include_archived is set
POST/api/projectsCreate a project
GET/api/projects/limit-statusCheck the tenant's project count against its plan limit
GET/api/projects/{id}Get a project
PUT/api/projects/{id}Update a project
DELETE/api/projects/{id}Delete a project
POST/api/projects/{id}/avatarUpload a project avatar
DELETE/api/projects/{id}/avatarRemove the project avatar
POST/api/projects/{id}/archiveArchive the project
POST/api/projects/{id}/restoreRestore an archived project
POST/api/projects/{id}/pinPin or unpin the project for the current user

Create Project

POST /api/projects

Request Fields

FieldTypeRequiredDescription
namestringYesProject name, max 255 characters
codestringYes2-5 uppercase letters (e.g., KD), must be unique
descriptionstringYesProject description, max 5,000 characters
user_idintegerYesProject owner user ID
team_idsinteger[]YesTeam IDs to grant access (can be empty [])
direct_member_idsinteger[]YesIndividual user IDs to grant access (can be empty [])
bash
curl -X POST https://{tenant}.kendo.dev/api/projects \
  -H "Authorization: Bearer your-token" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Website Redesign",
    "code": "WEB",
    "description": "Complete redesign of the public website with new branding.",
    "user_id": 1,
    "team_ids": [1],
    "direct_member_ids": [4, 5]
  }'
json
{
  "id": 3,
  "name": "Website Redesign",
  "code": "WEB",
  "description": "Complete redesign of the public website with new branding.",
  "user_id": 1,
  "issue_ids": [],
  "lane_ids": [10, 11, 12, 13],
  "team_ids": [1],
  "direct_member_ids": [4, 5],
  "created_at": "2026-03-13T10:30:00.000000Z",
  "avatar": null
}

Project Limit Status

GET /api/projects/limit-status

Reports the tenant's current project count against its plan limit, so a client can warn the user before they fill in the create-project form. Requires Projects: Create — the same permission that gates POST /api/projects — rather than billing-management access, so any user who can create a project can also check the limit. Free-plan limits are enforced regardless of this endpoint: POST /api/projects still returns 402 Payment Required if the tenant is over its limit at submit time. This endpoint is UX only, not authorization.

Paid (subscribed) tenants get an effectively unlimited project_limit.

bash
curl https://{tenant}.kendo.dev/api/projects/limit-status \
  -H "Authorization: Bearer your-token"
json
{
  "project_limit": 1,
  "project_count": 1
}

Update Project

PUT /api/projects/{id}

Request Fields

FieldTypeRequiredDescription
namestringYesProject name, max 255 characters
codestringYes2-5 uppercase letters, unique (except self)
descriptionstringYesMax 5,000 characters
user_idintegerYesProject owner user ID
lane_idsinteger[]YesOrdered lane IDs (min 1, must belong to project)
team_idsinteger[]YesTeam IDs (can be empty [])
direct_member_idsinteger[]YesIndividual user IDs (can be empty [])
bash
curl -X PUT https://{tenant}.kendo.dev/api/projects/3 \
  -H "Authorization: Bearer your-token" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Website Redesign",
    "code": "WEB",
    "description": "Complete redesign of the public website with new branding.",
    "user_id": 1,
    "lane_ids": [10, 11, 12, 13],
    "team_ids": [1, 2],
    "direct_member_ids": []
  }'
json
{
  "id": 3,
  "name": "Website Redesign",
  "code": "WEB",
  "description": "Complete redesign of the public website with new branding.",
  "user_id": 1,
  "issue_ids": [50, 51, 52],
  "lane_ids": [10, 11, 12, 13],
  "team_ids": [1, 2],
  "direct_member_ids": [],
  "created_at": "2026-03-13T10:30:00.000000Z",
  "avatar": null
}

Delete Project

DELETE /api/projects/{id}

Returns 204 No Content on success.

bash
curl -X DELETE https://{tenant}.kendo.dev/api/projects/3 \
  -H "Authorization: Bearer your-token"

WARNING

Deleting a project removes all its issues, sprints, epics, lanes, and time entries. This action cannot be undone.

Archive / Restore Project

POST /api/projects/{id}/archive · POST /api/projects/{id}/restore

Archiving takes a finished project out of the default project list without deleting anything. The project keeps every issue, sprint, epic, lane and time entry, stays reachable at GET /api/projects/{id}, and its issues keep appearing in search and in time-entry exports. Both endpoints require Projects: Update and return the updated project resource.

Archiving is workspace-wide, not a per-user preference: once archived, the project is out of the list for everyone in the tenant. Restoring puts it back for everyone, with pins intact.

Every project resource carries two fields for this:

FieldTypeDescription
archived_atstring | nullISO timestamp of the moment the project was archived, null while active
archivedbooleanDerived from archived_at — never stored separately, so the two cannot disagree
bash
curl -X POST https://{tenant}.kendo.dev/api/projects/3/archive \
  -H "Authorization: Bearer your-token"
json
{
  "id": 3,
  "name": "Website Redesign",
  "code": "WEB",
  "archived_at": "2026-08-03T11:34:00.000000Z",
  "archived": true
}

Both calls are idempotent: archiving an already-archived project leaves archived_at as it was, and restoring an active project is a no-op.

Listing archived projects

GET /api/projects returns active projects only. Pass include_archived to get active and archived in one list:

bash
curl "https://{tenant}.kendo.dev/api/projects?include_archived=1" \
  -H "Authorization: Bearer your-token"

Any value the parameter does not recognise is treated as false, so a malformed request hides archived projects rather than exposing them unexpectedly. There is no archived-only variant of this endpoint — filter on archived client-side.

Endpoints that address a single project (GET /api/projects/{id}, and everything nested under it) never apply this filter; an archived project is fully readable and writable by id.

Project Avatar

A project can have an uploaded avatar image. On upload, the image is re-encoded into two optimised variants — AVIF and WebP — and its EXIF metadata is stripped. When a project has no avatar, the avatar field is null; clients should fall back to a generated placeholder.

The avatar field on the project resource is either null or an object of absolute CDN URLs to the two variants:

json
"avatar": {
  "avif": "https://cdn.kendo.dev/k7fq2p.../project-avatars/1ChqqBvEsCTjLG8G.avif",
  "webp": "https://cdn.kendo.dev/k7fq2p.../project-avatars/1ChqqBvEsCTjLG8G.webp"
}

These URLs are served straight from the CDN, so fetching one needs no Authorization header. Each is cached for a year and the filename changes on every upload, so a replaced avatar is always a new URL.

Upload Avatar

POST /api/projects/{id}/avatar

Send the image as multipart/form-data. Requires Projects: Update.

FieldTypeRequiredDescription
filefileYesImage file — JPEG, PNG, WebP, or AVIF, max 10 MB

Returns the updated project resource with the avatar field populated. Re-uploading replaces the previous image.

bash
curl -X POST https://{tenant}.kendo.dev/api/projects/3/avatar \
  -H "Authorization: Bearer your-token" \
  -F "[email protected]"
json
{
  "id": 3,
  "name": "Website Redesign",
  "code": "WEB",
  "description": "Complete redesign of the public website with new branding.",
  "user_id": 1,
  "issue_ids": [50, 51, 52],
  "lane_ids": [10, 11, 12, 13],
  "team_ids": [1, 2],
  "direct_member_ids": [],
  "created_at": "2026-03-13T10:30:00.000000Z",
  "avatar": {
    "avif": "https://cdn.kendo.dev/k7fq2p.../project-avatars/1ChqqBvEsCTjLG8G.avif",
    "webp": "https://cdn.kendo.dev/k7fq2p.../project-avatars/1ChqqBvEsCTjLG8G.webp"
  }
}

Fetch Avatar

Use either URL from the avatar object directly — there is no API endpoint for the image bytes.

bash
curl https://cdn.kendo.dev/k7fq2p.../project-avatars/1ChqqBvEsCTjLG8G.webp \
  --output avatar.webp

You normally won't call this directly — use the URLs from the project resource's avatar field, which already point at this endpoint.

Remove Avatar

DELETE /api/projects/{id}/avatar

Deletes the stored image and its variants. Requires Projects: Update. Returns the updated project resource with avatar set to null.

bash
curl -X DELETE https://{tenant}.kendo.dev/api/projects/3/avatar \
  -H "Authorization: Bearer your-token"

Pin / Unpin Project

POST /api/projects/{id}/pin

Toggles the project as "pinned" for the authenticated user, personalizing their own sidebar — it does not affect any other user's view of the project. Requires the same access as viewing the project. Calling this endpoint again on an already-pinned project unpins it.

The response is your own profile, with pinned_project_ids reflecting the new state — not the project resource.

bash
curl -X POST https://{tenant}.kendo.dev/api/projects/3/pin \
  -H "Authorization: Bearer your-token"
json
{
  "id": 1,
  "first_name": "Ada",
  "last_name": "Lovelace",
  "pinned_project_ids": [3]
}

See Also