Manifst API Documentation
Welcome to the Manifst REST API v1 documentation. API access is included starting from the Portfolio plan, and is billed based on the plan of the account owning the key. Accounts subscribed prior to the current pricing model retain their access.
baseUrl and apiKey in the collection variables.Introduction
Base URL:
https://manifst.net/api/v1/
All responses are returned in JSON format with the following structure:
// Success
{
"data": { ... }, // object or array
"meta": { "total": 42 } // present on list responses
}
// Error
{
"error": {
"code": "INSUFFICIENT_SCOPE",
"message": "This API key does not have the required scope: 'projects:write'."
}
}
Authentication
All requests must include your API key in the Authorization HTTP header:
Authorization: Bearer mfst_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Generate a key
Navigate to Settings → API Keys in your Manifst dashboard. API keys are shown only once upon creation.
Request example
curl https://manifst.net/api/v1/projects \
-H "Authorization: Bearer mfst_live_xxxxx"
Rate limiting
| Window | Limit |
|---|---|
| Per hour | 1,000 requests |
| Per month | 10,000 requests |
Every response includes these HTTP headers:
X-RateLimit-Limit-Hour: 1000
X-RateLimit-Remaining-Hour: 847
X-RateLimit-Limit-Month: 10000
X-RateLimit-Remaining-Month: 9231
X-RateLimit-Reset-Hour: 1711234567
When quota is exceeded, the API returns HTTP 429 with a Retry-After header (seconds until reset).
Pagination & incremental refresh
Large collection endpoints : /tickets and /time-entries : support optional pagination and incremental filtering. Without these parameters, responses remain identical to legacy versions (backward compatible).
| Parameter | Description |
|---|---|
page | Page number (default 1). Enables pagination. |
per_page | Page size (default 100, max 500). Enables pagination. |
updated_since | Returns only items updated since this date (YYYY-MM-DD or ISO datetime). Ideal for Power BI incremental refresh. |
The meta object always returns the true total count before pagination:
GET /api/v1/tickets?project=abc-123&page=2&per_page=50&updated_since=2026-07-01
{
"data": [ ... ],
"meta": { "total": 320, "count": 50, "page": 2, "per_page": 50 }
}
Error codes
| Code | HTTP | Description |
|---|---|---|
| MISSING_API_KEY | 401 | Missing Authorization header |
| INVALID_API_KEY | 401 | Key not found |
| REVOKED_API_KEY | 401 | Key revoked |
| EXPIRED_API_KEY | 401 | Key expired |
| PLAN_REQUIRED | 403 | Portfolio plan required |
| ACCOUNT_INACTIVE | 403 | Account inactive |
| IP_NOT_ALLOWED | 403 | IP address not whitelisted |
| INSUFFICIENT_SCOPE | 403 | Missing scope on the API key |
| RATE_LIMIT_EXCEEDED | 429 | Quota limit reached |
| NOT_FOUND | 404 | Resource not found |
| EXTERNAL_ACCESS_DENIED | 403 | A project you neither own nor administer, whose owner has not opened AI assistant or API access to your role (use_ai_api permission, Settings → Access → AI assistants & API; no role by default). Such projects are also left out of GET /projects, the portfolio, metrics and notifications. |
| FORBIDDEN | 403 | Forbidden: project you are not a member of, or action your role in the project does not allow (a key has its owner’s rights, no more) |
| VALIDATION_ERROR | 422 | Missing or invalid parameter |
| METHOD_NOT_ALLOWED | 405 | HTTP method not supported |
Scopes
Each API key is assigned a set of scopes defining its permissions. Available scopes:
| Scope | Description |
|---|---|
| projects:read | Read projects and project members |
| projects:write | Create, edit, and delete projects |
| super-epics:read | Read super-epics |
| super-epics:write | Create, edit, and delete super-epics |
| epics:read | Read epics |
| epics:write | Create, edit, and delete epics |
| sprints:read | Read sprints |
| sprints:write | Create and edit sprints |
| tickets:read | Read tickets |
| tickets:write | Create, edit, and delete tickets |
| comments:read | Read ticket comments |
| comments:write | Create, edit, and delete comments |
| team:read | Read project team members |
| time:read | Read time entries |
| time:write | Log time entries |
| statuses:read | Read project statuses |
| finance:read | Read finance / EVM indicators (project & portfolio) |
| roadmap:read | Read roadmap dependencies and milestones |
| roadmap:write | Create, update, delete roadmap milestones and dependencies |
| governance:read | Read the risk register and the RACI matrix |
| governance:write | Create, update, delete risks and the RACI matrix |
| notifications:read | Read the key holder's notifications |
| notifications:write | Mark as read, delete the key holder's notifications |
| docs:read | Read MyDoc spaces, pages and files |
| docs:write | Create, update, delete MyDoc pages |
Project reference data follows the existing scopes: tags use tickets:read / tickets:write, ticket types use projects:read / projects:write, transition rules use projects:read, and a ticket's history uses tickets:read. Search requires projects:read and only searches the types whose read scope the key holds.
Projects
Required scope: projects:read
curl /api/v1/projects -H "Authorization: Bearer mfst_live_xxx"
// Response 200
{
"data": [
{
"uuid": "abc-123",
"name": "My Project",
"description": "Description",
"color": "#6366f1",
"default_dod": ["Code review done", "Tests passing"],
"contingency_pct": 10.0,
"budget": 350000.00,
"user_role": "owner",
"member_count": 5,
"created_at": "2026-01-15 10:00:00",
"updated_at": "2026-03-01 14:22:00"
}
],
"meta": { "total": 1 }
}
Required scope: projects:read
curl /api/v1/projects/abc-123 -H "Authorization: Bearer mfst_live_xxx"
Required scope: team:read
Required scope: projects:write
Project creation automatically initializes the 6 default statuses (Backlog, To Do, In Progress, In Review, Done, Blocked) and 2 default ticket types (Story, Bug).
Body
{
"name": "My Project", // required, 1 - 80 chars
"description": "...", // optional
"color": "#6366f1", // optional, hex #rrggbb
"default_dod": ["Tests OK", "Code review"], // optional : Definition of Done (array)
"contingency_pct": 10.00, // optional : financial reserve, 0 - 100% (default: 10)
"budget": 350000.00 // optional : BAC / budget at completion (≥ 0, null to clear)
}
Response 201
{
"data": {
"uuid": "...",
"name": "My Project",
"description": null,
"color": "#6366f1",
"default_dod": null,
"contingency_pct": 10.0,
"budget": 350000.00,
"user_role": "owner",
"created_at": "2026-03-24 20:00:00",
"updated_at": "2026-03-24 20:00:00"
}
}
Required scope: projects:write : owner only
Body
{
"name": "New name", // optional
"description": "...", // optional
"color": "#f59e0b", // optional
"default_dod": ["item 1", "item 2"], // optional : null or [] to clear
"contingency_pct": 15.00, // optional : 0 - 100%
"budget": 420000.00 // optional : BAC (≥ 0, null to clear)
}
Response 200
{
"data": {
"uuid": "...",
"name": "New name",
"description": null,
"color": "#f59e0b",
"default_dod": ["item 1", "item 2"],
"contingency_pct": 15.0,
"budget": 420000.00,
"user_role": "owner",
"created_at": "2026-03-24 20:00:00",
"updated_at": "2026-03-24 20:00:00"
}
}
Required scope: projects:write : owner only
// Response 200
{ "data": { "deleted": true } }
Super-epics
Required scope: super-epics:read
Required scope: super-epics:read
Required scope: super-epics:write
Body
{
"project_uuid": "abc-123",
"title": "Title",
"description": "...", // optional
"status": "backlog", // optional : backlog | in_progress | done (default: backlog)
"tags": ["tag-uuid-1"] // optional : array of tag UUIDs
}
Response 201
{
"data": {
"uuid": "...",
"project_id": 1,
"title": "Title",
"description": null,
"status": "backlog",
"created_at": "2026-03-24 20:00:00",
"updated_at": "2026-03-24 20:00:00",
"tags": [{ "uuid": "tag-uuid", "name": "bug" }]
}
}
Required scope: super-epics:write
Body
{
"title": "New title", // optional
"description": "...", // optional
"status": "in_progress", // optional : backlog | in_progress | done
"tags": ["tag-uuid-1"] // optional : replaces all tags
}
Response 200
{
"data": {
"uuid": "...",
"project_id": 1,
"title": "New title",
"description": null,
"status": "in_progress",
"created_at": "2026-03-24 20:00:00",
"updated_at": "2026-03-24 20:00:00",
"tags": [{ "uuid": "tag-uuid", "name": "bug" }]
}
}
Required scope: super-epics:write : linked epics are unlinked, not deleted.
Epics
Required scope: epics:read
Required scope: epics:read
Required scope: epics:write
Body
{
"project_uuid": "abc-123",
"title": "Title",
"description": "...", // optional
"super_epic_uuid": "se-uuid", // optional
"status": "backlog", // optional : backlog | todo | in_progress | in_review | done | blocked (default: backlog)
"tags": ["tag-uuid-1"] // optional : array of tag UUIDs
}
Response 201
{
"data": {
"uuid": "...",
"project_id": 1,
"super_epic_id": null,
"super_epic_uuid": null,
"title": "Title",
"description": null,
"status": "backlog",
"created_at": "2026-03-24 20:00:00",
"updated_at": "2026-03-24 20:00:00",
"tags": [{ "uuid": "tag-uuid", "name": "bug" }]
}
}
Required scope: epics:write
Body
{
"title": "...", // optional
"description": "...", // optional
"super_epic_uuid": null, // optional : uuid or null to unlink
"status": "done", // optional : backlog | todo | in_progress | in_review | done | blocked
"tags": ["tag-uuid-1"] // optional : replaces all tags
}
Response 200
{
"data": {
"uuid": "...",
"project_id": 1,
"super_epic_id": null,
"super_epic_uuid": null,
"title": "...",
"description": null,
"status": "done",
"created_at": "2026-03-24 20:00:00",
"updated_at": "2026-03-24 20:00:00",
"tags": [{ "uuid": "tag-uuid", "name": "bug" }]
}
}
Required scope: epics:write : linked tickets are unlinked.
Sprints
Required scope: sprints:read
Required scope: sprints:read
Required scope: sprints:write
Body
{
"project_uuid": "abc-123",
"title": "Sprint 1",
"goal": "...", // optional
"status": "planning", // optional : planning | active | completed (default: planning)
"start_date": "2026-04-01", // optional
"end_date": "2026-04-14", // optional
"dod": ["item 1", "item 2"] // optional : Sprint Definition of Done (array)
}
Response 201
{
"data": {
"uuid": "...",
"project_id": 1,
"title": "Sprint 1",
"goal": null,
"status": "planning",
"start_date": "2026-04-01",
"end_date": "2026-04-14",
"dod": null,
"created_at": "2026-03-24 20:00:00"
}
}
Required scope: sprints:write
Body
{
"title": "Sprint 1", // optional
"status": "active", // optional : planning | active | completed
"goal": "...", // optional
"start_date": "2026-04-01", // optional
"end_date": "2026-04-14", // optional
"dod": ["item 1"] // optional : null or [] to clear
}
Response 200
{
"data": {
"uuid": "...",
"project_id": 1,
"title": "Sprint 1",
"goal": null,
"status": "active",
"start_date": "2026-04-01",
"end_date": "2026-04-14",
"dod": ["item 1"],
"created_at": "2026-03-24 20:00:00"
}
}
Tickets
Required scope: tickets:read
Optional query filters: ?sprint={uuid} and ?epic={uuid}
Required scope: tickets:read
{
"data": {
"uuid": "...", "title": "...", "description": "...",
"priority": "haute", "status": "todo", "story_points": 5,
"epic_id": 1, "sprint_id": 2, "assignee": "Alice Martin",
"project_uuid": "abc-123", // project UUID : join key
"epic_uuid": "epic-uuid", // null when the story has no epic
"sprint_uuid": "sprint-uuid", // null when the story is in no sprint
"acceptance_criteria": ["Criteria 1", "Criteria 2"],
"dependencies": [{ "uuid": "...", "type": "story", "title": "Other story" }], // tickets to finish BEFORE this one (blockers)
"blocked_by": [{ "uuid": "...", "number": 12, "key": "US-12", "title": "Other story", "status": "todo" }],
"blocks": [], "duplicates": [], "duplicated_by": [], "relates": [], // see Ticket links
"tags": [{ "uuid": "tag-uuid", "name": "bug" }],
"subtasks": [
{ "uuid": "...", "title": "Technical task", "status": "todo",
"sort_order": 1, "assignee_id": 3, "assignee_name": "Alice Martin",
"created_at": "2026-03-25 10:00:00", "updated_at": "2026-03-25 10:00:00" }
],
"subtasks_total": 1,
"subtasks_done": 0
}
}
Required scope: tickets:write
Body
{
"project_uuid": "abc-123",
"title": "As a user...",
"description": "...",
"priority": "haute", // critique | haute | normale | faible
"status": "backlog", // backlog | todo | in_progress | in_review | done | blocked
"story_points": 5, // optional
"epic_uuid": "epic-uuid", // optional
"sprint_uuid": "sprint-uuid",// optional
"assignee_id": 3, // optional
"acceptance_criteria": ["Criteria 1"], // optional
"dependencies": ["us-uuid-1"], // optional
"tags": ["tag-uuid-1"] // optional : array of tag UUIDs
}
Response 201
{
"data": {
"uuid": "...",
"project_id": 1,
"epic_id": null,
"sprint_id": null,
"assignee_id": null,
"assignee": null,
"title": "As a user...",
"description": null,
"priority": "haute",
"status": "backlog",
"story_points": 5,
"created_at": "2026-03-24 20:00:00",
"updated_at": "2026-03-24 20:00:00",
"acceptance_criteria": ["Criteria 1"],
"dependencies": [],
"tags": [{ "uuid": "tag-uuid", "name": "bug" }]
}
}
Required scope: tickets:write
Body
{
"title": "...", // optional
"description": "...", // optional
"priority": "haute", // optional : critique | haute | normale | faible
"status": "in_progress", // optional : backlog | todo | in_progress | in_review | done | blocked
"story_points": 8, // optional
"epic_uuid": "epic-uuid", // optional : uuid or null to unlink
"sprint_uuid": "sprint-uuid", // optional : uuid or null to unlink
"assignee_id": 3, // optional : id or null
"acceptance_criteria": ["Criteria 1"], // optional : replaces existing criteria
"dependencies": ["us-uuid-1"], // optional : replaces the tickets this one depends on. Loops refused: 409
"tags": ["tag-uuid-1"] // optional : replaces all tags
}
Response 200
{
"data": {
"uuid": "...",
"project_id": 1,
"epic_id": null,
"sprint_id": null,
"assignee_id": null,
"assignee": null,
"title": "...",
"description": null,
"priority": "haute",
"status": "in_progress",
"story_points": 8,
"created_at": "2026-03-24 20:00:00",
"updated_at": "2026-03-24 20:00:00",
"acceptance_criteria": ["Criteria 1"],
"dependencies": [],
"tags": [{ "uuid": "tag-uuid", "name": "bug" }]
}
}
Required scope: tickets:write
Ticket links
Five relations, read from the ticket in the URL: blocked_by (the other ticket must be finished first), blocks (the other ticket waits for this one), duplicates (this one duplicates the other, which is the original), duplicated_by and relates (no direction). A link is saved immediately and shows on both tickets.
Refused: a link that would create a blocking loop (409 CONFLICT), two tickets duplicating each other (409), a ticket linked to itself or an unknown kind (422), a ticket from another project (404).
Required scope: tickets:read
{
"data": {
"blocked_by": [{ "uuid": "...", "type": "story", "number": 12, "key": "US-12", "title": "Prerequisite", "status": "todo" }],
"blocks": [],
"duplicates": [],
"duplicated_by": [],
"relates": []
}
}
Required scope: tickets:write · edit_us permission in the project
Body
{
"other_uuid": "us-uuid", // required: ticket of the same project
"kind": "duplicates" // required: blocked_by | blocks | duplicates | duplicated_by | relates
}
Response 201: the five lists, up to date, as with GET. Repeating an existing link does nothing.
Required scope: tickets:write · edit_us permission in the project
Same body as POST (or ?other_uuid=…&kind=… in the query). Response 200: the five lists, up to date.
Ticket history
Required scope: tickets:read
Changes from newest to oldest. A change groups the fields modified by a single action. source is app, api, mcp, import, or the tracker / Git provider that pushed the change. Optional pagination (?page, ?per_page, 200 max).
{
"data": [
{
"change_id": "c0a8…",
"created_at": "2026-10-27 14:02:11",
"source": "api",
"actor": { "user_id": 42, "uuid": "user-uuid", "name": "Camille Martin" },
"items": [
{ "field": "status", "old_value": "todo", "new_value": "in_progress",
"old_label": "To do", "new_label": "In progress" }
]
}
],
"meta": { "total": 1, "count": 1 }
}
Subtasks
Subtasks have fixed statuses (todo, in_progress, done), independent of project custom statuses. They serve as a technical checklist linked to a ticket.
Required scope: tickets:read
{
"data": [
{
"uuid": "...",
"title": "Technical task",
"status": "todo", // todo | in_progress | done
"sort_order": 1,
"assignee_id": 3,
"assignee_name": "Alice Martin",
"created_at": "2026-03-25 10:00:00",
"updated_at": "2026-03-25 10:00:00"
}
],
"meta": { "total": 1 }
}
Required scope: tickets:write
Body
{
"ticket_uuid": "us-uuid", // required
"title": "Technical task", // required
"status": "todo", // optional : todo | in_progress | done (default: todo)
"assignee_id": 3 // optional
}
Response 201
{
"data": {
"uuid": "...", "title": "Technical task", "status": "todo",
"sort_order": 1, "assignee_id": null, "assignee_name": null,
"created_at": "...", "updated_at": "..."
}
}
Required scope: tickets:write
Body
{
"title": "...", // optional
"status": "in_progress", // optional : todo | in_progress | done
"assignee_id": 3, // optional : id or null to unassign
"sort_order": 2 // optional
}
Response 200
{
"data": {
"uuid": "...", "title": "...", "status": "in_progress",
"sort_order": 2, "assignee_id": 3, "assignee_name": "Alice Martin",
"created_at": "...", "updated_at": "..."
}
}
Required scope: tickets:write
Comments
Comments are attached to a ticket. Only the author (or an administrator) can edit or delete their own comments. Content is sanitized HTML (scripts and JS event handlers removed).
Required scope: comments:read
| Parameter | Type | Description |
|---|---|---|
us | string | Ticket UUID : required |
Response 200
{
"data": [
{
"uuid": "...",
"author_id": 3,
"author_name": "Alice Martin",
"author_initials": "AM",
"content": "<p>Comment content</p>",
"mentions": [4, 7],
"ticket_uuid": "...",
"created_at": "2026-04-02 10:00:00",
"updated_at": "2026-04-02 10:00:00"
}
],
"meta": { "total": 1 }
}
Required scope: comments:read
Required scope: comments:write
Body
{
"ticket_uuid": "...", // required : Ticket UUID
"content": "<p>My comment</p>", // required : Sanitized HTML
"mentions": [4, 7] // optional : Mentioned user IDs
}
Response 201
{
"data": {
"uuid": "...",
"author_id": 3,
"author_name": "Alice Martin",
"author_initials": "AM",
"content": "<p>My comment</p>",
"mentions": [4],
"ticket_uuid": "...",
"created_at": "2026-04-02 10:00:00",
"updated_at": "2026-04-02 10:00:00"
}
}
Required scope: comments:write : author or admin only.
Body
{
"content": "<p>Updated content</p>", // optional
"mentions": [4, 7] // optional : replaces full mention list
}
Required scope: comments:write : author or admin only.
{ "data": { "deleted": true } }
Team
Required scope: team:read
{
"data": [
{
"uuid": "...", "firstname": "Alice", "lastname": "Martin",
"email": "alice@example.com", "scrum_role": "dev",
"project_role": "member", "status": "active",
"tjm": 550.00, "service": "Delivery"
}
],
"meta": { "total": 4 }
}
tjm = average daily rate (cost/day) of the member on this project, used as the basis for financial indicators; null if unspecified.
Backlog in one transaction
Required scopes: tickets:write, plus epics:write if the call creates epics and super-epics:write if it creates a super-epic. The role needs the matching creation rights in the project.
The Launchpad engine, opened to the API: everything is created in a single transaction. At the slightest problem (unknown ticket type, story points off the planning poker scale 0, 1, 2, 3, 5, 8, 13, 21, UUID not in the project, empty title…), nothing is created and the 422 response lists every problem. Tickets land in the backlog status, outside any sprint. Limits: 40 epics, 300 tickets and 30 criteria per ticket per call.
Each epic is either existing (epic_uuid: the tickets are added to it) or new (title): it is then attached to the super-epic created by the call, if any, or to the existing super-epic given in super_epic_uuid.
Idempotency: send a commit_token (UUID). Sending the same token again creates nothing twice and answers 200 with "idempotent": true. No sync to Jira, Azure Boards or Linear (as with Launchpad): tracker_not_synced flags it when the project is linked to a tracker.
Body
{
"project_uuid": "abc-123", // required
"commit_token": "3f2b…-uuid", // optional, recommended
"super_epic": { "title": "Customer portal", "description": "<p>…</p>" }, // optional
"epics": [
{ "title": "Authentication",
"tickets": [
{ "title": "Sign in by e-mail", "type": "story", "priority": "haute",
"story_points": 3, "description": "<p>…</p>",
"acceptance_criteria": ["Magic link sent", "Expires after 15 min"] }
] },
{ "epic_uuid": "existing-epic-uuid",
"tickets": [ { "title": "SSO sign-in", "story_points": 8 } ] }
]
}
Response 201
{
"data": {
"super_epic_uuid": "…",
"n_epics": 1,
"n_tickets": 2,
"n_epics_touched": 2,
"created": {
"super_epic": { "uuid": "…", "title": "Customer portal" },
"epics": [
{ "uuid": "…", "title": "Authentication", "tickets": [ { "uuid": "…", "title": "Sign in by e-mail" } ] },
{ "uuid": "existing-epic-uuid", "existing": true, "tickets": [ { "uuid": "…", "title": "SSO sign-in" } ] }
]
},
"commit_token": "3f2b…-uuid"
}
}
Response 422
{ "error": { "code": "VALIDATION_ERROR",
"message": "2 problem(s), nothing was created: epics[0].tickets[1].type: unknown type 'task' (valid: story, bug); epics[1].epic_uuid: no epic with this UUID in the project." } }
Time entries
Required scope: time:read
Optional filters: ?story={uuid} for a specific story, ?user={user_id} for one member’s entries, ?from=YYYY-MM-DD and ?to=YYYY-MM-DD for the day of the work (inclusive).
Required scope: time:write
Body
{
"ticket_uuid": "story-uuid", // required
"duration_minutes": 90, // required
"logged_at": "2026-03-24", // required, format YYYY-MM-DD
"comment": "Code review" // optional
}
Response 201
{
"data": {
"uuid": "...",
"ticket_id": 12,
"user_id": 3,
"user_name": "Alice Martin",
"ticket_uuid": "...",
"ticket_title": "As a user...",
"duration_minutes": 90,
"comment": "Code review",
"logged_at": "2026-03-24 00:00:00",
"created_at": "2026-03-24 20:00:00"
}
}
Statuses
Required scope: statuses:read
Returns all project statuses (default and custom), sorted by sort_order.
Response 200
[
{
"key": "backlog",
"label": "Backlog",
"color": "#64748b",
"bg_color": "#f1f5f9",
"sort_order": 1,
"is_done": false,
"is_default": true
},
{
"key": "in_validation",
"label": "In Validation",
"color": "#7c3aed",
"bg_color": "#f5f3ff",
"sort_order": 7,
"is_done": false,
"is_default": false
}
]
Tags
Reusable labels of a project. To tag a ticket, an epic or a super-epic, send the list of tag UUIDs in the tags field of that resource. Writing: project owner / admin, or a role holding the manage_tags permission (every role by default).
Required scope: tickets:read · GET /api/v1/tags/{uuid} for a single tag
{
"data": [ { "uuid": "tag-uuid", "name": "Front-end", "slug": "front-end", "usage_count": 12 } ],
"meta": { "total": 1 }
}
Required scope: tickets:write
{ "project_uuid": "proj-uuid", "name": "Front-end" } // 50 characters max
Response 201: the tag. 409 CONFLICT if a tag with the same name (case and punctuation ignored) already exists.
Required scope: tickets:write · body { "name": "…" }
Required scope: tickets:write · the tag is removed from every ticket, epic and super-epic that carried it.
Ticket types
A type's slug is the value of a ticket's type field; its abbreviation prefixes the ticket key (US-42). Writing: project owner / admin, or a role holding the manage_ticket_types permission.
Required scope: projects:read
{
"data": [
{ "uuid": "…", "name": "User Story", "slug": "story", "abbreviation": "US", "is_default": true, "sort_order": 1 }
],
"meta": { "total": 1 }
}
Required scope: projects:write
{ "project_uuid": "proj-uuid", "name": "Spike", "abbreviation": "SPK" }
The abbreviation is upper-cased, 10 characters max, unique in the project (409); SE, EP and SP are reserved (422).
Required scope: projects:write · partial body { "name": "…", "abbreviation": "…" }. The slug does not change.
Required scope: projects:write · tickets of this type become story. A default type cannot be deleted (403).
Transition rules
Required scope: projects:read · &entity_type=ticket|epic|super_epic for a single entity type
forbidden_transitions lists, for the key holder, the status changes the rules refuse them: { from_status: { to_status: reason } }. A client knows in advance what it cannot do, instead of discovering it through a 403 WORKFLOW_BLOCKED. Checks that depend on a specific item (subtasks done, required fields, QA verdict) are not listed: they are only evaluated on PUT. Project owners / admins are not subject to conditions (bypasses_conditions). Rules are configured in the application.
{
"data": {
"rules": [
{ "uuid": "…", "entity_type": "ticket", "status_from": null, "status_to": "done",
"rule_type": "condition", "action_type": "require_role",
"config": { "roles": ["product_owner"] }, "sort_order": 1, "enabled": true }
],
"forbidden_transitions": {
"ticket": { "review": { "done": { "reason": "role", "roles": ["product_owner"] } } },
"epic": {},
"super_epic": {}
},
"bypasses_conditions": false
},
"meta": { "total": 1 }
}
Dependencies
Finish-to-start links between roadmap items (epic or super-epic). Each node is exposed by its UUID (resolvable via /epics and /super-epics) to reconstruct the graph and compute the critical path client-side.
Required scope: roadmap:read
{
"data": [
{
"uuid": "dep-uuid",
"type": "FS", // finish-to-start
"pred_type": "epic", // epic | super_epic
"pred_uuid": "epic-uuid-1",
"succ_type": "epic",
"succ_uuid": "epic-uuid-2"
}
],
"meta": { "total": 1 }
}
Required scope: roadmap:write · project owner / admin
{
"project_uuid": "proj-uuid",
"pred_type": "epic", // epic | super_epic: must finish before…
"pred_uuid": "epic-uuid-1",
"succ_type": "epic", // …this one starts
"succ_uuid": "epic-uuid-2"
}
Response 201: the dependency. Refused: self-link (422), duplicate or cycle (409 CONFLICT), epic from another project (404). "Blocks / blocked by" links between tickets go through /tickets/{uuid}/links.
Required scope: roadmap:write · project owner / admin
Milestones
Required scope: roadmap:read
{
"data": [
{ "uuid": "...", "name": "Go-live", "date": "2026-09-30", "color": "#e11d48" }
],
"meta": { "total": 1 }
}
Required scope: roadmap:write · project owner / admin
{
"project_uuid": "proj-uuid",
"name": "Go-live", // required, 120 characters max
"date": "2026-12-01", // required, YYYY-MM-DD
"color": "#e11d48" // optional, #rgb or #rrggbb
}
Response 201: the milestone.
Required scope: roadmap:write · partial body: only the fields sent (name, date, color) change.
Required scope: roadmap:write · project owner / admin
Ticket attachments
The same gate as the app's upload form: 10 MB max per file, the project's storage quota, and the real file type detected from the content (never the name or the declared header). SVG, HTML, scripts and executables are refused. Writing requires the edit_us or create_us permission (owner / admin always).
Required scope: tickets:read
{
"data": [
{ "uuid": "...", "ticket_uuid": "...", "name": "export.csv", "size": 2048,
"type": "text/csv", "downloadable": true, "created_at": "..." }
],
"meta": { "total": 1 }
}
downloadable: false: the attachment stays in the connected tracker (Jira, Azure Boards) with no local copy.
Required scope: tickets:write · edit_us or create_us permission
As multipart/form-data, field file:
curl -X POST https://manifst.net/api/v1/tickets/{uuid}/attachments \
-H "Authorization: Bearer mfst_live_..." \
-F "file=@export.csv"
Or as JSON with base64 content (a data URL is accepted):
{ "name": "capture.png", "content_base64": "iVBORw0KGgo..." }
Response 201: the attachment. 413 when the file exceeds 10 MB or the project quota is reached (STORAGE_QUOTA_REACHED).
Required scope: tickets:read
Required scope: tickets:read · returns the file itself (Content-Disposition: attachment), not JSON.
Required scope: tickets:write · the file is removed from disk along with the record.
Documentation (MyDoc)
MyDoc spaces and pages, with the app's rights: the MyDoc section must be open to the key holder's role in the project, then their permission on the space applies. The project owner is admin everywhere. An explicit permission prevails. A public space gives edit to every member; a private space gives nothing. Reading needs read, writing edit.
Content is sent as HTML (content, cleaned by the app's sanitizer) or Markdown (content_markdown). It is read as HTML, Markdown or both (?format=html|markdown|both).
Required scope: docs:read
{
"data": [
{ "uuid": "...", "name": "Specifications", "description": "", "icon": "bi-journal-text",
"color": "#6366f1", "is_public": true, "page_count": 12, "my_permission": "edit", "updated_at": "..." }
],
"meta": { "total": 1 }
}
Required scope: docs:read · flat list: top-level pages first, then sub-pages (parent_uuid). Archived pages are excluded unless ?include_archived=1.
{ "data": [ { "uuid": "...", "parent_uuid": null, "title": "Accounting export", "emoji": null,
"status": "published", "sort_order": 0, "word_count": 240, "rev": 7,
"updated_at": "...", "updated_by_name": "Jane Martin" } ] }
Required scope: docs:read
{
"data": {
"uuid": "...", "space_uuid": "...", "space_name": "Specifications", "project_uuid": "...",
"parent_uuid": null, "title": "Accounting export", "emoji": null, "status": "published",
"rev": 7,
"content_markdown": "### Goal\n\nProduce a monthly **CSV**…",
"word_count": 240, "labels": ["finance"],
"created_by": { "uuid": "...", "name": "Jane Martin" },
"updated_by": { "uuid": "...", "name": "Bob Dev" },
"created_at": "...", "updated_at": "...", "my_permission": "edit"
}
}
Keep rev: it is required to rewrite the content.
Required scope: docs:write · edit permission on the space
{
"space_uuid": "space-uuid",
"parent_uuid": "page-uuid", // optional: sub-page (two levels max)
"title": "Accounting export", // required, 500 characters max
"emoji": "📄", // optional
"status": "published", // published (default), draft, archived
"content_markdown": "## Goal\n\nProduce a monthly **CSV**." // or "content" as HTML
}
Response 201: the page, with its rev.
Required scope: docs:write · partial body: title, emoji, status, content or content_markdown.
{ "content_markdown": "…", "base_rev": 7 }
Rewriting the content requires base_rev, the rev of the page you read (or the If-Match: 7 header). Without it: 428. If the page changed in the meantime (a person co-editing, another agent): 409 CONFLICT and nothing is written. Read the page again, merge, and resend with the new rev. An accepted write archives the previous version in the page history, and tabs open on the page reload.
If someone is co-editing the page and their latest changes are not saved yet, the response is 409 PAGE_BEING_EDITED and nothing is written: retry a minute later. Their keystrokes are never overwritten.
Title, emoji and status alone need no base_rev.
Required scope: docs:write · also deletes its sub-pages and attached files. To retire a page without losing it, prefer "status": "archived".
Required scope: docs:read · list (uuid, name, type, size, created_at, created_by_name), then download. Uploading files into a page remains in the app.
Testing (QA)
The ticket testing frame, with the app's rules:
- a session opens only for a ticket in a column of type
review; - the acceptance criteria (
ac) and the sprint's Definition of Done (dod) are copied in as checks, and kept up to date while the session is open; - accepting requires no check left pending or failed; rejecting requires at least one failed check.
Writing requires the perform_qa and edit_us permissions (owner / admin always). The verdict does not move the ticket: change its status afterwards with PUT /tickets/{uuid}, where the transition rules apply, including "accepted testing required".
Required scope: tickets:read
{
"data": {
"ticket_uuid": "...", "status": "in_review", "column_type": "review",
"can_perform": true, "accepted": false,
"sessions": [
{ "uuid": "...", "round": 1, "verdict": "open", "tester_name": "Jane Martin",
"version_label": "1.4.0", "environment": "staging",
"counts": { "pending": 1, "passed": 1, "failed": 0, "untestable": 0 },
"checks": [
{ "uuid": "...", "source": "ac", "label": "Amounts are in euros",
"result": "pending", "note": null, "orphan": false, "...": "..." }
] }
]
}
}
result: pending, passed, failed, untestable. orphan: a check whose criterion has since been removed from the ticket.
Required scope: tickets:write · optional body { "version_label": "1.4.0", "environment": "staging" }.
Idempotent: if a session is already open it is returned as is (200); otherwise a new round opens (201). Two agents starting at the same time work on the same session.
Required scope: tickets:write
{
"checks": [
{ "uuid": "check-1", "result": "passed" },
{ "uuid": "check-2", "result": "failed", "note": "Amount in dollars, line 12" }
]
}
At most 200 checks, all or nothing. A single check: PUT /qa/checks/{uuid} with { "result", "note" }.
Required scope: tickets:write · { "label": "Export of more than 10,000 rows" }. Only free checks can be deleted (DELETE /qa/checks/{uuid}); mark a criterion or DoD item untestable instead.
Required scope: tickets:write · version_label, environment, tester_uuid (a project member).
Required scope: tickets:write · { "verdict": "accepted" } or "rejected".
422 when the verdict rule is not met (the message gives the number of pending and failed checks); 409 when the session is already closed. A closed session cannot change: start a new round.
Sprint rituals
The same computations as the Sprint Review and Daily screens: no second calculation, the figures are the ones the team sees.
Required scope: sprints:read
Committed vs delivered, velocity of previous sprints, burndown, scope added or removed, carried-over tickets, epic progress, milestones, linked risks, EVM cost of the sprint. The finance and risks blocks are null when the key holder has no access to them (view_financial, Risks section), as on screen; access says which.
Required scope: sprints:write · run_review permission (Product Owner and Scrum Master by default; can_review in the read response tells whether the key has it)
{
"goal_met": "partial", // yes, partial, no, or null (not assessed)
"goal_comment": "Export delivered without credit notes",
"review_notes": "Demo approved by the client."
}
Partial: saving the verdict does not erase the notes.
Required scope: sprints:read · Sprints section open to the role
Sprint progress, issues to address (blocked tickets, in progress for more than 3 days without movement, testing queue, concentrated workload), round table per person (what moved since the last working day, what is in progress). closed_today: the daily already closed today, or null. Closing the daily (report sent to the team) stays in the app.
Required scope: sprints:read · Sprints section + edit_sprint permission, as on the retrospective screen
{
"data": [ { "uuid": "...", "category": "action", "content": "Add credit notes", "done": false, "created_at": "..." } ],
"meta": { "total": 1, "by_category": { "went_well": 0, "problem": 0, "action": 1 } }
}
Required scope: sprints:write · edit_sprint permission
// POST /sprints/{uuid}/retro
{ "category": "problem", "content": "Late testing" } // went_well, problem, action
// PUT /retro-items/{uuid}
{ "done": true } // or "content"
Flow metrics
Required scope: tickets:read
The computation behind the dashboard's Flow, Quality and Scope blocks, over 90 days: weekly throughput, cycle and lead time, work in progress and its age, defect rate, testing results. available: false until ticket history exists.
| Parameter | Description |
|---|---|
cfd=1 | Cumulative flow, day by day |
sprints=1 | Scope of recent sprints (added, removed, delivered) |
forecast=1 | Completion forecast by Monte Carlo simulation (Portfolio plan) |
projects=uuid1,uuid2 | Roll-up of several projects (Portfolio plan). Projects the key holder is not a member of are ignored. No cfd, sprints or forecast: they belong to one team. |
Risks
The project's risk register. Severity is computed (probability × impact, 1 to 25): low up to 4, medium up to 9, high up to 14, critical above. Reading requires the Risks section to be open to the role (the same rule as the Sprint Review's risks block). Writing requires the Team plan or above and project owner / admin or the manage_risks permission. Out-of-range values are rejected (422), never silently clamped.
Required scope: governance:read
| Parameter | Description |
|---|---|
status | open, mitigating, monitored, closed, occurred (comma-separated) |
category | technique, delai, budget, ressources, externe, securite, qualite |
severity | low, medium, high, critical |
entity_type + entity_uuid | Risks linked to a ticket, an epic or a super_epic |
updated_since | Changed since this date (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS) |
{
"data": [
{
"uuid": "...", "title": "Stripe API delay", "description": "",
"category": "externe", "probability": 4, "impact": 5,
"severity_score": 20, "severity_level": "critical",
"status": "open", "owner_uuid": "...", "owner_name": "Jane Martin", "owner_initials": "JM",
"mitigation": "Plan B: stub", "contingency": "", "due_date": "2026-12-01",
"links": [ { "entity_type": "ticket", "uuid": "...", "title": "Payment migration" } ],
"created_at": "...", "updated_at": "..."
}
],
"meta": { "total": 1, "categories": ["technique", "delai", "..."] }
}
Required scope: governance:read
Required scope: governance:write · manage_risks permission
{
"project_uuid": "proj-uuid",
"title": "Stripe API delay", // required, 160 characters max
"category": "externe", // required
"probability": 4, // integer 1 to 5, default 3
"impact": 5, // integer 1 to 5, default 3
"status": "open", // default open
"owner_uuid": "user-uuid", // project member, optional
"due_date": "2026-12-01",
"description": "...", // 5,000 characters max
"mitigation": "...", // 2,000 characters max
"contingency": "...", // 2,000 characters max
"links": [ { "entity_type": "ticket", "uuid": "ticket-uuid" } ]
}
Response 201: the risk. A links target not found in the project rejects the whole call.
Required scope: governance:write · partial body. links replaces the whole list ([] removes them); owner_uuid: null removes the owner.
Required scope: governance:write · manage_risks permission
RACI matrix
Phases → tasks as rows, participants as columns (project members or external people without an account), one role per cell: R, A, C, I or C/I. Same rights as risks: the RACI section open to the role, and the manage_raci permission to write.
Required scope: governance:read
{
"data": {
"participants": [
{ "participant_uuid": "p1", "user_uuid": "...", "is_external": false, "name": "Jane Martin", "initials": "JM", "...": "..." },
{ "participant_uuid": "p2", "user_uuid": null, "is_external": true, "name": "Client", "initials": "CL", "...": "..." }
],
"phases": [
{ "uuid": "...", "name": "Scoping", "color": "#6366f1", "position": 0,
"tasks": [ { "uuid": "t1", "title": "Approve the budget", "position": 0,
"cells": { "p1": "A", "p2": "C/I" } } ] }
],
"can_edit": true
}
}
cells is keyed by participant_uuid; empty cells are omitted. can_edit tells whether the key holder can write.
Required scope: governance:write · manage_raci permission
// POST /raci/phases
{ "project_uuid": "proj-uuid", "name": "Scoping", "color": "#6366f1", "position": 0 }
// POST /raci/tasks
{ "phase_uuid": "phase-uuid", "title": "Approve the budget", "position": 0 }
// POST /raci/participants: a member…
{ "project_uuid": "proj-uuid", "user_uuid": "user-uuid" }
// …or an external person
{ "project_uuid": "proj-uuid", "name": "Jane Client" }
Response 201: the created item. position (0 = first) is optional; items are appended by default. A member who is already a participant returns 409.
Required scope: governance:write · phase: name, color, position; task: title, position; participant: name, external people only (a member keeps their account name).
Required scope: governance:write · manage_raci permission
{
"cells": [
{ "task_uuid": "t1", "participant_uuid": "p1", "role": "A" },
{ "task_uuid": "t1", "participant_uuid": "p2", "role": "C/I" },
{ "task_uuid": "t2", "participant_uuid": "p1", "role": null } // null or "" clears the cell
]
}
At most 500 cells, all from the same project. All or nothing: one invalid cell rejects the whole batch. Response: the updated matrix, with meta.updated. A single cell can also be sent flat: { "task_uuid", "participant_uuid", "role" }.
Required scope: governance:write · deleting a phase deletes its tasks and their cells; removing a participant deletes their column.
Search
Required scope: projects:read, plus the read scope of each type searched.
The same engine as the app's search: titles, descriptions, keys (US-12, EP-3, SE-1, including a ticket's former global number), sprint goals, MyDoc pages (in readable spaces), member names and emails.
| Parameter | Description |
|---|---|
q | 2 to 200 characters |
types | super_epic (super-epics:read), epic (epics:read), sprint (sprints:read), ticket (tickets:read), doc (docs:read), user (team:read). Default: all. |
limit | Results per type, 1 to 50 (default 10) |
{
"data": [
{ "type": "ticket", "uuid": "...", "key": "US-12", "title": "Payment migration", "status": "in_progress", "matched_legacy_key": false },
{ "type": "user", "uuid": "...", "user_id": 42, "title": "Jane Martin", "email": "jane@example.com" }
],
"meta": { "total": 2, "types_searched": ["super_epic", "epic", "sprint", "ticket", "user"] }
}
A type whose read scope the key lacks is not searched and is listed in meta.types_skipped. user_id is the identifier expected by mentions on comments.
Notifications
The key holder's notification bell, across all projects: mentions, assignments, owner designations, status changes… These are the app's notifications, under the same conditions (expired ones and projects left are excluded). A key never sees another account's notifications.
Required scope: notifications:read
| Parameter | Description |
|---|---|
unread | 1: unread only |
type | One or more comma-separated types: mention, doc_mention, story_assigned, lot_owner_assigned, ticket_updated, poker_started… |
project | A project UUID |
since | Received since this date (server time) |
lang | fr or en for message; defaults to the account's language |
page, per_page | Pagination, 50 by default, 200 max |
{
"data": [
{
"uuid": "...", "type": "mention",
"message": "Bob Dev mentioned you on “Payment migration”",
"params": { "actor_name": "Bob Dev", "ticket_title": "Payment migration" },
"project_uuid": "...", "project_name": "Redesign",
"subject_type": "ticket", "subject_uuid": "ticket-uuid",
"is_read": false, "read_at": null,
"created_at": "2026-10-27 09:12:00", "age_seconds": 340
}
],
"meta": { "total": 1, "count": 1, "unread": 3, "page": 1, "per_page": 50 }
}
For an agent watching its inbox: ?unread=1&per_page=1 returns the counter in meta.unread in a single call. Read subject_uuid with the endpoint of its subject_type (for instance GET /tickets/{uuid}).
Required scope: notifications:write · body { "read": true } (default) or { "read": false }.
Required scope: notifications:write
{ "uuids": ["notif-uuid-1", "notif-uuid-2"] } // 500 max
{ "all": true } // everything received so far
Response: { "marked": 2, "unread": 1 }. Prefer uuids: all also marks what arrived since the agent last read its inbox.
Required scope: notifications:write
Finance / EVM
Project Earned Value Management (EVM) performance indicators, computed server-side from story points, logged time (× TJM), and the declared BAC (projects.budget).
bac, pv, ev, cpi, cv, sv, eac, etc, vac, tcpi) evaluate to null as long as no BAC is declared for the project. Progress and cost metrics (ac, ac_total, no_sprint_cost, engaged_charge, spi, progress_pct, planned_pct) remain calculated.ac is the cost of the sprint scope: the only basis comparable to ev, hence the only one admissible in the cpi denominator and in cv. ac_total = ac + no_sprint_cost is total actual spend: time booked outside any sprint receives no share of the BAC (which is spread pro rata across sprint story points) so it earns no value, yet the money is gone. Forecasting (eac, tcpi) therefore uses ac_total. On a project with no out-of-sprint booking, ac_total == ac and eac collapses exactly onto BAC ÷ CPI.Required scope: finance:read
The key owner must also be allowed to read the project finances (the “Finance” right of their role, or project owner/admin); otherwise 403 FORBIDDEN.
{
"data": {
"project_uuid": "abc-123",
"bac": 350000.00, // budget at completion (declared)
"base_budget": 315000.00, // BAC − contingency reserve
"reserve": 35000.00, // contingency reserve (included in BAC)
"contingency_pct": 10.00,
"pv": 326341.95, // planned value = % planned SP to date × BAC
"ev": 257455.27, // earned value = % completed SP × BAC
"ac": 186573.75, // sprint-scope cost = Σ (time ÷ 480) × daily rate in force on the work date
"no_sprint_cost": 8250.00, // time booked on tickets with no sprint
"ac_total": 194823.75, // total actual spend = ac + no_sprint_cost
"engaged_charge": 137253.75, // actual cost of unfinished epics
"cpi": 1.38, // EV ÷ ac (> 1 = under budget)
"spi": 0.79, // EV ÷ PV (> 1 = ahead of schedule)
"tcpi": 0.60, // (BAC − EV) ÷ (BAC − ac_total)
"eac": 261889.45, // estimate at completion = ac_total + etc
"etc": 67065.70, // estimate to complete = (BAC − EV) ÷ CPI
"vac": 88110.55, // variance at completion = BAC − EAC
"cv": 70881.52, // cost variance = EV − ac
"sv": -68886.68, // schedule variance = EV − PV
"total_sp": 503,
"done_sp": 374, // story points physically completed (raw progress)
"earned_sp": 370, // earned SP = Σ min(done, committed) per sprint: the EV basis
"planned_sp": 469.10,
"undated_sp": 12, // SP committed in sprints with no dates: they count
// towards EV but contribute 0 to PV — they inflate SPI
"progress_pct": 73.56, // earned_sp ÷ total_sp
"planned_pct": 93.24 // % planned SP to date
}
}
Portfolio
EVM roll-up across all projects accessible to the API key holder : a single request replacing project-by-project polling. Consolidated indexes are calculated on the sum of values (EV, PV, AC), not an average of ratios.
Required scope: finance:read
Optional query filter: ?projects=uuid1,uuid2 to target a specific team / program. Only projects whose finances you can read are rolled up; the others are counted in totals.excluded_no_financial_access.
{
"data": {
"totals": {
"projects": 12, "with_bac": 4, "without_bac": 8,
"bac": 1250000.00, "pv": 910000.00, "ev": 742000.00, "ac": 690000.00,
"ac_total": 712000.00,
"engaged_charge": 305000.00,
"cpi": 1.08, "spi": 0.82,
"etc": 472398.92, "eac": 1184398.92, "vac": 65601.08,
"cv": 52000.00, "sv": -168000.00,
"ac_all": 812000.00, // total actual spend across ALL projects (BAC declared or not)
"engaged_all": 410000.00 // committed charge across all projects
},
"projects": [
{ "project_uuid": "abc-123", "project_name": "CMA-CGM", "bac": 350000.00, "ev": 257455.27, "ac": 186573.75, "cpi": 1.38, "spi": 0.79, "vac": 96360.55, "engaged_charge": 137253.75, "...": "..." }
]
},
"meta": { "total": 12 }
}
Manifst API v1 Documentation · Back to site