XY Logo
Developer hub

Custom workflows

Describe the work. Test the reality. Promote deliberately.

The Planner agent turns a natural-language operating specification into tested activities and a reusable workflow. It is a durable review loop—not a single prompt that silently deploys code.

Mental model

The production boundary is explicit.

A build is authoring state. A step is a proposed activity. Promotion creates a reusable workflow definition. A schedule operates that definition. A run is one execution.
1

Build

Describe a new flow or supply existing YAML to modify.

2

Review plan

Approve the whole plan before step generation or testing, or reject it with revision feedback.

3

Test and decide

Test each generated step, inspect its results, then explicitly accept or request changes.

4

Promote

Use separate promotion authority to create an organization production definition.

5

Operate

Run once or on a schedule, observe phases, and query analytics.

Capability boundary

What you can build through the API today.

Production workflows demonstrate that the orchestration model can support much more than a linear browser macro. The table below says which of those building blocks are actually partner-accessible today.
CapabilityAvailabilityWhat that means
Build from a Browser Agent recordingSelf-serviceCopy the displayed Recorded Task ID from an organization-owned recording and send it as context.recorded_task_id. REST, MCP, and the web app use the same FDE Planner path.
Portal navigation, extraction, upload, and downloadSelf-serviceThe Planner agent creates a browser step; use Studio when it needs direct work.
Pure computation and data normalizationSelf-serviceThe Planner agent can create organization-scoped compute activities.
OCR and structured document extractionSelf-serviceThe Planner agent can compose the platform OCR activity and downstream transforms.
Sequence, parallel work, loops, branches, child flows, and error handlingSelf-serviceDescribe cardinality, concurrency, stop rules, and failure behavior explicitly.
Call an existing integration capabilityExisting setup requiredThe organization must already have an active connection and exposed capability.
Create a new HTTP API integration from documentationSelf-serviceThe Integration Factory discovers, generates, validates, and promotes organization-owned capabilities before the Planner agent uses them.
Password, TOTP, PIN, and portal credential usePassword Vault referenceAdd the secret in Password Vault or provision it with SYSTEM credentials:write. Discover safe metadata with credentials:read, then bind the exact visible ID with personal or SYSTEM planner:write. Secrets never belong in the Planner agent request.
Automated SMS OTP retrievalSelf-service first numberProvision or retrieve the organization default with POST /sms-inbox. Use GET /sms-inbox/messages (sms:read) to complete the portal's initial number validation outside a workflow, then bind a credential with sms_phone_type: voip. Replays return the existing number; only XY can add a second inbox.
Automated email OTP retrievalExisting setup requiredAuthorize the organization mailbox first, then bind that mailbox connection and a narrow sender or subject query in the workflow. A credential email field alone does not grant inbox access.
Specialized queues, entity writeback, webhooks, or typed domain clientsXY-builtSubmit the interfaces and acceptance criteria as a custom-agent request.
Recurring production executionSelf-servicePromote, then create and operate a workflow schedule.
One-off execution of an eligible promoted Planner agent definitionSelf-servicePOST /workflow-definitions/{workflow_definition_id}/executions starts an idempotent asynchronous run with optional runtime input. No schedule is required. Coalescing singleton definitions use their managed trigger instead.
Run phases and governed analyticsSelf-service read-onlyRead phase projections and approved metrics without changing runtime state.
Build reporting in a partner UISelf-service read-onlyUse aggregate metrics or presentation-ready metric views with tile data and visualization metadata.
Design an existing workflow's operator queuePersonal or system accessPreview eligible app-managed promoted Planner queues. Approve through human web review or a system key with queue_ui:approve, then publish separately with a live-receipt check. Personal callers need explicit workflow access/configure permission.
Propose an analytics dashboardRelease previewWith dashboards:propose, create or edit an immutable proposal using registered governed query refs. Proposals stop at PREVIEW_READY; a human approves and publishes the exact version in XY.
Start Browser StudioExisting Planner agent step requiredStudio creates a separate retained authoring Chrome for an existing run_browser_workflow step.

