> ## Documentation Index
> Fetch the complete documentation index at: https://developer.opus.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Off-Platform Review

> Send an earlier step's output to your own application for a person to review, and resume the workflow with their decision.

The Off-Platform Review task sends an earlier step's output to a system you run, so a person can review it in your application instead of in Opus. Opus posts the step's full execution context to your webhook and pauses the workflow. Your reviewer looks it over, submits their decision, and the workflow resumes with what they decided.

You own the reviewer's experience completely — the layout, the branding, who can see what, how the decision is captured. Opus only hands over the item and waits for the answer.

<Note>
  For reviews that happen inside Opus, use the [Review Task](/tasks/review). Off-Platform Review is for when the reviewer works in your application and never signs in to Opus.
</Note>

## Key Capabilities

<CardGroup cols={2}>
  <Card title="Full Node Context" icon="diagram-project">
    Your system receives the reviewed step's inputs, outputs, schemas and execution metadata.
  </Card>

  <Card title="Your Reviewer Experience" icon="palette">
    Build the review screen in your own application, for people who have no Opus account.
  </Card>

  <Card title="Decisions You Define" icon="list-check">
    The output variables you add to the task are the decision form your system fills in.
  </Card>

  <Card title="Edits Flow Downstream" icon="pen-to-square">
    Whatever your reviewer returns is what later steps consume.
  </Card>
</CardGroup>

## When to Use It

| Situation | Real-World Example |
| - | - |
| **Reviewers work elsewhere** | Claims assessors work in your operations console all day |
| **Reviewers are external** | A partner approves output but has no Opus account |
| **The review needs your context** | The screen must show account history Opus does not hold |
| **Your own approval rules** | Routing, escalation and permissions already exist in your system |

## Setting It Up

| Setting | What It Means |
| - | - |
| **Review Node** | The step being reviewed. This is the task's one input, and it carries that step's whole execution context |
| **Output** | The decision fields your system sends back — a verdict, edited values, reviewer notes |
| **Webhook URL** | The HTTPS endpoint Opus posts review requests to. Required |
| **Webhook Headers** | Optional headers sent with every request, so your endpoint can confirm the call came from Opus |
| **Timeout** | How long Opus waits for your endpoint to acknowledge the request. Defaults to 15 seconds |

<Note>
  The webhook is configured per workspace when the task is connected, so the same task can point at a different endpoint in each workspace. Header values are encrypted at rest and are never shown again once saved.
</Note>

<Warning>
  Off-Platform Reviews only run for a verified organization. An unverified organization cannot create one, and if the workflow reaches one anyway, nothing is sent to your webhook and the review fails. Make sure your organization is verified before you build against it.
</Warning>

## How to Add an Off-Platform Review

<Steps>
  <Step title="Drop it into your workflow">
    Drag an Off-Platform Review from the sidebar and place it after the step you want reviewed.
  </Step>

  <Step title="Connect the step being reviewed">
    Set the Review Node input to the step whose output needs a human decision.
  </Step>

  <Step title="Point it at your endpoint">
    Enter the webhook URL your reviewer application listens on, plus any authentication headers it expects.
  </Step>

  <Step title="Define the decision fields">
    Add an output variable for each thing the reviewer decides. These become the form your system renders, and Opus rejects a callback returning anything you have not declared.
  </Step>

  <Step title="Build the review screen">
    Render the item from the request, collect the decision, and post it back to the callback URL that came with it.
  </Step>
</Steps>

## What Opus Sends

```json theme={null}
{
  "execution_id": "c4e8a1f2-9b3d-4a7e-8f1c-2d6b5a9e3c14",
  "workflow_id": "wf_support_reply_v3",
  "workflow_name": "Customer Support Reply Pipeline",
  "inputs": {
    "review_node": {
      "type": "node",
      "display_name": "Review Node",
      "variable_name": "review_node",
      "value": {
        "node_execution_id": "ne_2b8c",
        "handler_id": "h_draft_reply",
        "handler_version_id": "hv_9c2e",
        "handler_active_id": "ha_7a2f",
        "handler_active_version_id": "hav_e41d",
        "input_schema": {
          "customer_message": {
            "variable_name": "customer_message",
            "display_name": "Customer Message",
            "allowed_types": [{ "type": "str" }]
          }
        },
        "output_schema": {
          "draft_reply": {
            "variable_name": "draft_reply",
            "display_name": "Drafted Reply",
            "allowed_types": [{ "type": "str" }]
          }
        },
        "input": {
          "customer_message": {
            "value": "I'm still being charged the old rate after upgrading.",
            "type": { "type": "str" }
          }
        },
        "output": {
          "draft_reply": {
            "value": "Hi Sarah, thanks for reaching out — I've corrected the billing record and issued a $150 refund.",
            "type": { "type": "str" }
          }
        },
        "process": {}
      }
    }
  },
  "callback": {
    "url": "https://operator.opus.com/v1/execution/callback/c4e8a1f2-9b3d-4a7e-8f1c-2d6b5a9e3c14",
    "token": "wT8_kP3rN9vQ2xL5mY7jH4bC6dF1aE0sZ",
    "token_header": "X-Opus-Callback-Token"
  },
  "expected_output_schema": {
    "final_reply": {
      "type": "str",
      "display_name": "Final Reply",
      "variable_name": "final_reply",
      "is_nullable": false
    },
    "reviewer_notes": {
      "type": "str",
      "display_name": "Notes",
      "variable_name": "reviewer_notes",
      "is_nullable": false
    }
  }
}
```

### Inside the review node

Everything the reviewer needs sits in `inputs.review_node.value`:

