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",
  "auto_cycle_sprints": false,
  "sprint_rollover": 0,
  "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 [])
auto_cycle_sprintsbooleanNoTurn the automatic sprint cycle on or off. Left out, the stored value is kept
sprint_rolloverintegerNoWhere a completed sprint's unfinished issues go: 0 next sprint, 1 backlog. Left out, the stored value is kept

The two sprint settings are optional, so a client written before they existed keeps working: a PUT without them leaves them as they are.

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",
  "auto_cycle_sprints": false,
  "sprint_rollover": 0,
  "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 ​