# A task that asks for human approval

Some steps you don't want fully automated: applying a discount, sending a contract, issuing
a credit note. A **task** can do all the preceding work and **stop** right before that
step, waiting for a person.

This recipe wires that pause into your own system, so the decision happens where your team
already works instead of in one more dashboard.

## Before you start

Your key needs `tasks:read` and `tasks:write`.

## The two pauses, which are not the same

| | who does the work | how it unblocks |
|---|---|---|
| **Approval** | the Met, asking permission to continue | `approveStep` / `rejectStep` |
| **Human step** | the person | `completeHumanStep` |

Mixing them up is the common mistake: if the Met did the work and you call
`completeHumanStep`, the execution moves on without anyone having approved anything.

## 1. Fire the task

```ts
import Met from '@meteor.ia/sdk';

const met = new Met(process.env.MET_API_KEY!, {
  workspaceId: Number(process.env.MET_WORKSPACE_ID),
});

const execution = await met.tasks.execute('tsk_123');
// → { id: 'tex_…', status: 'running', … }
```

## 2. Detect that it stopped, and at which step

The execution detail carries the result of every step of that run in
`task_step_executions`:

```ts
const state = await met.tasks.execution(execution.id);

const pending = state.task_step_executions.find(
  (s) => s.status === 'waiting_for_approval' || s.status === 'waiting_for_human',
);

if (pending) {
  await notifyYourTeam({
    execution: execution.id,
    step: pending.step_id,       // ← the step's, not its run's
    kind: pending.status,
    whatTheMetDid: pending.output,
  });
}
```

A step execution moves through `pending`, `running`, `completed`, `failed`,
`waiting_for_approval`, `waiting_for_human`, `approved` and `rejected`.

> Each row carries **two** identifiers: `id` is that run of the step, and `step_id` is the
> step inside the recipe. Approving and rejecting want the **`step_id`**. With the other
> one you get a 404 that reads as if the step didn't exist.

Polling is fine to start with, but in production it's better to be told: subscribe to
`task.completed` with [signed webhooks](recipe-signed-webhooks.html) and stop asking.

## 3. Let your team decide from where they already work

```ts
const stepId = pending.step_id;

// Approve: the execution moves to the next step, asynchronously.
await met.tasks.approveStep(execution.id, stepId, 'Good — the numbers add up');

// Reject: that branch stops.
await met.tasks.rejectStep(execution.id, stepId, 'That discount is not authorized');

// Human step: your system did it, not the Met.
await met.tasks.completeHumanStep(execution.id, stepId, 'Contract signed and filed');
```

The comment you send is stored in the step's `approval_comment`, along with who decided and
when. That's what later explains a strange run without having to reconstruct it.

> All three operate on the **execution id**, not the task's. The step belongs to the
> recipe, but the pause belongs to the run: using the task id is the mistake that costs the
> most time here.

## 4. Cutting a run short

```ts
await met.tasks.cancelExecution(execution.id);
```

Different from pausing the task with `met.tasks.setStatus(task.id, 'paused')`: that stops
new executions from being born and **does not stop the ones already running**.

## What to decide before this goes to production

A stopped execution stays stopped. If nobody approves, there it sits — no timeout resolves
it for you. Decide up front what happens to an approval nobody looked at in 48 hours:
remind, escalate, or cancel and redo. It's a product decision, and the answer "someone will
get to it" always ends in the same call from a customer asking about something left
half-done.

For the same reason, a task with human pauses doesn't belong in CI: the job waits until the
timeout. For unattended work, see
[run a Met from GitHub Actions](recipe-github-actions.html).

## Next

- Steps, triggers and the full model: [Tasks](tasks.html).
- Finding out it finished without polling: [Live events](events.html).