Tool map

Choose tools by the job they need to do.

Use the REST routes directly or call the named tools from an MCP client. Both interfaces enforce the same organization, scope, resource, and state boundaries.
NeedMCP toolsWhen to use them
Create and follow the workflow draftplanner_start_build, planner_get_build, planner_get_events, planner_wait_for_eventsStart here for every self-service workflow; reconnect to the same build instead of starting over.
Review the whole planplanner_respond_to_planWith planner:write, APPROVE before generating or testing steps, or REJECT with a comment requesting a revised plan. This does not accept a step or grant publication.
Reuse a demonstrated browser taskplanner_start_buildSet context.recorded_task_id to the ID shown on an organization-owned Browser Agent recording. The Planner uses its transcript, action log, and existing code as context; supply credentials separately.
Supply login or MFA materialcredentials_create, credentials_get, credentials_rotateCreate a secret-backed reference first, then send only credential_id to the Planner agent or Browser Studio.
View a Planner agent test browserplanner_get_browser_session, planner_set_browser_controlObserve or briefly control Chrome already running for generation or a step test. These tools do not create a browser.
Develop one browser stepbrowser_create_session, browser_prompt, browser_run_command, browser_apply_draftCreate a retained Studio session only after the Planner agent has proposed a browser step; apply the draft back for a fresh Planner agent test.
Review and advance stepsplanner_test_step, planner_accept_step, planner_reject_step, planner_retry_step, planner_message_stepTest edits and make every business decision explicitly.
Create the production definitionplanner_promoteRequires organization-service access with planner:promote after all steps are accepted; promotion does not publish a catalog agent.
Run once on demandworkflow_executeStart an asynchronous run of an eligible promoted definition with optional input. Supply your own idempotency key when the caller may retry. Coalescing singleton definitions return a conflict before the key is reserved and must use their managed trigger.
Run on a cadenceschedule_create, schedule_trigger, schedule_pause, schedule_resumeOperate the promoted definition through a recurring schedule or trigger that schedule immediately.
Observe and reportworkflow_get_phases, workflow_metric_dashboards_list, workflow_metric_dashboard_get, dashboard_agent_queryRead safe run progress, exact approved metric views, or governed natural-language analytics; render returned data in your own UI.
Propose a governed analytics dashboarddashboard_proposal_start, dashboard_proposal_getRelease preview: return a durable run immediately, follow shared progress, and hand the immutable preview to a human in XY. No API or MCP approval or publication.

Start and follow

Treat the Planner agent like a durable state machine.

Build creation is asynchronous and idempotent. The request describes outcomes and constraints; context supplies public URLs, safe input/output descriptions, and credential references—not secrets.
  1. Queue the build
    # Use an exact Password Vault credential ID visible to this API identity.
    # The example works with personal or organization-service planner:write access.
    curl --fail-with-body --request POST \
      --header "Authorization: Bearer ${XY_API_KEY}" \
      --header "Idempotency-Key: portal-reconciliation-v1" \
      --header "Content-Type: application/json" \
      "${XY_API_BASE}/planner/builds" \
      --data '{
        "request": "Sign in, collect open records, normalize them, and return a per-record result.",
        "context": {
          "portal_url": "https://portal.example.com/open-records",
          "credential_id": "cred_REPLACE",
          "recorded_task_id": "portal-open-records-v1",
          "inputs": [{"name": "cutoff_date", "type": "string"}],
          "outputs": [{"name": "results", "type": "array"}]
        }
      }'
  2. Read the existing build
    # Inspect the same build after a reload or interrupted command.
    : "${BUILD_ID:?Use the existing public build ID}"
    curl --fail-with-body --max-time 30 \
      --header "Authorization: Bearer ${XY_API_KEY}" \
      "${XY_API_BASE}/planner/builds/${BUILD_ID}"

