---
title: "Backfill Plan History"
method: POST
path: "/extensions/roboledger/{graph_id}/operations/backfill-plan-history"
tags: ["RoboLedger: Fiscal Close"]
---

# Backfill Plan History

`POST /extensions/roboledger/{graph_id}/operations/backfill-plan-history`

## Request

- Base URL: `https://api.robosystems.ai` (the document declares no server; this is the origin it was published from)
- URL: `https://api.robosystems.ai/extensions/roboledger/{graph_id}/operations/backfill-plan-history`
- Auth: one of:
  - API key in header `X-API-Key`
  - HTTP bearer

Compile monthly statement history behind the close boundary — the plan's historical columns. Seeds any missing FiscalPeriod rows (baseline-closed) back to the clamped `start_period`, then restamps each month lacking canonical statement FactSets by running the real reopen → reclose cycle (balance validation, statement rules, and audit events per month). Chunked: at most `max_periods` months per call, oldest first — loop until `remaining_periods` comes back empty. Idempotent: already-stamped months are never touched. Months holding draft entries are skipped, never posted. `start_period` is clamped to the earliest month with ledger data, so deep-history tenants only backfill what actually exists.

**Idempotency**: supply an `Idempotency-Key` header to make safe retries; replays within 24 hours return the same envelope. Reusing the key with a different body returns HTTP 409 Conflict.

## Path parameters

- `graph_id` string, required

## Headers

- `Idempotency-Key` string, nullable

## Request body

- BackfillPlanHistoryOperation: Compile monthly statement history behind the close boundary.
  - `entity_id` string, nullable: The entity whose books this acts on, by id. Omit for the group parent — the single-entity default.
  - `start_period` string, nullable: YYYY-MM period to backfill from. Clamped to the earliest month with ledger data; defaults to that month when omitted. Must be on or before `closed_through`.
  - `max_periods` integer: Maximum months to restamp in this call. Each month runs a full reopen → reclose cycle; keep chunks modest and loop on `remaining_periods`.
  - `allow_stale_sync` boolean: Override the sync-currency gate on each reclose. Historical months predate the last sync in the normal case, so this is rarely needed.
  - `allow_stranded_obligations` boolean: Override the stranded-obligation gate on each reclose. Only needed when a matured classified obligation without a drafted entry exists inside the backfill window and you have decided not to draft or void it first.
  - `allow_reconciling_items` boolean: Override the reconciling-item gate on each reclose. Only needed when an event inside the backfill window is still flagged as changed upstream and you have decided not to resolve it first.
  - `allow_unposted_source_events` boolean: Override the unposted-source-event gate on each reclose. Only needed when a source event inside the backfill window was never committed and you have decided not to commit or void it first.
  - `allow_unreconciled_accounts` boolean: Override the reconciliation gate on each reclose. Needed when a month inside the backfill window has a reconciliation the close waits on that was never compared for it, which is every month closed before the reconciliation existed.
  - `allow_unexplained_residuals` boolean: Override the group close's residual gate on each reclose of the consolidation entity's months.
  - `restamp` boolean: Also re-derive months that ALREADY have canonical statement sets (default: skip them). Use after an engine improvement changes what a stamp produces — each month reruns the full reopen → reclose cycle and replaces its sets. A restamp run is not self-resuming (every month in range stays a candidate); advance `start_period` between chunks.
  - `note` string, nullable: Free-form note attached to each close audit event

## Response `200`

Successful Response

