---
title: "Promote Due Schedule Obligations"
method: POST
path: "/extensions/roboledger/{graph_id}/operations/promote-obligations"
tags: ["RoboLedger: Fiscal Close"]
---

# Promote Due Schedule Obligations

`POST /extensions/roboledger/{graph_id}/operations/promote-obligations`

## 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/promote-obligations`
- Auth: one of:
  - API key in header `X-API-Key`
  - HTTP bearer

Promote matured pending schedule obligations (schedule_entry_due events whose period boundary has passed) to 'classified', and — when dispatch_handlers=true (default) — draft their closing entries in the same transaction. Also reaches stranded obligations: events already 'classified' (by an earlier flip-only sweep) whose closing entry was never drafted are dispatched in the same pass, and reported via stranded_count. This is the on-demand form of the background obligation-promotion sweep; run it before close-period when a schedule was just created or when you can't wait for the Dagster sensor. Idempotent: re-running skips already-classified obligations and reconciles to existing drafts.

**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

- PromoteObligationsRequest: On-demand trigger for the obligation-promotion sweep. Mirrors what the `scheduled_obligation_promoter` Dagster sensor does on its tick, but lets an interactive caller or an MCP close co-pilot run it now instead of waiting for the background cadence — required to drive a schedule-driven close to completion in a single session. Flips matured `pending` `schedule_entry_due` events (period boundary passed) to `classified`; with `dispatch_handlers` it also drafts the closing entries in the same transaction (idempotent — reconciles to an existing draft). The sweep also reaches *stranded* obligations — already `classified` (by an earlier flip-only sweep) but with no closing entry ever drafted — dispatching them in the same pass.
  - `dispatch_handlers` boolean: When True (default), also fire the schedule_entry_due handler for each promoted obligation so the draft closing entry materializes immediately (autopilot). When False, flip status only (co-pilot) — the draft is created separately.

## Response `200`

Successful Response

- OperationEnvelopePromoteObligationsResponse
  - `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` PromoteObligationsResponse: Counts from a single on-demand promotion sweep.
    - `classified_count` integer, required: Matured obligations flipped pending → classified.
    - `dispatched_count` integer, required: Obligations whose closing entry was drafted this run.
    - `error_count` integer, required: Per-obligation handler errors (non-fatal).
    - `stranded_count` integer: Matured obligations found already at 'classified' with no drafted closing entry. With dispatch_handlers=true they were drafted this run (included in dispatched_count); with dispatch_handlers=false they still have no draft — re-run with dispatch_handlers=true or void them.
    - `classified_event_ids` string[]
    - `stranded_event_ids` string[]: Event ids of the stranded obligations found this sweep.
    - `errors` object[]: Per-obligation errors as {event_id, error}; the sweep continues past them.
  - `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)
