# Human approvals

Some actions a Met does not take on its own. When it asks to **publish on social media, charge,
issue an invoice, spend ad budget, send a template that Meta bills for, or delete something**, the
action does not run: it becomes an **approval** waiting for a person at the business. That person
approves or rejects it in Met —on the chat card, on the Home screen or over WhatsApp— and only then
does it happen.

With the API you **see** those approvals: what was requested, who decided it and what came out of
it. **You do not decide them.** Approving something that charges or publishes belongs to a person,
never to a key.

## Before you start

Your key needs `approvals:read`. Keys issued before this scope existed do not carry it: add it in
**Settings › Developers** or issue a new one. It also works with a test key (`met_test_`), because
it only reads.

## A Run that was left waiting

When a Run asks for one of those actions, the Met replies with something like "this is pending your
approval" and the Run ends as `completed`. The response tells you the action was not taken:

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

const met = new Met(process.env.METEOR_API_KEY!);
const run = await met.runs.create({ input: 'Publica en Facebook el post de la promo de otoño' });

if (run.awaiting_approval) {
  for (const a of run.approvals ?? []) {
    console.log(`Awaiting approval: ${a.action} (${a.approval_id})`);
  }
}
```

| Run field | What it is |
|---|---|
| `awaiting_approval` | `true` if it left at least one action waiting. |
| `approvals[]` | Each action with its `approval_id`, `action`, `status` and `requested_at`. |

When the Run is created, `status` is the one at that moment. On `GET /runs/{id}` (`runs.retrieve`)
it is the current one: if someone already approved it, you see it there. The `run.completed` event
carries the same two fields when there are approvals.

If a Run asks again for something that was already waiting (a retry of yours, or the Met
insisting), no new approval is created: the existing one still belongs to the Run that asked first,
and the new Run does not list it. To see everything that is waiting, no matter which Run asked for
it: `approvals.list({ status: 'pending' })`.

## Reading approvals

```ts
// What is waiting for a person right now
const { data } = await met.approvals.list({ status: 'pending' });

// Everything a Run left behind
const fromRun = await met.approvals.list({ run_id: run.id });

// A single one, with all its detail
const a = await met.approvals.retrieve(run.approvals![0].approval_id);
console.log(a.status, a.decided_by?.name, a.result?.text);
```

```bash
curl "https://api.met.meteor.com.co/api/v1/workspaces/$WS/approvals?status=pending,approved" \
  -H "Authorization: Bearer $METEOR_API_KEY"
```

Filters on `GET /workspaces/{workspaceId}/approvals`:

| Parameter | What for |
|---|---|
| `status` | One or several, comma-separated: `pending`, `approved`, `rejected`, `executed`, `failed`. |
| `run_id` | The ones an API Run left behind. |
| `conversation_id` | The ones requested in a conversation (its 100 most recent turns). |
| `since` / `until` | By request date, ISO 8601 with time and zone. `since` is included, `until` is not. |
| `limit` / `starting_after` | Pagination: 1–100 per page; pass `next_cursor` exactly as it arrived. |

The list goes from newest to oldest and returns `{ data, has_more, next_cursor }`. To walk all of
it, `met.approvals.iterate({ ... })`.

## The statuses

| `status` | What happened |
|---|---|
| `pending` | Waiting for a person. |
| `approved` | A person approved it and it is running (a video can take minutes). |
| `executed` | It ran. `result` says what came out, with the pieces it produced. |
| `failed` | It was approved but failed to run. `result.error_code` says why: `execution_failed` (the tool responded with an error) or `interrupted` (it was cut off before finishing). The business sees the error detail in Met. |
| `rejected` | It was not done. `rejection_reason` tells apart `rejected`, `changes_requested` (they asked for a change: it is in `change_request`) and `replaced` (the Met asked for a newer version: it is in `replaced_by`). |

## What each approval carries

| Field | What it is |
|---|---|
| `action` | Which action it is, in words: "Publicar una imagen en Instagram", "Crear una factura en Siigo". |
| `summary`, `details` | About what: the data needed to decide, readable ("vertical 9:16", "Monto: 120000"). No emails, phone numbers or documents of your customers. |
| `draft` | What the Met wrote (the text or the instruction for the piece). |
| `preview` | The text and images that would be published, and the ad budget if it spends. |
| `met` | Which Met asked for it (`id: null` is the main Met). |
| `origin` | Where it came from: `api_run`, `routine`, `channel` or `panel`. |
| `decided_by`, `decided_at` | Who decided it and when. `decided_by.user_id` is the same id as in `met.team.list()`. |
| `result` | What came out when it ran: a line of text, the files (`files`) and, if it failed, `error_code`. |

It never carries the raw input of the tool, credentials (not even inside a text, like a provider's
error), contact data of your customers, internal ids or the name of a model.

## Finding out without polling

Subscribe your endpoint to two events (see [Webhooks](webhooks.html)). Both carry the full
approval, with the same shape as `approvals.retrieve`:

| Event | When |
|---|---|
| `approval.requested` | A Met asked for an action that needs approval. |
| `approval.decided` | A person approved it (`status: approved`) or rejected it (`status: rejected`). |

```ts
await met.webhooks.subscriptions.create({
  url: 'https://your-system.com/met/events',
  enabled_events: ['approval.requested', 'approval.decided'],
});
```

To find out how an approved one ended, read the approval after `approval.decided` (`executed` or
`failed`).

## From an assistant (MCP)

Meteor's MCP server has `met_list_approvals` and `met_get_approval`, also read-only. If you ask
your assistant "what do I have pending approval?", it tells you; to approve, it sends you to Met.

## Why approvals are not made through the API

The actions that wait for approval are precisely the ones that cannot be undone: a charge, an
electronic invoice, a post, a campaign with a budget. If a key could approve them, an instruction
hidden in a contact's message, or a bug in your integration, would be enough to carry them out
without anyone at the business seeing it. That is why the decision lives in Met, with the person
the business chose under **Settings › Roles › Who approves?**.
