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

# Pascal API (v1)

> Call Pascal's Architect from the OpenAI SDKs or the AI SDK to build and edit a project, run long turns in the background, cap a turn's credits, and download the 3D result.

The Pascal API lets your code ask the Architect, Pascal's building model, to work on a
project. It speaks the OpenAI Responses and Chat Completions formats, so the OpenAI
SDKs and the AI SDK's OpenAI providers call it the way they call a reasoning model. Each
request is one turn on one project: the Architect edits the scene, answers in text, and
saves what it changed as a named version of the project.

The base URL is:

```text theme={null}
https://editor.pascal.app/api/v1
```

Every route, parameter and response is in the
[OpenAPI document](https://editor.pascal.app/api/openapi.json).

## Before you start

* **An API key.** Create one in [Settings](https://editor.pascal.app/settings) under
  **API keys**, the same keys the [MCP server](/docs/developers/mcp#create-an-api-key)
  uses. Its user or organization must be able to edit the project.
* **A project id.** Every request names an existing project; the API never creates one.
* **Credits.** Turns are paid with Pascal credits, like messages to the
  [AI assistant](/docs/ai/assistant). See [Credits](/docs/account/credits).

## Run a turn

```ts theme={null}
import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.PASCAL_API_KEY,
  baseURL: "https://editor.pascal.app/api/v1",
});

const response = await client.responses.create({
  model: "architect-high",
  metadata: {
    project_id: "project_your_project_id",
    commit_message: "Add a bedroom off the hallway",
  },
  input: "Add a 4 m × 3 m bedroom off the hallway with a window facing the garden.",
});

console.log(response.output_text); // the reply
console.log(response.metadata?.pascal_version_url); // the saved version in the editor
```

With the AI SDK, pass the project in a header:

```ts theme={null}
import { createOpenAI } from "@ai-sdk/openai";
import { streamText } from "ai";

const openai = createOpenAI({
  apiKey: process.env.PASCAL_API_KEY,
  baseURL: "https://editor.pascal.app/api/v1",
  headers: { "X-Pascal-Project": "project_your_project_id" },
});

const result = streamText({
  model: openai("architect-high"),
  prompt: "Add a 4 m × 3 m bedroom",
  maxRetries: 0,
});
```

| Model | Effort |
| - | - |
| `architect-low`, `architect-medium`, `architect-high` | Low, medium or high effort |
| `architect-pro` | The chat's Pro effort |
| `architect` | Medium, unless the request sets `reasoning.effort` |

If your plan doesn't include the effort you asked for, the request is refused with
`403 upgrade_required`. A request without enough credits gets
`402 insufficient_quota`.

Images and PDFs can go in the request as `input_image` and `input_file` parts, inline
or as file ids from `POST /v1/files`.

## What a turn returns

* **The reply** is plain text, which can include Markdown. Rich cards that the editor's
  chat draws, such as a room-area report, arrive as their text, never as raw JSON.
* **The reasoning** comes as `reasoning` items (`reasoning_content` in Chat
  Completions): the model's thinking and one line per step it took.
* **The version.** A turn that changed the scene saves one version, named by
  `metadata.commit_message`. The response reports it as `metadata.pascal_version`,
  `pascal_version_label` and `pascal_version_url`. A turn that changed nothing saves
  nothing.
* **Questions.** When the Architect needs an answer, the turn ends `completed` with
  `metadata.pascal_waiting: "question"` and the question as the reply. Your next request
  on the project is the answer; sending just an option's number picks that option.

Each call joins the project's chat thread, so the turn appears in the editor's chat
too.

## The Architect sees its work

During an API turn, the Architect can render views of the scene with `view_scene` to
check what it built, as it does in the editor's chat. This works even when you don't
have the project open.

## Run long turns in the background

A request without `background` follows its turn for up to 13 minutes. A turn still
running then is stopped, except an `architect-pro` turn, which keeps running and
answers `status: "in_progress"` for you to poll. Send work that may take longer, such
as building a house from a floor plan, with `background: true`. It works with every
model:

```ts theme={null}
let turn = await client.responses.create({
  model: "architect-high",
  background: true,
  metadata: { project_id: "project_your_project_id" },
  input: "Build the ground floor from the attached plan.",
});

while (turn.status === "queued" || turn.status === "in_progress") {
  await new Promise((resolve) => setTimeout(resolve, 10_000));
  turn = await client.responses.retrieve(turn.id);
}
```

The request answers `queued` at once. The turn runs to its end with nobody connected:
credits are charged and the version is saved as for any other turn. Read it with
`GET /v1/responses/{id}` until it ends `completed`, `incomplete`, `failed` or
`cancelled`. Stop it with `POST /v1/responses/{id}/cancel`.

## See the Architect's tool calls

By default a turn shows one reasoning line per step, not the tools behind it. To get
each tool call with its arguments and result, as the editor's chat lists them, ask for
them with `include` (Responses only):

```ts theme={null}
const response = await client.responses.create({
  model: "architect-high",
  include: ["pascal.tool_calls"],
  metadata: { project_id: "project_your_project_id" },
  input: "Furnish the living room.",
});

for (const item of response.output) {
  if (item.type === "mcp_call") {
    console.log(item.name, item.arguments, item.output ?? item.error);
  }
}
```

Each call is an `mcp_call` output item with `server_label: "pascal"`, in the order the
Architect made it: `arguments` and `output` are JSON strings, and a call that failed has
`error` and `status: "failed"`. A background turn asked this way returns its calls when
you poll it; `GET /v1/responses/{id}?include=pascal.tool_calls` returns them for any
turn you can read.

## Cap the credits a turn spends

Set `metadata.pascal_max_credits` to the most credits one turn may spend. Like every
metadata value, it's a string:

```ts theme={null}
const response = await client.responses.create({
  model: "architect-high",
  metadata: { project_id: "project_your_project_id", pascal_max_credits: "50" },
  input: "Furnish the living room.",
});

if (response.status === "incomplete") {
  // incomplete_details.reason "max_credits": stopped at the cap, its changes saved
}
```

With the AI SDK, send it through `providerOptions.openai.metadata`. Without a cap there
is no limit. A value that isn't a number above 0 is refused with a `400` naming
`metadata.pascal_max_credits`, so a turn never runs uncapped by mistake.

When the turn reaches the cap, it stops starting new work and ends with
`status: "incomplete"` and `incomplete_details.reason: "max_credits"` (Chat
Completions: `finish_reason: "length"`). The work it finished is kept and saved as a
version. Work already under way when the cap is reached finishes, so a turn can end
somewhat above the cap.

Paid service tools such as `generate_studio_media` and `pull_site_data` don't count
toward the cap. Each is charged separately, up to its own `maxCredits`; see
[Hosted services](/docs/developers/hosted-services).

## Publish and download the 3D file

A turn's version is saved, not published. To get the project's 3D file, publish it
with the same API key, then download the GLB once it has baked:

```bash theme={null}
curl -X POST -H "Authorization: Bearer $PASCAL_API_KEY" \
  https://editor.pascal.app/api/scenes/project_your_project_id/publish

curl -L -o scene.glb -H "Authorization: Bearer $PASCAL_API_KEY" \
  https://editor.pascal.app/api/scenes/project_your_project_id/glb
```

Publishing does what the editor's **Save & publish** does: it saves the draft as a
version if it changed, publishes the latest version, and starts its
[bake](/docs/export/bake-and-export). It needs permission to publish the project and leaves
its privacy alone, so a private project stays private. The response gives
`publishedVersion`, `didSaveVersion` and `didPublishVersion`.

The GLB route answers:

| Status | Meaning |
| - | - |
| `307` | Ready: redirects to a short-lived download URL |
| `202` | Still baking: try again shortly |
| `409` | The bake failed |
| `404` | Nothing is published, the published version has no 3D file, or the key can't download this project's files |

Every answer except the `404` carries `X-Pascal-Version`, the published version it
refers to.

## Errors and retries

Don't retry a failed turn automatically: it may already have edited the scene. Every
error carries `x-should-retry: false`, which the OpenAI SDKs obey. The AI SDK retries
by status code, so pass `maxRetries: 0` with its OpenAI providers. A non-streamed
Responses turn that fails after it started answers `200` with `status: "failed"` and
an `error` object.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.