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

# Workflows API

> Start, follow, and approve workflow runs over HTTP with an API key

You can create goals, start runs, follow them, and answer approvals over the
CostGraph API. This lets a script or pipeline start a workflow and approve its
changes without opening the app.

For every endpoint and schema, see the
[API reference](/api-reference/costgraph/introduction).

## Authenticate

Send an API key in the `X-API-Key` header, and your tenant ID in
`X-CostGraph-Tenant-ID`. Create a key under **Settings > API keys**.

An API key carries the role of the person who created it. A key created by
any member of the tenant can start runs and answer approvals.

All calls use the base URL `https://api.costgraph.ai` and the path prefix
`/api/v1/tenant/automation`.

```bash theme={null}
export CG=https://api.costgraph.ai/api/v1/tenant/automation
export H_KEY="X-API-Key: $COSTGRAPH_API_KEY"
export H_TENANT="X-CostGraph-Tenant-ID: $TENANT_ID"
```

## Create a goal

[`POST /goals`](/api-reference/workflows/create-a-workflow-goal) creates a goal. A goal that runs once starts its first run
right away. Send a new UUID in `Idempotency-Key`. Repeating a request with the
same key doesn't create a second goal.

The `specification` holds the same settings as the goal form. Set
`allowedCategories` to include `integrations` for changes in
[connected services](/costgraph/workflows/what-workflows-can-change#connected-services),
and `approval.mode` to `every_change`, `low_risk_reversible`, or `any_change`.

```bash theme={null}
curl -X POST "$CG/goals" -H "$H_KEY" -H "$H_TENANT" \
  -H "Idempotency-Key: $(uuidgen)" -H "Content-Type: application/json" \
  -d '{
    "name": "Clean up idle droplets",
    "objective": "Find idle DigitalOcean droplets and delete the ones CostGraph created.",
    "specification": {
      "allowedConnectionIds": ["<connection id>"],
      "allowedCategories": ["integrations"],
      "exclusions": [],
      "schedule": {"kind": "once"},
      "limits": {"maxActiveRuntimeSeconds": 1800, "maxDecisions": 20},
      "approval": {"mode": "every_change"}
    }
  }'
```

The response includes the goal and, for a goal that runs once, the `run_id`.
[`GET /connections`](/api-reference/workflows/list-workflow-connections) lists connection IDs, and [`GET /capability-categories`](/api-reference/workflows/list-available-workflow-capability-categories)
lists the categories a goal can allow.

## Run a goal

[`POST /goals/{goal_id}/runs`](/api-reference/workflows/run-a-workflow-goal-now) starts a new run from the goal's current settings.
It also needs an `Idempotency-Key`.

```bash theme={null}
curl -X POST "$CG/goals/$GOAL_ID/runs" -H "$H_KEY" -H "$H_TENANT" \
  -H "Idempotency-Key: $(uuidgen)"
```

## Follow in Slack

Add `slack_channel_id` to [`POST /goals`](/api-reference/workflows/create-a-workflow-goal) or [`PATCH /goals/{goal_id}`](/api-reference/workflows/update-a-workflow-goal)
to post the goal's runs to a Slack channel. Send an empty value on `PATCH` to
clear it. The goal's responses return the channel.

[`POST /goals/{goal_id}/runs`](/api-reference/workflows/run-a-workflow-goal-now) also accepts `slack_channel_id`, which replaces the goal's
channel for that run only.

```bash theme={null}
curl -X POST "$CG/goals/$GOAL_ID/runs" -H "$H_KEY" -H "$H_TENANT" \
  -H "Idempotency-Key: $(uuidgen)" -H "Content-Type: application/json" \
  -d '{"slack_channel_id": "C0123456789"}'
```

`GET /slack/channels` lists the channels you can choose. A request is refused if
Slack isn't connected or CostGraph can't post in the channel. Invite the app to
private channels first. See [Follow workflow runs in Slack](/costgraph/workflows/follow-in-slack).

[`GET /runs/{run_id}`](/api-reference/workflows/get-workflow-run-detail) includes `slack_follow` for a run that posts to Slack. It holds the
`channel_id`, a `status`, and a `message` when CostGraph couldn't post.

## Follow a run

[`GET /runs/{run_id}`](/api-reference/workflows/get-workflow-run-detail) returns the run's state, its plan, the changes it made,
and any approval or question waiting for you.

```bash theme={null}
curl "$CG/runs/$RUN_ID" -H "$H_KEY" -H "$H_TENANT"
```

[`GET /events/v2`](/api-reference/workflows/stream-workflow-lifecycle-events) streams run and goal updates for the tenant as
server-sent events. Each update is an `automation` event whose `id:` line is
its sequence number:

```text theme={null}
event: automation
id: 42
data: {"sequence":42,"run_id":"...","goal_id":"...","kind":"...","created_at":"..."}
```

A new connection starts from now. It first sends a `ready` event whose `id:`
is the current position, then only updates that happen after it. A
`heartbeat` event arrives while nothing changes.

```bash theme={null}
curl -N "$CG/events/v2" -H "$H_KEY" -H "$H_TENANT"
```

To pick up where you left off after a disconnect, save the `id:` of the last
event you processed and send it back in the `Last-Event-ID` header. The
stream replays every update after that number, then continues live.

```bash theme={null}
curl -N "$CG/events/v2" -H "$H_KEY" -H "$H_TENANT" -H "Last-Event-ID: 42"
```

The `after` query parameter does the same, for clients that cannot set
headers: `$CG/events/v2?after=42`.

## Answer an approval

When a run needs you, [`GET /runs/{run_id}`](/api-reference/workflows/get-workflow-run-detail) lists the pending approval with its
ID, version, and `effect_hash`. Answer it with
[`POST /runs/{run_id}/checkpoints/{checkpoint_id}/responses`](/api-reference/workflows/respond-to-a-workflow-checkpoint). The `outcome` is
`approved`, `rejected`, or `answered` for a question. Send the version and
`effect_hash` from the approval you reviewed. If the approval no longer matches, the
call is refused and nothing is applied.

```bash theme={null}
curl -X POST "$CG/runs/$RUN_ID/checkpoints/$CHECKPOINT_ID/responses" \
  -H "$H_KEY" -H "$H_TENANT" -H "Content-Type: application/json" \
  -d '{"checkpoint_version": 1, "effect_hash": "<effect hash>", "outcome": "approved", "payload": {}}'
```

For a question, put the answer in `payload`. For a rejection, put your reason
in `payload` so the agent can read it.

## Pause, resume, retry, and cancel

| Action | Call |
| - | - |
| Pause a goal's schedule | [`POST /goals/{goal_id}/pause`](/api-reference/workflows/pause-a-workflow-goal) |
| Resume a goal's schedule | [`POST /goals/{goal_id}/resume`](/api-reference/workflows/resume-a-workflow-goal) |
| Retry a run | [`POST /runs/{run_id}/retry`](/api-reference/workflows/retry-a-workflow-run), with an `Idempotency-Key` |
| Cancel a run | [`POST /runs/{run_id}/cancel`](/api-reference/workflows/cancel-a-workflow-run) |

```bash theme={null}
curl -X POST "$CG/runs/$RUN_ID/cancel" -H "$H_KEY" -H "$H_TENANT"
```

## Next steps

<CardGroup cols={2}>
  <Card title="Approvals" icon="circle-check" href="/costgraph/workflows/approvals">
    What an approval shows and when a change waits.
  </Card>

  <Card title="API reference" icon="code" href="/api-reference/costgraph/introduction">
    Authentication, errors, and every endpoint.
  </Card>
</CardGroup>


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