Lific
REST API

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.

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:

  • weeks
  • created_per_week and closed_per_week: arrays of week_start and count
  • status_counts: the issue-count object above
  • priority_counts: urgent, high, medium, low, none and total
  • module_counts: module_id (null for no module), name and count
  • top_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, ts
  • actor_user_id, actor_username, actor_display_name (null once the account is gone) and actor_is_bot
  • transport: web, mcp, api, cli or system
  • entity_type, entity_id, entity_label, and project_id, issue_id, page_id where they apply
  • action: such as create, update, delete, attach, detach, link or unlink
  • field, old_value and new_value for 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:

FieldDefaultMeaning
reporequiredowner/name.
tokennoneA GitHub token, needed for private repositories and higher rate limits. It is used for this request only and not stored.
stateallWhich GitHub issues to read: open, closed or all.
map_openbacklogLific status for open GitHub issues.
map_closeddoneLific status for closed GitHub issues.
dry_runfalseCount 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.

On this page