Browse the API reference

Close Fiscal Period

post/extensions/roboledger/{graph_id}/operations/close-period

Part of Extensions: RoboLedger.

Lock a single fiscal period. Posts draft entries, runs the balance-sheet equation check, advances closed_through by one, auto-advances close_target if this close caught up to it, and stamps the period's canonical statement FactSets from the posted ledger (statements_stamped / stamped_statement_sets in the response; soft-skipped with statement_stamp_note when reporting isn't set up). Period must be exactly closed_through + 1 — sequence violations return 422 with structured blockers. Common blockers: sync_stale (override with allow_stale_sync=true after manual verification), period_incomplete (draft entries unbalanced), sequence_violation (out-of-order).

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.

Authentication

Authenticate in any one of these ways — not all of them:

  • API key in the X-API-Key header.
  • Bearer token in the Authorization header.

Path parameters

NameTypeDescription
graph_idrequiredstringGraph Id

Header parameters

NameTypeDescription
Idempotency-KeyoptionalstringIdempotency-Key

Request body

Required, application/json.

FieldTypeDescription
noteoptionalstring

Free-form note attached to the close event

allow_stale_syncoptionalboolean

Override the sync-currency gate. Only use when you have manually verified that the source data for the period is complete.

Default: false

allow_stranded_obligationsoptionalboolean

Override the stranded-obligation gate — close even though matured classified obligations have no drafted closing entry, knowingly omitting those adjusting entries from the period. Prefer running promote-obligations with dispatch_handlers=true (which drafts them) or voiding the obligations instead. The override is recorded in the close audit note.

Default: false

allow_reconciling_itemsoptionalboolean

Override the reconciling-item gate — close even though posted events in the period are still flagged as changed in the source system, leaving those differences undecided. The next sync will still report them, and the statements stamped by this close may disagree with the source. Prefer resolve-reconciling-item on each first. The override is recorded in the close audit note.

Default: false

periodrequiredstring

Period to close, in YYYY-MM. Must be exactly `closed_through + 1` — close runs sequentially.

Example request

curl
curl -X POST "https://api.robosystems.ai/extensions/roboledger/{graph_id}/operations/close-period" \
  -H "X-API-Key: $ROBOSYSTEMS_API_KEY" \
  -H "Idempotency-Key: <Idempotency-Key>" \
  -H "Content-Type: application/json" \
  -d '{
  "period": "2026-03"
}'

Responses

200 Successful Response

FieldTypeDescription
operationrequiredstring

Kebab-case operation name

operationIdrequiredstring

op_-prefixed ULID for audit and SSE correlation

statusrequiredstring

Operation lifecycle state

One of: completed, pending, failed

resultoptionalClosePeriodResponse

Command-specific result payload

ClosePeriodResponse fields
FieldTypeDescription
fiscal_calendarrequiredFiscalCalendarResponse

Current fiscal calendar state for a graph.

FiscalCalendarResponse fields
FieldTypeDescription
graph_idrequiredstring
fiscal_year_start_monthrequiredinteger
closed_throughoptionalstring

Latest closed period (YYYY-MM), or null if nothing closed

close_targetoptionalstring

Target period the user wants closed through (YYYY-MM)

gap_periodsoptionalinteger

Number of periods between closed_through and close_target (inclusive of close_target). 0 means caught up.

Default: 0

catch_up_sequenceoptionalstring[]

Ordered list of periods that a close run would process

closeable_nowoptionalboolean

Whether the next period in the catch-up sequence passes all closeable gates

Default: false

blockersoptionalstring[]

Structured blocker codes when closeable_now is False: 'sequence_violation', 'period_incomplete', 'sync_stale', 'calendar_not_initialized', 'period_already_closed', 'pending_obligations', 'stranded_obligations'

pending_obligation_countoptionalinteger

Number of pending schedule_entry_due events blocking close. Non-zero only when `pending_obligations` is in `blockers`.

Default: 0

pending_obligation_sampleoptionalPendingObligationDetailResponse[]

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.

earliest_pending_periodoptionalstring

Earliest period (YYYY-MM) with a pending obligation blocking close. Null when no pending_obligations blocker is active.

sync_stale_daysoptionalinteger

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_countoptionalinteger

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.

Default: 0

stranded_obligation_sampleoptionalPendingObligationDetailResponse[]

Sample of up to 5 stranded obligations (schedule_id, schedule_name, period, event_id) ordered by occurred_at.

reconciling_item_countoptionalinteger

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.

Default: 0

reconciling_item_sampleoptionalstring[]

Source identifiers (or event ids) of up to 5 unresolved reconciling items, so the blocker names what is holding the close.

last_close_atoptionalstring (date-time)
initialized_atoptionalstring (date-time)
last_sync_atoptionalstring (date-time)

Most recent QB sync timestamp (if connected)

periodsoptionalFiscalPeriodSummary[]

Fiscal period rows for this graph

periodrequiredstring
entries_postedoptionalinteger

Total draft entries the close transitioned to posted, across both post paths (QB pre-publish + local bulk transition). See entries_published_to_qb / entries_posted_locally for the split.

Default: 0

entries_published_to_qboptionalinteger

Drafts published to QuickBooks by the close's pre-publish step (each is promoted to posted at publish time).

Default: 0

entries_posted_locallyoptionalinteger

Drafts posted by the local bulk transition (entries that don't publish to QuickBooks, e.g. native-only graphs or local-only sources).

Default: 0

target_auto_advancedoptionalboolean

Whether close_target was auto-advanced because it was reached

Default: false

rule_summaryoptionalobject

Aggregated rule-eval outcome across every schedule Structure with facts in the closed period — keys: pass/fail/error/skipped. None when no schedules had facts in the period (auto-run on close).

evaluated_structure_idsoptionalstring[]

ids of schedule Structures whose rules were evaluated during the close. Pairs with rule_summary.

statements_stampedoptionalboolean

Whether the close stamped the period's canonical statement FactSets (the close-time pivot). False when the tenant hasn't set up reporting yet — see statement_stamp_note.

Default: false

statement_stamp_noteoptionalstring

Soft-skip reason when statements_stamped is false: no_coa_mapping | no_entity | no_statement_structures | no_taxonomy.

stamped_statement_setsoptionalobject

structure_id -> fact_set_id for every canonical statement FactSet minted by this close (report_id NULL; replaced on reclose).

statement_rule_summaryoptionalobject

Aggregated statement-rule verification outcome across the stamped structures — keys: pass/fail/error/skipped. None when no statement rules exist. Distinct from rule_summary (the schedule-rule pass).

atrequiredstring

ISO-8601 UTC timestamp

createdByoptionalstring

User ID that initiated the operation (null for legacy callers)

idempotentReplayoptionalboolean

True when this envelope came from the idempotency cache — the underlying command did not execute again. False on fresh executions.

Default: false

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