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

# Actions

> Understand action lifecycle, fields, available operations, and computed metadata.

## What is an action?

An action is a single HTTP request task: SafeFetch stores the request intent, makes the HTTP request to any URL (API endpoint, internal service, etc.), retries if needed, and stores the final result.

## Lifecycle

```mermaid theme={null}
stateDiagram-v2
    [*] --> pending
    pending --> active : dispatch
    pending --> cancelled : cancel
    active --> completed : success
    active --> pending : retry
    active --> failed : max retries
    [*] --> awaiting_approval : approve=true
    awaiting_approval --> pending : approved
    awaiting_approval --> cancelled : rejected
```

| Status              | Description                       |
| ------------------- | --------------------------------- |
| `pending`           | Queued, waiting for dispatch      |
| `active`            | HTTP request in flight            |
| `awaiting_approval` | Held for human approval           |
| `completed`         | Target API responded successfully |
| `failed`            | Max retries exhausted             |
| `cancelled`         | Cancelled by user                 |

## Action fields

<ResponseField name="id" type="string" required>
  Unique action ID (for example `act_...`).
</ResponseField>

<ResponseField name="status" type="string" required>
  Lifecycle status: `pending`, `active`, `awaiting_approval`, `completed`, `failed`, `cancelled`.
</ResponseField>

<ResponseField name="url" type="string" required>
  Target URL for the HTTP request.
</ResponseField>

<ResponseField name="method" type="string" required>
  HTTP method (`GET|POST|PUT|PATCH|DELETE`).
</ResponseField>

<ResponseField name="body" type="object|null">
  JSON body sent to the target URL.
</ResponseField>

<ResponseField name="headers" type="object|null">
  Optional per-action outbound headers.
</ResponseField>

<ResponseField name="dedupe" type="string|null">
  Deduplication key for semantic duplicates (24-hour window).
</ResponseField>

<ResponseField name="idempotency_key" type="string|null">
  Idempotency key from request header (24-hour window).
</ResponseField>

<ResponseField name="attempts" type="integer" required>
  Number of delivery attempts already used.
</ResponseField>

<ResponseField name="retries" type="integer" required>
  Maximum delivery attempts before `failed`.
</ResponseField>

<ResponseField name="callback" type="string|null">
  Optional URL notified on completion/failure.
</ResponseField>

<ResponseField name="actions" type="string[]">
  Allowed operations for current status (`approve`, `cancel`, `retry`).
</ResponseField>

<ResponseField name="error" type="object | null">
  Structured error detail, present only when `status` is `failed`. Contains:

  * `source` — `"target"` if the endpoint returned a non-2xx response; `"dispatch"` for network errors, DNS failures, or timeouts.
  * `message` — human-readable description (same as `last_error`).
  * `response_code` — HTTP status code from the target (`null` for dispatch errors).
  * `response_body` — response body from the target (`null` for dispatch errors).
</ResponseField>

## Synchronous mode

Pass `sync: true` when creating an action to block the request until the action reaches a terminal status (`completed`, `failed`, or `cancelled`). The response is the final action object with `response_code`, `response_body`, and `duration_ms` populated.

```bash theme={null}
curl -X POST http://localhost:3000/v1/actions \
  -H "Authorization: Bearer <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://api.example.com/charge", "body": { "amount": 500 }, "sync": true }'
# Response arrives only after the action completes (or times out)
```

**Timeout:** The server holds the connection for up to 30 seconds by default (max 120 seconds). If the action hasn't completed by then, the response returns the current action state with `status: pending` or `status: active`.

**Incompatible with `approve: true`.** Synchronous mode cannot wait for a human to approve. Combining both returns `400 invalid_sync`.

## Available operations

<Tabs>
  <Tab title="pending">
    `actions: ["cancel"]`
  </Tab>

  <Tab title="awaiting_approval">
    `actions: ["approve", "cancel"]`
  </Tab>

  <Tab title="failed/cancelled">
    `actions: ["retry"]`
  </Tab>
</Tabs>

## Receipt page

Every action gets a shareable `receipt_url` (e.g. `/r/rcpt_...`) returned in the API response on creation and retrieval. The receipt page shows the full action lifecycle — status, URL, timing, attempts, request body, and response — with sensitive headers masked. No authentication is required to view a receipt; access is secured by the unguessable receipt token.

## Computed fields

<ResponseField name="receipt_url" type="string">
  Shareable URL for this action's receipt page. Secured by an unguessable token — no authentication required to view.
</ResponseField>

<ResponseField name="retries_remaining" type="integer">
  Returned for retried pending/active actions. Computed as `retries - attempts`.
</ResponseField>

<ResponseField name="next_retry_in_seconds" type="integer">
  Seconds until `next_retry_at` when a retry is scheduled.
</ResponseField>
