---
title: "Diff (and optionally apply) a plan document against the live board"
method: POST
path: "/board/sync-plan"
tags: ["Internal"]
---

# Diff (and optionally apply) a plan document against the live board

`POST /board/sync-plan`

## Request

- Base URL: `https://api.apphandoff.com/api`
- URL: `https://api.apphandoff.com/api/board/sync-plan`
- Auth: API key in cookie `sb-access-token`

Parses a plan document, diffs it against the live board (programs, milestones, plan-managed tickets) for the target project, and either reports the diff (`mode: dry_run`, the default) or applies it (`mode: apply`). `dry_run` never touches the DB. `apply` is refused whole when the diff has conflicts (`PLAN_CONFLICT`) or when any op fails partway through (`PLAN_PARTIAL`) — both return the receipt so far for inspection. Board edits are protected (plan rule 11): a field changed on the board since the last sync is reported as a conflict `board edit on <kind> <key>: <field>` and never overwritten, unless that program, milestone or ticket carries `force: true` in the plan document (optional boolean per item), which overwrites it with a receipt warning.

## Request body

- object
  - `project_id` string, uuid, required
  - `mode` 'dry_run' | 'apply'
  - `plan` object, required: The plan document (programs, milestones, tickets) to diff against the board.

## Response `200`

Diff report (dry_run) or write receipt (apply)

- object
  - `receipt` object, required
  - `diff_summary` object, required
  - `ops` object[]: Full op list — dry_run responses only.

## Other responses

- `400`: Plan failed schema validation
- `401`: Not authenticated
- `403`: Forbidden
- `409`: Apply blocked by board conflicts, or partially applied
- `429`: Rate limited

---

[API](https://skmtc.dev/apphandoff/apis/apphandoff-api.md) · [All operations](https://skmtc.dev/apphandoff/apis/apphandoff-api/llms.txt) · [OpenAPI document](https://skmtc.dev/apphandoff/apis/apphandoff-api/revisions/9f138fe46d56?raw)
