# HTTP API reference

> Core Executor HTTP API groups: `tools`, `integrations`, `connections`, `providers`, `executions`, `oauth`, and `policies` routes, payloads, and error shapes.

- Repository: RhysSullivan/executor
- GitHub: https://github.com/RhysSullivan/executor
- Human docs: https://grok-wiki.com/public/docs/rhyssullivan-executor-564383868052
- Complete Markdown: https://grok-wiki.com/public/docs/rhyssullivan-executor-564383868052/llms-full.txt

## Source Files

- `packages/core/api/src/api.ts`
- `packages/core/api/src/tools/api.ts`
- `packages/core/api/src/integrations/api.ts`
- `packages/core/api/src/connections/api.ts`
- `packages/core/api/src/executions/api.ts`
- `packages/core/api/src/oauth/api.ts`
- `packages/core/api/src/policies/api.ts`

---

---
title: "HTTP API reference"
description: "Core Executor HTTP API groups: `tools`, `integrations`, `connections`, `providers`, `executions`, `oauth`, and `policies` routes, payloads, and error shapes."
---

Executor exposes a typed Effect `HttpApi` (`CoreExecutorApi`) composed with plugin groups via `composePluginApi(plugins)`. Every host serves the combined API under the `/api` path prefix (for example `http://localhost:4788/api` on the local daemon). Routes are grouped by resource name; the typed client mirrors group names (`api.tools.list`, `api.connections.create`, and so on).

<Note>
Tool invocation is not a standalone HTTP route in the core API. Call tools through `POST /executions` (sandbox code) or the MCP `execute` tool. The `tools` group is catalog read-only: list metadata and fetch schemas.
</Note>

## Base URL and authentication

| Runtime | Typical base URL | Auth mechanism |
| --- | --- | --- |
| Local daemon | `http://localhost:4788/api` | Single-user token (see `apps/local/src/auth.ts`) |
| Self-hosted | `https://<host>/api` | Better Auth session cookie or API key |
| Executor Cloud | `https://<org>.executor.dev/api` | WorkOS sealed session or Bearer API key |

Protected routes run behind `IdentityProvider` middleware. Auth failures use tagged errors:

| Error | Status | When |
| --- | --- | --- |
| `Unauthorized` | 401 | Missing or invalid credential |
| `NoOrganization` | 403 | Valid credential, no organization binding |
| `Unavailable` | 503 | Transient auth backend outage (cloud API-key validation) |

Hosts may attach optional `code` and `message` fields on auth errors for UI display. Credential precedence (cookie vs Bearer vs API key) is host-specific and stays inside each `IdentityProvider` implementation.

Unauthenticated probes exist outside the seven core groups: `GET /api/health` (liveness) and, on self-host, `GET /api/setup-status`. OpenAPI Swagger is available at `/api/docs` when the host wires `HttpApiSwagger`.

## API composition

```text
CoreExecutorApi ("executor")
├── tools
├── integrations
├── connections
├── providers
├── executions
├── oauth
└── policies

+ plugin groups (per loaded plugin)
  ├── openapi   → /openapi/*
  ├── graphql   → /graphql/*
  ├── mcp       → /mcp/*
  └── …
```

Plugin routes extend the same `/api` namespace. Registration endpoints (`POST /openapi/specs`, GraphQL `addIntegration`, MCP `addServer`, and similar) live in plugin groups, not in the core `integrations` group. The core `integrations` group is the tenant catalog (list, get, update, remove, detect).

## Error shapes

Executor uses Effect Schema tagged errors on the wire. Domain failures include a `_tag` discriminator and typed fields. HTTP status comes from `httpApiStatus` annotations on each error schema.

<ResponseField name="_tag" type="string">
Tagged error name, for example `ConnectionNotFoundError`, `ToolNotFoundError`, `OAuthStartError`.
</ResponseField>

<ResponseField name="traceId" type="string">
Present only on `InternalError` (HTTP 500). Opaque correlation id for operator lookup. No internal message or stack crosses the wire.
</ResponseField>

| Error | Status | Typical fields |
| --- | --- | --- |
| `InternalError` | 500 | `traceId` |
| `ToolNotFoundError` | 404 | `address`, optional `suggestions` |
| `IntegrationNotFoundError` | 404 | `slug` |
| `IntegrationRemovalNotAllowedError` | 409 | `slug` |
| `ConnectionNotFoundError` | 404 | `owner`, `integration`, `name` |
| `InvalidConnectionInputError` | 400 | `message` |
| `CredentialProviderNotRegisteredError` | 409 | `provider` |
| `ExecutionNotFoundError` | 404 | `executionId` |
| `OAuthStartError` | 400 | `message` |
| `OAuthCompleteError` | 400 | `message`, optional `restartRequired` |
| `OAuthProbeError` | 400 | `message` |
| `OAuthRegisterDynamicError` | 400 | `message` |
| `OAuthSessionNotFoundError` | 404 | `state` |

