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

# How to run Workflows using a script

> Run a saved workflow in your code using the Compass HTTP API

This tutorial shows how to run a [Workflows](/docs/capabilities/workflows/introduction-to-workflows) workflow programmatically over HTTPS. You need an API key, your account ID, and a workflow that is already defined and saved in the Workflows UI. You do not need to manage the underlying infrastructure.

## Prerequisites

* A workflow defined and saved in the Workflows UI
* A Scale API key
* Your Scale account ID (the account that owns the workflow)

If you are missing credentials, contact your Scale representative.

## Authentication

Send requests over HTTPS to your SGP host—the same origin you use to open the Workflows UI. Every example below uses a base URL of the form `https://<your-sgp-host>/corp-api/compass/api`.

Include these headers on each request:

```http theme={null}
x-scale-api-key: <your-api-key>
x-selected-account-id: <your-account-id>
```

`<your-api-key>` is your Scale API key. `<your-account-id>` is the ID of the account that owns the workflow you want to run.

## Run and wait

The usual pattern is to start a run, then block until it finishes (up to a configurable timeout).

```typescript theme={null}
const BASE_URL = 'https://<your-sgp-host>/corp-api/compass/api';
const HEADERS = {
  'x-scale-api-key': process.env.SCALE_API_KEY!,
  'x-selected-account-id': process.env.SCALE_ACCOUNT_ID!,
  'Content-Type': 'application/json',
};

async function runWorkflow(workflowId: string, variables?: Record<string, string>) {
  const runRes = await fetch(`${BASE_URL}/v1/workflow/run`, {
    method: 'POST',
    headers: HEADERS,
    body: JSON.stringify({ workflowId, variables }),
  });
  if (!runRes.ok) throw new Error(`Failed to start workflow: ${await runRes.text()}`);
  const { sessionId } = await runRes.json();

  const resultRes = await fetch(
    `${BASE_URL}/v1/sessions/${sessionId}/result?timeout=600000`,
    { headers: HEADERS },
  );
  if (!resultRes.ok) throw new Error(`Failed to get result: ${await resultRes.text()}`);
  return resultRes.json();
}

async function main() {
  const result = await runWorkflow('wf-abc123', { region: 'US', date: '2024-01-01' });
  console.log(`${result.data.length} rows returned`);
  console.log(result.data);
}

main().catch(console.error);
```

### curl equivalent

```bash theme={null}
BASE_URL="https://<your-sgp-host>/corp-api/compass/api"

SESSION_ID=$(curl -s -X POST "$BASE_URL/v1/workflow/run" \
  -H "x-scale-api-key: $SCALE_API_KEY" \
  -H "x-selected-account-id: $SCALE_ACCOUNT_ID" \
  -H "Content-Type: application/json" \
  -d '{"workflowId": "wf-abc123", "variables": {"region": "US"}}' \
  | jq -r '.sessionId')

curl -s "$BASE_URL/v1/sessions/$SESSION_ID/result?timeout=600000" \
  -H "x-scale-api-key: $SCALE_API_KEY" \
  -H "x-selected-account-id: $SCALE_ACCOUNT_ID"
```

## API reference

All paths below are relative to `BASE_URL` (`https://<your-sgp-host>/corp-api/compass/api`).

### `POST /v1/workflow/run`

Starts a workflow run and returns immediately with a `sessionId`.

**Request body:**

```json theme={null}
{
  "workflowId": "wf-abc123",
  "variables": {
    "region": "US",
    "threshold": "0.8"
  }
}
```

`variables` is optional. Values override defaults configured in the workflow and are substituted into `{{variable_name}}` placeholders in your cards. For typical use cases, use flat string values.

**Response:**

```json theme={null}
{ "sessionId": "compass-sess-..." }
```

### `GET /v1/sessions/:sessionId`

Returns the current status of a running session. Use this endpoint when you want to poll instead of blocking on the result endpoint.

**Example response:**

```json theme={null}
{
  "sessionState": {
    "status": "idle",
    "cards": [
      {
        "id": "card-0",
        "context": { "cardType": "import_csv" },
        "status": "completed",
        "output": {}
      }
    ]
  }
}
```

**Session status values**

| Status         | Meaning                                |
| -------------- | -------------------------------------- |
| `starting`     | Session is initializing                |
| `pending_work` | Cards are queued to run                |
| `processing`   | A card is currently executing          |
| `idle`         | All cards finished; results are ready  |
| `errored`      | The session hit an unrecoverable error |
| `inactive`     | Session expired or was cancelled       |

**Card status values**

| Status       | Meaning                         |
| ------------ | ------------------------------- |
| `idle`       | Card has not been scheduled yet |
| `pending`    | Queued to run                   |
| `processing` | Currently executing             |
| `completed`  | Finished successfully           |
| `errored`    | Failed; check `errorMessage`    |