Write a request that can be tested

  • Name the source, destination, and trigger.
  • Define input and output shapes, including empty cases.
  • State which confirmation proves a write succeeded.
  • Describe list cardinality and concurrency limits.
  • Say what retries, skips, and partial failures mean.
  • Identify human gates such as login, MFA, or review.
  • Separate required results from diagnostic metadata.
  • Use a credential ID; never embed a secret.

Durable progress

Follow existing work across a disconnect.

A public build identifies authoring state; run_id identifies one durable runtime operation. A queued command or closed connection is not proof that work succeeded, failed, or reached review.
  1. Poll saved Planner events
    # Use 0 for the first read; otherwise use the last applied event ID.
    : "${BUILD_ID:?Use the existing public build ID}"
    : "${LAST_EVENT_ID:?Use the cursor saved for this build event stream}"
    curl --fail-with-body --max-time 30 \
      --header "Authorization: Bearer ${XY_API_KEY}" \
      "${XY_API_BASE}/planner/builds/${BUILD_ID}/events?after=${LAST_EVENT_ID}"
  2. Reconnect the Planner event stream
    # Reconnect using the last applied SSE ID from this same stream.
    : "${BUILD_ID:?Use the existing public build ID}"
    : "${LAST_EVENT_ID:?Use the cursor saved for this build event stream}"
    curl --fail-with-body --no-buffer --max-time 60 \
      --header "Authorization: Bearer ${XY_API_KEY}" \
      --header "Accept: text/event-stream" \
      --header "Last-Event-ID: ${LAST_EVENT_ID}" \
      "${XY_API_BASE}/planner/builds/${BUILD_ID}/events?stream=true"
    # A time limit or disconnect ends this read, not the worker.
    # Persist applied event IDs before opening another read; never repeat a mutation.
  1. Load the current snapshot, replay retained events, then tail new events through the same state reducer.
  2. Keep each cursor with its exact stream identity. Shared progress sequences increase per run; a build event cursor and a runtime sequence are not interchangeable.
  3. Apply events in sequence order, ignore already-applied IDs, and save the cursor only after applying the event. On refresh, reconstruct from the snapshot and retained history.
  4. Use bounded polling when SSE or MCP notifications are unavailable. Honor advertised page and retention limits; recover an expired cursor from the documented snapshot/replay boundary, not by starting new work.

The shared projection includes phase_started / phase_completed, content-safe message_delta and cumulative message_snapshot, tool_started / tool_completed, immutable artifact_changed references, approval_required / approval_resolved, and warning, failed, cancelled, or succeeded. Transport heartbeats are not durable progress. Silence alone is not a failure, and a slow observer does not block the worker.

Public progress excludes hidden reasoning, prompts by default, credentials, customer records, unrestricted stdout, raw provider payloads, and unbounded patches. Fetch full diffs only through a separate authorized, size-bounded artifact endpoint. Every replay and artifact fetch rechecks access to the immutable run identity. MCP clients use bounded event polling as the reliable path; host progress notifications are optional. Terminal clients follow the same contract; this guide does not require an installed XY CLI or silently write artifacts into a repository.

Plan review

Approve the whole plan before generating or testing steps.

Start a build, follow its events, then inspect the proposed plan before deciding whether its steps should be generated and tested.

Read GET /api/v1/planner/builds/{build_id} with planner:read. Plan review is required when status is awaiting_input and planner_state.awaiting_plan_approval is true. Other input gates also use awaiting_input, so check both fields. Inspect the proposed steps, inputs, outputs, and constraints, keeping the returned state_version for the plan you reviewed.

Read and review the pending plan
# Use the build ID returned by build creation. Requires planner:read.
if ! BUILD="$(curl --fail-with-body \
  --header "Authorization: Bearer ${XY_API_KEY}" \
  "${XY_API_BASE}/planner/builds/${BUILD_ID}")"; then
  exit 1
