Projoodle API (1.0.0)

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.

Getting started

  1. In Projoodle, open Account settings → API & AI Access and create a token. Pick the smallest scope that does the job and set an expiry date.
  2. Copy the token while it is shown. Projoodle stores only a hash of it and cannot display it a second time.
  3. Send it as a bearer token:
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.

What a token can 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.

Connecting an AI client instead

There is a second way in, and it is the one to use for ChatGPT or another MCP client: Projoodle speaks the Model Context Protocol at https://app.projoodle.com/mcp, with OAuth 2.1 (authorization code, PKCE S256) instead of a token you copy by hand.

The difference is worth knowing before you pick one:

  • A service token reaches one project and works on both this REST API and the MCP endpoint. Good for a script or a local tool.
  • An OAuth connection is bound to your account, so it reaches every project you are a member of, and the set of projects follows your memberships as they change. It works at /mcp only - an OAuth access token is deliberately rejected on this REST surface.

OAuth uses finer-grained scopes than the two above:

Scope Allows
projoodle.read Reading projects, tasks, comments, members and labels
projoodle.tasks.write Creating and changing tasks, including repetition
projoodle.comments.write Adding comments
projoodle.projects.write Creating projects, project settings, Kanban columns and label names

They are discoverable at https://app.projoodle.com/.well-known/oauth-authorization-server, so a client that speaks the protocol finds them without being told.

The same sentence applies here as above: a scope never widens your own permissions. Your role, your project membership and any plan freeze decide what happens, exactly as they do in the browser.

PRO features

Some features belong to Projoodle PRO, on this API exactly as in the browser: text search (q on the task lists) and repeating tasks. A call that needs one of them without PRO on the project or account returns 403 with the code plan_feature_required. Ending an existing series with frequency none always works, so a lapsed plan never traps a task in a loop.

Errors

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.

Limits of v1

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.

Security

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.

Projects

Validate the bearer used by the internal MCP gateway

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/json
{
  • "error": {
    }
}

List visible projects

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/json
{
  • "items": [
    ]
}

Create a project owned by the connected account

Only account-bound OAuth connections may create a project: a service token is tied to one existing membership and could not reach the new project afterwards. The project starts with one TODO column and no description.

The plan limit is decided inside the same serialisable transaction that inserts the project, so it cannot be raced; reaching it returns 422 plan_limit_reached.

Authorizations:
bearerAuth
Request Body schema: application/json
required
title
required
string [ 1 .. 100 ] characters

Responses

Request samples

Content type
application/json
{
  • "title": "string"
}

Response samples

Content type
application/json
{
  • "project": {
    }
}

Get a project and its ordered Kanban states

Authorizations:
bearerAuth
path Parameters
projectId
required
string^[0-9A-Fa-f]{32}$

Projoodle project UUID

Responses

Response samples

Content type
application/json
{
  • "project": {
    }
}

Change project title, description or default view

Only the project lead may change these. The admin role is deliberately not enough: the same rule as in the Projoodle interface, because the owner row carries the personal project link.

Authorizations:
bearerAuth
path Parameters
projectId
required
string^[0-9A-Fa-f]{32}$

Projoodle project UUID

Request Body schema: application/json
required
non-empty
title
string [ 1 .. 100 ] characters
description
string <= 500 characters
default_view
string
Enum: "list" "board"

Responses

Request samples

Content type
application/json
{
  • "title": "string",
  • "description": "string",
  • "default_view": "list"
}

Response samples

Content type
application/json
{
  • "project": {
    }
}

Add a Kanban column to a project

The plan limit is enforced inside the same serialisable transaction that inserts the column, so it cannot be raced. Reaching it returns 422 plan_limit_reached.

Authorizations:
bearerAuth
path Parameters
projectId
required
string^[0-9A-Fa-f]{32}$

Projoodle project UUID

Request Body schema: application/json
required
title
required
string [ 1 .. 50 ] characters

Responses

Request samples

Content type
application/json
{
  • "title": "string"
}

Response samples

Content type
application/json
{
  • "project": {
    }
}

Rename a Kanban column

Authorizations:
bearerAuth
path Parameters
kanbanStateId
required
integer [ 1 .. 2147483647 ]
Request Body schema: application/json
required
title
required
string [ 1 .. 50 ] characters

Responses

Request samples

Content type
application/json
{
  • "title": "string"
}

Response samples

Content type
application/json
{
  • "project": {
    }
}

List the labels available in a project

A task carries its label as a plain string. This list shows which values the project actually offers, so a client does not have to guess. The display name can be overridden per project; name returns the project's own wording and falls back to the global one.