### `GET /v1/sessions/:sessionId/result?timeout=<ms>`

Waits until the session reaches `idle` or `errored`, or until `timeout` milliseconds elapse (default `600000`, ten minutes), then returns the final output.

**Query parameters**

* `timeout` — milliseconds to wait (default `600000`)

**Example response:**

```json theme={null}
{
  "data": [
    { "name": "Alice", "score": 0.92 },
    { "name": "Bob", "score": 0.85 }
  ],
  "schema": [
    { "id": "name", "header": "Name", "type": "string" },
    { "id": "score", "header": "Score", "type": "number" }
  ],
  "start": 0,
  "end": 2,
  "result": {
    "summary": {
      "totalNumberOfRows": 2,
      "executionDurationMillis": 3210
    }
  }
}
```

`data` contains the rows. `schema` describes the columns. `start` and `end` describe the row index range returned.

## Polling instead of blocking

If you prefer short requests instead of a long-lived connection, poll `GET /v1/sessions/:sessionId` until the session is `idle`, then call `GET /v1/sessions/:sessionId/result` to fetch the tabular output.

The following example reuses the same `BASE_URL` and `HEADERS` as in [Run and wait](#run-and-wait).

```typescript theme={null}
async function waitForResult(sessionId: string, pollIntervalMs = 5000, timeoutMs = 600000) {
  const deadline = Date.now() + timeoutMs;

  while (Date.now() < deadline) {
    const res = await fetch(`${BASE_URL}/v1/sessions/${sessionId}`, { headers: HEADERS });
    if (!res.ok) throw new Error(`[${res.status}] Failed to poll session: ${await res.text()}`);
    const { sessionState } = await res.json();

    if (sessionState.status === 'errored' || sessionState.status === 'inactive') {
      throw new Error(`Session ended with status: ${sessionState.status}`);
    }

    if (sessionState.status === 'idle') {
      const failedCards = sessionState.cards.filter((c: { status: string }) => c.status === 'errored');
      if (failedCards.length > 0) {
        throw new Error(
          `${failedCards.length} card(s) failed: ${failedCards.map((c: { errorMessage?: string }) => c.errorMessage).join(', ')}`,
        );
      }
      return sessionState;
    }

    await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
  }

  throw new Error('Timed out waiting for session to complete');
}
```

<Note>
  Polling returns session state (status and card metadata), not the full workflow table. After the session reaches `idle`, call `GET /v1/sessions/:sessionId/result` to retrieve the rows.
</Note>

## Workflow variables

Variables let you parameterize a workflow without editing its definition. They replace `{{variable_name}}` placeholders in card configuration (for example, filter values, prompt templates, or model parameters).

```typescript theme={null}
await runWorkflow('wf-abc123', {
  model: 'gpt-4o',
  prompt: 'Summarize the following in one sentence',
  min_score: '0.7',
});
```

Values you pass override defaults saved on the workflow. Any variable you omit uses the workflow’s saved default.

## Error handling

Error bodies are JSON. Many errors look like this:

```json theme={null}
{
  "error": "Workflow not found or you do not have access.",
  "status_code": 404
}
```

Authentication failures may include both `error` and `message`:

```json theme={null}
{
  "error": "Unauthorized",
  "message": "Missing authentication header."
}
```

| HTTP status | Meaning                                                  |
| ----------- | -------------------------------------------------------- |
| 400         | Malformed request (missing required field, bad format)   |
| 401         | Missing or invalid API key or account ID                 |
| 404         | Workflow or session not found, or you do not have access |
| 500         | Internal server error                                    |

This variant parses JSON error bodies from failed responses:

```typescript theme={null}
async function runWorkflowSafe(workflowId: string, variables?: Record<string, string>) {
  const runRes = await fetch(`${BASE_URL}/v1/workflow/run`, {
    method: 'POST',
    headers: HEADERS,
    body: JSON.stringify({ workflowId, variables }),
  });

  if (!runRes.ok) {
    const err = await runRes.json().catch(() => ({ error: runRes.statusText }));
    throw new Error(`[${runRes.status}] ${err.error ?? err.message}`);
  }

  const { sessionId } = await runRes.json();

  const resultRes = await fetch(
    `${BASE_URL}/v1/sessions/${sessionId}/result?timeout=600000`,
    { headers: HEADERS },
  );

  if (!resultRes.ok) {
    const err = await resultRes.json().catch(() => ({ error: resultRes.statusText }));
    throw new Error(`[${resultRes.status}] ${err.error ?? err.message}`);
  }

  return resultRes.json();
}
```

`runWorkflowSafe` assumes `BASE_URL` and `HEADERS` are defined the same way as in [Run and wait](#run-and-wait).
