---
title: Execution Reference
description: REST endpoints for executing resources, querying execution history, and managing deployments; plus React UI components and hooks for triggering executions from custom pages via @elevasis/ui
---

This page covers two complementary surfaces for driving resource executions:

- **REST API** -- the `/api/external/` endpoints you call directly or through the CLI.
- **UI components** -- the `@elevasis/ui` components and hooks for building interactive Run dialogs in custom React pages.

---

## REST API

The Elevasis external API exposes REST endpoints under `/api/external/` for executing your deployed resources and inspecting results. All endpoints require your `ELEVASIS_PLATFORM_KEY` sent as a Bearer token. Organization is resolved server-side from the API key -- you never pass an org identifier in requests.

**Authentication header:**

```
Authorization: Bearer sk_...
```

### API Base URL

| Environment | Base URL                    |
| ----------- | --------------------------- |
| Production  | `https://api.elevasis.io`   |
| Development | `http://localhost:<port>` |

Override the base URL by setting `ELEVASIS_API_URL` in your environment or passing `--api-url` to any CLI command.

### Endpoint Overview

| Method | Path                                                       | Purpose                                  |
| ------ | ---------------------------------------------------------- | ---------------------------------------- |
| `GET`  | `/api/external/resources`                                  | List all resources for your organization |
| `GET`  | `/api/external/resources/:resourceId/definition`           | Get resource metadata and schemas        |
| `POST` | `/api/external/execute`                                    | Execute a resource synchronously         |
| `POST` | `/api/external/execute-async`                              | Execute a resource asynchronously        |
| `POST` | `/api/external/executions/:resourceId/:executionId/cancel` | Cancel a running execution               |
| `GET`  | `/api/external/executions/:resourceId`                     | List execution history for a resource    |
| `GET`  | `/api/external/executions/:resourceId/:executionId`        | Get full execution detail                |
| `POST` | `/api/external/deploy`                                     | Upload bundle and deploy resources       |
| `GET`  | `/api/external/deployments`                                | List all deployments                     |
| `GET`  | `/api/external/deployments/:id`                            | Get a deployment by ID                   |

### Execution Endpoints

#### POST /api/external/execute

Execute a resource synchronously. The request waits for the resource to complete and returns the full result.

**Request body:**

```json
{
  "resourceId": "onboard-client",
  "input": {
    "clientName": "Jane",
    "email": "jane@example.com"
  }
}
```

**Response:**

```json
{
  "executionId": "550e8400-e29b-41d4-a716-446655440000",
  "success": true,
  "data": {
    "success": true,
    "clientId": "client_1708521600000",
    "welcomeEmailSent": true
  }
}
```

#### POST /api/external/execute-async

Execute a resource asynchronously. Returns immediately with an `executionId`. Poll `GET /executions/:resourceId/:executionId` to check status and retrieve output.

**Request body:**

```json
{
  "resourceId": "onboard-client",
  "input": { "clientName": "Jane" }
}
```

**Response:**

```json
{
  "executionId": "550e8400-e29b-41d4-a716-446655440000",
  "status": "running"
}
```

#### POST /api/external/executions/:resourceId/:executionId/cancel

Cancel a running execution. If the execution is found in memory, sends a cancellation signal immediately. Falls back to a database status update if no in-memory signal is found.

**Response:**

```json
{
  "success": true
}
```

### Resource Endpoints

#### GET /api/external/resources

List all resources registered to your organization. In production, only `status: 'prod'` resources are returned.

**Response:**

```json
[
  {
    "resourceId": "onboard-client",
    "resourceType": "workflow",
    "name": "Onboard Client",
    "status": "prod"
  },
  {
    "resourceId": "email-assistant",
    "resourceType": "agent",
    "name": "Email Assistant",
    "status": "prod"
  }
]
```

#### GET /api/external/resources/:resourceId/definition

Get the full definition for a single resource: metadata, input schema, and output schema.

**Response:**

```json
{
  "resourceId": "onboard-client",
  "resourceType": "workflow",
  "name": "Onboard Client",
  "description": "Creates a client record and sends a welcome email",
  "status": "prod",
  "inputSchema": {
    "type": "object",
    "properties": {
      "clientName": { "type": "string" },
      "email": { "type": "string", "format": "email" }
    },
    "required": ["clientName", "email"]
  },
  "outputSchema": {
    "type": "object",
    "properties": {
      "success": { "type": "boolean" },
      "clientId": { "type": "string" }
    }
  }
}
```

