# Getting Started

Base URL, first API call, response format, error handling, and pagination

## Base URL

All API requests are made to:

```
https://steelengine.com
```

## Quick Start

### Get your API key

Go to the SteelEngine platform and navigate to **Settings**, then go to **API Keys** and click **Create**. See [Authentication](/api-reference/authentication) for details on key types.

### Find your workflow ID

Open a workflow in the SteelEngine editor. The workflow ID is the last UUID in the URL:

```
https://steelengine.com/{orgSlug}/{workspaceSlug}/w/{workflowId}
```

The first segment after the host is your organization's slug, the second is your workspace's slug, and `{workflowId}` is the workflow's UUID. See [Organizations and Workspaces](/organizations) for the slug rules.

You can also use the [List Workflows](/api-reference/workflows/listWorkflows) endpoint to get all workflow IDs in a workspace.

### Deploy your workflow

A workflow must be deployed before it can be executed via the API. Click the **Deploy** button in the editor toolbar, or use the dashboard to manage deployments.

### Make your first request

The top-level fields of the request body are the workflow's input. Name them after the input fields defined on the workflow's Start block:

**curl**

```bash
curl -X POST https://steelengine.com/api/workflows/{workflowId}/execute \
  -H "Content-Type: application/json" \
  -H "X-API-Key: YOUR_API_KEY" \
  -d '{"message": "Hello"}'
```

**TypeScript**

```typescript
const response = await fetch(
  `https://steelengine.com/api/workflows/${workflowId}/execute`,
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': process.env.STEELENGINE_API_KEY!,
    },
    body: JSON.stringify({ message: 'Hello' }),
  }
)

const data = await response.json()
console.log(data.output)
```

**Python**

```python
import requests
import os

response = requests.post(
    f"https://steelengine.com/api/workflows/{workflow_id}/execute",
    headers={
        "Content-Type": "application/json",
        "X-API-Key": os.environ["STEELENGINE_API_KEY"],
    },
    json={"message": "Hello"},
)

data = response.json()
print(data["output"])
```

You can also wrap the fields in a top-level `input` object. `{"input": {"message": "Hello"}}` reaches the workflow the same way. Send a plain string, number, or array as `input`. The fields `stream`, `selectedOutputs`, `useDraftState`, `includeFileBase64`, `base64MaxBytes`, and `stopAfterBlockId` are execution controls. SteelEngine never passes them to the workflow. See [Execute Workflow](/api-reference/workflows/executeWorkflow) for what each one does.

## Sync vs Async Execution

By default, workflow executions are **synchronous** — the API blocks until the workflow completes and returns the result directly.

For long-running workflows, use **asynchronous execution** by sending the `X-Execution-Mode: async` header:

```bash
curl -X POST https://steelengine.com/api/workflows/{workflowId}/execute \
  -H "Content-Type: application/json" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "X-Execution-Mode: async" \
  -d '{"message": "Hello"}'
```

This returns immediately (HTTP 202) with a `jobId`:

```json
{
  "success": true,
  "async": true,
  "jobId": "exec_def456",
  "executionId": "exec_def456",
  "message": "Workflow execution queued",
  "statusUrl": "https://steelengine.com/api/jobs/exec_def456"
}
```

The `jobId` is the execution ID. Poll the [Get Job Status](/api-reference/workflows/getJobStatus) endpoint until the status is `completed`, `failed`, or `cancelled`:

```bash
curl https://steelengine.com/api/jobs/{jobId} \
  -H "X-API-Key: YOUR_API_KEY"
