ProjectLayer

Documentation

Everything ProjectLayer does, in one place


Setup, concepts, the REST API, and how the Claude Code and SupportLayer integrations work.

Overview

ProjectLayer is a project management tool built for development teams and the agencies who build for clients. It provides Kanban boards and Scrum, tasks with epics, subtasks, blockers and time logging, milestones, labels, test management, an Intake queue fed by clients, Sentry and SupportLayer, a branded client portal with release acceptance testing, reports, notifications, a full activity log, a REST API with webhooks, and Claude-assisted planning.

Quick start

  1. Create an account — Register here. Your first seat is free.
  2. Verify your email — Check your inbox for a verification link and click it to activate your account.
  3. Create a project — Give it a name and a prefix (e.g. API). This prefix is used in task keys like API-42.
  4. Add tasks — Create tasks with titles, descriptions, priorities, assignees, due dates, and labels.
  5. Use the board — Every project gets a default Kanban board with To Do, In Progress, and Done columns. Move tasks between columns.
  6. Create milestones — Set deadlines and track progress toward releases or goals.
  7. Invite your team — Go to Team Members in the admin sidebar and send email invitations with a chosen role.
  8. Invite a client (optional) — Under Clients, add the client, tick their projects and invite their contacts to the client portal.

Key concepts

  • Organization — Your workspace. All projects, members, and settings belong to an organization. A user can belong to multiple organizations.
  • Project — Groups related tasks, boards, milestones, labels, and intake items. Each project has a unique prefix for task keys.
  • Task — An individual work item with a key (e.g. API-42), status, type, priority, assignee, due date, labels, and comments.
  • Board — A Kanban view with ordered columns. Tasks are placed in columns and moved between them.
  • Milestone — A target date or goal. Assign tasks to a milestone to track progress.
  • Label — A color-coded tag for categorizing tasks within a project.
  • Intake — A holding area for items that haven't been promoted to tasks yet. Items can arrive from the API (e.g. SupportLayer) or be converted to tasks.
  • Access group — A role that defines what a member can do. Default groups: Administrator, Member, Viewer, Tester, Test Manager. You can add your own.
  • Client — A customer of your organization. Its contacts sign in to the client portal and see only the projects linked to that client. Contacts are not members and never take a seat.

Projects

Projects are the top-level container for all work. Each project has:

  • Name and prefix — The prefix (e.g. WEB) is used in task keys like WEB-15.
  • Description — Optional project description visible on the overview page.
  • Owner — An optional team member who owns the project.
  • Status — Active, Completed, or Archived. Archived projects are hidden from the main list.

From a project's overview page, you can navigate to Tasks, Board, Intake, Milestones, Sprints and Labels (plus Team on Scrum projects) using the tabbed sub-navigation.

Projects can be edited or archived from the project overview page.

Tasks & filtering

Tasks are the core work items. Each task includes:

  • Task key — Auto-generated from the project prefix (e.g. API-42).
  • Status — Open, In Progress, Testing, On Hold, Done, or Closed. Change inline on the task detail page.
  • Type — Epic, User story, Feature, Bug, Task, Investigation, or Idea. Default: Task. See Epics.
  • Epic — The epic the task belongs to, if any. Subtasks use their parent's epic.
  • Priority — Low, Medium, High, or Critical.
  • Assignee — A team member responsible for the task. Change inline.
  • Due date — Optional deadline. Set at creation or change inline.
  • Labels — Color-coded tags from the project's label set.
  • Description — Detailed information about the work.
  • Comments — Threaded discussion on each task. Type @ to mention a teammate.
  • Story points, estimated hours and attachments — Optional, set on the task page.

Subtasks, blockers and watchers

  • Subtasks — Add from the task's Subtasks section (needs task.create). A subtask can't have subtasks of its own.
  • Blocked by — Link the tasks that must finish first (same project, needs task.edit). Links that would create a loop are refused. A task with open blockers shows a Blocked badge and can't be set to Done or Closed, dropped into a board's done column, or signed off after testing.
  • Watchers — Use Watch on any task. You start watching automatically when you create a task, are assigned it, comment, or add a subtask. Watchers get comment and status notifications.
  • @mentions — Mention someone by the part of their email before the @ (e.g. @ada for ada@example.com). They get an in-app notification and an email.

Time logging

Log hours from the task page (needs task.edit): decimal hours such as 1.5, up to 1000 per entry, with an optional date and note. The page shows the total against the task's estimate. You can delete only your own entries.

Testing a task

Each task can carry a test script: plain-language steps, each with what to do and what should happen (up to 50 steps).

  1. Write the steps in the task's Testing section and choose Save test steps (needs task.edit).
  2. Choose Send for testing. The task moves to Testing and a numbered round starts. Steps are locked while it's in Testing.
  3. A tester (needs task.test) marks each step Pass or Fail. A fail needs a note: the round ends, the task goes back to In Progress and a comment records what happened.
  4. When every step passes, Mark as Done signs the task off. Open blockers, and on Scrum projects the Definition of Done, still apply.

Moving a task out of Testing any other way abandons the round. Earlier rounds are listed under Previous rounds. The same flow is available over the API. For tests you reuse across releases, see Test management; for testing by your client, see Client portal.

Import and export (CSV)

  • Export — Export CSV on a project's task list downloads task_key, title, status, type, priority, assignee_email, milestone, story_points, due_date, estimated_hours, created_at, epic. Closed tasks are left out. The Reports page has Export all tasks and Export all intake, which add a project_slug column. The Intake page has its own export.
  • Import — Import tasks on the task list (needs task.create): upload a CSV of up to 2 MB and 1000 rows, then map its columns. Only the title is required. You can map status, type, priority, assignee email, milestone name, story points, due date, estimated hours and epic key. Values that can't be used become warnings (an unknown status imports as Open, an unknown assignee as unassigned).
  • Your own data — Download my data on the Account page exports your profile, the tasks you reported or are assigned, and your comments.

Closed tasks are listed on the project's Archive tab.

Filtering tasks

The task list includes a filter toolbar. You can filter by:

  • Text search — Matches against task titles. (The search box at the top of every page also searches descriptions and comments; see Search.)
  • Status — Show only tasks in a specific status.
  • Type — Show only tasks of a specific type.
  • Priority — Show only tasks of a specific priority.
  • Assignee — Show tasks assigned to a specific member, or show unassigned tasks.
  • Epic — Show only the items in one epic, or the items that belong to no epic.

Filters are applied via URL query parameters, so filtered views can be bookmarked or shared.

Deleting tasks

Tasks can be permanently deleted from the task detail page. This requires the task.delete permission.

Epics

An epic is a container for a large body of work. The hierarchy is Epic → item (user story, task, bug and so on) → subtask.

  • Create one with New epic on the project's Epics tab, or by choosing the Epic type on a new task.
  • Add work to it by choosing the epic when you create a task, from the Epic field on any task, or with Add item on the epic. Subtasks follow their parent's epic.
  • Progress on the Epics tab and on the epic itself shows items done out of total, and story points done out of total. Subtasks are not counted separately, so points are never counted twice.
  • Epics are containers, not planned work. They never appear on the board, in sprints or on the Product Backlog; their items do. An epic cannot belong to another epic or have subtasks of its own.
  • Closing: an epic's status is set by a person. It cannot be marked Done or Closed while any of its items is unfinished; the error lists them. An epic that still contains items cannot be changed to another type, and a task cannot become an epic while it has a parent, subtasks, an epic or a sprint.

Kanban boards

Every project comes with a default Kanban board containing three columns: To Do, In Progress, and Done.

Tasks can be moved between columns using the move buttons on each task card. Moving a task to a "complete" column (like Done) automatically sets its status to Done.

Tasks not assigned to any column appear in a separate "No Column" section on the board, with buttons to move them into any column.

Milestones

Milestones represent releases or goals with optional due dates.

  • Create milestones from the Milestones tab within a project.
  • Assign tasks to milestones to track progress.
  • Open or close milestones to mark them as complete.
  • The milestone list shows the number of associated tasks.

Labels

Labels are color-coded tags for categorizing tasks within a project.

  • Create labels from the Labels tab — pick a name and color.
  • Assign labels to tasks using checkboxes on the task detail page.
  • Labels appear as colored pills on both the task list and task detail views.

Intake

Intake is a holding area for work items that haven't been promoted to full tasks.

  • Items can arrive via the Intake API (e.g. from SupportLayer).
  • Each item shows its source, priority, and a link back to the originating ticket.
  • Accept — Mark as approved for future work.
  • Reject — Decline the item.
  • Convert to task — Create a full task from the intake item, with the title, description, and priority carried over. Sentry items become bugs.
  • Sentry items can be resolved in Sentry from here, and show as resolved when Sentry resolves them.
  • Accept, reject and convert are also available from the Intake API, so Claude Code can triage Intake for you.

Scrum mode

Every project starts in Kanban mode. To run Scrum, open the project, choose Edit, and under Way of working pick Scrum and choose a Product Owner. You can switch back to Kanban at any time; your team and roles are kept for if you switch back.

The Scrum team

