Skip to main content

Overview

A project in Xenia is a recurring schedule that generates tasks over time. It is not a task itself — it is the rule that keeps producing them, plus the history of everything it has produced. Through a client key you can browse your workspace’s projects, read one in full, inspect the schedules and tasks a project has generated, pull its status analytics, edit it, pause or resume it, end it so it stops generating new work, and delete it outright. Creating a project is done through the task endpoint, not a project endpoint — see Create a project. Projects require the Projects feature on your workspace. Every route in this guide returns 403 without it.

Authentication & base URL

All requests go to https://api.xenia.team and every path starts with /api/v1. Authenticate with your client credentials (see Client Key Setup):
The workspace is derived from the key, so the {workspaceId} in each path must be your key’s own workspace. Always include the /workspaces/{workspaceId} segment — the unscoped project variants are not available to API keys and return 403.
Project routes use the extra_meta envelope: a data payload plus an extra_meta object that carries a human-readable message on the lifecycle calls. The one exception is Search Projects, which also returns a meta object with pagination counts. Successful POST and PUT calls return HTTP 200.
Every route addressed by a {projectId} honours that project’s access list. A project with no access list is visible to everyone in the workspace; once an access list exists, the key’s user must match it by user id, one of their team ids, or their role id, and gets 403 otherwise.

Create a project

There is no POST /projects. A project is created by calling Create Task with "isProject": true:
Pass projectType as user, asset or role and supply the matching target: assignees for user, assets for asset, assigneesRoles.roles for role. A missing target returns 400 (Assignees are required for project, Assets are required for project or Role is required for project), and any other projectType returns 400 Unsupported project type. The call returns as soon as the project row exists; its schedules are generated in the background. Until that finishes the project’s state is not ready, and the lifecycle routes below return 400 Project is under processing. Poll Get Project until state is ready. See Recurring projects in the task guide for the full create-body contract.

List projects

Returns every project the key’s user can access, each with its schedule-status breakdown. This endpoint is not paginated — it returns the full set.
It is compared against the literal string true; any other value counts as false. Each row carries an analytics array with that project’s schedule-status counts, so a single call is usually enough to build a dashboard.

Search projects

Filtered, sorted and offset-paged search. It is a POST because the filters travel in the body — it creates nothing.
Every field is optional: offset (0), limit (10), searchText (""), sortField (createdAt), sortOrder (DESC), status (ALL) and advanceFilters (null). status accepts ALL, ACTIVE, PAUSED or EXPIRED. Unknown fields are ignored. This is the one project route that returns a meta object alongside data, carrying total (how many projects matched the filter in total), filtered (how many are in this page) and the offset, limit, searchText, sortField and sortOrder the query actually ran with. Page by increasing offset until offset + filtered reaches total.

Project details

Returns one project with its next upcoming task (NextTask), its currently active task (ActiveTask), its access list, and a server-computed isEditable flag.
The useful fields to read: Requires Manage Tasks. An id that does not exist in your workspace returns 403, not 404 — the project access check runs before the handler and denies when it cannot load the project.

Schedules and generated tasks

Returns every schedule on the project, the tasks each schedule has generated grouped by schedule, and a stats block.
data.stats gives totalTasks, completedTasks and completionRate. data.tasksBySchedule maps each seriesId to the tasks it produced, which is how you tell which schedule inside a project is falling behind.

Analytics

Two endpoints, both POST with no request body:
Schedule analytics returns one row per status with a count (data) and its share of the total (percentage), including statuses with data: 0. Task analytics returns one row per status with a count only, omits statuses with no tasks, and returns data as a string because it comes straight from a SQL COUNT. Both require Manage Tasks.

Edit a project

Updates the project and, optionally, the tasks it has already generated.
Editable fields: title, description, additionalDescription, priority, taskStatus, startTime, dueTime, recurringOnDay, recurringDueDay, recurringByEvery, intervalWeek, dueIntervalWeek, isRangedTask, dueCycleOffset, requestThisTask, notification, LocationId, AssetId, ChecklistLogId, assignees, assigneesRoles, isChecklistRequired, isTimeBound. Anything else in the body is ignored. targetTasksStatus decides which already-generated tasks the change is applied to — e.g. ["Open"] updates open tasks and leaves completed ones alone. Omit it to change only the project going forward.
You cannot change the recurrence pattern. If requestThisTask or recurringByEvery differs from the project’s current value the call returns 400 Can not change recurrence or frequency of the project!. To move a project to a different cadence, end it and create a replacement.
Two other cases return 400: a project whose state is not yet ready (Project is under processing), and a non-recurring project that already has active one-off tasks (Can not edit project with active one-off tasks!). On success the project’s state becomes updating while its schedules regenerate in the background. Requires Edit Project and Manage Tasks.

Pause or resume a project

Pausing stops the project generating new tasks; resuming starts it again.
This is a GET that changes state, and it is a toggle — there is no way to ask for a specific target state. A running project becomes paused and a paused project becomes running. Read isPaused from Project details first if you need to be certain which way the call will go, and do not put this URL anywhere that retries or prefetches GET requests.
The response echoes the updated project, and extra_meta.message states which way it went (Project has been paused / Project has been resumed). Requires Pause/Resume Project and Manage Tasks. Returns 400 Project is under processing if state is not yet ready.

End a project

Ends every schedule on the project so it stops generating work, while leaving the project and every task it has already produced intact and reportable.
Send no request body. data is always an empty object on success. Requires Manage Tasks, and returns 400 Project is under processing if state is not yet ready. Ending is permanent — there is no “un-end” call — but it is not destructive: nothing is deleted. If you only want to stop work temporarily, use pause instead.

Delete a project

This deletes the project and every task it has ever generated, including completed history, and hard-deletes its schedule rows. There is no undo and no confirmation step. Projects routinely carry hundreds to tens of thousands of tasks, so calling this with the wrong projectId destroys a large amount of records.If your goal is to stop a project rather than erase it, use end or pause — both keep the history intact.
Note the path is the same as Get Project — only the HTTP method differs. Read the project first and check its title before deleting, so a mistyped id fails safely instead of succeeding on the wrong project. Requires Delete Project and Manage Tasks.

Permissions

Each route enforces the key’s default-user role (or the user named in x-client-user), on top of the Projects feature and an active subscription:

What you can’t do via API key

These project operations exist in Xenia but are not reachable with a client key — they return 403 Forbidden: API key not authorized for this route. Use the web or mobile app for them:
  • Add a schedule to a project (POST …/projects/{projectId}/add-schedule)
  • Attach a new task to a one-off project (POST …/projects/{projectId}/tasks/add)
  • Change a project’s access list (PUT …/projects/{projectId}/access)
Changing a project’s recurrence pattern is also not possible through Edit — end the project and create a replacement through Create Task with "isProject": true.