fi
printf '%s\n' "${BUILD}" | jq '{build_id, status, state_version, planner_state}'
if ! STATE_VERSION="$(printf '%s' "${BUILD}" | jq -er '
  select(.status == "awaiting_input" and .planner_state.awaiting_plan_approval == true)
  | .state_version | select(type == "number" and . > 0 and . == floor)
')"; then
  printf 'No whole plan is awaiting approval with a positive state version.\n' >&2
  exit 1
fi
# Stop here and review the proposed plan. Keep this STATE_VERSION for the decision.

Send POST /api/v1/planner/builds/{build_id}/plan/actionswith action set to APPROVE or REJECT, a positive integer expected_state_version, and an Idempotency-Key header. A nonblank comment is required for rejection and optional for approval. Personal or SYSTEM keys with planner:write can decide a plan for a build they can access. Approval allows step generation and testing to begin; it grants no workflow promotion, catalog publication, or Queue UI approval/publication authority.

Approve the reviewed plan once
# Requires planner:write.
: "${STATE_VERSION:?Use the state_version of the plan you just reviewed}"
: "${PLAN_DECISION_ID:?Set a unique ID for this logical plan decision; retain it with the body}"
if ! PLAN_BODY="$(jq -en --argjson version "${STATE_VERSION}" '
  $version | select(type == "number" and . > 0 and . == floor)
  | {action: "APPROVE", expected_state_version: .}
')"; then
  exit 1
fi
if ! curl --fail-with-body --request POST \
  --header "Authorization: Bearer ${XY_API_KEY}" \
  --header "Idempotency-Key: ${PLAN_DECISION_ID}" \
  --header "Content-Type: application/json" \
  --data "${PLAN_BODY}" \
  "${XY_API_BASE}/planner/builds/${BUILD_ID}/plan/actions"; then
  printf 'Read the existing build and events before any further decision; do not retry this POST after ambiguous output.\n' >&2
  exit 1
fi
# 202 queues a durable command. Poll the existing build and events for its outcome.

To request a revised plan, use the same route with the rejection body below instead of sending approval. Replace 7 with the version you reviewed and use a new decision key. Read and review the revised plan before deciding again.

Alternative: reject with revision feedback
{
  "action": "REJECT",
  "comment": "Collect only open records and exclude archived records from the plan.",
  "expected_state_version": 7
}

For MCP, read with planner_get_build, then call planner_respond_to_plan with state_versionset to the version you reviewed; the tool maps it to REST expected_state_version. Replace the illustrative ID, version, and key below. To reject, change action to REJECT and include a nonblank comment in the arguments.

MCP alternative: approve the reviewed plan
{
  "tool": "planner_respond_to_plan",
  "arguments": {
    "build_id": "pbld_REPLACE",
    "action": "APPROVE",
    "state_version": 7,
    "idempotency_key": "plan-decision-REPLACE"
  }
}

Design the promoted workflow's operator queue.

For an eligible app-managed Planner workflow with an entity queue, describe presentation changes through the regular public API or MCP.

Queue UI Designer creates a durable design session with immutable versions and previews. A person approves an exact version and hash in the web review page, or an authorized system key records system approval through the separate approval operation. A publish call can then make it live. Publish takes the design version ID and compares the expected live publication receipt ID. Rollback takes a retained publication or captured baseline receipt ID, and each activation returns a fresh live receipt. Personal keys and personal MCP OAuth require Queue UI scopes and explicit workflow access/configure permission. System keys use authorized organization access; only they can hold queue_ui:approve. Custom React, new backend actions, and live editing of Git-managed queues are outside this surface.

Queue UI Designer guide

Review

Every step waits for your decision.

