---
title: "Get or create an unnamed environment for a composed stack"
method: POST
path: "/projects/{projectId}/environments/ensure-adhoc"
tags: ["Environments"]
---

# Get or create an unnamed environment for a composed stack

`POST /projects/{projectId}/environments/ensure-adhoc`

GET-OR-CREATE an UNNAMED, content-addressed environment for a composed stack — a host plus an optional server group, model override, computer image and pinned skills.

Distinct from `POST /environments`, which mints a NAMED row that lands in the project's environment list forever. A composed stack is a throwaway: the caller wants to run this exact combination, not to add a permanent entry someone else has to reason about.

Deduplicated by a server-side fingerprint of the stack, so the same stack always returns the same environment. `created` says which happened — the status is `200` either way, because get-or-create cannot honestly answer `201` on the dedupe path.

A deployment that predates ad-hoc environments answers `400` with `details.reason: "ADHOC_UNAVAILABLE"`.

Requires project membership (admin only when the stack pins plugin versions). Promote one to a named environment with `POST /environments/{environmentId}/name`.

## Path parameters

- `projectId` string, required

## Request body

- AdhocEnvironmentRequest — A composed stack: the same execution axes a named environment carries, minus the name.
  - `hostId` string, required — The host the stack runs as.
  - `serverAttachmentId` string — Standalone server group to pin. Omit to use the host's own servers.
  - `modelId` string — Model to run instead of the host's pinned one. Stored verbatim.
  - `skillSelection` EnvironmentSkillSelection — An explicit pinned skill selection. Cannot be empty — clear the field instead (send `null` on update) to mean "no pinned skills".
    - `mode` 'explicit', required
    - `skillIds` string[], required — Project-shared skill IDs. Skills carrying supporting files or extra frontmatter, and plugin-component skills, cannot be pinned.
  - `pluginVersionIds` string[] — Pinning plugin versions requires project ADMIN; the rest of a stack needs only membership.
  - `sandboxImageId` string — Project-shared sandbox image to pin, so runs boot a fresh computer from it.

## Response `200`

The environment for this stack, created or reused.

- AdhocEnvironmentEnsured
  - `environment` AdhocEnvironment, required — An UNNAMED, content-addressed environment: a composed client/model/computer/skills stack rather than a saved entry in the project's environment list. Its own shape rather than `ProjectEnvironment` with a nullable name, because that schema's `name` is required and every environment listing filters ad-hoc rows out precisely so that promise holds.
    - `id` string, required
    - `projectId` string, required
    - `name` unknown, required
    - `adhoc` true, required — Always `true`. Present so a reader never has to infer it from the null name.
    - `description` string
    - `hostId` string, required
    - `serverAttachmentId` string
    - `modelId` string — The stack's model override. Absent means it inherits the host's.
    - `skillSelection` EnvironmentSkillSelection — An explicit pinned skill selection. Cannot be empty — clear the field instead (send `null` on update) to mean "no pinned skills".
      - `mode` 'explicit', required
      - `skillIds` string[], required — Project-shared skill IDs. Skills carrying supporting files or extra frontmatter, and plugin-component skills, cannot be pinned.
    - `pluginVersionIds` string[]
    - `sandboxImageId` string
    - `revision` integer, required — Pass back as `expectedRevision` when promoting it with a name.
    - `archived` boolean, required
    - `archivedAt` integer
    - `createdAt` integer, required
    - `updatedAt` integer, required
  - `created` boolean, required — `false` when the same stack had already been composed. The only way to tell a first compose from a repeat — the status line cannot, because get-or-create answers `200` either way.

## Other responses

- `400` — Malformed body or parameters.
- `401` — Missing, invalid, revoked, or orphaned key (`UNAUTHORIZED`) — or the **target MCP server** needs an OAuth grant (`OAUTH_REQUIRED`), which is a property of the server, not your key.
- `403` — Key is valid but not allowed to do this.
- `404` — Unknown project, server, or resource.
- `500` — Something failed on MCPJam's side.
- `502` — Could not connect to the target MCP server.

---

[API](https://skmtc.net/mcpjam/apis/mcpjam-api.md) · [All operations](https://skmtc.net/mcpjam/apis/mcpjam-api/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/mcpjam/mcpjam-api/revisions/8bf151c2dd8b/schema)
