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

# GET /v1/agents/plans/{plan_id}

> One agent's plan: its goal, reasons, steps and where it stands.

One plan and its steps.

<Note>Needs the `agents:read` scope.</Note>

## Path parameters

| Field | Type | Required | Notes |
| :- | :- | :- | :- |
| `plan_id` | integer | Yes | The plan's id. |

## Request

<CodeGroup>
  ```bash cURL theme={null}
  curl "https://api.fireflo.au/v1/agents/plans/4812" \
    -H "Authorization: Bearer $OMNI_API_KEY"
  ```

  ```python Python theme={null}
  import os

  import requests

  response = requests.get(
      "https://api.fireflo.au/v1/agents/plans/4812",
      headers={"Authorization": f"Bearer {os.environ['OMNI_API_KEY']}"},
  )
  print(response.status_code, response.json())
  ```

  ```javascript Node theme={null}
  const response = await fetch("https://api.fireflo.au/v1/agents/plans/4812", {
    headers: { Authorization: `Bearer ${process.env.OMNI_API_KEY}` },
  });
  console.log(response.status, await response.json());
  ```
</CodeGroup>

## Response

`200 OK` — the plan. A plan has the `agent`, the `contact` and `deal` it is for, what started it (`trigger`, with `trigger_excerpt` — the customer's words or your note), its `goal` and `rationale`, and its `steps`. `contact.uuid` is the contact's id everywhere else in the API. `approval_reasons` say what needs approval, and `expires_at` when an unanswered plan is dropped. `decided_by` and `decision_note` record the decision (`decided_by` is null when it was made through the API), and `last_error` why it stopped or failed. `tokens` and `model` are what planning used. `scope` is `contact` for a plan for one contact; a broadcast plan has `audience` and `campaign_id`, and a story's has `story_id`.

Each step has its `action`, a readable `title`, `body` and `detail`, the agent's `reason`, when it runs (`run_at`), whether it `requires_approval`, its `status` and `result`, its `params`, and which of them can be changed before approving (`editable`).

`status` is one of:

| Status | Meaning |
| :- | :- |
| `queued`, `planning` | The agent is about to plan, or planning. |
| `awaiting_approval` | Waiting for someone to approve or reject it. |
| `scheduled`, `running` | Approved, or needing no approval: its steps run when due. In step-by-step mode, a step needing approval waits when its turn comes. |
| `completed` | Every step ran. |
| `rejected` | Someone rejected it. |
| `cancelled` | Stopped — by a person, the agent being switched off or taken off the contact. |
| `superseded` | Replaced by a newer plan for the same contact. |
| `expired` | Nobody decided in time. |
| `failed` | It couldn't be made or run; `last_error` says why. |

```json theme={null}
{
  "id": 4812,
  "agent": {
    "id": 7,
    "name": "Arjun",
    "role": "sales"
  },
  "scope": "contact",
  "campaign_id": null,
  "contact": {
    "id": 90417,
    "uuid": "3f6b2a1e-8c4d-4e2a-9b7f-5d1c0e8a9f42",
    "name": "Neha Arora",
    "msisdn": "+919876500102"
  },
  "deal": {
    "id": 3318,
    "title": "Arora Traders — 40 inverters",
    "stage": "Quoted"
  },
  "pipeline": {
    "id": 2,
    "name": "B2B sales"
  },
  "trigger": "inbound_message",
  "trigger_excerpt": "Can you do ₹6,200 per unit if we take 40?",
  "goal": "Answer Neha's price question and keep the quote moving",
  "rationale": "She asked for a lower unit price on a large order. The playbook allows up to 5% off above 25 units, so offer ₹6,350 and note it on the deal.",
  "status": "awaiting_approval",
  "approval_mode": "plan",
  "approval_reasons": [
    "Send a message needs approval"
  ],
  "created": "2026-10-06T10:14:22+05:30",
  "expires_at": "2026-10-07T10:14:31+05:30",
  "next_run_at": null,
  "tokens": 2310,
  "model": "gpt-4o-mini",
  "decided_by": null,
  "decision_note": null,
  "last_error": null,
  "steps": [
    {
      "id": 20931,
      "order": 1,
      "action": "send_message",
      "title": "Send WhatsApp message",
      "body": "Hi Neha, for 40 units we can do ₹6,350 each, delivered to Ludhiana within 10 days. Shall I update the quote?",
      "detail": "WhatsApp",
      "reason": "Answer the price question while she is online.",
      "run_at": "2026-10-06T10:14:31+05:30",
      "requires_approval": true,
      "status": "pending",
      "result": null,
      "editable": [
        "body"
      ],
      "params": {
        "channel": "whatsapp",
        "body": "Hi Neha, for 40 units we can do ₹6,350 each, delivered to Ludhiana within 10 days. Shall I update the quote?",
        "channel_locked": true
      }
    },
    {
      "id": 20932,
      "order": 2,
      "action": "add_note",
      "title": "Add note",
      "body": "Offered ₹6,350/unit for 40 units (5% off list).",
      "detail": null,
      "reason": "Keep the team aware of the offer.",
      "run_at": "2026-10-06T10:14:31+05:30",
      "requires_approval": false,
      "status": "pending",
      "result": null,
      "editable": [
        "body"
      ],
      "params": {
        "body": "Offered ₹6,350/unit for 40 units (5% off list)."
      }
    },
    {
      "id": 20933,
      "order": 3,
      "action": "follow_up",
      "title": "Follow up",
      "body": null,
      "detail": "Check whether Neha accepts the revised price",
      "reason": "No reply by tomorrow means the quote needs a nudge.",
      "run_at": "2026-10-07T11:00:00+05:30",
      "requires_approval": false,
      "status": "pending",
      "result": null,
      "editable": [],
      "params": {
        "goal": "Check whether Neha accepts the revised price"
      }
    }
  ],
  "audience": null,
  "story_id": null
}
```

## Errors

Every refusal is `{"error": {"code", "message", "field"}}`; `field` is there when one input is at fault.

| Status | Error code | When |
| :- | :- | :- |
| 404 | `not_found` | No plan of this account's has that id. |
| 404 | `module_off` | The account doesn't have AI agents on: they aren't in its plan, or are switched off for it. |
| 403 | `scope_missing` | The key doesn't have the `agents:read` scope. |

Any request can also be refused for its key, its account or its rate (`key_required`, `invalid_key`, `account_suspended`, `plan_excludes_api`, `address_not_allowed`, `rate_limited`); see [the overview](/api-reference/overview).


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