After whole-plan approval, review each generated and tested step. A passing mechanical test is evidence, not approval. Review item counts, output shape, business conditions, and downstream mappings before accepting.
Accept the tested step
BUILD="$(curl --fail-with-body \
  --header "Authorization: Bearer ${XY_API_KEY}" \
  "${XY_API_BASE}/planner/builds/${BUILD_ID}")"
if ! STATE_VERSION="$(printf '%s' "${BUILD}" | jq -er   '.state_version | select(type == "number")')"; then
  printf 'Planner agent build response did not include state_version.\n' >&2
  exit 1
fi


jq -n --argjson expected_state_version "${STATE_VERSION}" \
  '{action: "ACCEPT", expected_state_version: $expected_state_version}' |
curl --fail-with-body --request POST \
  --header "Authorization: Bearer ${XY_API_KEY}" \
  --header "Idempotency-Key: ${BUILD_ID}-step-1-accept-v1" \
  --header "Content-Type: application/json" \
  --data-binary @- \
  "${XY_API_BASE}/planner/builds/${BUILD_ID}/steps/1/actions"

Available actions

ACCEPT
Record approval and advance. Edits are tested first.
REJECT
Give concrete feedback and regenerate the step.
MESSAGE
Continue the Planner agent conversation with context.
RETRY
Reset and regenerate the current step.
TEST
Run supplied code or full step YAML without accepting it.

Promote and operate

Authoring ends before production begins.