Authorizations:
bearerAuth
path Parameters
projectId
required
string^[0-9A-Fa-f]{32}$

Projoodle project UUID

Responses

Response samples

Content type
application/json
{
  • "items": [
    ]
}

Change what a label is called in this project

Labels are a fixed global set; a project cannot add its own. What can be changed is the project's display name for one of them. Commas are stripped, because a task stores its labels comma-separated.

Authorizations:
bearerAuth
path Parameters
projectId
required
string^[0-9A-Fa-f]{32}$

Projoodle project UUID

labelId
required
integer >= 1
Request Body schema: application/json
required
name
required
string [ 1 .. 50 ] characters

Responses

Request samples

Content type
application/json
{
  • "name": "string"
}

Response samples

Content type
application/json
{
  • "items": [
    ]
}

Tasks

Filter and page tasks in a visible project

Authorizations:
bearerAuth
path Parameters
projectId
required
string^[0-9A-Fa-f]{32}$

Projoodle project UUID

query Parameters
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

Responses

Response samples

Content type
application/json
{
  • "items": [
    ],
  • "pagination": {
    }
}

Create a task through Projoodle task business logic

Authorizations:
bearerAuth
path Parameters
projectId
required
string^[0-9A-Fa-f]{32}$

Projoodle project UUID

Request Body schema: application/json
required
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}$ ]

Responses

Request samples

Content type
application/json
{
  • "title": "string",
  • "description": "string",
  • "kanban_state_id": 1,
  • "assignees": [
    ],
  • "labels": "string",
  • "start_date": "2019-08-24",
  • "due_date": "2019-08-24",
  • "dependencies": [
    ]
}

Response samples

Content type
application/json
{
  • "task": {
    }
}

Search tasks across every visible project

Applies the same role, freeze and archive checks per project as the per-project listing, so a hit can only come from a project the caller may currently enter. Results are sorted by due date; tasks without a due date come last.

Authorizations:
bearerAuth
query Parameters
q
string <= 200 characters

Case-insensitive title and description search

status
string
Enum: "open" "done"

Defaults to open

assignee
string^[0-9A-Fa-f]{32}$

Project-user UUID

project_id
string^[0-9A-Fa-f]{32}$

Restrict the search to one project instead of all visible ones

due_before
string <date>

Inclusive ISO date; tasks without a due date are excluded

due_after
string <date>

Inclusive ISO date; tasks without a due date are excluded

limit
integer [ 1 .. 100 ]
Default: 50
offset
integer >= 0
Default: 0

Responses

Response samples

Content type
application/json
{
  • "items": [
    ],
  • "pagination": {
    }
}

Get a visible task

Authorizations:
bearerAuth
path Parameters
taskId
required
string^[0-9A-Fa-f]{32}$

Projoodle task UUID

Responses

Response samples

Content type
application/json
{
  • "task": {
    }
}

Partially update, complete/reopen, or move a task

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.

Authorizations:
bearerAuth
path Parameters
taskId
required
string^[0-9A-Fa-f]{32}$

Projoodle task UUID

Request Body schema: application/json
required
non-empty
object

Set frequency none to end an existing series

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}$ ]

Responses

Request samples

Content type
application/json
{
  • "recurrence": {
    },
  • "title": "string",
  • "description": "string",
  • "status": "open",
  • "kanban_state_id": 1,
  • "assignees": [
    ],
  • "labels": "string",
  • "start_date": "2019-08-24",
  • "due_date": "2019-08-24",
  • "dependencies": [
    ]
}

Response samples

Content type
application/json
{
  • "task": {
    }
}

Comments

List comments on a visible task

Authorizations:
bearerAuth
path Parameters
taskId
required
string^[0-9A-Fa-f]{32}$

Projoodle task UUID

Responses

Response samples

Content type
application/json
{
  • "items": [
    ]
}

Add a comment through Projoodle comment business logic

Authorizations:
bearerAuth
path Parameters
taskId
required
string^[0-9A-Fa-f]{32}$

Projoodle task UUID

Request Body schema: application/json
required
comment
required
string [ 1 .. 20000 ] characters

Responses

Request samples

Content type
application/json
{
  • "comment": "string"
}

Response samples

Content type
application/json
{
  • "id": "string",
  • "comment": "string"
}

Users

Resolve safe assignee identities

Email addresses and account data are never returned.

Authorizations:
bearerAuth
query Parameters
project_id
string

Optional visible project UUID; omit to aggregate visible projects

Responses

Response samples

Content type
application/json
{
  • "items": [
    ]
}