Storage failures inside handlers are captured and re-thrown as `InternalError({ traceId })` at the HTTP edge.

## Shared identifiers

| Identifier | Shape | Example |
| --- | --- | --- |
| `Owner` | `"org"` or `"user"` | Workspace-shared vs personal credentials |
| `IntegrationSlug` | string | `vercel`, `executor` |
| `ConnectionName` | string | `prod` |
| `ConnectionAddress` | string | `tools.vercel.org.prod` |
| `ToolAddress` | dotted string | `tools.vercel.org.prod.listProjects` |
| `PolicyId` | string | Opaque rule id |
| `OAuthClientSlug` | string | Registered OAuth app slug |
| `OAuthState` | string | Flow correlation token from `start` |
| `ProviderKey` | string | `default`, `keychain`, `1password` |
| `ProviderItemId` | string | Opaque handle in the provider backend |

`ToolAddress` is always carried as a query parameter, never as a path segment, because dots are valid in addresses.

## Tools

Catalog read surface. Tools are per-connection and address-keyed.

| Method | Path | Purpose |
| --- | --- | --- |
| `GET` | `/tools` | List tool metadata |
| `GET` | `/tools/schema` | Full schema view for one address |

:::endpoint GET /tools List tools

<ParamField query="integration" type="IntegrationSlug">
Filter by integration slug.
</ParamField>

<ParamField query="owner" type="Owner">
Filter by owner (`org` or `user`).
</ParamField>

<ParamField query="connection" type="ConnectionName">
Filter by connection name.
</ParamField>

<ParamField query="query" type="string">
Free-text search across tool names and descriptions.
</ParamField>

<ParamField query="includeAnnotations" type="string">
Pass `"true"` to include `mayElicit`, `requiresApproval`, and `approvalDescription`.
</ParamField>

<ParamField query="includeBlocked" type="string">
Pass `"false"` to omit tools blocked by policy. Default includes blocked tools.
</ParamField>

<ResponseField name="address" type="ToolAddress" />
<ResponseField name="owner" type="Owner" />
<ResponseField name="integration" type="IntegrationSlug" />
<ResponseField name="connection" type="ConnectionName" />
<ResponseField name="name" type="string" />
<ResponseField name="pluginId" type="string" />
<ResponseField name="description" type="string" />
<ResponseField name="requiresApproval" type="boolean">
Plugin-derived default approval annotation when no user policy matches.
</ResponseField>

:::

:::endpoint GET /tools/schema Get tool schema

<ParamField query="address" type="ToolAddress" required>
Full tool address.
</ParamField>

Returns `ToolSchemaView`: `inputSchema`, `outputSchema`, `schemaDefinitions`, and optional TypeScript preview strings.

Errors: `ToolNotFoundError` (404), `InternalError` (500).

:::

## Integrations

Tenant-level catalog identities. No scope segment in paths; the executor binds catalog access from request auth.

| Method | Path | Purpose |
| --- | --- | --- |
| `GET` | `/integrations` | List integrations |
| `GET` | `/integrations/:slug` | Get one integration |
| `PATCH` | `/integrations/:slug` | Update display metadata |
| `DELETE` | `/integrations/:slug` | Remove integration |
| `POST` | `/integrations/detect` | Autodetect integration from URL |

:::endpoint GET /integrations/:slug Get integration

<ResponseField name="slug" type="IntegrationSlug" />
<ResponseField name="name" type="string">
Display name.
</ResponseField>
<ResponseField name="description" type="string" />
<ResponseField name="kind" type="string">
Owning plugin id (for example `openapi`, `mcp`, `built-in`).
</ResponseField>
<ResponseField name="canRemove" type="boolean" />
<ResponseField name="canRefresh" type="boolean" />
<ResponseField name="authMethods" type="AuthMethodDescriptor[]">
Declared auth methods with `kind` (`oauth`, `apikey`, `header`, `none`), `template`, optional `placements`, and optional `oauth` discovery fields.
</ResponseField>
<ResponseField name="displayUrl" type="string">
Non-secret URL for favicons.
</ResponseField>

Errors: `IntegrationNotFoundError` (404).

:::

:::endpoint POST /integrations/detect Detect integration from URL

<ParamField body="url" type="string" required>
Endpoint to probe. Max length 2048 characters.
</ParamField>

Returns an array of `IntegrationDetectionResult` objects (one per matching plugin): `kind`, `confidence` (`high` | `medium` | `low`), `endpoint`, `name`, `slug`.

:::

:::endpoint DELETE /integrations/:slug Remove integration

Returns `{ removed: true }`.

Errors: `IntegrationRemovalNotAllowedError` (409) when `canRemove` is false (static plugin integrations).