| Field | What It Is |
| - | - |
| `input` | What the reviewed step received. Each entry carries its `value` and `type` |
| `output` | What the reviewed step produced. Usually the thing being reviewed |
| `input_schema` / `output_schema` | Field definitions for the values above — labels and types, for rendering each field with the right widget |
| `process` | Execution metadata for the reviewed step |
| `node_execution_id`, `handler_*` | Identifiers for the step and the version of it that ran |

`expected_output_schema` is the decision form: one entry per output variable you defined, keyed by name, with the `type` your system must return and the `display_name` to label it with.

Acknowledge the request with a `200` promptly — within the configured timeout, 15 seconds by default. This only confirms receipt; the decision comes later, as a separate request.

<Tip>
  Store the `execution_id`, the whole `callback` block, and the `expected_output_schema` before you reply. You need them when the reviewer submits, and Opus will not send them again.
</Tip>

## Sending the Decision Back

Post to `callback.url`, putting `callback.token` in the header named by `callback.token_header`:

```bash theme={null}
curl --request POST \
  --url https://operator.opus.com/v1/execution/callback/c4e8a1f2-9b3d-4a7e-8f1c-2d6b5a9e3c14 \
  --header 'Content-Type: application/json' \
  --header 'X-Opus-Callback-Token: wT8_kP3rN9vQ2xL5mY7jH4bC6dF1aE0sZ' \
  --data '{
    "output_data": {
      "final_reply": "Hi Sarah, thanks for flagging this — the upgrade should have applied automatically. I have corrected the billing record and issued a $150 refund.",
      "reviewer_notes": "Softened the opener. Refund amount is correct."
    },
    "status": "success"
  }'
```

Send **bare values**. Opus knows each field's type from the output variables you declared and applies it for you.

<Warning>
  Do not wrap values as `{ "value": …, "type": … }`. That wrapper is stored as the value itself, and the workflow resumes with data your later steps cannot read.
</Warning>

Two patterns cover most review screens:

* **Accept as-is** — pre-fill the form from `review_node.value.output` and submit it unchanged.
* **Accept with edits** — the reviewer changes one or more fields, and those edited values are what later steps consume.

Every key in `output_data` must be one you declared. An undeclared field is rejected with `422`, naming both what you sent and what was expected — nothing is stored and the workflow stays paused, so you can correct the payload and post again.

If the review cannot be completed at all, report a failure instead of leaving the workflow waiting:

```json theme={null}
{
  "status": "failed",
  "error": "No reviewer available before the claim deadline"
}
```

## How It Behaves

<AccordionGroup>
  <Accordion title="If your endpoint is unreachable">
    Opus retries a failed request up to 3 times with exponential backoff. Network errors and the statuses `408`, `425`, `429`, `500`, `502`, `503` and `504` are treated as transient. Any other `4xx` is treated as a misconfigured webhook and fails immediately. After repeated failures a circuit breaker opens for 60 seconds.
  </Accordion>

  <Accordion title="One review, one decision">
    The callback token authenticates a single decision for a single execution, and is invalidated as soon as one succeeds. A second submission is rejected with `401`, so build for one reviewer per review and do not assume a request will be sent again.

    Keep the token server-side. It is the only credential needed to resolve the review, so it must never reach a browser.
  </Accordion>

  <Accordion title="If a reviewer submits too late">
    An execution accepts a decision only while it is still waiting. Once it has been completed or closed out by its deadline, a callback returns `401` with a message saying the execution is no longer accepting callbacks — the workflow has already moved on.

    Show the reviewer that the review expired, and do not let them retry.
  </Accordion>

  <Accordion title="These reviews never appear in the Opus inbox">
    An off-platform review is resolved by your system, not by a person in Opus. Attempting to pick it up or complete it through the Opus review screens is rejected with `409`. For reviewers working inside Opus, use the [Review Task](/tasks/review).
  </Accordion>
</AccordionGroup>

## Responses You May Get

| Status | Meaning |
| - | - |
| `200` | Decision accepted. The workflow resumes |
| `401` | The token is wrong or spent, or the execution is no longer accepting callbacks |
| `404` | No execution with that id |
| `422` | `output_data` contains a field the task does not declare |

## Tips for Better Results

<AccordionGroup>
  <Accordion title="Render the form from the schema, not from hard-coded fields">
    Build one form field per entry in `expected_output_schema`, using `display_name` as the label and `type` to pick the widget. A decision field added to the task then appears in your UI without a code change.
  </Accordion>

  <Accordion title="Acknowledge first, render afterwards">
    Reply `200` as soon as you have stored the request, then build the review screen. Loading related data before replying risks blowing the timeout and triggering a retry, which leaves you holding the same review twice.
  </Accordion>

  <Accordion title="Show the reviewer the input, not just the output">
    `review_node.value.input` is what the step was working from. A reviewer judging a drafted reply needs the customer's original message to judge it against.
  </Accordion>
</AccordionGroup>

## Related

<CardGroup cols={2}>
  <Card title="Off-Platform Task" icon="server" href="/tasks/off-platform-task">
    Hand any step to your own system, not just a review.
  </Card>

  <Card title="Review Task" icon="check-to-slot" href="/tasks/review">
    Accept or reject a step's output inside Opus.
  </Card>

  <Card title="Human Decision Agent" icon="code-branch" href="/tasks/agent/human-decision-agent">
    Let a person choose which path the workflow takes.
  </Card>

  <Card title="Opus Human Task" icon="user-pen" href="/tasks/agent/opus-human-task">
    Have a person complete work inside Opus.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.