The Team tab lists who is on the project's Scrum team and their roles:

  • Product Owner — exactly one per project. Owns the Product Backlog and accepts the Increment.
  • Scrum Master — at most one. Facilitates the ceremonies and can manage the team.
  • Developers — any number. Own the Sprint Backlog.

One person can hold several roles, but the Product Owner can't also be the Scrum Master. Anyone active in your organisation can join, including clients in the Tester group, so a client can be your Product Owner. Being on the team doesn't change what someone can see; their access group still decides that.

Actions that belong to a role can only be done by the person holding it, or by an administrator as an override. Anyone who can edit the project, and the Scrum Master, can manage the team. If a member is suspended, their roles stop counting until they're reactivated; a project whose Product Owner is suspended shows a warning until an admin assigns a new one.

Ceremonies and sprint length

Under Way of working you can also choose which ceremonies the team holds (Sprint Planning, Daily Scrum, Sprint Review and Sprint Retrospective, all on by default) and the usual sprint length (1–4 weeks, 2 by default).

Product Backlog and Product Goal

On a Scrum project the Backlog tab is the Product Backlog: every unfinished top-level task that isn't in a planned or active sprint, in the order the Product Owner chooses. New tasks join at the bottom. The Product Owner drags tasks into order or uses the move buttons; everyone else sees the order. Subtasks travel with their parent.

The Product Goal at the top of the Backlog is the team's next long-term objective. There is one at a time: the Product Owner marks it achieved or abandoned before setting the next, and past goals are kept.

Sprint Backlog and Sprint Goal

Developers and the Product Owner pull tasks into a planned or active sprint from the Backlog, or with Add from backlog on the sprint page, and write the Sprint Goal there. Removing a task from a sprint puts it back where it was on the Backlog. A completed sprint's tasks can't change.

Definition of Done

The Scrum team keeps its Definition of Done on the Team tab: a short checklist every task must meet. On a Scrum project with a Definition of Done, marking a task Done — from the task page, the board or a test sign-off — asks you to confirm every item, and the task page records who confirmed them. Closing a task doesn't need it. Reopening a Done task clears the confirmations. Tasks imported as Done arrive In Progress until someone confirms them.

Increment

Each Scrum sprint's page shows its Increment: the sprint's finished tasks that met the Definition of Done, with their story points, plus an optional version or tag and release note. Finished tasks that were never confirmed against the Definition of Done are listed separately.

Delivery Flow

Delivery Flow is an opt-in alternative to Scrum for teams that don't want to work in sprints. Each feature becomes its own phase that moves through fixed stages with a gate before Design and another before Release. Squads own the work and run one active phase at a time. Bugs follow their own flow, with P1–P4 priorities, SLA targets counted in working days, a regression test and a patch release for every fix, and verification with whoever reported it. Kanban, Scrum and Delivery Flow projects can exist side by side.

Turning it on

  1. An administrator (needs delivery.manage) opens Delivery Flow in the admin sidebar, ticks Use Delivery Flow in this organisation, and sets the organisation's timezone and holidays. The Delivery section (Squads, Triage, Priority review) then appears in the sidebar for everyone with delivery.view.
  2. Create at least one squad under Squads and give its members their roles.
  3. Open a project, choose Edit, and under Way of working pick Delivery Flow and a home squad. The project's tabs swap Sprints for Phases, Bugs and Releases, and priorities show as P1–P4.

A project can leave Delivery Flow only while it has no active phase. The organisation switch can't be turned off while any project still uses Delivery Flow; switch those projects back to Kanban or Scrum first. While it is off, the Delivery pages return Not found and the Delivery Flow API answers 409.

Squads, roles and responsibilities

A squad is an organisation-wide team that can work across several projects. It has a name, a colour, a description and a bug capacity: the share of its developers' and testers' time set aside for bugs (20% by default). A squad can be archived, except while it runs an active phase, is a project's home squad or is the Platform squad. Roles are given per squad, a person can hold several, and they only count while the person's membership is active:

RoleTypical responsibility
PDM (product delivery manager)Owns priorities across the queue, the Ready gate, overrides
Project ManagerEstimates, forecasts, releases, re-forecasting
Business AnalystWrites the spec and acceptance criteria
Squad ownerRuns the squad day to day, moves phases through Design and Build, overrides
DeveloperBuilds the scope, ships patch releases
TesterTests the phase, sends it back to Build when it fails
Test LeadAgrees the test approach
3rd line supportTriages bugs and verifies fixes with the reporter

Being in a squad is simply its membership; there is no separate "core team" role. What someone may do to a phase or bug comes from their roles in the squad that owns it (for a bug, its triaged squad, else the project's home squad):

ActionRoles
Create a phase, edit the specBusiness Analyst, Squad owner (a Project Manager may also create)
Sign spec_signed_offBusiness Analyst
Discover → PrioritiseSquad owner, Business Analyst
Estimate, rank, forecast, sign estimate_agreedProject Manager, PDM
Sign test_approach_agreedTest Lead
Pass the Ready gate, pull the next phasePDM, Project Manager
Set platform impactSquad owner
Approve a platform changePlatform squad owner, PDM
Design → Build, Build → DesignSquad owner
Build → Test & UATSquad owner, Developer
Test & UAT → BuildTester, Test Lead, Squad owner
Pass the Done gateSquad owner, Project Manager, PDM
Release notes, record the release, close, retroProject Manager, Squad owner, PDM
Override a gate or close rule, cancel, parkSquad owner, PDM
Triage3rd line support, Squad owner
Patch releaseSquad owner, Developer, Project Manager
Verify with the reporterProject Manager, 3rd line support
Resume a stopped lineSquad owner

People who are consulted or informed get notifications instead: entering a stage notifies the roles responsible for it.

The Platform squad (optional)

If shared platform code is looked after by one team, an administrator can mark that squad as the Platform squad in the Delivery Flow settings (the default is None, which is a normal setup). Each Delivery Flow project then has a Platform review toggle, off by default. Only when both are set does the project:

  • ask "Platform change needed?" in Design, and need the Platform squad's owner or a PDM to approve a change before Build;
  • include platform_impact_known in its Ready gate;
  • offer "Platform bug → Platform squad" in Triage;
  • consult the Platform squad on platform changes and put it on call for P1s.

Otherwise none of these steps appear. Clearing the Platform squad turns platform review off for every project at once; turning it off mid-phase means a pending approval no longer blocks the phase.

Phases, stages and gates

Each phase has its own Feature task (its description is the spec, its test steps are the acceptance criteria) and its own milestone, so UAT rounds, test cycles and "raise defect" work as usual. Phases are listed on the project's Phases tab and queued on the squad page.

StageKindLeaves when
DiscoverqueueThe feature has at least one acceptance criterion
PrioritisequeueThe Ready gate passes, there is a forecast, and the squad has no other active phase
DesignactiveThe Squad owner moves it on (with platform review: the platform question is answered and any change approved)
BuildactiveThe Squad owner or a Developer moves it to Test & UAT
Test & UATactiveThe Done gate passes
ReleaseactiveA feature release is recorded and retro notes are written
Closed / Cancelledfinished—

A squad has at most one active phase across all its projects. Test & UAT → Build counts a defect loop, Build → Design is allowed, and parking sends an active phase back to Prioritise to free the squad. A phase can be split while it is queued; an estimate over 30 working days shows a warning suggesting a split.

  • Ready gate: spec_signed_off (Business Analyst), estimate_agreed (Project Manager or PDM), test_approach_agreed (Test Lead), and platform_impact_known only with platform review. Sign-offs are given, and can be withdrawn, while the phase is in Discover or Prioritise.
  • Done gate (all automatic): the feature's latest test run passed and all scope is finished; at least one test case marked automated covers a phase task; no open P1 or P2 bug in the phase; release notes are written; and, when the project has a client, the latest UAT round is signed off.

A gate blocks until its criteria are met. The Squad owner or a PDM can override it with a written reason; the unmet criteria are recorded and audited. Forecast changes keep a history with a reason, and the client can optionally be told.

Bug priorities, SLAs and working days

PriorityTask priorityRespond withinFix within
P1Critical1 hour24 hours (calendar time); stops the line
P2HighSame working day2 working days
P3Medium2 working days10 working days
P4Low—Next phase (no clock)

These are the defaults. An administrator can override them for the organisation in the Delivery Flow settings, and anyone who can edit a project can override them for that project (SLA on the project), for example to match a customer contract. The clock starts when the bug is reported; triage counts as the response, and changing the priority recalculates both dates from the report time.

Working days are Monday to Friday minus the organisation's holidays, in its timezone. "N working days" ends at 23:59 on the Nth working day after the report day (the report day is day 0 if it's a working day; otherwise the next working day is). Editing holidays or the timezone recalculates open bugs. The SLA monitor notifies once when 75% of a window has gone and once at breach.

Triage, regression tests and verification

  • Every bug in a Delivery Flow project, including converted intake items, lands in Triage with its origin (customer, support, monitoring, testing, internal or in-phase). 3rd line support or the Squad owner sets the priority, the squad and the assignee. A P4 is bundled into a queued phase or the squad's P4 pool, and a bug that is really a feature can become a Feature with a new Discover phase.
  • A bug raised from a phase in Build or Test & UAT (a failed test run, test cycle or UAT step) stays in that phase as an in-phase defect, with no SLA; the Done gate covers it.
  • Closing a P1–P3 bug needs a regression test: a non-draft test case linked to it (Create regression test builds one from the bug).
  • Bugs from a customer, support or monitoring must be verified with the reporter before they close; verifying posts to the client conversation when there is one.
  • The Squad owner or a PDM can override either rule with a reason. In-phase defects are exempt.
  • Starting new phase work while the squad has an open, unassigned P2 asks for confirmation and is audited; it doesn't block.

