Projects
Create and configure projects, publish them, manage members and roles, arrange your sidebar, keep saved views, read the board and insights, and import from GitHub.
A project holds everything else and is the unit of access. The projects concept page explains identifiers, roles and what deletion destroys. The project's own issues, pages and plans are on Issues, pages and plans; its repository bindings are on Repositories and commits.
Projects
GET /api/projects
The projects you can read, as a Project array in your own sidebar order. With enforcement on, a project you are not a member of is absent rather than an error. Administrators see every project.
POST /api/projects
Creates a project. Any signed-in caller.
Body: name, identifier, and optional description (Markdown, default empty), emoji and lead_user_id. The identifier is one to five characters, an uppercase letter followed by uppercase letters or digits, and cannot be DOC; anything else is 400.
The lead gets a lead membership. Leaving lead_user_id out, or naming yourself, makes you the lead with no further check; for a connected tool, "yourself" is the person who owns it. Naming somebody else gives them lasting access, so it needs a recent browser session.
Returns the Project.
curl -X POST "$LIFIC/api/projects" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Mobile app","identifier":"APP","emoji":"lucide:Smartphone"}'GET /api/projects/{id}
One Project. Viewer.
PUT /api/projects/{id}
Changes project settings. Lead. Every field is optional: name, identifier, description, emoji (null clears it), lead_user_id (null clears it) and is_public.
Setting lead_user_id to a user also gives them a lead membership, so that one change needs a recent browser session. Clearing it does not.
is_public: true publishes the project on the anonymous public read API, and false withdraws it on the next request. See Public projects before turning it on: it exposes existing content, not just what you write afterwards.
Changing the identifier renames every issue, page and plan identifier in the project at once; old written references stop resolving. Returns the Project.
DELETE /api/projects/{id}
Deletes the project and everything in it, immediately and permanently, including anything in its trash. Lead, or administrator when enforcement is off. The check is repeated inside the delete transaction. Returns {"deleted": true}.
POST /api/projects/{id}/archive
Takes the project out of the instance and keeps it as one compressed file in the data directory. Same permission as deleting: Lead, or administrator when enforcement is off, checked again inside the transaction. Returns 201 with the ArchivedProject below. See Archiving projects.
GET /api/archived-projects
Every archived project, newest first. Administrator only, in both modes. Each entry has id, identifier, name, description, emoji, file_name, size_bytes, issue_count, page_count, archived_at, archived_by, archived_by_name, was_public, and file_present, which is false when the file is no longer on disk.
POST /api/archived-projects/{id}/unarchive
Brings the project back from its file. Administrator only, in both modes. Returns {"project_id", "identifier", "was_public"}. 409 when a live project already has the identifier or the file no longer matches its recorded checksum, 404 when the file is missing, 429 while another archive operation is running.
PUT /api/projects/reorder
Sets your own sidebar order. Other people's order is unaffected. Body: ids, the project ids in the order you want. Ids you leave out keep their relative order after the ones you list. A duplicate or an id you cannot see is 400. Returns the reordered Project array.
Sidebar groups
Groups are private to the person who made them. Every route here works only on your own groups; another person's group answers 404. Filing a project in a group changes nothing about the project.
A group has id, user_id, name, sort_order, project_ids, created_at and updated_at. project_ids lists only projects you can still read, so a project you lose access to drops out of the group without being unfiled.
GET /api/project-groups
Your groups, in order.
POST /api/project-groups
Creates a group. Body: name. A name you already use is 409. Returns the group with an empty project_ids.
PATCH /api/project-groups/{id}
Renames a group. Body: name. Returns the group.
DELETE /api/project-groups/{id}
Deletes a group. Its projects are not touched. Returns {"deleted": true}.
PUT /api/project-groups/assign
Files a project into one of your groups, or out of all of them. Body: project_id and group_id (null takes the project out of every group). A project sits in at most one of your groups. Filing a project you cannot read is 403. Returns {"ok": true}.
PUT /api/project-groups/reorder
Sets the order of your groups. Body: ids, group ids in the order you want; groups you leave out follow in their current order. Returns your groups.
Members and roles
The role rules, including what authz_enforced changes, are in Roles. A member record has project_id, user_id, role and created_at.
GET /api/projects/{id}/members
Members with their names: each record adds username and display_name. Viewer.
POST /api/projects/{id}/members
Adds a member. Lead, from a recent browser session, even for a caller who is a lead: an API key cannot hand out standing access. Both checks run inside the transaction that writes the row.
Body: user_id and optional role (viewer, maintainer or lead; default viewer). An unknown role is 400; a user who is already a member is 409. Returns the member record.
curl -X POST "$LIFIC/api/projects/1/members" \
-H "Authorization: Bearer $SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{"user_id":2,"role":"maintainer"}'PATCH /api/projects/{id}/members/{user_id}
Changes a member's role. Lead. Body: role. Raising a role needs a recent browser session; lowering it, or setting the role it already has, does not, so an API key can still reduce access during an incident. Demoting the last lead is 409. Returns the member record.
DELETE /api/projects/{id}/members/{user_id}
Removes a member. Lead, with no recency requirement. Removing the last lead is 409; promote somebody first. If the removed member was the primary lead, lead_user_id moves to another lead or is cleared. Returns {"deleted": true}.
GET /api/projects/{id}/my-role
Your own standing, so a client can decide what to show without reading the roster. Viewer. Returns role (viewer, maintainer, lead, or null when you are not a member), enforced (whether authz_enforced is on) and is_admin. For a connected tool, the role is its owner's.
Saved views
Saved views are personal. Every route lists and changes only your own views in one project, and needs a signed-in caller. Viewer.
A view has id, project_id, user_id, name, config, is_default, created_at and updated_at.
config is a string containing JSON, at most 8 KiB. Lific checks only that it parses and never reads inside it. The web UI stores this shape and tolerates missing fields:
{
"version": 1,
"layout": "list",
"filterStatus": "",
"filterPriority": "",
"filterLabel": "",
"filterModule": "",
"searchQuery": "",
"sortField": "updated",
"sortDir": "desc",
"groupBy": "status",
"density": "comfortable",
"laneBy": "status",
"hiddenStatuses": []
}GET /api/projects/{id}/views
Your views in the project.
POST /api/projects/{id}/views
Creates a view. Body: name, config and optional is_default. Returns the view.
PATCH /api/projects/{id}/views/{view_id}
Changes any of name, config and is_default. Returns the view.
DELETE /api/projects/{id}/views/{view_id}
Deletes the view. Returns {"deleted": true}.
Board and numbers
GET /api/projects/{id}/board
The project's issues grouped into columns. Viewer. Query: group_by, which is status (the default), priority or module. Returns an object whose keys are column names (a status, a priority, or a module name with unassigned for issues without one) and whose values are Issue arrays. Empty columns are left out. The board reads at most 500 issues.
GET /api/projects/{id}/issue-counts
Issue counts by status: backlog, todo, active, done, cancelled and total. Viewer. Issues in the trash are not counted.
GET /api/projects/{id}/insights
Charts for the project's insights tab. Viewer. Query: weeks (default 12, clamped to 1 through 52).
Returns:
weekscreated_per_weekandclosed_per_week: arrays ofweek_startandcountstatus_counts: the issue-count object abovepriority_counts:urgent,high,medium,low,noneandtotalmodule_counts:module_id(null for no module),nameandcounttop_actors: the actor summary described under project activity
GET /api/projects/{id}/mention-candidates
People who can be @-mentioned in the project's comments: user_id, username and display_name. Viewer. With enforcement on, only the project's members; otherwise every user.
Activity
The activity log records who changed what, through which interface. The per-issue, per-page and per-plan feeds are on Issues, pages and plans.
GET /api/projects/{id}/activity
The project's activity, newest first. Viewer. Query: limit (default 50, at most 200) and offset.
Returns items and has_more. Each item has:
id,tsactor_user_id,actor_username,actor_display_name(null once the account is gone) andactor_is_bottransport:web,mcp,api,cliorsystementity_type,entity_id,entity_label, andproject_id,issue_id,page_idwhere they applyaction: such ascreate,update,delete,attach,detach,linkorunlinkfield,old_valueandnew_valuefor a field change
GET /api/projects/{id}/activity/actors
Totals per actor in the project. Viewer. Each entry has actor_user_id, username, display_name, is_bot, actions, last_ts and top_transport (the interface they used most).
GitHub import
POST /api/projects/{id}/import/github
Copies a GitHub repository's issues and their comments and labels into the project, or previews what would be copied. Lead. The request body may be at most 16 KiB.
Body:
| Field | Default | Meaning |
|---|---|---|
repo | required | owner/name. |
token | none | A GitHub token, needed for private repositories and higher rate limits. It is used for this request only and not stored. |
state | all | Which GitHub issues to read: open, closed or all. |
map_open | backlog | Lific status for open GitHub issues. |
map_closed | done | Lific status for closed GitHub issues. |
dry_run | false | Count what would be created and write nothing. |
Each imported issue records its origin in source (such as github:owner/name#12), so running the import again skips issues it already brought in.
Returns dry_run, issues_created, issues_skipped_existing, comments_created, labels_created, comments_planned, labels_planned, skipped_non_issues (pull requests), skipped_assignees and skipped_other.
One import runs per project at a time and four across the instance; another request meanwhile is 409. A repository past Lific's import limits is 413, with the limit named.
Accounts and access
Instance settings, signing in and out, sessions, API keys, connected tools and user administration over REST.
Issues, pages and plans
Create, update, link, delete and restore issues; park issues on a person or a date; write comments; manage pages, plans, modules, labels and folders; read each item's activity.