Returns `404` if the resource is not found.

### Execution History Endpoints

#### GET /api/external/executions/:resourceId

List execution history for a resource.

**Query parameters:**

| Parameter | Description                                                     |
| --------- | --------------------------------------------------------------- |
| `limit`   | Maximum number of results (default: 20)                         |
| `status`  | Filter by status: `running`, `completed`, `failed`, `cancelled` |

**Response:**

```json
[
  {
    "executionId": "550e8400-e29b-41d4-a716-446655440000",
    "resourceId": "onboard-client",
    "status": "completed",
    "createdAt": "2026-02-25T14:32:01Z",
    "durationMs": 1234
  }
]
```

#### GET /api/external/executions/:resourceId/:executionId

Get the full detail for a single execution including input, output, logs, and error.

**Response:**

```json
{
  "executionId": "550e8400-e29b-41d4-a716-446655440000",
  "resourceId": "onboard-client",
  "status": "completed",
  "createdAt": "2026-02-25T14:32:01Z",
  "durationMs": 1234,
  "input": {
    "clientName": "Jane",
    "email": "jane@example.com"
  },
  "output": {
    "success": true,
    "clientId": "client_1708521600000",
    "welcomeEmailSent": true
  },
  "logs": [
    { "timestamp": "2026-02-25T14:32:01.123Z", "message": "Starting onboard-client workflow" },
    { "timestamp": "2026-02-25T14:32:01.456Z", "message": "Created client record" }
  ],
  "error": null
}
```

### Deployment Endpoints

#### POST /api/external/deploy

Upload a resource bundle and deploy it. Accepts a multipart request with the compiled bundle and resource metadata.

**Response:**

```json
{
  "deployId": "deploy_abc123",
  "status": "active",
  "resources": 4
}
```

Use `elevasis-sdk deploy` from the CLI rather than calling this endpoint directly -- the CLI handles bundling, metadata generation, and status streaming automatically.

#### GET /api/external/deployments

List all deployments for your organization.

**Response:**

```json
[
  {
    "id": "deploy_abc123",
    "status": "active",
    "createdAt": "2026-02-25T14:00:00Z",
    "resources": 4
  },
  {
    "id": "deploy_abc122",
    "status": "stopped",
    "createdAt": "2026-02-24T09:30:00Z",
    "resources": 3
  }
]
```

#### GET /api/external/deployments/:id

Get a single deployment by ID.

**Response shape:** Same as a single item from `GET /deployments`.

### CLI Execution Commands

The SDK CLI wraps all execution endpoints. Use these commands instead of calling the API directly during development:

| Command                                                | API call                                     | Purpose                  |
| ------------------------------------------------------ | -------------------------------------------- | ------------------------ |
| `elevasis-sdk resources`                               | `GET /api/external/resources`                | List all resources       |
| `elevasis-sdk describe <resource>`                   | `GET /api/external/resources/:id/definition` | Show resource definition |
| `elevasis-sdk exec <resource> --input '...'`         | `POST /api/external/execute`                 | Execute synchronously    |
| `elevasis-sdk exec <resource> --input '...' --async` | `POST /api/external/execute-async`           | Execute asynchronously   |
| `elevasis-sdk executions <resource>`                 | `GET /api/external/executions/:id`           | List execution history   |
| `elevasis-sdk execution <resource> <id>`           | `GET /api/external/executions/:id/:execId`   | Get execution detail     |
| `elevasis-sdk deployments`                             | `GET /api/external/deployments`              | List deployments         |

---

## UI Execution

`@elevasis/ui` ships a set of components and hooks that let your custom React pages trigger workflow and agent executions without wiring up API calls manually. This section is for template users building interactive resource pages in `external/_template` who want to add Run buttons, input forms, and execution result displays.

The API comes in three tiers: a convenience one-tag component that handles everything, a controlled form primitive for custom modal chrome, and low-level hooks for fully custom rendering. Most template pages need only the first tier.

### Quick Start -- `ResourceExecuteDialog`

`ResourceExecuteDialog` combines a modal shell, an input form, mutation state, and result display into a single component. Drop it next to a Button and you have a working Run dialog in about fifteen lines.