Promotion requires its own privileged scope and never happens automatically. Generated database activities are pinned to the accepted version in the production definition.
  1. Promote the completed build
    : "${XY_API_KEY:?Use an authorized key with planner:read to read the build and ETag}"
    : "${XY_PROMOTION_SERVICE_KEY:?Use an organization service key with planner:promote}"
    BUILD_HEADERS="$(mktemp)"
    BUILD="$(curl --fail-with-body --dump-header "${BUILD_HEADERS}" \
      --header "Authorization: Bearer ${XY_API_KEY}" \
      "${XY_API_BASE}/planner/builds/${BUILD_ID}")"
    BUILD_ETAG="$(awk 'tolower($1) == "etag:" {print $2; exit}' "${BUILD_HEADERS}" | tr -d '\r')"
    rm -f "${BUILD_HEADERS}"
    : "${BUILD_ETAG:?The Planner agent build response did not include ETag}"
    printf '%s\n' "${BUILD}" | jq '{id, status, state_version}'
    
    
    curl --fail-with-body --request POST \
      --header "Authorization: Bearer ${XY_PROMOTION_SERVICE_KEY}" \
      --header "Idempotency-Key: ${BUILD_ID}-promote-v1" \
      --header "If-Match: ${BUILD_ETAG}" \
      --header "Content-Type: application/json" \
      "${XY_API_BASE}/planner/builds/${BUILD_ID}/promote" \
      --data '{
        "name": "Portal record reconciliation",
        "description": "Collects and normalizes open records."
      }'
  2. Run the definition once
    # No schedule is required. Use the ID returned by promotion.
    : "${BUSINESS_ATTEMPT_ID:?Set a stable unique ID for this logical run}"
    RESPONSE_HEADERS="$(mktemp)"
    trap 'rm -f "${RESPONSE_HEADERS}"' EXIT
    if ! RUN_RESPONSE="$(curl --fail-with-body --request POST \
      --dump-header "${RESPONSE_HEADERS}" \
      --header "Authorization: Bearer ${XY_API_KEY}" \
      --header "Idempotency-Key: portal-reconciliation-${BUSINESS_ATTEMPT_ID}" \
      --header "Content-Type: application/json" \
      "${XY_API_BASE}/workflow-definitions/${WORKFLOW_DEFINITION_ID}/executions" \
      --data '{
        "input": {"region": "west"}
      }')"; then
      printf '%s\n' "${RUN_RESPONSE}" >&2
      exit 1
    fi
    printf '%s\n' "${RUN_RESPONSE}"
    
    
    # 202 Accepted
    # {
    #   "workflow_definition_id": "...",
    #   "workflow_instance_id": "...",
    #   "status": "running",
    #   "status_url": "/api/v1/workflow-runs/.../phases"
    # }
    
    
    # Follow the relative status_url returned above without duplicating /api/v1.
    STATUS_URL="$(printf '%s' "${RUN_RESPONSE}" | jq -r '.status_url')"
    STATUS_PATH="${STATUS_URL#/api/v1}"
    POLL_SECONDS="$(awk 'tolower($1) == "retry-after:" {gsub("\r", "", $2); print $2}' "${RESPONSE_HEADERS}")"
    POLL_SECONDS="${POLL_SECONDS:-2}"
    case "${POLL_SECONDS}" in
      *[!0-9]*|"") POLL_SECONDS=2 ;;
    esac
    if [ "${POLL_SECONDS}" -lt 1 ]; then
      POLL_SECONDS=1
    fi
    
    
    # A newly accepted run can return 404 until its projection is created.
    # Poll until the documented runtime status is terminal, or fail after 5 minutes.
    TERMINAL_STATUS=""
    DEADLINE=$(( $(date +%s) + 300 ))
    while :; do
      REMAINING=$(( DEADLINE - $(date +%s) ))
      if [ "${REMAINING}" -le 0 ]; then
        break
      fi
    
    
      if PHASE_RESULT="$(curl --silent --show-error \
        --max-time "${REMAINING}" \
        --write-out '\n%{http_code}' \
        --header "Authorization: Bearer ${XY_API_KEY}" \
        "${XY_API_BASE}${STATUS_PATH}")"; then
        HTTP_STATUS="${PHASE_RESULT##*$'\n'}"
        PHASE_RESPONSE="${PHASE_RESULT%$'\n'*}"
    
    
        if [ "${HTTP_STATUS}" -ge 400 ] && [ "${HTTP_STATUS}" != "404" ]; then
          printf '%s\n' "${PHASE_RESPONSE}" >&2
          exit 1
        fi
    
    
        if [ "${HTTP_STATUS}" != "404" ]; then
          EXECUTION_STATUS="$(printf '%s' "${PHASE_RESPONSE}" | jq -r '.execution_status // .workflow_status // ""')"
          case "${EXECUTION_STATUS}" in
            COMPLETED|COMPLETED_WITH_FAILURES|FAILED|TIMED_OUT|CANCELED|CANCELLED|TERMINATED)
              TERMINAL_STATUS="${EXECUTION_STATUS}"
              printf '%s\n' "${PHASE_RESPONSE}"
              break
              ;;
          esac
        fi
      fi
    
    
      REMAINING=$(( DEADLINE - $(date +%s) ))
      if [ "${REMAINING}" -le 0 ]; then
        break
      fi
      SLEEP_SECONDS="${POLL_SECONDS}"
      if [ "${SLEEP_SECONDS}" -gt "${REMAINING}" ]; then
        SLEEP_SECONDS="${REMAINING}"
      fi
      sleep "${SLEEP_SECONDS}"
    done
    
    
    if [ -z "${TERMINAL_STATUS}" ]; then
      printf 'Run did not reach a terminal state within 5 minutes.\n' >&2
      exit 1
    fi
  3. Create and trigger a schedule
    : "${SCHEDULE_TRIGGER_ATTEMPT_ID:?Set a stable unique ID for this logical trigger}"
    curl --fail-with-body --request POST \
      --header "Authorization: Bearer ${XY_API_KEY}" \
      --header "Content-Type: application/json" \
      "${XY_API_BASE}/workflow-definitions/${WORKFLOW_DEFINITION_ID}/schedule" \
      --data '{
        "cron_expression": "0 9 * * 1-5",
        "input": {"region": "west"},
        "overlap_policy": "SKIP",
        "note": "Weekday run"
      }'
    
    
    # Trigger that existing schedule now.
    curl --fail-with-body --request POST \
      --header "Authorization: Bearer ${XY_API_KEY}" \
      --header "Idempotency-Key: schedule-trigger-${SCHEDULE_TRIGGER_ATTEMPT_ID}" \
      --header "Content-Type: application/json" \
      "${XY_API_BASE}/workflow-definitions/${WORKFLOW_DEFINITION_ID}/schedule/trigger" \
      --data '{}'