Patch releases and stopping the line

A release is a record (version, notes, date and the tasks it shipped); nothing is deployed. A feature release ships a phase in its Release stage. A patch release (Releases → New patch release) ships bug fixes independently of phases and asks for verification where it's needed.

Triaging a P1 stops the line: the squad's active phase is paused, its stage moves are blocked and a banner shows on the squad and phase pages (tasks can still be edited). The squad and its PDMs are notified, plus the Platform squad when the project uses platform review. The line resumes on its own once every open P1 has shipped in a patch release, or the Squad owner resumes it by hand; the paused time is recorded and the Project Manager is asked to re-forecast. When bug work goes over a squad's capacity, the Project Managers and PDMs are likewise asked to re-forecast, once per phase. The forecast never changes on its own.

Stand-up, priority review and measures

  • Stand-up (on each squad page): the active phase and its stage, the gate ahead, the forecast against today, P1–P3 bugs with their countdowns, blockers and what changed since the previous working day. It updates live.
  • Priority review (Delivery → Priority review): every squad's queue (rankable), capacity, open bugs by age against their targets, at-risk and breached bugs, and forecast slips.
  • Phase review: the phase page at Release shows the release, forecast history against the actual date, defect loops and retro notes.
  • Measures: the Reports page has a Delivery section, and each squad page shows the same measures for that squad: lead time per closed phase (calendar and working days, from Discover and from activation to release, with stopped-line pauses left out exactly, so a pause over a weekend costs no working days), forecast accuracy (first and last forecast against the release date, slip in working days), time spent in each stage, open bugs by priority and working-day age at the end of the date range (or today), bugs reaching customers per month, and the share of fixes made within target.

Reports

The Reports page (needs report.view or settings.manage) provides an overview of your organization's work:

  • Summary stats — Total tasks, overdue count, active tasks, completed tasks.
  • Tasks by status — Bar chart showing the distribution of tasks across statuses.
  • Tasks by type — Bar chart showing type distribution.
  • Tasks by priority — Bar chart showing priority distribution.
  • Tasks by project — Count of tasks per project.
  • Team workload — Bar chart showing how many open tasks each team member has.
  • Cycle and lead time, throughput, sprint velocity, burndown (for the active sprint) and completed by member.

When Delivery Flow is switched on, a Delivery section adds lead time per closed phase, forecast accuracy, time in each stage, open bugs by priority and age, bugs reaching customers per month and the share of fixes within target (see Delivery Flow measures). It is included in the print view and the CSV export.

The page can be printed, and has Export all tasks and Export all intake CSV downloads.

Scheduled reports

Under Scheduled reports in the admin sidebar (needs settings.manage), send a summary by email weekly or monthly to one address per report (yours by default). It includes total and overdue tasks, tasks completed in the period, logged time, average cycle time, average velocity and tasks by status, plus a short Delivery Flow summary (phases released, average lead time, bugs reaching customers and fixes within target) when Delivery Flow is on. Reports can be paused or deleted.

Notifications

Notifications appear under the bell at the top of every page, and are also emailed. In-app notifications are always created; each person chooses which emails they get on the Profile & Notifications page.

EventWho is notifiedPreference
Task assigned to youThe new assigneeWhen a task is assigned to me
New comment on taskAssignee and reporter (excluding the comment author)When someone comments on my task
Task status changedAssignee, reporter and watchersWhen a task status changes
Task due soonThe assigneeWhen a task I own is due soon
You're @mentioned in a commentThe person mentionedAlways sent
A client sends a request or message, or fails or signs off a releaseEveryone with client.communicateAlways sent

Watchers also get comment notifications. Notifications link straight to the task or conversation. Client contacts get their own updates in the client portal and by email, which they can turn off.

Activity log

Every task has a full activity log visible on its detail page, below the comments section. The log records:

  • Task creation (including creation from intake items)
  • Status changes (with old and new values)
  • Type changes
  • Priority changes
  • Assignee changes
  • Due date changes
  • Comments added

It also records milestone, epic and description changes, attachments, blockers, time logged, test steps and results, Plan with Claude runs, client visibility changes and failed client acceptance tests. Each entry shows the actor, the action, and the timestamp.

Organization-wide events (sign-ins, settings changes, invitations, role changes, API keys, webhooks, clients, test cycles, release sign-offs) are listed in the Audit log in the admin sidebar (needs settings.manage).

Test management

Beyond each task's own test script, every project has a Tests tab (needs test.view) for tests you keep and rerun.

  • Test cases (create and edit with test.manage) have a key such as API-T3, a title, preconditions, priority, a status (Draft, Ready or Deprecated), an optional suite and linked task, and up to 50 steps. Copy to test case on a task turns its test script into a Ready case.
  • Suites group cases. Deleting a suite leaves its cases unfiled.
  • Test cycles (manage with test.cycle_manage) plan a round of testing, optionally tied to a milestone or sprint: add cases, assign each run to someone who can run tests, start, then complete. Unfinished runs are marked Skipped when a cycle completes, and completed cycles are read-only.
  • Runs (with test.execute) record Pass or Fail per step; a fail needs a note. A run can also be marked Blocked (with a reason) or Skipped. From a failed run, Raise bug creates a Bug task listing the failed steps (also needs task.create).

Test Management in the sidebar (needs test.view_all) shows every project's tests, pass rates, failing tests, active cycles and tasks waiting to be tested, plus the runs assigned to you.

The search box at the top of every page searches projects (name and description), tasks (title and description) and comments in your organization. Type at least two characters; up to 20 results are shown per group.

Boards, task lists, the Intake page and its badge, the comments and test results on a task, the notification bell, client conversations and client portal pages all update live when something changes, without reloading. If you're part-way through writing in a conversation, you're offered a reload instead so your draft isn't lost.

Team management

Manage your organization's members from Admin → Team Members.

  • View members — See name, email, role, and status for every member.
  • Change roles — Use the role dropdown on any member to change their access group instantly.
  • Suspend members — Temporarily revoke access. Suspended members cannot log in to the organization.
  • Reactivate members — Restore access for a suspended member.

You cannot suspend the last administrator — at least one admin must remain active.

Inviting members

Invite new members from the Team Members page:

  1. Enter the invitee's email address.
  2. Select a role (Administrator, Member, Viewer, Tester, or Test Manager).
  3. Click Send invite.

The invitee receives an email with a link to join. If they already have a ProjectLayer account, they sign in with it and click Accept invitation to join your organization. If not, they are prompted to create an account and are added upon completion.

Invitations expire after 7 days.

Roles & permissions

Every member belongs to one access group within an organization. Five default groups are created with each organization:

RolePermissions
Administrator All permissions — full control over projects, tasks, boards, intake, settings, and user management.
Member View, create and edit projects; full task management (create, edit, assign); test tasks and run test cases; board and intake management; client conversations. Cannot access settings or manage users.
Viewer View projects, tasks and tests only. Cannot create, edit, or assign.
Tester Can view projects and tasks, run task tests, and run test cases in a project's Tests section. Ideal for QA staff. (Clients test in the client portal instead.)
Test Manager Everything a Tester can do, plus writing test cases and suites, planning and completing test cycles, and the cross-project Test Management view.

Every member takes a seat, whatever their group (see Billing). Custom access groups can be created from Access groups in the admin sidebar (needs settings.manage): give the group a name and a machine key (lowercase with underscores, fixed once created) and tick any combination of permissions. Groups can be renamed and their permissions changed later; change a member's group from Team members.

PermissionDescription
project.viewView projects and everything inside them (board, backlog, tasks, tests, milestones, sprints), plus search, reports and exports
project.createCreate new projects
project.editEdit project details, manage milestones and labels
project.deleteDelete projects
task.view_ownView only the tasks assigned to you or that you created (without task.view_all); applies to lists, the board, backlog, search, reports, exports and the Delivery Flow pages and measures
task.view_allView all tasks in a project
task.createCreate new tasks
task.editEdit task details, move on board
task.assignAssign tasks to team members
task.commentPost comments and edit your own comments on tasks you can see
task.testRun a task's test steps, record Pass/Fail and sign off tested work
task.deleteDelete tasks
test.viewSee a project's Tests section: test cases, suites, cycles and run history
test.manageCreate, edit and delete test cases and suites; copy a task's test steps into a test case
test.executeRun test cases: record Pass/Fail per step, mark runs blocked or skipped, raise bugs from failures
test.cycle_manageCreate test cycles, add cases, assign runs, start and complete cycles
test.view_allOpen Test Management to see tests and cycles from every project in one place
board.manageManage board columns
backlog.manageManage intake: accept, reject and convert intake items
settings.manageAdmin settings: access groups, branding, API keys, AI / Claude, webhooks, scheduled reports, Sentry, the audit log; also opens Reports
report.viewOpen the Reports page, its print view and CSV exports
user.manageInvite, suspend, and manage team members
client.manageCreate clients, link their projects, invite and disable client contacts
client.communicateRead and reply to client conversations and requests (Client inbox)
uat.managePublish releases for client acceptance testing, waive items, withdraw rounds
delivery.viewDelivery Flow pages: squads, phases, bugs, releases, triage, the stand-up, the priority review and the Delivery measures; the Delivery Flow API. What you may do there comes from your squad roles. Granted to every built-in group
delivery.manageDelivery Flow settings: the on/off switch, timezone, holidays, default SLA targets and the Platform squad; create, edit and archive squads and set their members' roles. Administrators only by default (project-level SLA targets need project.edit)

