Skip to content

Milestone Operations (v2)

GraphQL operations for managing a goal's milestones. There is no v1 equivalent — milestones are new in the v2 API. See the v2 guide for archive/delete semantics across v2 entities, and Goal Operations for creating milestones alongside a goal.

API Reference

MilestoneOperations

MilestoneOperations(client: Any, graphql_url: str, user_id_cache: UserIdCache | None = None)

Bases: GraphQLOperations, MilestoneOperationsMixin

Class to handle v2 (GraphQL) operations related to goal milestones.

Note

The GraphQL API has no root milestone(id) query, so a milestone is read through its parent goal. update() and complete() therefore take goal_id: they check that milestone_id belongs to that goal before writing anything, then re-read the milestone through it.

Methods:

  • list –

    List the milestones of a goal.

  • create –

    Create a new milestone on a goal.

  • update –

    Update an existing milestone.

  • complete –

    Mark a milestone as completed.

  • delete –

    Delete (soft-delete) a milestone.

Methods:

list
list(goal_id: int) -> list[Milestone]

List the milestones of a goal.

Parameters:

  • goal_id (int) –

    The ID of the goal.

Returns:

  • list[Milestone] –

    A list of Milestone model instances, ordered by due date.

Example
client.v2.milestone.list(5265048)
# Returns: [Milestone(id=1, title='Draft spec', ...), ...]
create
create(goal_id: int, title: str, due_date: TimeInput, completed: bool = False) -> Milestone

Create a new milestone on a goal.

Parameters:

  • goal_id (int) –

    The ID of the goal (rock) to attach the milestone to.

  • title (str) –

    The title of the milestone.

  • due_date (TimeInput) –

    The due date, as a datetime, date, or unix timestamp (seconds).

  • completed (bool, default: False ) –

    Whether the milestone starts out completed.

Returns:

Example
client.v2.milestone.create(5265048, "Draft spec", date(2026, 1, 1))
# Returns: Milestone(id=1, goal_id=5265048, title='Draft spec', ...)
update
update(milestone_id: int, *, goal_id: int, title: str | None = None, due_date: TimeInput | None = None, completed: bool | None = None) -> Milestone

Update an existing milestone.

Parameters:

  • milestone_id (int) –

    The ID of the milestone to update.

  • goal_id (int) –

    The ID of the milestone's parent goal (see the class Note).

  • title (str | None, default: None ) –

    New title for the milestone.

  • due_date (TimeInput | None, default: None ) –

    New due date, as a datetime, date, or unix timestamp (seconds).

  • completed (bool | None, default: None ) –

    New completed state.

Returns:

Raises:

  • ValueError –

    If no update fields are provided.

Example
client.v2.milestone.update(1, goal_id=5265048, title="Draft spec v2")
# Returns: Milestone(id=1, title='Draft spec v2', ...)
complete
complete(milestone_id: int, *, goal_id: int) -> Milestone

Mark a milestone as completed.

Parameters:

  • milestone_id (int) –

    The ID of the milestone to complete.

  • goal_id (int) –

    The ID of the milestone's parent goal (see the class Note).

Returns:

delete
delete(milestone_id: int) -> None

Delete (soft-delete) a milestone.

Parameters:

  • milestone_id (int) –

    The ID of the milestone to delete.

Example
client.v2.milestone.delete(1)

Async Version

The async version AsyncMilestoneOperations provides the same methods as above, but with async/await support:

AsyncMilestoneOperations

AsyncMilestoneOperations(client: Any, graphql_url: str, user_id_cache: UserIdCache | None = None)

Async class to handle v2 (GraphQL) operations related to goal milestones.

Note

See MilestoneOperations for why update() and complete() require goal_id.

Async Usage

All methods have the same parameters and return types as their sync counterparts. Simply add await before each method call.

Usage Examples

from datetime import date
from bloomy import Client

with Client(api_key="your-api-key") as client:
    # Create a milestone on an existing goal
    milestone = client.v2.milestone.create(
        goal_id=5265048,
        title="Draft spec",
        due_date=date(2026, 1, 1),
    )

    # List a goal's milestones
    milestones = client.v2.milestone.list(goal_id=5265048)

    # Update it (goal_id is keyword-only: verified before anything is
    # written, and needed again to re-read the milestone afterward)
    updated = client.v2.milestone.update(
        milestone.id, goal_id=milestone.goal_id, title="Draft spec v2"
    )

    # Mark it complete
    completed = client.v2.milestone.complete(
        milestone.id, goal_id=milestone.goal_id
    )

    # Delete it (no goal_id needed, and there is no restore)
    client.v2.milestone.delete(milestone.id)
import asyncio
from bloomy import AsyncClient
from datetime import date

async def main():
    async with AsyncClient(api_key="your-api-key") as client:
        # Create a milestone on an existing goal
        milestone = await client.v2.milestone.create(
            goal_id=5265048,
            title="Draft spec",
            due_date=date(2026, 1, 1),
        )

        # Mark it complete
        completed = await client.v2.milestone.complete(
            milestone.id, goal_id=milestone.goal_id
        )

        # Delete it
        await client.v2.milestone.delete(milestone.id)

asyncio.run(main())

Available Methods

Method Description Parameters Returns
list() List a goal's milestones, ordered by due date goal_id list[Milestone]
create() Create a milestone on a goal goal_id, title, due_date, completed Milestone
update() Update a milestone milestone_id, goal_id (keyword-only), title, due_date, completed Milestone
complete() Mark a milestone as completed milestone_id, goal_id (keyword-only) Milestone
delete() Delete a milestone milestone_id None

Why update() and complete() need goal_id

There is no root GraphQL query to read a single milestone by id — a milestone can only be read through its parent goal (goal(id){ milestones }). update() and complete() need goal_id (keyword-only) in addition to milestone_id: both verify that milestone_id actually belongs to goal_id before sending any edit, so a wrong goal_id fails without writing anything, and then need goal_id again to re-read the milestone afterward. delete() needs neither, since DeleteMilestone does not return a Milestone.

No archive/restore — only delete

Milestones do not archive; delete() permanently removes one (soft-deleted server-side, but with no SDK-exposed way to bring it back).

Update requirements

At least one field must be provided to update(), or it raises ValueError.