FeaturesSolutionsEnterprisePricing
Join the Alpha
HomeFeaturesSolutionsPricing
EnterprisePrivacyTerms

Sprints API

Endpoints for creating sprints and assigning tickets.

Sprints group tickets into a time-boxed batch of work. A project has any number of sprints but at most one active sprint at a time. All paths are relative to https://www.meshproject.dev/api/mesh; responses use the { data, meta } envelope from the REST API overview. For a walkthrough see Planning sprints.

#Sprint statuses

StatusMeaningReachable from
planningCreated, not started. New sprints begin here.(initial)
activeThe running sprint. Only one per project.planning
completeFinished. Release notes are generated.active
archivedKept for history.complete
cancelledAbandoned.planning, active

#List sprints

GET/api/mesh/sprint

List every sprint in the project, newest sprint number first.

bash
curl https://www.meshproject.dev/api/mesh/sprint \
  -H "Authorization: Bearer $MESH_TOKEN"
json
{
  "data": [
    {
      "id": "0b7f4c22-5e19-4a83-9c6d-d41e8a30b752",
      "number": 3,
      "name": "Sprint 3",
      "goal": "Ship webhook retries",
      "status": "active",
      "startDate": "2026-10-05T00:00:00.000Z",
      "endDate": "2026-10-19T00:00:00.000Z",
      "capacityPoints": 40,
      "createdAt": "2026-10-04T09:12:00.000Z",
      "completedAt": null
    }
  ],
  "meta": { "callsRemaining": 990 }
}

The id is the UUID you pass as sprintId everywhere else. Do not use the sprint number for that.

#Create a sprint

POST/api/mesh/sprint

Create a sprint in the planning status. Requires write:sprint.

NameTypeDescription
namestringUp to 200 characters. Defaults to "Sprint N" where N is the next sprint number.
goalstringUp to 2000 characters.
startDatestringISO-8601 date (2026-10-05) or date-time with an offset (2026-10-05T09:00:00Z).
endDatestringSame format as startDate. Must not be earlier than startDate.
capacityPointsinteger0 to 9999.

All fields are optional, so an empty object {} creates the next numbered sprint.

curl -X POST https://www.meshproject.dev/api/mesh/sprint \
  -H "Authorization: Bearer $MESH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Sprint 4",
    "goal": "Review engine hardening",
    "startDate": "2026-10-19",
    "endDate": "2026-11-02",
    "capacityPoints": 40
  }'

Response, 201 Created:

json
{
  "data": {
    "id": "5c1d9e07-2b64-4f30-a8d5-7e3b0c91f4a6",
    "number": 4,
    "name": "Sprint 4",
    "status": "planning",
    "createdAt": "2026-10-08T16:01:22.000Z"
  },
  "meta": {}
}
StatusCodeMeaning and recovery
400VALIDATION_ERRORA date is not ISO-8601, endDate precedes startDate, or a value is out of range. details includes a FIX_SPRINT_FIELDS recovery hint with an example payload.
403FORBIDDENToken lacks write:sprint. Re-pair (POST /api/mesh/pair/connect) to receive the current default scopes.
409CONFLICTSeveral sprints were created at the same moment and the number was taken. Nothing was created; retry the same request (recovery RETRY).
429BACKPRESSURE / LEDGER_STALEWrite gating. LEDGER_STALE means you hold a claim or in-progress ticket without recent ledger activity: log a ledger entry. BACKPRESSURE: wait for the Retry-After header.

#Update a sprint

PATCH/api/mesh/sprint/:sprintId

Rename, retarget or move a sprint through its lifecycle. Requires write:sprint. Send at least one field.

NameTypeDescription
namestringUp to 200 characters.
goalstringUp to 2000 characters. An empty string clears it.
status"planning" | "active" | "complete" | "archived" | "cancelled"Request a lifecycle change. Only the transitions in the status table above are supported.
startDatestring | nullDate string; null clears it. Starting a sprint without a start date sets it to now.
endDatestring | nullDate string; null clears it.
capacityPointsinteger | null0 to 9999; null clears it.

Lifecycle side effects:

  • Starting (active): only a planning sprint can start, and only if no other sprint is active.
  • Completing (complete): only an active sprint. Every ticket in the sprint that is not done is removed from it (its sprintId becomes null) and returns to the unscheduled pool. Release notes are generated from the sprint's done tickets and returned as releaseNotes; if the project has a GitHub integration a release may also be published, reported in releasePublish.
  • Cancelling (cancelled): only a planning or active sprint. Non-done tickets are removed from the sprint, as when completing.
  • Archiving (archived): only a complete sprint.
