Download OpenAPI specification:
Programmatic access to the projects, tasks, comments and members you already work with in Projoodle. It is the same application underneath: your role, your project membership and any plan freeze apply exactly as they do in the browser.
curl -H "Authorization: Bearer pj_..." \
https://app.projoodle.com/api/v1/projects
Every response is JSON. Requests carry no cookies and never receive a session cookie, so the token alone decides what a call may do.
A token is bound to one membership in one project. It cannot see or change another project, even when your account belongs to several. Create a separate token per project you want to reach.
| Scope | Allows |
|---|---|
read |
Reading projects, tasks, comments and members |
readwrite |
The above, plus creating and changing tasks and comments |
A scope never widens your own permissions. A read token cannot write even
when your role would allow it, and a readwrite token cannot write where an
archived project, a frozen column or your role would stop you in the browser
either. Revoking a token takes effect on the very next request.
| Status | Meaning |
|---|---|
401 |
Token missing, expired or revoked |
403 |
Scope, role, archive or freeze rules forbid this call |
404 |
The project or task does not exist, or this token cannot reach it |
421 |
Wrong host - check the base URL |
422 |
The request body is malformed or fails validation |
429 |
Too many requests; slow down and retry |
Unexpected failures return a generic error together with a correlation ID,
both in the body and in the X-Correlation-ID header. Quote that ID when
reporting a problem: it identifies the server-side log entry, which never
contains your token.
List endpoints are paginated - pass limit and offset. Projoodle's task
model currently has no priority and no parent/subtask relation, so those
fields are deliberately absent rather than stored outside the shared
application model.
Treat a token like a password: it acts with your permissions inside its project. Keep it out of chats, tickets, commits, screenshots and shell history, prefer a short expiry, and revoke what you no longer use.
Tokens are stored only as SHA-256 hashes and stay bound to the membership they were created for. Account-bound OAuth access tokens exist for the MCP endpoint only and are not accepted on this REST surface.
| projectId required | string^[0-9A-Fa-f]{32}$ Projoodle project UUID |
{- "project": {
- "id": "string",
- "title": "string",
- "description": "string",
- "status": 0,
- "archived": true,
- "kanban_states": [
- {
- "id": 0,
- "title": "string",
- "position": 0
}
]
}
}| projectId required | string^[0-9A-Fa-f]{32}$ Projoodle project UUID |
| status | string Enum: "open" "done" Friendly alias for the existing completed flag |
| completed | integer Enum: 0 1 Existing Projoodle completion flag; takes precedence over status |
| kanban_state_id | integer [ 1 .. 2147483647 ] |
| assignee | string^[0-9A-Fa-f]{32}$ Project-user UUID |
| q | string <= 200 characters Case-insensitive title and description search |
| limit | integer [ 1 .. 100 ] Default: 50 |
| offset | integer >= 0 Default: 0 |
{- "items": [
- {
- "id": "string",
- "project_id": "string",
- "title": "string",
- "description": "string",
- "status": "open",
- "kanban_state_id": 0,
- "owner_id": "string",
- "assignees": [
- "string"
], - "labels": "string",
- "start_date": "string",
- "due_date": "string",
- "progress": 0,
- "dependencies": [
- "string"
], - "sort_order": 0,
- "completed_at": "string",
- "completed_by": "string",
- "created_at": "string",
- "updated_at": "string"
}
], - "pagination": {
- "limit": 0,
- "offset": 0,
- "total": 0
}
}| projectId required | string^[0-9A-Fa-f]{32}$ Projoodle project UUID |
| title required | string [ 1 .. 200 ] characters |
| description | string <= 100000 characters |
| kanban_state_id | integer [ 1 .. 2147483647 ] |
| assignees | Array of strings <= 100 items [ items^[0-9A-Fa-f]{32}$ ] |
| labels | string <= 500 characters |
| start_date | string <date> |
| due_date | string <date> |
| dependencies | Array of strings <= 13 items [ items^[0-9A-Fa-f]{32}$ ] |
{- "title": "string",
- "description": "string",
- "kanban_state_id": 1,
- "assignees": [
- "string"
], - "labels": "string",
- "start_date": "2019-08-24",
- "due_date": "2019-08-24",
- "dependencies": [
- "string"
]
}{- "task": {
- "id": "string",
- "project_id": "string",
- "title": "string",
- "description": "string",
- "status": "open",
- "kanban_state_id": 0,
- "owner_id": "string",
- "assignees": [
- "string"
], - "labels": "string",
- "start_date": "string",
- "due_date": "string",
- "progress": 0,
- "dependencies": [
- "string"
], - "sort_order": 0,
- "completed_at": "string",
- "completed_by": "string",
- "created_at": "string",
- "updated_at": "string"
}
}| taskId required | string^[0-9A-Fa-f]{32}$ Projoodle task UUID |
{- "task": {
- "id": "string",
- "project_id": "string",
- "title": "string",
- "description": "string",
- "status": "open",
- "kanban_state_id": 0,
- "owner_id": "string",
- "assignees": [
- "string"
], - "labels": "string",
- "start_date": "string",
- "due_date": "string",
- "progress": 0,
- "dependencies": [
- "string"
], - "sort_order": 0,
- "completed_at": "string",
- "completed_by": "string",
- "created_at": "string",
- "updated_at": "string"
}
}Ordinary fields use Projoodle's existing edit logic. status uses the
existing completion/reopen transition. kanban_state_id uses the same
move logic as Kanban drag-and-drop. Fields may be combined in one request.
| taskId required | string^[0-9A-Fa-f]{32}$ Projoodle task UUID |
| title | string [ 1 .. 200 ] characters |
| description | string <= 100000 characters |
| status | string Enum: "open" "done" |
| kanban_state_id | integer [ 1 .. 2147483647 ] |
| assignees | Array of strings <= 100 items [ items^[0-9A-Fa-f]{32}$ ] |
| labels | string <= 500 characters |
| start_date | string <date> |
| due_date | string <date> |
| dependencies | Array of strings <= 13 items [ items^[0-9A-Fa-f]{32}$ ] |
{- "title": "string",
- "description": "string",
- "status": "open",
- "kanban_state_id": 1,
- "assignees": [
- "string"
], - "labels": "string",
- "start_date": "2019-08-24",
- "due_date": "2019-08-24",
- "dependencies": [
- "string"
]
}{- "task": {
- "id": "string",
- "project_id": "string",
- "title": "string",
- "description": "string",
- "status": "open",
- "kanban_state_id": 0,
- "owner_id": "string",
- "assignees": [
- "string"
], - "labels": "string",
- "start_date": "string",
- "due_date": "string",
- "progress": 0,
- "dependencies": [
- "string"
], - "sort_order": 0,
- "completed_at": "string",
- "completed_by": "string",
- "created_at": "string",
- "updated_at": "string"
}
}| taskId required | string^[0-9A-Fa-f]{32}$ Projoodle task UUID |
{- "items": [
- {
- "id": "string",
- "comment": "string",
- "author": {
- "id": 0,
- "display_name": "string"
}, - "created_at": "string"
}
]
}| taskId required | string^[0-9A-Fa-f]{32}$ Projoodle task UUID |
| comment required | string [ 1 .. 20000 ] characters |
{- "comment": "string"
}{- "id": "string",
- "comment": "string"
}Email addresses and account data are never returned.
| project_id | string Optional visible project UUID; omit to aggregate visible projects |
{- "items": [
- {
- "id": "string",
- "display_name": "string",
- "role": "admin",
- "is_owner": true,
- "project_id": "string"
}
]
}