```tsx
import { useState } from 'react'
import { Button } from '@mantine/core'
import { ResourceExecuteDialog } from '@elevasis/ui/features/operations'
import { useNavigate } from '@tanstack/react-router'

const resource = {
  resourceId: 'qualify-lead-workflow',
  resourceType: 'workflow' as const,
  name: 'Qualify Lead',
  formSchema: {
    fields: [
      { name: 'email', label: 'Lead email', type: 'text', required: true },
      { name: 'company', label: 'Company', type: 'text', required: false },
    ],
  },
}

export function QualifyLeadPage() {
  const [opened, setOpened] = useState(false)
  const navigate = useNavigate()

  return (
    <>
      <Button onClick={() => setOpened(true)}>Run</Button>

      <ResourceExecuteDialog
        opened={opened}
        onClose={() => setOpened(false)}
        resource={resource}
        onViewExecution={(executionId) =>
          navigate({ to: '/logs/$executionId', params: { executionId } })
        }
      />
    </>
  )
}
```

What the user sees when they click Run:

- A modal opens titled "Run workflow" with the resource ID displayed.
- The form renders one field per entry in `formSchema.fields`. Required fields show validation errors on submit.
- On submit, a loading overlay appears while the POST is in flight.
- On success, a teal confirmation card shows the execution ID. A "View execution" button calls `onViewExecution` with that ID.
- On error, a red alert shows the error message with a "Try again" option.

If `formSchema` is omitted or has no fields, the form skips to a single "Run" button with the message "This workflow takes no input."

### The Three-Tier API

| Tier            | Component / Hook                           | Use case                                                                                        |
| --------------- | ------------------------------------------ | ----------------------------------------------------------------------------------------------- |
| Convenience     | `ResourceExecuteDialog`                    | One-tag Run button + modal + form. Parent only manages `opened` state.                          |
| Controlled form | `ResourceExecuteForm`                      | Custom modal chrome; parent owns the mutation and decides where to show results.                |
| Low-level       | `ExecuteWorkflowModal` + `useExecuteAsync` | Fully custom rendering, non-standard modal behavior, or composing execution into a larger flow. |

Start with `ResourceExecuteDialog`. Move down the tiers only when you need something the convenience component cannot do.

### Controlled Usage with `ResourceExecuteForm`

Use `ResourceExecuteForm` when you want to own the modal or display the result somewhere outside the dialog -- for example, rendering the execution ID inline on the page after submission.

`ResourceExecuteForm` accepts `onSubmit` and `isPending` from the parent. The parent calls `useExecuteAsync()` directly and passes `mutateAsync` as the submit handler.

```tsx
import { useState } from 'react'
import { Modal, Button, Text } from '@mantine/core'
import { ResourceExecuteForm } from '@elevasis/ui/features/operations'
import { useExecuteAsync } from '@elevasis/ui/hooks'

const formSchema = {
  fields: [
    { name: 'topic', label: 'Topic', type: 'text', required: true },
  ],
}

export function CustomRunPanel() {
  const [opened, setOpened] = useState(false)
  const mutation = useExecuteAsync()

  const handleSubmit = async (input: Record<string, unknown>) => {
    await mutation.mutateAsync({
      resourceId: 'research-agent',
      resourceType: 'agent',
      input,
    })
  }

  return (
    <>
      <Button onClick={() => setOpened(true)}>Run agent</Button>

      {mutation.data && (
        <Text size="sm">Started: {mutation.data.executionId}</Text>
      )}

      <Modal opened={opened} onClose={() => setOpened(false)} title="Run research agent">
        <ResourceExecuteForm
          formSchema={formSchema}
          onSubmit={handleSubmit}
          isPending={mutation.isPending}
          disabled={mutation.isPending}
          submitLabel="Start research"
        />
      </Modal>
    </>
  )
}
```

`ResourceExecuteForm` props:

| Prop          | Type                                 | Default  | Notes                                |
| ------------- | ------------------------------------ | -------- | ------------------------------------ |
| `formSchema`  | `SerializedExecutionFormSchema`      | required | Drives field rendering               |
| `onSubmit`    | `(input) => void | Promise<void>` | required | Called with mapped field values      |
| `isPending`   | `boolean`                            | `false`  | Shows loading state on submit button |
| `disabled`    | `boolean`                            | `false`  | Disables all fields and button       |
| `submitLabel` | `string`                             | `'Run'`  | Button label when not pending        |

### Low-Level: Hooks and `ExecuteWorkflowModal`

For complete rendering control, use `useExecuteAsync` and `ExecuteWorkflowModal` independently. `ExecuteWorkflowModal` is a Mantine Modal pre-wired with a loading overlay, a success card (execution ID + "View execution" button), and an error alert. You pass `children` for the input area.

