neosourceDocs
Search docs

End a cycle and carry its unfinished work forward (idempotent)

POST/api/cycles/{cycle_id}/complete

completeCycle

Completing a cycle used to set `completed_at` and nothing else, which left every unfinished issue pointing at a window that had closed. It now moves them, in the same transaction, to the team's next cycle — or unbinds them when there is none. The response reports what was moved and where; `GET /api/cycles/{cycle_id}/carryover` answers the same question before the write. Re-completing an already-complete cycle moves nothing and says so.

Requires authentication using a bearer token or a session cookie — see tokens and scopes.

curl

curl -X POST 'https://neosource.dev/api/cycles/CYCLE_ID/complete' \
  -H 'Authorization: Bearer $NEOSOURCE_TOKEN'

fetch

fetch("https://neosource.dev/api/cycles/CYCLE_ID/complete", {
  method: "POST",
  headers: {
    Authorization: "Bearer $NEOSOURCE_TOKEN",
  },
});

Path parameters

cycle_idrequired

string

Responses

200Cycle completed

application/json

CompleteCycleResponse

object

Receipt for `POST /api/cycles/{cycle_id}/complete`.

already_completedrequired

boolean

The cycle was already complete, so this call moved nothing. Without it `carried.count == 0` would mean either "nothing was left" or "someone else finished it first", and the two deserve different words.

carriedrequired

CycleCarryoverResponse

object

What completing a cycle does with the work still open in it. The SAME shape answers `GET /api/cycles/{id}/carryover` (before the write, so the confirmation can name the move) and rides `CompleteCycleResponse` (after it, as the receipt). One shape, because the forecast and the receipt are the same two facts, and because a user who reads "3 tickets move to Cycle 26" is owed a report in the same terms. `to_cycle: null` is not "unknown" — it is the team's backlog. A cycle with no successor still ends, and its remainder becomes uncycled rather than staying pinned to a window that is over.

countrequired

integerint64

Unfinished issues: the forecast on the plan, the actual on the receipt.

to_cycle
one of
cyclerequired

CycleResponse

object

completed_at

integer | nullint64

created_atrequired

integerint64

cycle_idrequired

string

ends_atrequired

integerint64

initiative_id

string | null

namerequired

string

progressrequired
CycleProgressResponse
starts_atrequired

integerint64

team_idrequired

string

403Forbidden — one of: forbidden, needs_scope

application/json

one of
  • ErrorForbidden
  • NeedsScopeError

    object

    `403` body returned when listing private repos but the linked identity lacks the required provider scope. The SPA turns this into an incremental-authorization prompt (Tier 2) that calls the `/elevate` OAuth endpoint with this `scope`.

    errorrequired

    string

    Always `needs_scope` — this body exists to carry the extra fields that kind needs.

    "needs_scope"

    scoperequired

    string

    The provider scope to request via elevation (e.g. `"repo"`).

Standard errors

Bodies documented once for the whole API — see standard errors.

  • 400Bad Request — one of: invalid_input
  • 401Authentication required
  • 404Not Found — one of: not_found
  • 409Conflict — one of: already_exists, conflict
  • 429Rate limited — retry after the `Retry-After` header
  • 500Internal server error
  • 503Service temporarily unavailable / at capacity — retry after the `Retry-After` header
  • 504Gateway timeout — the request exceeded the server's handling budget

Schemas

Referenced above. Listed here rather than expanded inline, so the same definition is not repeated at every level.

CycleRef

object

Just enough of a cycle to name it in a sentence about another cycle.

cycle_idrequired

string

namerequired

string

CycleProgressResponse

object

How much work a cycle holds and how far through it the team is. Present on EVERY cycle payload, not just the listing: a surface that has a cycle in hand should never have to ask a second endpoint whether it contains anything. The four numbers are computed over one set — every live issue bound to the cycle — which is the same set the cycle's issue listing returns and the same set completing the cycle carries over, so a progress bar can never describe a different cycle from the rows under it. `started` and `blocked` overlap each other and both sit inside `total - done`; they are two views of the unfinished remainder rather than a partition, and are reported as such.

blockedrequired

integerint64

...flagged blocked and unfinished.

donerequired

integerint64

...in a terminal status (`done` / `cancelled` / `duplicate`).

startedrequired

integerint64

...started and unfinished: `in_progress` or `in_review`.

totalrequired

integerint64

Live issues bound to this cycle.