```

Job status transitions follow: `pending` → `processing` → `completed`, `failed`, or `cancelled`. A run that is waiting for human input reports `paused` and is not finished. The `output` field is present once the run has a result.

On SteelEngine Cloud a run's execution record expires 7 days after it finishes. After that the endpoint answers from run history: the same status and times, `metadata.source: "run-history"`, and the run-history copy of the output, which is size-capped and has secrets removed. Once run history is deleted, 30 days after the run finishes, the endpoint returns `404`.

Async requests cannot carry the execution controls `useDraftState`, `selectedOutputs`, `stopAfterBlockId`, `includeFileBase64`, or `base64MaxBytes`. If they are present, the API returns `400`.

A synchronous request can also return HTTP 202. This happens when the run was admitted but its result did not arrive in time. The body is then a pending receipt: `{"success": true, "pending": true, "executionId": "...", "statusUrl": "..."}`. The run is still going. Poll `statusUrl`. Sending the request again would start a second run.

You can also receive block outputs as they are produced. Set `stream: true` in the body. List the outputs in `selectedOutputs` as `blockName.field`, for example `agent1.content`. Send the `X-SteelEngine-Stream-Version: 2` header. The response is a Server-Sent Events stream. See the [TypeScript SDK](/api-reference/typescript#streaming-workflow-execution) for the frame format and a reader.

## Response Format

A successful synchronous execution returns the execution result, marked with the `X-SteelEngine-Response-Type: execution` header:

```json
{
  "success": true,
  "executionId": "exec_def456",
  "output": {
    "result": "Hello, world!"
  },
  "metadata": {
    "startTime": "2025-01-01T00:00:00.000Z",
    "endTime": "2025-01-01T00:00:01.250Z",
    "duration": 1250
  }
}
```

`status` is included when the engine reports one (`success`, `failed`, `cancelled`, or `paused`). A failed execution returns the same shape with `success: false`, an `error` message, and HTTP `500`.

If the workflow ends in a [Response block](/blocks/response), the API returns the body, status code, and headers that block authored instead, and sets `X-SteelEngine-Response-Type: response`. Every execution response also carries the execution ID in the `X-Execution-Id` header.

Execution responses do not include rate limit or usage information. The `/api/v1` workflows, logs, and audit logs endpoints attach a `limits` object with `workflowExecutionRateLimit` and `usage`. For everything else, call [Get Usage Limits](/api-reference/usage/getUsageLimits).

## Error Handling

The API uses standard HTTP status codes. Error responses include a human-readable `error` message:

```json
{
  "error": "Workflow not found"
}
```

| Status | Meaning                    | What to do                                                                                                                                              |
| ------ | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Invalid request parameters | Check the `details` array for specific field errors. `code: "INVALID_SELECTED_OUTPUT"` means a `selectedOutputs` entry names no existing block or field |
| `401`  | Missing or invalid API key | Verify your `X-API-Key` header                                                                                                                          |
| `402`  | Usage limit exceeded       | The billed account has spent its budget for the period. Check [Get Usage Limits](/api-reference/usage/getUsageLimits) for the current spend and limit   |
| `403`  | Access denied              | Check you have permission for this resource and that the key's scope covers it                                                                          |
| `404`  | Resource not found         | Verify the ID exists and belongs to your workspace                                                                                                      |
| `409`  | Conflict                   | The workflow call chain is invalid, or the workflow's environment promotion binding rejected the run                                                    |
| `429`  | Rate limit exceeded        | Back off and retry; see [Rate Limits](#rate-limits) for which responses carry `Retry-After`                                                             |
| `503`  | Result unavailable         | The run was admitted but its result could not be recovered (`code: "RESULT_UNAVAILABLE"`). Poll the `statusUrl` in the body instead of resubmitting     |

Use the [Get Usage Limits](/api-reference/usage/getUsageLimits) endpoint to check your current rate limit status and billing usage at any time.

## Rate Limits

Rate limits depend on your subscription plan. Self-hosted deployments with billing disabled use the enterprise limits for every account: 600 sync, 5000 async, and 500 API endpoint requests per minute by default, with the sync and async limits overridable through `RATE_LIMIT_ENTERPRISE_SYNC` and `RATE_LIMIT_ENTERPRISE_ASYNC`. Your account has a sync bucket and an async bucket, both reported by [Get Usage Limits](/api-reference/usage/getUsageLimits) as `requestsPerMinute`, `maxBurst`, `remaining`, and `resetAt`. The execute endpoint currently counts every API execution against the sync bucket, including requests sent with `X-Execution-Mode: async`.

When you are rate limited, the execute endpoint returns `429` with `{"error": "Rate limit exceeded. Please try again later."}`. This response has no `Retry-After` header. Back off and check `resetAt` from Get Usage Limits before you retry. A `429` that carries `Retry-After: 5` means the server admission gate is at capacity, not that your bucket is empty.

The `/api/v1` workflows, logs, and audit logs endpoints have their own per-key request limit. They report it in the `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` headers. Their `429` responses include `Retry-After`.

## Pagination

List endpoints (workflows, logs, audit logs) use **cursor-based pagination**:

```bash
# First page
curl "https://steelengine.com/api/v1/logs?limit=20" \
  -H "X-API-Key: YOUR_API_KEY"

# Next page — use the nextCursor from the previous response
curl "https://steelengine.com/api/v1/logs?limit=20&cursor=abc123" \
  -H "X-API-Key: YOUR_API_KEY"
```

The response includes a `nextCursor` field. When `nextCursor` is absent or `null`, you have reached the last page.
