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
- Create an account — Register here. Your first seat is free.
- Verify your email — Check your inbox for a verification link and click it to activate your account.
- Create a project — Give it a name and a prefix (e.g.
API). This prefix is used in task keys likeAPI-42. - Add tasks — Create tasks with titles, descriptions, priorities, assignees, due dates, and labels.
- Use the board — Every project gets a default Kanban board with To Do, In Progress, and Done columns. Move tasks between columns.
- Create milestones — Set deadlines and track progress toward releases or goals.
- Invite your team — Go to Team Members in the admin sidebar and send email invitations with a chosen role.
- 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 likeWEB-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.
@adafor 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).
- Write the steps in the task's Testing section and choose Save test steps (needs
task.edit). - Choose Send for testing. The task moves to Testing and a numbered round starts. Steps are locked while it's in Testing.
- 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. - 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 aproject_slugcolumn. 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.
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.
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. 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.
| Event | Who is notified | Preference |
|---|---|---|
| Task assigned to you | The new assignee | When a task is assigned to me |
| New comment on task | Assignee and reporter (excluding the comment author) | When someone comments on my task |
| Task status changed | Assignee, reporter and watchers | When a task status changes |
| Task due soon | The assignee | When a task I own is due soon |
| You're @mentioned in a comment | The person mentioned | Always sent |
| A client sends a request or message, or fails or signs off a release | Everyone with client.communicate | Always 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 asAPI-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 needstask.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.
Search & live updates
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:
- Enter the invitee's email address.
- Select a role (Administrator, Member, Viewer, Tester, or Test Manager).
- Click Send invite.
The invitee receives an email with a link to join. If they already have a ProjectLayer account, they are automatically added to 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:
| Role | Permissions |
|---|---|
| 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.
| Permission | Description |
|---|---|
project.view | View projects |
project.create | Create new projects |
project.edit | Edit project details, manage milestones and labels |
project.delete | Delete projects |
task.view_own | View tasks assigned to you |
task.view_all | View all tasks in a project |
task.create | Create new tasks |
task.edit | Edit task details, move on board |
task.assign | Assign tasks to team members |
task.test | Run a task's test steps, record Pass/Fail and sign off tested work |
task.delete | Delete tasks |
test.view | See a project's Tests section: test cases, suites, cycles and run history |
test.manage | Create, edit and delete test cases and suites; copy a task's test steps into a test case |
test.execute | Run test cases: record Pass/Fail per step, mark runs blocked or skipped, raise bugs from failures |
test.cycle_manage | Create test cycles, add cases, assign runs, start and complete cycles |
test.view_all | Open Test Management to see tests and cycles from every project in one place |
board.manage | Manage board columns |
backlog.manage | Manage intake: accept, reject and convert intake items |
settings.manage | Admin settings: access groups, branding, API keys, AI / Claude, webhooks, scheduled reports, Sentry, the audit log; also opens Reports |
report.view | Open the Reports page, its print view and CSV exports |
user.manage | Invite, suspend, and manage team members |
client.manage | Create clients, link their projects, invite and disable client contacts |
client.communicate | Read and reply to client conversations and requests (Client inbox) |
uat.manage | Publish releases for client acceptance testing, waive items, withdraw rounds |
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.managepermission): 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:
- Go to Forgot password from the sign-in page.
- Enter your email address.
- Check your inbox for a reset link (valid for 1 hour).
- 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:
- Scan the QR code with an authenticator app and enter the 6-digit code it shows. Nothing changes until a valid code is entered.
- 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, and stops working if they're suspended or leave. 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:
| Scope | Can do |
|---|---|
| Read & write | All GET requests, plus POST/PATCH/PUT/DELETE |
| Read only | Only 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).
| Field | Type | Description |
|---|---|---|
mode | string | kanban or scrum |
product_owner_id | integer | User 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. |
ceremonies | object | Partial object of booleans: planning, daily_scrum, review, retrospective |
default_sprint_length_days | integer | 7, 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 .../teamreturns{"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 returns204 No Content.PUT .../product-ownertakes{"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": "…"}(ornullto clear it).PUT .../sprints/{id}/tasks/{taskId}pulls a task from the Backlog into the sprint.DELETEon the same path puts it back where it was on the Backlog and returns204 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 with422or409and a message saying why.PATCH .../sprints/{id}/incrementtakes{"version": "v2.4.0", "release_note": "…"}. Either field is optional, andnullclears 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 parameter | Type | Description |
|---|---|---|
has_plan | boolean | When 1 (or true), returns only tasks that have a Claude build plan, most recently planned first. |
project_id | integer | Restrict to a single project. Can be combined with has_plan. An unknown ID returns 404. |
type | string | One type, or several separated by commas (for example epic or user_story,bug). An unknown value returns 400. |
epic_id | integer | The items in that epic. 0 returns items that belong to no epic. |
page, per_page | integer | See 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"
}
| Field | Type | Required | Description |
|---|---|---|---|
status | string | Yes | open, 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.
| Field | Type | Required | Description |
|---|---|---|---|
project_id | integer | Yes | Target project ID. An unknown ID returns 404. |
title | string | Yes | Task title |
description | string | No | Description |
priority | string | No | low, medium, high, or critical. Default: medium |
type | string | No | epic, user_story, feature, bug, task, investigation, or idea. Default: task. An unrecognised value returns 400 (invalid_request). |
assignee_id | integer | No | Must be an active member of your organization |
milestone_id | integer | No | Must belong to the same project |
story_points | integer | No | 0 to 10000. null clears it on update. |
epic_id | integer | No | The 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_plan | string | No | Build 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.
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.
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.
| Field | Type | Required | Description |
|---|---|---|---|
project_id | integer | Yes | Target project ID |
name | string | Yes | Milestone name |
description | string | No | Description |
due_date | string | No | YYYY-MM-DD |
status | string | No | open 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. 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
POST /api/v1/backlog
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"
}
| Field | Type | Required | Description |
|---|---|---|---|
project_id | integer | Yes | Target project ID |
title | string | Yes | Intake item title |
description | string | No | Detailed description |
priority | string | No | low, medium, high, or critical. Default: medium |
source_service | string | No | Source system identifier. Default: supportlayer |
source_ticket_id | integer | No | External ticket ID (used for deduplication) |
source_ticket_url | string | No | URL back to the source ticket |
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",
"updated_at": "2026-04-10T14:30:00+00:00"
}
source_external_id holds non-numeric source ids, such as a Sentry issue id.
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 leave out description; fetch the item to read it.
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
}
| Field | Type | Description |
|---|---|---|
title | string | 1–500 characters. Default: the item's title |
description | string | HTML, sanitized like a task description. Default: the item's text, with a link back to its source |
priority | string | low, medium, high or critical. Default: the item's priority |
type | string | epic, user_story, feature, bug, task, investigation or idea. Default: bug for Sentry items, otherwise task |
assignee_id | integer | An active member of your organization |
milestone_id | integer | A milestone of the item's project |
epic_id | integer | An epic in the item's project to file the new task under (not with type epic) |
story_points | integer | 0 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.
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 fullclaude_plan.PATCH /api/v1/tasks/{id}/status— mark the taskin_progresswhile 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 totesting.- 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}andPOST …/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_progressandlast_test_outcomefailed, fixes it, and setstestingagain.
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.
| Event | When |
|---|---|
task.created | A task is created |
task.updated | A field changes (the payload names the field) |
task.status_changed | The status changes |
task.commented | A comment is added |
task.moved | The task moves board column (the payload names the column) |
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.
- Open the project's Intake page and choose Sentry (needs the
settings.managepermission), then Generate webhook URL. - 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.
- 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
- Generate an API key in ProjectLayer (Settings → API Keys).
- Configure the key in your SupportLayer instance under Integrations.
- When a support agent clicks "Send to ProjectLayer" on a ticket, SupportLayer calls the
POST /api/v1/backlogendpoint. - The ticket appears in your ProjectLayer Intake with a link back to the original ticket.
- Your team reviews Intake and converts accepted items into tasks.
Intake statuses
| Status | Meaning |
|---|---|
pending | Newly arrived, awaiting review |
accepted | Approved by the team, not yet a task |
rejected | Declined — won't be worked on |
converted | Promoted 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.