Client portal

Your customers get their own area at /client, in your workspace's colours. There they can send requests, talk to your team, follow progress and test releases. They never see your boards, sprints, internal descriptions, comments or assignees.

  • Set up a client in Settings → Clients (needs the client.manage permission): name it, tick the projects that are for them, and invite their contacts by email. A project belongs to one client. Contacts never take a seat.
  • Contacts are not team members. They can't open the staff portal or use the API. The same email can't be both a team member and a client contact in one workspace.
  • Requests a client sends land in the project's Intake with the client's name and who asked. Declining one needs a reason, which the client sees. Converting one keeps the new task visible to the client.
  • Conversations: each request (and the task it becomes), each client-visible task, and each project has a conversation. Reply from the Client inbox (needs client.communicate). Choose Reply to client or Add internal note; internal notes and their files are never shown to the client. Task comments are always internal.
  • What the client sees of a task is set in the task's Client panel: whether it's shown, and a plain-language summary used instead of the internal description. Statuses are simplified: Received, Under review, Planned, In progress, On hold, Ready for your testing, Changes requested, Done, Declined. Contacts are emailed when one of theirs changes (they can turn email off).
  • Release testing (UAT): on a milestone, open Client testing (needs uat.manage), choose the tasks and publish. Every task needs test steps, written so the client can follow them. The client marks each step passed or failed, with a note and optional screenshot. A failure adds a comment to the task and notifies the team, but never changes the task's status: reopen the task or waive the item, then publish a new round. A contact allowed to approve releases signs off once every item has passed or been waived.

Registration

Create an account at the registration page. You'll provide your name, email, password, and organization name. A new organization is created automatically with you as the administrator.

Five default access groups (Administrator, Member, Viewer, Tester, Test Manager) are provisioned with your organization.

Email verification

After registration, you'll receive a verification email. Click the link to verify your email address. You must verify before you can sign in.

If you didn't receive the email, use the resend verification page.

Password reset

If you've forgotten your password:

  1. Go to Forgot password from the sign-in page.
  2. Enter your email address.
  3. Check your inbox for a reset link (valid for 1 hour).
  4. Click the link and set a new password (minimum 8 characters).

Two-factor authentication

Turn on two-factor sign-in from Profile & Notifications → Set up two-factor authentication:

  1. Scan the QR code with an authenticator app and enter the 6-digit code it shows. Nothing changes until a valid code is entered.
  2. Save the 8 recovery codes. They're shown only once, and each works once, in place of an app code.

After that you're asked for a code once per session. To turn it off, enter a current code or a recovery code.

Branding