Schedule safely

Choose an overlap policy, freeze explicit input, and update the schedule when its input contract changes.

Observe phases

Read execution or workflow-run phases without mutating the underlying run.

Query analytics

Read aggregate metrics or presentation-ready tile data, then render the charts and dashboards in your own product.

Metrics for one production workflow
# XY first grants this API identity analytics-only view access to the exact
# production workflow definition. No catalog agent or installation is required.
curl --fail-with-body \
  --header "Authorization: Bearer ${XY_API_KEY}" \
  "${XY_API_BASE}/workflow-definitions/${WORKFLOW_DEFINITION_ID}/metrics/dashboards"


# Render one approved view as presentation-ready JSON for your own UI.
# FROM_DATE and TO_DATE are YYYY-MM-DD dates and must be supplied together.
curl --fail-with-body \
  --header "Authorization: Bearer ${XY_API_KEY}" \
  "${XY_API_BASE}/workflow-definitions/${WORKFLOW_DEFINITION_ID}/metrics/dashboards/operations?from=${FROM_DATE}&to=${TO_DATE}"

Governed analytics

Propose a dashboard. Leave publication to a human.

Analytics dashboards present governed metrics. Queue UI Designer owns operational lists and detail screens over workflow entities; its approval and publication tools do not authorize analytics dashboard publication.

Query existing analytics with dashboards:read. Authoring requires the separate, sensitive, opt-in dashboards:propose scope, absent from default key scopes. Neither scope implies the other. Retired dashboards:writegrants no proposal authority, including on older keys.

Personal keys and MCP OAuth
Each request is attributed to the authenticated human and requires current organization membership, an explicit analytics seat, both useWorkflowAnalytics and useDashboardAuthoring capability access, and configure permission on the exact workflow. Editing also requires dashboard view permission for a non-STAFF caller. Revocation takes effect on subsequent requests.
System keys
The key must explicitly carry dashboards:propose and the organization must enable Marketplace keys, workflow analytics, and dashboard authoring. It acts as its managed organization service principal and can target eligible organization workflows without a human's capability or per-workflow grants. Mutations are attributed to that principal and key, never an impersonated human. A system key cannot approve or publish.
Personal key: propose a new dashboard
# Personal key: dashboards:propose plus current human authoring permission.
: "${XY_DASHBOARD_PERSONAL_KEY:?Use an authorized personal key}"
: "${WORKFLOW_DEFINITION_ID:?Select the exact active workflow}"
: "${PROPOSAL_ATTEMPT_ID:?Set a stable unique ID for this logical proposal}"
BODY="$(jq -n --arg workflow_definition_id "${WORKFLOW_DEFINITION_ID}" \
  '{prompt: "Create an operations dashboard using only registered governed query refs for this workflow.", workflow_definition_id: $workflow_definition_id}')" || exit 1
curl --fail-with-body --max-time 60 --request POST \
  --header "Authorization: Bearer ${XY_DASHBOARD_PERSONAL_KEY}" \
  --header "Idempotency-Key: ${PROPOSAL_ATTEMPT_ID}" \
  --header "Content-Type: application/json" \
  --data "${BODY}" \
  "${XY_API_BASE}/dashboard-agent/proposals"