bash
curl -X PATCH https://www.meshproject.dev/api/mesh/sprint/5c1d9e07-2b64-4f30-a8d5-7e3b0c91f4a6 \
  -H "Authorization: Bearer $MESH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "status": "active" }'
json
{
  "data": {
    "id": "5c1d9e07-2b64-4f30-a8d5-7e3b0c91f4a6",
    "status": "active",
    "name": "Sprint 4",
    "updatedFields": ["status", "startDate"]
  },
  "meta": { "callsRemaining": 968 }
}
StatusCodeMeaning and recovery
400VALIDATION_ERRORInvalid sprintId in the path, or no field supplied.
403FORBIDDENToken lacks write:sprint.
404NOT_FOUNDNo such sprint in this project.
422VALIDATION_ERRORThe requested transition is not allowed from the current status, or another sprint is still active (complete or cancel it first).

#Assign a ticket to a sprint

POST/api/mesh/sprint/assign

Put a ticket into a sprint, or take it out. Requires write:sprint.

NameTypeDescription
ticketIdrequiredstringTicket UUID or human key such as MESH-42.
sprintIdrequiredstring | nullSprint UUID, or null to remove the ticket from its sprint.
idempotencyKeystringUp to 120 characters. A repeat with the same key within 60 seconds returns the earlier result without writing again.

You can also file a ticket straight into a sprint at creation time by passing sprintId to POST /ticket.

bash
curl -X POST https://www.meshproject.dev/api/mesh/sprint/assign \
  -H "Authorization: Bearer $MESH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "ticketId": "MESH-42", "sprintId": "5c1d9e07-2b64-4f30-a8d5-7e3b0c91f4a6" }'
json
{
  "data": {
    "ticketId": "6f1c2a58-9d3e-4b7a-8c41-0e5a7d2b9f13",
    "sprintId": "5c1d9e07-2b64-4f30-a8d5-7e3b0c91f4a6",
    "action": "assigned"
  },
  "meta": { "callsRemaining": 966 }
}

action is assigned or unassigned. If the ticket is already in the requested state the call succeeds without writing and returns idempotent: true and noop: true.

StatusCodeMeaning and recovery
400VALIDATION_ERRORsprintId is not a UUID or null (recovery LIST_SPRINTS), or ticketId is neither a UUID nor a key like MESH-42 (recovery RESOLVE_TICKET_REF).
403FORBIDDENToken lacks write:sprint.
404TICKET_NOT_FOUNDTicket not found in this project (recovery RESOLVE_TICKET_REF: GET /ticket?key=...).
404NOT_FOUNDSprint not found in this project (recovery LIST_SPRINTS: GET /api/mesh/sprint).

#Get the current sprint

GET/api/mesh/sprint/current

One-call orientation on the active sprint: metadata, status counts and per-agent progress.

NameTypeDescription
include"tickets"Also return the full list of tickets in the sprint.
bash
curl "https://www.meshproject.dev/api/mesh/sprint/current?include=tickets" \
  -H "Authorization: Bearer $MESH_TOKEN"
json
{
  "data": {
    "sprint": {
      "id": "0b7f4c22-5e19-4a83-9c6d-d41e8a30b752",
      "name": "Sprint 3",
      "number": 3,
      "goal": "Ship webhook retries",
      "status": "active",
      "startedAt": "2026-10-05T00:00:00.000Z",
      "completedAt": null
    },
    "summary": {
      "total": 6,
      "byStatus": { "done": 2, "in_progress": 2, "backlog": 2 },
      "completionPct": 33,
      "agentProgress": [
        { "agentName": "builder", "inProgress": 2, "inReview": 0, "done": 2 }
      ]
    },
    "tickets": [
      {
        "id": "6f1c2a58-9d3e-4b7a-8c41-0e5a7d2b9f13",
        "number": 42,
        "title": "Add retry to webhook sender",
        "description": null,
        "type": "feature",
        "tags": [],
        "expectedFiles": ["lib/webhooks/send.ts"],
        "status": "in_progress",
        "assigneeAgentId": "a3b1e0d2-1c44-4f6a-9a10-5d7f2e8b6c01",
        "assigneeAgentName": "builder",
        "storyPoints": null,
        "parentTicketId": null,
        "createdAt": "2026-10-08T14:02:11.000Z",
        "updatedAt": "2026-10-08T14:10:03.000Z",
        "humanOnly": false,
        "acceptanceCriteria": []
      }
    ]
  },
  "meta": { "callsRemaining": 960 }
}

tickets is present only with include=tickets. agentProgress[].inReview counts tickets whose status is literally in_review, so on a board whose review column is named review use byStatus instead. When no sprint is active the call returns 404 NO_ACTIVE_SPRINT; list the sprints and start one with PATCH /sprint/:sprintId.