Customize the portal appearance from Branding in the admin sidebar (needs settings.manage):

  • Logo — PNG, JPEG or WebP, up to 1 MB. Shown in the sidebar and across the client portal, including the page clients accept their invitation on. A wide logo about 40px tall works best. Tick Remove the current logo to go back to the default.
  • Primary colour — Used for the sidebar, buttons, and links. Enter a hex value (e.g. #3d2f7c).
  • Accent colour — Used for highlights and secondary actions.

Leave fields empty to use ProjectLayer's default colours. Your colours are also used in the client portal and on buttons in emails. Changes take effect immediately. Custom domains aren't available.

Billing

ProjectLayer uses simple per-seat pricing:

  • Free plan — 1 seat included. No credit card required.
  • Pro plan — 1 free seat + additional paid seats at $15 USD / £12 GBP / €15 EUR / A$22 AUD / NZ$25 NZD per seat per month.

All features are available on both plans. There are no feature gates.

Who takes a seat: every active member of your organization, whatever their access group: administrators, members, viewers, testers, test managers and custom groups. Suspended members don't count. Client contacts never take a seat, however many you invite. An invitation can't be accepted, and a suspended member can't be reactivated, while every seat is in use.

Manage your subscription from Account & Plan. Pro plan customers can manage billing, change payment methods, and adjust seat counts via the Stripe billing portal.

API Authentication

The ProjectLayer API uses Bearer token authentication. Generate API keys from API keys in the admin sidebar (needs settings.manage): give the key a name and choose Read & write or Read only. The full key is shown once; the list shows each key's prefix and when it was last used, and keys can be revoked. A key acts as the person who created it: every call needs the same permission as the matching action in the app (for example task.create to create a task, task.comment to comment), checked against the creator's current access group, so changing their group changes what the key can do. A call they aren't allowed to make returns 403 with error code forbidden. The key stops working if they're suspended or leave. Each key may make up to 600 requests per minute; responses carry X-RateLimit-Limit and X-RateLimit-Remaining headers, and a key over the limit gets 429 (error code rate_limited) with a Retry-After header giving the seconds to wait. Download the OpenAPI spec for a complete, machine-readable description of every endpoint.

Include the key in the Authorization header:

Authorization: Bearer pl_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

API keys are scoped to your organization. All requests made with a key will only see and modify data within that organization.

Scopes

Each key is created with one of two access levels:

ScopeCan do
Read & writeAll GET requests, plus POST/PATCH/PUT/DELETE
Read onlyOnly GET/HEAD/OPTIONS requests

A request that exceeds a key's scope returns 403 Forbidden:

{
  "error": {
    "code": "insufficient_scope",
    "message": "This API key lacks the 'write' scope."
  }
}

Keys created before scopes existed have no scope recorded, which is treated as full (read & write) access.

Base URL

https://your-projectlayer-domain.com/api/v1/

Pagination

Every list endpoint accepts ?page (default 1) and ?per_page (default 25, clamped to 1–100) and returns a meta block alongside data:

{
  "data": [ ... ],
  "meta": { "page": 1, "per_page": 25, "total": 137, "total_pages": 6 }
}

Error responses

Errors return a JSON object with an error field:

{
  "error": {
    "code": "authentication_failed",
    "message": "Invalid or revoked API key."
  }
}

API — Projects

List projects

GET /api/v1/projects

Returns a page of active projects in your organization (see Pagination).

Response:

{
  "data": [
    {
      "id": 1,
      "name": "API Platform",
      "slug": "api-platform",
      "prefix": "API",
      "status": "active"
    }
  ],
  "meta": { "page": 1, "per_page": 25, "total": 1, "total_pages": 1 }
}

Get a project

GET /api/v1/projects/{id}

Returns the project with its Scrum settings and team. mode is kanban or scrum; product_owner and scrum_master are null when nobody active holds the role. A member's active is false while they are suspended, and their roles don't count.

{
  "id": 1,
  "name": "API Platform",
  "slug": "api-platform",
  "prefix": "API",
  "status": "active",
  "mode": "scrum",
  "ceremonies": {
    "planning": true,
    "daily_scrum": true,
    "review": true,
    "retrospective": false
  },
  "default_sprint_length_days": 14,
  "product_owner": { "id": 4, "name": "Sam Owner" },
  "scrum_master": { "id": 7, "name": "Alex Master" },
  "team": [
    { "user": { "id": 4, "name": "Sam Owner" }, "roles": ["product_owner"], "active": true },
    { "user": { "id": 7, "name": "Alex Master" }, "roles": ["scrum_master", "developer"], "active": true }
  ]
}

Update a project's way of working

PATCH /api/v1/projects/{id}

Requires the write scope, and the key's creator needs permission to edit projects. Only the fields present are applied, and the response is the updated project (same shape as above).

FieldTypeDescription
modestringkanban, scrum or delivery_flow (see Delivery Flow)
home_squad_idintegerDelivery Flow only: the project's home squad. Needed when switching to Delivery Flow without one.
platform_reviewbooleanDelivery Flow only: turn Platform review on or off (on needs a Platform squad).
product_owner_idintegerUser to make Product Owner. Required when switching a project to Scrum without an active Product Owner; on a project already in Scrum it reassigns the Product Owner. Only valid for Scrum projects: sending it for a Kanban project returns 422.
ceremoniesobjectPartial object of booleans: planning, daily_scrum, review, retrospective
default_sprint_length_daysinteger7, 14, 21 or 28

Errors: 400 for bad types or values, 422 validation_failed for rule violations (switching to Scrum without a Product Owner, a sprint length other than 7, 14, 21 or 28, or a user id that is unknown or not in your organisation), and 403 forbidden.

Manage the Scrum team

GET /api/v1/projects/{id}/team
PUT /api/v1/projects/{id}/team/{userId}
DELETE /api/v1/projects/{id}/team/{userId}
PUT /api/v1/projects/{id}/product-owner
  • GET .../team returns {"team": [...]}, using the member shape shown above.
  • PUT .../team/{userId} takes {"roles": ["developer", "scrum_master"]} (values: product_owner, scrum_master, developer), sets that person's roles, and returns the member.
  • DELETE .../team/{userId} removes the person and returns 204 No Content.
  • PUT .../product-owner takes {"user_id": 4}, reassigns the Product Owner, and returns the project.

Writes need the write scope, and the key's creator must be able to edit the project or be its Scrum Master. Errors: 409 on Kanban projects and when removing the Product Owner (assign a new one instead), 422 for rule violations (including a user id that is unknown or belongs to another organisation), 404 when deleting someone who isn't on the team, and 403 forbidden.

API keys act as the user who created them: Scrum role checks use that user's role and membership.

API — Scrum artifacts

These endpoints cover a Scrum project's Product Backlog, Product Goals and Definition of Done, plus each sprint's goal, tasks and Increment. Reads need the read scope; writes need the write scope. API keys act as the user who created them, so the role checks below use that person's Scrum role. Every endpoint answers 409 on a project that isn't using Scrum.

Product Backlog

GET  /api/v1/projects/{id}/backlog?limit=&offset=
POST /api/v1/projects/{id}/backlog/move

GET returns the Product Backlog in order. limit is 1–100 (100 by default) and offset starts at 0. rank_position counts from 1 across the whole Backlog.

{
  "items": [
    {
      "rank_position": 1,
      "task": {
        "id": 12,
        "task_key": "PORT-12",
        "title": "Add CSV export",
        "status": "open",
        "type": "feature",
        "priority": "high",
        "story_points": 3,
        "assignee": null
      }
    }
  ],
  "total": 1,
  "total_points": 3
}

POST .../backlog/move is for the Product Owner only. Body: {"task_id": 12, "after_task_id": null}. The task goes directly after after_task_id, or to the top when it is null. The response is the Backlog again, in the shape above.

Product Goals

GET  /api/v1/projects/{id}/product-goals
POST /api/v1/projects/{id}/product-goals
POST /api/v1/projects/{id}/product-goals/{goalId}/close

GET returns {"current": {...} | null, "history": [...]}. Each goal looks like this:

{
  "id": 5,
  "text": "Customers can self-serve their invoices",
  "status": "active",
  "set_by": { "id": 4, "name": "Sam Owner" },
  "set_at": "2026-09-01T09:30:00+00:00",
  "closed_at": null
}

POST .../product-goals takes {"text": "…"}, is for the Product Owner only, and returns 201 with the new goal, or 409 when a goal is already active. POST .../product-goals/{goalId}/close takes {"status": "achieved"} or {"status": "abandoned"}, is for the Product Owner only, and returns the closed goal.

Definition of Done

GET /api/v1/projects/{id}/definition-of-done
PUT /api/v1/projects/{id}/definition-of-done

GET returns {"criteria": [{"id": 3, "text": "Code reviewed", "position": 1}]}.

PUT replaces the whole ordered list and needs a member of the Scrum team. Body:

{"criteria": [{"id": 3, "text": "Code reviewed"}, {"text": "New item"}]}

Send an id to keep and reword an existing item, and leave it out to add a new one; items you leave out of the list are removed. An id that isn't one of this project's current items returns 404, and a repeated id returns 422. The response is the new list.

Sprints

GET    /api/v1/sprints/{id}
PATCH  /api/v1/sprints/{id}
PUT    /api/v1/sprints/{id}/tasks/{taskId}
DELETE /api/v1/sprints/{id}/tasks/{taskId}
PATCH  /api/v1/sprints/{id}/increment

Kanban projects have sprints too, but these endpoints only serve Scrum projects: a sprint on a Kanban project returns 409 for every request, including GET. The writes need a Developer or the Product Owner.

  • PATCH .../sprints/{id} takes {"goal": "…"} (or null to clear it).
  • PUT .../sprints/{id}/tasks/{taskId} pulls a task from the Backlog into the sprint. DELETE on the same path puts it back where it was on the Backlog and returns 204 No Content. A completed sprint's tasks can't change (409). A task that's already finished, already in another sprint or not in this sprint is refused with 422 or 409 and a message saying why.
  • PATCH .../sprints/{id}/increment takes {"version": "v2.4.0", "release_note": "…"}. Either field is optional, and null clears it.

Every sprint call except DELETE returns the sprint:

{
  "id": 8,
  "project_id": 1,
  "name": "Sprint 8",
  "status": "active",
  "start_date": "2026-09-14",
  "end_date": "2026-09-28",
  "goal": "Ship invoice export",
  "tasks": [ { "id": 12, "task_key": "PORT-12", "title": "Add CSV export", "status": "done", "type": "feature", "priority": "high", "story_points": 3, "assignee": null } ],
  "increment": {
    "version": "v2.4.0",
    "release_note": "Invoices can now be exported.",
    "updated_at": "2026-09-28T16:00:00+00:00",
    "points": 3,
    "included_task_ids": [12],
    "excluded_task_ids": []
  }
}

included_task_ids are the finished tasks that met the Definition of Done; excluded_task_ids are finished tasks that never were confirmed against it. points adds up the story points of the included tasks.

Completing tasks with a Definition of Done

When a Scrum project has a Definition of Done, moving a task to done means confirming every current item. Send the item ids as dod_confirmed on any of these:

PATCH /api/v1/tasks/{id}                                 {"status": "done", "dod_confirmed": [3, 4]}
PATCH /api/v1/tasks/{id}/status                          {"status": "done", "dod_confirmed": [3, 4]}
POST  /api/v1/tasks/{id}/test-runs/current/sign-off      {"dod_confirmed": [3, 4]}

If any current id is missing, the task isn't changed and the response is 422, listing what still needs confirming:

{
  "error": {
    "code": "dod_not_confirmed",
    "message": "Confirm every Definition of Done item before marking this task Done.",
    "details": { "criteria": [ { "id": 3, "text": "Code reviewed" } ] }
  }
}

dod_confirmed must be a list of integers, otherwise the response is 400. Closing a task doesn't need it. Other errors from PATCH|POST /api/v1/tasks/{id}/status keep their existing shape, {"error": "message"}; only this refusal and a malformed dod_confirmed (400) use the structured {"error": {"code", "message"}} form. GET /api/v1/tasks/{id} now also returns backlog_rank_position (the task's place on the Product Backlog, or null when it isn't on it) and done_checks:

"done_checks": [
  {
    "criterion_id": 3,
    "text": "Code reviewed",
    "confirmed_by": { "id": 7, "name": "Alex Master" },
    "confirmed_at": "2026-09-28T15:45:00+00:00"
  }
]

Errors

These endpoints return {"error": {"code": "…", "message": "…"}} with:

  • 404 not_found — the project, sprint, task, goal or Definition of Done item doesn't exist in your organization.
  • 400 invalid_request — the body isn't in the shape shown.
  • 422 validation_failed — the request breaks a Scrum rule; the message says which.
  • 409 conflict — the request clashes with the current state, including "This project isn't using Scrum." on Kanban projects.
  • 403 forbidden — the key's creator doesn't hold the needed role; the message names who can act.

API — Tasks

List tasks

GET /api/v1/tasks

Returns a page of tasks in your organization, most recently updated first (see Pagination).

Query parameterTypeDescription
has_planbooleanWhen 1 (or true), returns only tasks that have a Claude build plan, most recently planned first.
project_idintegerRestrict to a single project. Can be combined with has_plan. An unknown ID returns 404.
typestringOne type, or several separated by commas (for example epic or user_story,bug). An unknown value returns 400.
epic_idintegerThe items in that epic. 0 returns items that belong to no epic.
page, per_pageintegerSee Pagination.

Response:

{
  "data": [
    {
      "id": 42,
      "task_key": "API-42",
      "title": "Add CSV export",
      "project_id": 1,
      "status": "in_progress",
      "type": "bug",
      "priority": "high",
      "story_points": 3,
      "epic": { "id": 40, "task_key": "API-40", "title": "Checkout" },
      "has_plan": true,
      "plan_updated_at": "2026-07-05T13:55:00+00:00"
    }
  ],
  "meta": { "page": 1, "per_page": 25, "total": 1, "total_pages": 1 }
}

Get a task

GET /api/v1/tasks/{id}

Returns a single task, including its description and full Claude build plan. claude_plan and plan_updated_at are null when no plan has been generated. Returns 404 if the task is not found in your organization.

Response:

{
  "id": 42,
  "task_key": "API-42",
  "title": "Add CSV export",
  "project_id": 1,
  "status": "in_progress",
  "type": "bug",
  "priority": "high",
  "has_plan": true,
  "plan_updated_at": "2026-07-05T13:55:00+00:00",
  "description": "Export the report as CSV.",
  "claude_plan": "1. Add the export endpoint...\n2. Stream rows...\n3. Verify with a functional test."
}

Claude build plans: the claude_plan field holds the development plan generated by the "Plan with Claude" button on a task. Use GET /api/v1/tasks?has_plan=1 to discover which tasks already have a plan, then fetch each one to read it.

Update a task's status

PATCH /api/v1/tasks/{id}/status

Updates the status of a task — for example, to mark it in_progress when work begins or done when it is completed. Automation such as a Claude Code skill can call this to reflect build progress. POST is also accepted for clients that cannot send PATCH.

Request body:

{
  "status": "in_progress"
}
FieldTypeRequiredDescription
statusstringYesopen, in_progress, done, or closed.

Setting the status to done or closed stamps the task's completion time; moving it back to open or in_progress clears it. The change is recorded in the task's activity log and notifies the assignee and reporter. An unknown status returns 400; a task not found in your organization returns 404.

Response: the updated task (same shape as Get a task), for example:

{
  "id": 42,
  "task_key": "API-42",
  "title": "Add CSV export",
  "project_id": 1,
  "status": "in_progress",
  "type": "bug",
  "priority": "high",
  "has_plan": true,
  "plan_updated_at": "2026-07-05T13:55:00+00:00",
  "description": "Export the report as CSV.",
  "claude_plan": "1. Add the export endpoint..."
}

Create a task

POST /api/v1/tasks

Requires the write scope. Allocates the next task number for the project, the same way the "New task" form does.

FieldTypeRequiredDescription
project_idintegerYesTarget project ID. An unknown ID returns 404.
titlestringYesTask title
descriptionstringNoDescription
prioritystringNolow, medium, high, or critical. Default: medium
typestringNoepic, user_story, feature, bug, task, investigation, or idea. Default: task. An unrecognised value returns 400 (invalid_request).
assignee_idintegerNoMust be an active member of your organization
milestone_idintegerNoMust belong to the same project
story_pointsintegerNo0 to 10000. null clears it on update.
epic_idintegerNoThe epic this task belongs to: a task of type epic in the same project. null or 0 removes it on update. An unknown id returns 400; breaking an epic rule (an epic inside an epic, a subtask choosing its own epic) returns 409.
claude_planstringNoBuild plan in Markdown. Sets plan_updated_at when provided.

Response (201 Created): the created task (same shape as Get a task). Invalid input returns 400. Task details also carry epic_progress (items_total, items_done, points_total, points_done) on an epic, and null on anything else. An epic cannot be marked done or closed while any item is unfinished (409).

Update a task

PATCH /api/v1/tasks/{id}

Requires the write scope. Partial update: only fields present in the body are changed. Accepts title, description, priority, type, status, assignee_id, milestone_id, and claude_plan (same rules as create). Setting assignee_id or milestone_id to null clears it. Writing claude_plan sets the task's build plan (Markdown), stamps plan_updated_at, and clears any in-progress or errored planning state; sending null or an empty string clears the plan.

Response: the updated task. Invalid input returns 400; not found in your organization returns 404.

Delete a task

DELETE /api/v1/tasks/{id}

Requires the write scope. Returns 204 No Content on success, 404 if not found in your organization.

Task attachments

GET /api/v1/tasks/{taskId}/attachments
GET /api/v1/tasks/{taskId}/attachments/{id}/download

Read-only: uploading and deleting task files stays in the app. The list is paginated, oldest first, and includes files moved from a converted intake item. Each entry has id, task_id, filename, mime_type, size (bytes), is_image, uploaded_by, created_at and download_url; the download returns the file with its original filename.

API — Testing

Every task can carry a plain-language test script that a non-technical tester can run in the task page, marking each step Pass or Fail. Sending a task to the testing status opens a numbered round with a copy of the script; a failed step sends the task back to in_progress with a comment, and when every step passes someone signs the round off, which marks the task done. Earlier rounds are kept as history.

Get or replace the script

GET /api/v1/tasks/{id}/test-steps
PUT /api/v1/tasks/{id}/test-steps

PUT requires the write scope and replaces every step. Body: {"steps": [{"instruction": "Open Invoices and click Export", "expected_result": "A CSV file downloads"}]}. Up to 50 steps, each field up to 2000 characters. Returns 409 while the task is in testing and 422 for invalid steps.

Send for testing

PATCH /api/v1/tasks/{id}/status   {"status": "testing"}

Returns 409 if the task has no test steps. While a task is in testing, setting it to done or closed through the API also returns 409: finish the round with a sign-off instead.

Rounds and results

GET   /api/v1/tasks/{id}/test-runs
GET   /api/v1/tasks/{id}/test-runs/{number|current}
PATCH /api/v1/tasks/{id}/test-runs/current/results/{resultId}   {"result": "pass"}
PATCH /api/v1/tasks/{id}/test-runs/current/results/{resultId}   {"result": "fail", "note": "Nothing downloaded"}
POST  /api/v1/tasks/{id}/test-runs/current/sign-off

result is pass, fail (a note is required) or pending (undo). Sign-off needs every step passed and no open blockers. Writes need the write scope; outside an open round they return 409. GET /api/v1/tasks/{id} also returns test_steps, current_test_run and test_run_count, and GET /api/v1/tasks?status=testing lists tasks waiting to be tested (status also takes a comma-separated list, such as open,in_progress). Every task includes last_test_outcome, the result of its latest round: failed on an in_progress task means a tester found a problem.

Test cases

GET    /api/v1/projects/{id}/test-cases[?task_id=&suite_id=&status=&automated=true|false]
POST   /api/v1/projects/{id}/test-cases
GET    /api/v1/test-cases/{id}
PATCH  /api/v1/test-cases/{id}
DELETE /api/v1/test-cases/{id}
POST   /api/v1/tasks/{id}/test-cases

Standalone test cases from a project's Tests section. Reading needs project.view and test.view; creating, editing and deleting need test.manage and the write scope. A case has id, key, title, preconditions, priority, status (draft, ready or deprecated), suite, task, automated, automation_ref (where the automated test lives, such as a file or CI job), step_count and latest_execution; one case adds steps. POST takes title and any of preconditions, priority, status, suite_id, task_id, steps ([{"instruction", "expected_result"}]), automated and automation_ref; PATCH takes any of them, keeps the rest, and replaces the whole script when steps is sent. Changing the steps of a case that is being run, or deleting it, returns 409. POST /api/v1/tasks/{id}/test-cases copies the task's test script into a new ready case linked to it (409 when the task has no steps). In Delivery Flow, automated cases linked to a phase's tasks satisfy the Done gate's “automated tests added” check. A linked task the key's user can't see shows as null.

API — Comments

List a task's comments

GET /api/v1/tasks/{taskId}/comments

Returns the task's comments, oldest first, paginated (see below). 404 if the task isn't found in your organization.

Response:

{
  "data": [
    {
      "id": 9,
      "task_id": 42,
      "author": "Ada Lovelace",
      "author_type": "user",
      "body": "Looks good to me.",
      "created_at": "2026-07-05T09:12:00+00:00"
    }
  ],
  "meta": { "page": 1, "per_page": 25, "total": 1, "total_pages": 1 }
}

Create a comment

POST /api/v1/tasks/{taskId}/comments

Requires the write scope. body is required and is HTML-sanitized the same way the web comment form is (scripts and event handler attributes are stripped). A body that's empty after sanitizing returns 400.

Response (201 Created): the created comment (same shape as above, a single object rather than a list).

API — Client messages

A task's client conversation. The API key's user needs the client.communicate permission, and the task's project must have a client (otherwise 404).

List

GET /api/v1/tasks/{taskId}/client-messages

Returns client, client_visible and data: every message, oldest first, each with id, side (client, staff or system), internal, author, body and created_at. Internal notes are included and flagged.

Post

POST /api/v1/tasks/{taskId}/client-messages
{ "body": "Deployed to staging.", "internal": false }

Requires the write scope. internal defaults to true, so nothing reaches the client unless you send "internal": false. Replying to the client on a task that isn't client-visible returns 409.

Tasks also carry client_visible (in lists and detail) and client_summary (in detail); both can be set with PATCH /api/v1/tasks/{id}. Intake items carry client, requested_by and client_decline_reason; declining a client's request needs a body of {"reason": "..."}.

API — Milestones

List milestones

GET /api/v1/milestones?project_id=1
GET /api/v1/projects/{projectId}/milestones

project_id is optional on the first form; both forms return the same shape, paginated.

Response:

{
  "data": [
    {
      "id": 3,
      "project_id": 1,
      "name": "v1.0",
      "description": null,
      "due_date": "2026-08-01",
      "status": "open"
    }
  ],
  "meta": { "page": 1, "per_page": 25, "total": 1, "total_pages": 1 }
}

Get a milestone

GET /api/v1/milestones/{id}

Response: a single milestone (same shape as above). 404 if not found in your organization.

Create a milestone

POST /api/v1/milestones

Requires the write scope.

FieldTypeRequiredDescription
project_idintegerYesTarget project ID
namestringYesMilestone name
descriptionstringNoDescription
due_datestringNoYYYY-MM-DD
statusstringNoopen or closed. Default: open

Response (201 Created): the created milestone.

Update / delete a milestone

PATCH /api/v1/milestones/{id}
DELETE /api/v1/milestones/{id}

Both require the write scope. PATCH applies only the fields present in the body and returns the updated milestone. DELETE returns 204 No Content.

API — Intake

The endpoint path stays /api/v1/backlog for compatibility. Intake items arrive from SupportLayer, from Sentry, or from this API — for example a “Report a problem” screen in your own web or mobile app. Reads need the read scope; creating, accepting, rejecting and converting need write, and accepting, rejecting and converting also need the key owner to have the backlog.manage permission.

Create an intake item (report an issue)

POST /api/v1/backlog

Requires the write scope and the key owner's task.create permission. Send JSON, or multipart/form-data to include files (see Attachments below).

Request body:

{
  "project_id": 1,
  "title": "Customer reports broken CSV export",
  "description": "Details from the support ticket...",
  "priority": "high",
  "source_service": "supportlayer",
  "source_ticket_id": 1842,
  "source_ticket_url": "https://support.example.com/portal/tickets/1842"
}

A report from an app, naming the app and its signed-in user:

{
  "project_id": 1,
  "title": "Login button does nothing",
  "description": "Tapped Log in twice, nothing happened.",
  "source_service": "acme-mobile",
  "app_name": "Acme Mobile",
  "app_version": "2.4.1 (iOS 18.1)",
  "reporter_id": "u-42",
  "reporter_name": "Sam Lee",
  "reporter_email": "sam@example.com"
}
FieldTypeRequiredDescription
project_idintegerYesTarget project ID
titlestringYesIntake item title
descriptionstringNoDetailed description
prioritystringNolow, medium, high, or critical. Default: medium
source_servicestringNoSource system identifier. Default: supportlayer
source_ticket_idintegerNoExternal ticket ID (used for deduplication)
source_ticket_urlstringNohttp(s) URL back to the source ticket
app_namestringNoThe app or product the report came from, up to 100 characters
app_versionstringNoIts version (and platform, if useful), up to 50 characters
reporter_idstringNoThe reporting user's id in your app, up to 191 characters
reporter_namestringNoTheir name, up to 200 characters
reporter_emailstringNoTheir email; must be a valid address
attachments[]filesNoMultipart requests only. See below

The app and reporter fields are free text you supply: they describe a user of your app and are not linked to anyone in ProjectLayer. They show on the Intake page, and converting the item adds a line such as “Reported in Acme Mobile 2.4.1 (iOS 18.1) by Sam Lee <sam@example.com> (user id u-42)” to the task's description. A field that is too long or not a string returns 400.

Attachments

To attach screenshots, logs or other files, send the same fields as multipart/form-data with up to 5 files in attachments[]. Each file can be up to 10 MB and must be one of: jpg, jpeg, png, gif, webp, pdf, txt, csv, md, doc, docx, xls, xlsx, ppt, pptx, zip. If any file is refused, the response is 400 naming the file, and nothing is created.

curl -X POST https://your-portal.example.com/api/v1/backlog \
  -H "Authorization: Bearer pl_live_..." \
  -F project_id=1 \
  -F "title=Login button does nothing" \
  -F "app_name=Acme Mobile" \
  -F "app_version=2.4.1 (iOS 18.1)" \
  -F reporter_id=u-42 \
  -F "reporter_email=sam@example.com" \
  -F "attachments[]=@screenshot.png" \
  -F "attachments[]=@device-log.txt"

The files are listed in the item's attachments. When the item is converted they move to the new task, where the task attachment endpoints list and serve them. Files on client-portal requests stay with the client conversation instead.

Mobile and other client-side apps: don't build an API key into an app you ship. Anyone can extract it, and the key carries every permission of the person who created it. Have the app send its report to your own server, and have the server call this endpoint with the key. If the app must call ProjectLayer directly, create the key from a dedicated account with the least access that can still create intake items in the project, and be ready to revoke it.

Response (201 Created):

{
  "id": 15,
  "status": "pending"
}

Duplicate detection: If an intake item already exists for the same source_service + source_ticket_id, the API returns 409 Conflict with the existing item's ID and status.

Get an intake item

GET /api/v1/backlog/{id}

Response:

{
  "id": 15,
  "title": "Customer reports broken CSV export",
  "description": "Details from the support ticket...",
  "status": "pending",
  "priority": "high",
  "project_id": 1,
  "source_service": "supportlayer",
  "source_ticket_id": 1842,
  "source_external_id": null,
  "source_ticket_url": "https://support.example.com/portal/tickets/1842",
  "converted_task_id": null,
  "created_at": "2026-04-10T14:30:00+00:00",
  "client": null,
  "requested_by": null,
  "client_decline_reason": null,
  "updated_at": "2026-04-10T14:30:00+00:00",
  "app_name": "Acme Mobile",
  "app_version": "2.4.1 (iOS 18.1)",
  "reporter": { "id": "u-42", "name": "Sam Lee", "email": "sam@example.com" },
  "attachments": [
    {
      "id": 31,
      "filename": "screenshot.png",
      "mime_type": "image/png",
      "size": 48213,
      "is_image": true,
      "created_at": "2026-04-10T14:30:00+00:00",
      "download_url": "https://your-portal.example.com/api/v1/backlog/15/attachments/31/download"
    }
  ]
}

source_external_id holds non-numeric source ids, such as a Sentry issue id. reporter is null when no reporter field was sent.

Download an intake attachment

GET /api/v1/backlog/{id}/attachments/{attachmentId}/download

Returns the file with its original filename; use the download_url from the item. As in the app, the key owner needs backlog.manage or client.communicate, otherwise the response is 403.

List intake items

GET /api/v1/backlog?project_id=1&status=pending

Both query parameters are optional. status is pending, accepted, rejected, converted or resolved (resolved at the source, e.g. in Sentry, before anyone acted on it). Returns items newest first, paginated (see below). List rows include app_name, app_version and reporter, but leave out description and attachments; fetch the item to read them.

Accept or reject an intake item

POST /api/v1/backlog/{id}/accept
POST /api/v1/backlog/{id}/reject

Only a pending item can be accepted or rejected; otherwise the response is 409. Returns the updated item.

Convert an intake item to a task

POST /api/v1/backlog/{id}/convert

Works on a pending or accepted item. The body is optional; any field you send overrides what the item carries.

{
  "title": "Checkout crashes when the basket is empty",
  "description": "<p>Steps to reproduce...</p>",
  "priority": "high",
  "type": "bug",
  "assignee_id": 7,
  "milestone_id": 3
}
FieldTypeDescription
titlestring1–500 characters. Default: the item's title
descriptionstringHTML, sanitized like a task description. Default: the item's text, with a link back to its source and the app/reporter line
prioritystringlow, medium, high or critical. Default: the item's priority
typestringepic, user_story, feature, bug, task, investigation or idea. Default: bug for Sentry items, otherwise task
assignee_idintegerAn active member of your organization
milestone_idintegerA milestone of the item's project
epic_idintegerAn epic in the item's project to file the new task under (not with type epic)
story_pointsinteger0 to 10000

Response (200): the intake item (now converted) plus the new task:

{
  "id": 15,
  "status": "converted",
  "converted_task_id": 88,
  "task": {
    "id": 88,
    "task_key": "API-42",
    "project_id": 1,
    "title": "Checkout crashes when the basket is empty",
    "status": "open",
    "type": "bug",
    "priority": "high"
  }
}

Converting an item that was already converted returns 409 with its converted_task_id, so a retry never creates a second task. An invalid field returns 400 and leaves the item untouched. The new task is reported by, and the conversion is recorded against, the API key's user. The item's files move to the task, unless it is a client-portal request.

API — Delivery Flow

Delivery Flow endpoints need delivery.view (plus project.view for project-scoped ones, and task.edit for triage and verify), and answer 409 while Delivery Flow is switched off. Writes need the write scope, and actions are checked against the squad roles of the key's creator (403 otherwise). Project ids are numeric. Errors use the structured shape {"error": {"code", "message"}}; a rule that isn't met (an unmet gate, a paused phase, a wrong stage) is 422.

Squads

GET    /api/v1/squads[?include_archived=1]
POST   /api/v1/squads
GET    /api/v1/squads/{id}
PATCH  /api/v1/squads/{id}
POST   /api/v1/squads/{id}/archive
POST   /api/v1/squads/{id}/restore
PUT    /api/v1/squads/{id}/members/{userId}
DELETE /api/v1/squads/{id}/members/{userId}
GET    /api/v1/squads/{id}/queue
POST   /api/v1/squads/{id}/queue/rank
POST   /api/v1/squads/{id}/pull-next
POST   /api/v1/squads/{id}/resume
GET    /api/v1/squads/{id}/bugs

The list is paginated. Each squad has id, name, slug, colour, description, bug_capacity_percent, is_platform_squad, line_stopped, line_stopped_at, archived and member_count. One squad adds members (user, roles, active) and capacity: bug_minutes, total_minutes, share (percent, or null with no time logged), limit, over, from and to over the last 10 working days.

Creating, editing, archiving and restoring squads and setting members need delivery.manage, and answer with the squad. POST takes name and optional colour (#RRGGBB), description and bug_capacity_percent (0–100, default 20); PATCH takes any of them and keeps the rest. PUT …/members/{userId} takes {"roles": ["owner", "developer"]} and replaces that person's roles (they must be an active member of the organisation); roles are pdm, project_manager, business_analyst, owner, developer, tester, test_lead and third_line. Archiving is refused (422) while the squad has an active phase, is a project's home squad or is the Platform squad; an archived squad can't be edited until it is restored.

GET …/queue returns active, prioritise and discover (phases in queue order), next_candidate_id, line_stopped and p2_warning. POST …/queue/rank takes phase_id and after_phase_id (null for the top of its stage) and needs the Project Manager or PDM role. POST …/pull-next takes optional override_reason and acknowledge_p2, needs the PDM or Project Manager role, answers with the pulled phase, and returns 409 p2_waiting while a P2 bug waits unless acknowledged. POST …/resume restarts a stopped line (Squad owner). GET …/bugs lists the open triaged P1–P3 bugs (soonest fix-due first) as queue and the P4 pool as p4_pool, each with its Delivery Flow fields and sla_status (on_track, at_risk, breached or none); only tasks the key's user can see are listed.

Projects

PATCH /api/v1/projects/{id} also accepts mode: "delivery_flow" with home_squad_id, and platform_review (needs a Platform squad).

Phases

GET  /api/v1/projects/{id}/phases
POST /api/v1/projects/{id}/phases
GET  /api/v1/phases/{id}
POST /api/v1/phases/{id}/advance
POST /api/v1/phases/{id}/regress
GET  /api/v1/phases/{id}/forecasts
POST /api/v1/phases/{id}/forecasts
POST /api/v1/phases/{id}/platform-approval
POST /api/v1/phases/{id}/signoffs
DELETE /api/v1/phases/{id}/signoffs/{criterion}
  • POST .../phases takes {"title"} or {"feature_task_id"} and creates the phase in Discover.
  • A phase includes its stage, forecast, gates (each criterion met or not), scope, forecast history and stage log.
  • advance takes an optional override_reason (Squad owner or PDM) to pass an unmet gate. regress takes {"to": "build" | "design" | "prioritise", "reason"}.
  • forecasts takes {"date", "reason", "note", "notify_customer"}; signoffs takes {"criterion", "note"}.

Releases

GET  /api/v1/projects/{id}/releases
POST /api/v1/projects/{id}/releases

Post {"kind": "feature", "phase_id"} or {"kind": "patch", "task_ids": [...]}, with an optional version (defaults to the next semantic version) and notes.

Bugs

POST /api/v1/tasks/{id}/triage
POST /api/v1/tasks/{id}/verify

triage takes {"priority", "squad_id", "assignee_id", "platform_bug"} (platform_bug only with platform review). verify takes an optional note. Task payloads carry p_label, squad, phase, bug_stage, bug_origin, triaged_at, response_due_at, fix_due_at, verified_at and release. Closing a P1–P3 bug without a regression test or verification returns 422 bug_close_blocked; a Squad owner or PDM can send override_reason.

The P2 pickup warning

While a squad has an open P2 bug no one is assigned to, requests that start new phase work return 409 p2_waiting: POST /phases/{id}/advance from Prioritise, and moving a phase task to in_progress with PATCH /tasks/{id} or PATCH|POST /tasks/{id}/status. Repeat the request with "acknowledge_p2": true to go ahead; that is audited.

Claude Code

Plan a task with Claude, then let Claude Code build it. Press Plan with Claude on a task and Claude — reading your connected GitHub repo for context — writes a build plan onto the task. Claude Code, running in your own terminal, then discovers planned tasks over the REST API, reads the plan, implements the work, and updates the task's status as it goes.

Install the plugin

The skills that drive this loop are published as a Claude Code plugin marketplace at github.com/XegenLtd/Claude-Skills. Add it from inside Claude Code:

/plugin marketplace add XegenLtd/Claude-Skills

Then install the ProjectLayer skills from that marketplace and authenticate with a ProjectLayer API token (see API Authentication).

How the loop uses the API

  • GET /api/v1/tasks?has_plan=1 — discover tasks that already have a Claude build plan.
  • GET /api/v1/tasks/{id} — read a task's full claude_plan.
  • PATCH /api/v1/tasks/{id}/status — mark the task in_progress while building.
  • PUT /api/v1/tasks/{id}/test-steps — when the code is finished, write plain-language test steps for a non-technical tester, then set the status to testing.
  • A person (for example someone in the Tester group) runs the round on the task page. Only if you ask it to does Claude run the steps itself, record them with PATCH …/test-runs/current/results/{resultId} and POST …/sign-off. Anything it records is attributed to the API key's creator.
  • When a step fails, Claude finds the task with GET /api/v1/tasks?status=in_progress and last_test_outcome failed, fixes it, and sets testing again.

Plan with Claude

Set it up under AI / Claude in the admin sidebar (needs settings.manage):

  • Anthropic API key — your own; Claude usage is billed to it. Without a key, tasks show a Set up Claude link instead.
  • GitHub access token (optional) — needed for private repos; public repos work without it.
  • Model — Always the latest Claude Opus (the default), Fable, Sonnet or Haiku, or pin a specific model. If a pinned model is retired, the organization moves to the newest model in that family and admins are notified.
  • Plan with Claude switch — untick to hide the button everywhere without removing the key. The key and token can also be removed.

Connect a repository per project under the project's Edit page (owner, repo name and optional branch). On a task, Plan with Claude (needs task.edit) writes the plan into Claude's Build Plan; only one run per task at a time. If Claude has questions, it asks them in a comment: reply in that thread and choose Send replies to Claude to continue.

Webhooks

Send task events to your own systems from Webhooks in the admin sidebar (needs settings.manage). Enter an http or https URL and choose events. Private, internal and loopback addresses are refused.

EventWhen
task.createdA task is created
task.updatedA field changes (the payload names the field)
task.status_changedThe status changes
task.commentedA comment is added
task.movedThe task moves board column (the payload names the column)
phase.stage_changedA Delivery Flow feature phase changes stage (the payload is the phase, with from_stage and to_stage; sent for web and API changes alike)
phase.releasedA Delivery Flow phase's feature release is recorded (the payload is the phase, with the release id and version)
release.publishedA Delivery Flow feature or patch release is recorded (the payload is the release, with the tasks it shipped)
bug.triagedA Delivery Flow bug is triaged, in the web app or through the API (the task payload plus p_label, squad, bug_stage, response_due_at, fix_due_at and platform_bug)
bug.sla_breachedA Delivery Flow bug misses its SLA, found by the SLA monitor (sent once per bug; the task payload plus the Delivery Flow fields, sla_window (response or fix) and due_at)

Each delivery is a POST with a JSON body {"event", "occurred_at", "data": {id, task_key, title, status, type, priority, project, …}} and the headers X-ProjectLayer-Event, X-ProjectLayer-Delivery and X-ProjectLayer-Signature: sha256=…, an HMAC-SHA256 of the raw body using the signing secret. The secret is shown once, when you create the webhook. Any response other than 2xx within 10 seconds counts as a failure, and a delivery is tried up to 3 times. Deliveries shows the last 20 attempts with their responses.

Note: webhooks fire for changes made in the web app. Changes made through the REST API don't send webhooks.

Sentry Integration

New Sentry issues arrive in a project's Intake as pending items, ready to accept, reject or convert into bug tasks. Each project has its own connection.

  1. Open the project's Intake page and choose Sentry (needs the settings.manage permission), then Generate webhook URL.
  2. In Sentry go to Settings → Integrations → Custom Integrations and create an Internal Integration. Set its Webhook URL to the generated URL, give it Read access to Issue & Event, subscribe it to Issue events and save.
  3. Copy the integration's Client Secret back into the project's Sentry page and choose Connect.

Every request is verified with the Client Secret. A new issue creates one Intake item (duplicates are ignored); an issue that regresses after being resolved reopens its item as pending with a note. Sentry's level sets the priority: fatal is critical, error high, info and debug low. Pause or disconnect the integration at any time; existing items are kept.

Resolving issues both ways

Sentry to ProjectLayer. When an issue is resolved in Sentry, an Intake item nobody has acted on becomes Resolved (with the release, if Sentry names one). If the item was already turned into a task, the task gets a Sentry comment and its assignee and reporter are notified; the task itself is left for a person to verify and close. An issue that regresses reopens its item as pending.

ProjectLayer to Sentry. Give the integration Read & Write access to Issue & Event, then paste its Token in the project's Sentry settings (step 4). A Resolve in Sentry button then appears on Sentry items in Intake, and the issue is resolved automatically when its task is marked Done, however that happens (portal, API or tester sign-off). Regional or self-hosted Sentry can set its own URL. The token is stored encrypted and never shown again; without it the connection stays inbound-only.

SupportLayer Integration

ProjectLayer integrates with SupportLayer to convert support tickets into intake items automatically.

How it works

  1. Generate an API key in ProjectLayer (Settings → API Keys).
  2. Configure the key in your SupportLayer instance under Integrations.
  3. When a support agent clicks "Send to ProjectLayer" on a ticket, SupportLayer calls the POST /api/v1/backlog endpoint.
  4. The ticket appears in your ProjectLayer Intake with a link back to the original ticket.
  5. Your team reviews Intake and converts accepted items into tasks.

Intake statuses

StatusMeaning
pendingNewly arrived, awaiting review
acceptedApproved by the team, not yet a task
rejectedDeclined — won't be worked on
convertedPromoted to a full task (linked to the new task)

Deduplication

If SupportLayer sends the same ticket twice (same source_service + source_ticket_id), the API returns 409 Conflict instead of creating a duplicate. The response includes the existing intake item's ID and current status.