:::

## Connections

Owner-scoped credentials bound 1:1 to an integration. Identity is `(owner, integration, name)`.

| Method | Path | Purpose |
| --- | --- | --- |
| `GET` | `/connections` | List connections |
| `POST` | `/connections` | Create connection |
| `GET` | `/connections/:owner/:integration/:name` | Get connection |
| `PATCH` | `/connections/:owner/:integration/:name` | Update metadata |
| `DELETE` | `/connections/:owner/:integration/:name` | Remove connection |
| `POST` | `/connections/:owner/:integration/:name/refresh` | Refresh tools from upstream |

:::endpoint POST /connections Create connection

Exactly one credential origin is required: `value`, `values`, or `from`.

<ParamField body="owner" type="Owner" required />
<ParamField body="name" type="ConnectionName" required />
<ParamField body="integration" type="IntegrationSlug" required />
<ParamField body="template" type="AuthTemplateSlug" required />
<ParamField body="value" type="string">
Single pasted credential (sugar for a `token` input).
</ParamField>
<ParamField body="values" type="Record<string, string>">
Named inputs for multi-field auth methods.
</ParamField>
<ParamField body="from" type="object">
External reference: `{ provider: ProviderKey, id: ProviderItemId }`.
</ParamField>
<ParamField body="identityLabel" type="string | null">
Optional display label.
</ParamField>
<ParamField body="description" type="string | null">
Optional description.
</ParamField>

<ResponseField name="address" type="ConnectionAddress" />
<ResponseField name="provider" type="ProviderKey" />
<ResponseField name="oauthClient" type="OAuthClientSlug | null">
OAuth app slug when minted via OAuth; never a secret.
</ResponseField>
<ResponseField name="expiresAt" type="number | null">
Token expiry epoch ms when applicable.
</ResponseField>

Errors: `IntegrationNotFoundError` (404), `CredentialProviderNotRegisteredError` (409), `InvalidConnectionInputError` (400).

:::

:::endpoint POST /connections/:owner/:integration/:name/refresh Refresh connection tools

Re-discovers tools from the upstream integration and returns the updated tool list for that connection.

:::

## Providers

Credential-backend discovery for external secret stores.

| Method | Path | Purpose |
| --- | --- | --- |
| `GET` | `/providers` | List registered provider keys |
| `GET` | `/providers/:key/items` | Browse provider entries |

<ResponseField name="id" type="ProviderItemId">
Opaque item handle for `from: { provider, id }` on connection create.
</ResponseField>
<ResponseField name="name" type="string">
Human-readable entry label.
</ResponseField>

## Executions

Code-mode execution via QuickJS. Paused states cover auth elicitation, approval gates, and form input.

| Method | Path | Purpose |
| --- | --- | --- |
| `POST` | `/executions` | Execute sandbox code |
| `GET` | `/executions/:executionId` | Read paused execution info |
| `POST` | `/executions/:executionId/resume` | Resume a paused execution |

:::endpoint POST /executions Execute code

<ParamField body="code" type="string" required>
JavaScript executed in the QuickJS sandbox with access to `tools.*` bindings.
</ParamField>

<ResponseExample>

```json
{
  "status": "completed",
  "text": "…",
  "structured": {},
  "isError": false
}
```

```json
{
  "status": "paused",
  "text": "Execution paused: …\nexecutionId: …",
  "structured": { "executionId": "…", "elicitation": { … } }
}
```

</ResponseExample>

:::

:::endpoint POST /executions/:executionId/resume Resume paused execution

<ParamField body="action" type="string" required>
One of `accept`, `decline`, or `cancel`.
</ParamField>
<ParamField body="content" type="unknown">
Form elicitation payload when the pause expects user input.
</ParamField>

Returns the same `completed` | `paused` union as `execute`.

Errors: `ExecutionNotFoundError` (404) when the id is unknown or no longer paused.

:::

<RequestExample>

```bash
curl -X POST "$BASE/api/executions" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"code":"const r = await tools.executor.coreTools.tools.list({}); return JSON.stringify(r);"}'
```

</RequestExample>

## OAuth

OAuth is a credential mechanism, not an integration type. Register apps, run flows, and mint connections.

| Method | Path | Purpose |
| --- | --- | --- |
| `POST` | `/oauth/clients` | Register OAuth app (manual client id/secret) |
| `POST` | `/oauth/clients/register-dynamic` | RFC 7591 dynamic client registration |
| `GET` | `/oauth/clients` | List client summaries (no secrets) |
| `DELETE` | `/oauth/clients/:slug` | Remove OAuth app |
| `POST` | `/oauth/start` | Start flow to mint a connection |
| `POST` | `/oauth/complete` | Exchange authorization code |
| `POST` | `/oauth/cancel` | Cancel in-flight session |
| `POST` | `/oauth/probe` | Discover authorization-server metadata |
| `GET` | `/oauth/callback` | Browser popup callback (HTML) |