```tsx
import { ExecuteWorkflowModal } from '@elevasis/ui/features/operations'
import { useExecuteAsync } from '@elevasis/ui/hooks'

const { mutateAsync, isPending, error, data, reset } = useExecuteAsync()

<ExecuteWorkflowModal
  opened={opened}
  onClose={() => setOpened(false)}
  resource={{ resourceId: 'my-workflow', resourceType: 'workflow', name: 'My Workflow' }}
  isPending={isPending}
  error={error}
  result={data ?? null}
  onViewExecution={handleViewExecution}
  onReset={reset}
>
  {/* your custom form or content here */}
</ExecuteWorkflowModal>
```

The modal locks close interactions (click-outside, Escape, close button) while `isPending` is true, preventing accidental dismissal mid-execution.

Source: `packages/ui/src/features/operations/executions/ExecuteWorkflowModal.tsx`, `packages/ui/src/hooks/executions/useExecuteAsync.ts`

### Zod-Validated Execution with `useExecuteWorkflow`

`useExecuteWorkflow` wraps `useExecuteAsync` with a Zod parse step before the POST. Use it when you are constructing input programmatically (not from a form) and want to catch schema mismatches before they reach the API.

```tsx
import { z } from 'zod'
import { useExecuteWorkflow } from '@elevasis/ui/hooks'

const inputSchema = z.object({
  email: z.string().email(),
  score: z.number().min(0).max(100),
})

function useScoredExecution() {
  return useExecuteWorkflow({ schema: inputSchema })
}

// In a component:
const { execute, isPending, data, error } = useScoredExecution()

await execute({
  resourceId: 'score-lead-workflow',
  resourceType: 'workflow',
  input: { email: 'lead@example.com', score: 72 },
})
```

If `input` fails the Zod parse, `execute` throws synchronously with the message `"Invalid workflow input: ..."` before making any network request. The `mutation` object on the return value is the underlying `useExecuteAsync` mutation, giving you full TanStack Query state if needed.

Prefer `useExecuteWorkflow` over raw `useExecuteAsync` when:

- Input is assembled from multiple sources (not a single form submit).
- You want TypeScript inference on the input shape via `z.infer`.
- You want a clear validation error message rather than a 400 from the API.

Source: `packages/ui/src/hooks/executions/useExecuteWorkflow.ts`

### Input Forms

`ResourceExecuteDialog` and `ResourceExecuteForm` auto-render form fields from `formSchema.fields`. Each field is a `SerializedFormField` with at minimum `name`, `label`, and `type`.

Supported field types:

- `text` -- single-line text input
- `textarea` -- multi-line text input
- `number` -- numeric input
- `select` -- dropdown with `options` array
- `checkbox` -- boolean toggle; defaults to `false`
- `radio` -- radio group with `options` array
- `richtext` -- rich text editor

Required fields (`required: true`) are validated on submit. Custom field-to-input-key remapping is supported via `formSchema.fieldMappings` -- a `Record<string, string>` that renames field values before they are passed to `onSubmit`.

If `formSchema` is undefined, an empty object, or has `fields: []`, the form skips rendering fields entirely and shows "This workflow takes no input." with a single Run button.

### Error and Result Handling

Mutation state flows automatically into the modal chrome when using `ResourceExecuteDialog` or `ExecuteWorkflowModal`:

- **Success** -- a teal alert card displays the execution ID from `result.executionId`. If `onViewExecution` is provided, a "View execution" button calls it with the ID. This is the recommended way to deep-link to the Execution Logs page.
- **Error** -- a red alert displays `error.message`. A "Try again" button calls `onReset`, which clears the mutation state and re-shows the form.
- **Pending** -- a blur overlay covers the form area while the POST is in flight. The modal cannot be dismissed during this state.

`useExecuteAsync` also invalidates the executions list query on success (keyed by `organizationId` + `resourceId`), so any `useExecutions` query on the page refreshes automatically.

---

## Related

- [Deployment Overview](index.mdx) -- deploy pipeline, versioning, and bundle upload
- [Command Center](command-center.mdx) -- built-in managed UI; includes Run buttons for all deployed resources
- [Resource Patterns](../resources/patterns.mdx) -- HITL approval patterns and `approval.create()`
- [Platform Adapters](../platform-tools/adapters-platform.mdx) -- `approval.create()` reference