# HTTP 202 admits a durable run, not a dashboard. Retain the returned run_id.
# On ambiguous output, inspect existing work; do not automatically repeat this POST.
System key: propose an exact-version edit
# System key: explicitly delegated proposal capability and workflow access.
: "${XY_DASHBOARD_SYSTEM_KEY:?Use an authorized system key}"
: "${WORKFLOW_DEFINITION_ID:?Select the exact active workflow}"
: "${BASE_DASHBOARD_VERSION_ID:?Use the exact immutable dashboard version you reviewed}"
: "${PROPOSAL_ATTEMPT_ID:?Set a stable unique ID for this logical edit}"
BODY="$(jq -n --arg workflow_definition_id "${WORKFLOW_DEFINITION_ID}" \
  --arg base_dashboard_version_id "${BASE_DASHBOARD_VERSION_ID}" \
  '{prompt: "Revise the operations dashboard using only its registered governed query refs.", workflow_definition_id: $workflow_definition_id, base_dashboard_version_id: $base_dashboard_version_id}')" || exit 1
curl --fail-with-body --max-time 60 --request POST \
  --header "Authorization: Bearer ${XY_DASHBOARD_SYSTEM_KEY}" \
  --header "Idempotency-Key: ${PROPOSAL_ATTEMPT_ID}" \
  --header "Content-Type: application/json" \
  --data "${BODY}" \
  "${XY_API_BASE}/dashboard-agent/proposals"
# Stop on a stale base or revoked authority. Re-read and review before a new edit.
# A system key cannot approve or publish the resulting dashboard.
  1. Read the admitted proposal run
    # Use the same authorized personal or system key as the proposal caller.
    : "${XY_API_KEY:?Use an authorized proposal key}"
    : "${RUN_ID:?Use the run_id returned by proposal start}"
    curl --fail-with-body --max-time 30 \
      --header "Authorization: Bearer ${XY_API_KEY}" \
      "${XY_API_BASE}/dashboard-agent/proposals/${RUN_ID}"
  2. Read durable proposal events
    # Read the existing run's bounded event page; do not start another proposal.
    : "${XY_API_KEY:?Use an authorized proposal key}"
    : "${RUN_ID:?Use the run_id returned by proposal start}"
    curl --fail-with-body --max-time 30 \
      --header "Authorization: Bearer ${XY_API_KEY}" \
      "${XY_API_BASE}/dashboard-agent/proposals/${RUN_ID}/events"
    # For subsequent pages or SSE, use the shared progress cursor contract
    # advertised by the live API reference. Keep the cursor with this run_id.
Only when stopping work: request cancellation
# Explicit cancellation is a mutation, not a way to reconnect.
: "${XY_API_KEY:?Use an authorized proposal key}"
: "${RUN_ID:?Use the run_id of the proposal to cancel}"
curl --fail-with-body --max-time 30 --request POST \
  --header "Authorization: Bearer ${XY_API_KEY}" \
  "${XY_API_BASE}/dashboard-agent/proposals/${RUN_ID}/cancel"
# Read the same run afterward. A cancel request is not proof of cancellation
# and does not undo a committed dashboard version.

HTTP 202 returns a run ID and selected workflow identity, plus a Locationheader for the REST status resource, not a fabricated dashboard or version ID. Follow durable progressfor reconnects. Terminal output may contain committed dashboard ID/slug, immutable version ID,DRAFTING or PREVIEW_READY, a validation summary, missing governed-query requirements, and review_version_id. A finished run is not a LIVE dashboard.

  1. Create or edit the proposal through REST or dashboard_proposal_start; MCP returns run_id immediately.
  2. Read it with dashboard_proposal_get and use bounded polling from the shared progress catalog. Inspect validation; submitted proposals stop at PREVIEW_READY.
  3. Use review_version_id to open the exact proposal in XY's dashboard review screen. An authorized human reviews and approves that immutable version in the web app and completes the publication flow.
  4. After human publication, the LIVE dashboard appears in the normal gallery and can be queried through governed analytics. Existing governed-query dashboard content needs no application or infrastructure deployment.