:::endpoint POST /oauth/start Start OAuth flow

<ParamField body="client" type="OAuthClientSlug" required />
<ParamField body="clientOwner" type="Owner" required>
Owner of the OAuth app (a personal connection may use a workspace app).
</ParamField>
<ParamField body="owner" type="Owner" required>
Owner of the connection being minted.
</ParamField>
<ParamField body="name" type="ConnectionName" required />
<ParamField body="integration" type="IntegrationSlug" required />
<ParamField body="template" type="AuthTemplateSlug" required />
<ParamField body="redirectUri" type="string | null">
Required for `authorization_code` grants.
</ParamField>

Returns either:

- `{ status: "connected", connection: ConnectionResponse }` for `client_credentials` or inline completion
- `{ status: "redirect", authorizationUrl, state }` when the user must visit the authorization URL

Errors: `OAuthStartError` (400).

:::

:::endpoint POST /oauth/complete Complete authorization code exchange

<ParamField body="state" type="OAuthState" required />
<ParamField body="code" type="string" required />
<ParamField body="callbackDomain" type="string | null">
Regional host echoed by the authorization server (Datadog `domain`/`site`).
</ParamField>

Returns a `ConnectionResponse`. Errors: `OAuthCompleteError` (400), `OAuthSessionNotFoundError` (404).

:::

:::endpoint GET /oauth/callback Browser callback

Query params: `state`, optional `code`, optional `error`, optional `error_description`, optional `domain`, optional `site`.

Renders HTML for the OAuth popup. The popup posts completion results to the opener via `postMessage` / `BroadcastChannel`. Allow-list redirect URI: `${origin}/api/oauth/callback`.

:::

<Warning>
`DELETE /oauth/clients/:slug` is idempotent but does not cascade to connections. Connections that referenced the removed slug keep their stored value and fail at the next token refresh.
</Warning>

## Policies

Owner-scoped tool policies gate invocation by pattern and action. Org rules are the outer guardrail; the most restrictive matched action across owners wins.

| Method | Path | Purpose |
| --- | --- | --- |
| `GET` | `/policies` | List policies |
| `POST` | `/policies` | Create policy |
| `PATCH` | `/policies/:policyId` | Update policy |
| `DELETE` | `/policies/:policyId` | Remove policy |

:::endpoint POST /policies Create policy

<ParamField body="owner" type="Owner" required />
<ParamField body="pattern" type="string" required>
Tool address glob (for example `tools.github.org.*.createIssue`).
</ParamField>
<ParamField body="action" type="string" required>
One of `approve`, `require_approval`, or `block`.
</ParamField>
<ParamField body="position" type="string">
Ordering hint for rule precedence within the owner.
</ParamField>

<ResponseField name="id" type="PolicyId" />
<ResponseField name="createdAt" type="number">
Epoch milliseconds.
</ResponseField>

:::

Delete and update payloads include `owner` to scope the mutation. Delete returns `{ removed: true }`.

| Action | Effect |
| --- | --- |
| `approve` | Allow without extra gate |
| `require_approval` | Pause execution until user approves via resume |
| `block` | Reject invocation with `ToolBlockedError` |

## Typed client

Use `HttpApiClient.make(composePluginApi(plugins), { baseUrl, transformClient })` with auth headers on every request. The e2e harness resolves `baseUrl` to `new URL("/api", target.baseUrl)`.

```typescript
import { HttpApiClient } from "effect/unstable/httpapi";
import { composePluginApi } from "@executor-js/api/server";

const api = composePluginApi([/* plugins */] as const);
const client = await Effect.runPromise(
  HttpApiClient.make(api, { baseUrl: "http://localhost:4788/api" }),
);
const tools = await Effect.runPromise(client.tools.list({ query: {} }));
```

The `@executor-js/api` package exports each group's route schemas (`ToolsApi`, `ConnectionsApi`, and so on) for OpenAPI generation and client derivation.

## Related pages

<CardGroup>
<Card title="Tools" href="/tools">
Tool addresses, discovery, and how invocation flows through executions and MCP.
</Card>
<Card title="Connections" href="/connections">
Owner-scoped credential model and OAuth minting.
</Card>
<Card title="Executions" href="/executions">
Paused states, resume actions, and elicitation handling.
</Card>
<Card title="Configure credentials" href="/configure-credentials">
Credential providers and connection create payloads.
</Card>
<Card title="SDK reference" href="/sdk-reference">
`createExecutor` and promise-mode entry points that mirror these routes.
</Card>
<Card title="Configuration reference" href="/configuration-reference">
`EXECUTOR_DATA_DIR`, ports, and web base URL env vars.
</Card>
</CardGroup>