- OperationEnvelopeBackfillPlanHistoryResponse
  - `operation` string, required: Kebab-case operation name
  - `operationId` string, required: op_-prefixed ULID for audit and SSE correlation
  - `status` 'completed' | 'pending' | 'failed', required: Operation lifecycle state
  - `result` BackfillPlanHistoryResponse: Response from one chunked plan-history backfill call.
    - `fiscal_calendar` FiscalCalendarResponse, required: Current fiscal calendar state for one entity of a graph.
      - `graph_id` string, required
      - `entity_id` string, nullable: The entity whose calendar this is.
      - `fiscal_year_start_month` integer, required
      - `closed_through` string, nullable: Latest closed period (YYYY-MM), or null if nothing closed
      - `close_target` string, nullable: Target period the user wants closed through (YYYY-MM)
      - `gap_periods` integer: Number of periods between closed_through and close_target (inclusive of close_target). 0 means caught up.
      - `catch_up_sequence` string[]: Ordered list of periods that a close run would process
      - `closeable_now` boolean: Whether the next period in the catch-up sequence passes all closeable gates
      - `blockers` string[]: Structured blocker codes when closeable_now is False: 'sequence_violation', 'period_incomplete', 'sync_stale', 'calendar_not_initialized', 'period_already_closed', 'pending_obligations', 'stranded_obligations', 'reconciling_items', 'unposted_source_events', 'unreconciled_accounts'; on the consolidation entity, whose close is the group's, also 'entity_open', 'adjustments_unposted', 'residual_unexplained', 'consolidated_does_not_foot'
      - `pending_obligation_count` integer: Number of pending schedule_entry_due events blocking close. Non-zero only when `pending_obligations` is in `blockers`.
      - `pending_obligation_sample` PendingObligationDetailResponse[]: Sample of up to 5 pending obligations (schedule_id, schedule_name, period, event_id) ordered by occurred_at. Use `list-event-blocks` with event_type=schedule_entry_due&status=pending for the full set.
        - `event_id` string, required
        - `schedule_id` string, nullable
        - `schedule_name` string, nullable
        - `period` string, required: Period in YYYY-MM format
      - `earliest_pending_period` string, nullable: Earliest period (YYYY-MM) with a pending obligation blocking close. Null when no pending_obligations blocker is active.
      - `sync_stale_days` integer, nullable: Days the most recent sync is stale relative to the period to close. Populated only when `sync_stale` is in `blockers` and last_sync_at exists (null when there's a connection but no sync has ever run).
      - `stranded_obligation_count` integer: Matured schedule_entry_due events already at 'classified' with no drafted closing entry for their (schedule, period) — adjusting entries a close would silently omit. Resolve by running promote-obligations with dispatch_handlers=true (which reaches them) or voiding the obligation.
      - `stranded_obligation_sample` PendingObligationDetailResponse[]: Sample of up to 5 stranded obligations (schedule_id, schedule_name, period, event_id) ordered by occurred_at.
        - `event_id` string, required
        - `schedule_id` string, nullable
        - `schedule_name` string, nullable
        - `period` string, required: Period in YYYY-MM format
      - `reconciling_item_count` integer: Posted events in or before this period whose source payload changed afterwards and that nobody has dispositioned — differences between the books and the source system. Resolve each with resolve-reconciling-item, or close over them knowingly with allow_reconciling_items.
      - `reconciling_item_sample` string[]: Source identifiers (or event ids) of up to 5 unresolved reconciling items, so the blocker names what is holding the close.
      - `unposted_source_event_count` integer: Source events dated in this period that were never committed: bank-feed lines still captured or classified, and QuickBooks transactions whose automatic posting failed. Commit or void each, or close over them knowingly with allow_unposted_source_events.
      - `unposted_source_event_sample` string[]: Source identifiers (or event ids) of up to 5 unposted source events, so the blocker names what is holding the close.
      - `unreconciled_account_count` integer: Reconciliations the close waits on that are not reconciled for this period, including any never compared for it. Run refresh-reconciliations and clear what it reports, release one with set-reconciliation-policy, or close over them knowingly with allow_unreconciled_accounts.
      - `unreconciled_account_sample` string[]: Name and status of up to 5 of those reconciliations, so the blocker names what is holding the close.
      - `group_open_entities` string[]: The group close: the entities the month consolidates, the parent and its equity-method members included, that have not closed it. Close each first.
      - `group_pending_adjustments` string[]: The group close: the consolidating entries the consolidation entity neither holds nor has drafted, by kind and member, or what the engine left out and why. Run propose-consolidation-entries.
      - `group_unexplained` string[]: The group close: residuals and intercompany disagreements left unexplained. Correct the books or post the explaining entry, or close over them knowingly with allow_unexplained_residuals.
      - `group_not_footing` string[]: The group close: the consolidated balance sheet's and equity statement's validation failures for the month.
      - `last_close_at` string, date-time, nullable
      - `initialized_at` string, date-time, nullable
      - `last_sync_at` string, date-time, nullable: Most recent QB sync timestamp (if connected)
      - `periods` FiscalPeriodSummary[]: Fiscal period rows for this graph
        - `name` string, required: Period name (YYYY-MM)
        - `start_date` string, date, required
        - `end_date` string, date, required
        - `status` string, required: 'open' | 'closing' | 'closed'
        - `closed_at` string, date-time, nullable
        - `has_close_receipt` boolean: Whether this period carries a close receipt. A flag rather than the receipt itself keeps the calendar listing compact; fetch the receipt from `get-period-close-status` for the period. False on open periods and on periods closed before receipts shipped.
    - `earliest_available_period` string, required: First month with ledger data — the hard floor for backfill
    - `effective_start_period` string, required: The start actually used after clamping to earliest_available_period
    - `closed_through` string, required: The close boundary the backfill runs up to (inclusive)
    - `period_rows_created` integer: FiscalPeriod rows seeded (baseline-closed) for months the calendar didn't cover yet
    - `processed` BackfillPeriodOutcome[]: Months this call attempted, oldest first
      - `period` string, required: The month, in YYYY-MM
      - `status` string, required: stamped: reopen → reclose completed. skipped_drafts: the month holds draft entries the backfill refuses to post — review via list-period-drafts, then close-period or re-run. failed: the reclose raised; processing halted (see detail).
      - `statements_stamped` boolean: Whether the reclose stamped canonical statement FactSets. False with a statement_stamp_note soft-skip when reporting isn't set up.
      - `statement_stamp_note` string, nullable: Soft-skip reason when statements_stamped is false
      - `statement_rule_summary` object, nullable: Statement-rule verification tally for the month's stamped sets (pass/fail/error/skipped); None when no rules ran.
      - `detail` string, nullable: Human-readable detail for skipped/failed months
    - `remaining_periods` string[]: Months still lacking canonical statement sets that this call did not attempt (beyond max_periods, or after a failure halt). Loop until empty.
  - `at` string, required: ISO-8601 UTC timestamp
  - `createdBy` string, nullable: User ID that initiated the operation
  - `idempotentReplay` boolean: True when this envelope came from the idempotency cache — the underlying command did not execute again. False on fresh executions.

## Other responses

- `400`: Invalid request
- `401`: Authentication required
- `403`: Access denied
- `404`: Resource not found
- `409`: Idempotency-Key conflict — key reused with different body
- `422`: Validation error
- `429`: Rate limit exceeded
- `500`: Internal server error

---

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