> ## 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.

# Use the OpenAI SDK

> Call Pascal's Architect from the OpenAI Python and TypeScript SDKs or the AI SDK's OpenAI provider: point the client at Pascal, name a project, cap credits, run or cancel long turns in the background, and handle errors.

You can call the Architect with the OpenAI SDK you already use. Point the client at
Pascal's base URL, use your Pascal API key, and ask for the `pascal-architect` model.
Requests and responses use the OpenAI Responses format, with Pascal-specific values in
`metadata`.

For what a turn does and returns, see [Pascal API (v1)](/docs/developers/api).

## 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 works on an existing project.
* **Credits.** Turns are paid with Pascal credits. See [Credits](/docs/account/credits).

Every example on this page runs as is once you set these two variables:

```bash theme={null}
export PASCAL_API_KEY="sk_live_your_key"
export PASCAL_PROJECT_ID="project_your_project_id"
```

Install the SDK for your language:

<CodeGroup>
  ```bash Python theme={null}
  pip install openai
  ```

  ```bash TypeScript theme={null}
  npm install openai
  ```

  ```bash AI SDK theme={null}
  npm install ai @ai-sdk/openai
  ```
</CodeGroup>

The TypeScript and AI SDK examples are ES modules with top-level `await`. Save one as
`example.ts` and run it with `npx tsx example.ts`.

## Point the client at Pascal

Set the base URL to `https://editor.pascal.app/api/v1` and pass your Pascal API key.
Name the project in `metadata.project_id`, and the Architect does the turn.

<CodeGroup>
  ```python Python theme={null}
  import os
  from openai import OpenAI

  client = OpenAI(
      api_key=os.environ["PASCAL_API_KEY"],
      base_url="https://editor.pascal.app/api/v1",
  )

  response = client.responses.create(
      model="pascal-architect",
      metadata={"project_id": os.environ["PASCAL_PROJECT_ID"]},
      input="Add a 4 m × 3 m bedroom off the hallway with a window facing the garden.",
  )

  print(response.output_text)
  print((response.metadata or {}).get("pascal_version_url"))
  ```

  ```ts TypeScript 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: "pascal-architect",
    metadata: { project_id: process.env.PASCAL_PROJECT_ID! },
    input: "Add a 4 m × 3 m bedroom off the hallway with a window facing the garden.",
  });

  console.log(response.output_text);
  console.log(response.metadata?.pascal_version_url);
  ```

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

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

  const result = await generateText({
    model: pascal("pascal-architect"),
    prompt: "Add a 4 m × 3 m bedroom off the hallway with a window facing the garden.",
    providerOptions: {
      openai: { metadata: { project_id: process.env.PASCAL_PROJECT_ID! } },
    },
    maxRetries: 0,
  });

  console.log(result.text);
  ```
</CodeGroup>

You can also name the project with the `X-Pascal-Project` header, for example through
the client's default headers. The header wins over `metadata.project_id`.

With the AI SDK, always pass `maxRetries: 0`. The AI SDK retries by status code, and a
retried turn could edit the scene twice. The OpenAI SDKs already obey Pascal's
`x-should-retry: false` header.

## Choose an effort

`pascal-architect` runs at medium effort unless you set `reasoning.effort`. To fix the
effort, use a model with a suffix instead:

<CodeGroup>
  ```python Python theme={null}
  import os
  from openai import OpenAI

  client = OpenAI(
      api_key=os.environ["PASCAL_API_KEY"],
      base_url="https://editor.pascal.app/api/v1",
  )

  response = client.responses.create(
      model="pascal-architect-high",
      metadata={"project_id": os.environ["PASCAL_PROJECT_ID"]},
      input="Furnish the living room.",
  )

  print(response.output_text)
  ```

  ```ts TypeScript 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: "pascal-architect-high",
    metadata: { project_id: process.env.PASCAL_PROJECT_ID! },
    input: "Furnish the living room.",
  });

  console.log(response.output_text);
  ```

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

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

  const result = await generateText({
    model: pascal("pascal-architect-high"),
    prompt: "Furnish the living room.",
    providerOptions: {
      openai: { metadata: { project_id: process.env.PASCAL_PROJECT_ID! } },
    },
    maxRetries: 0,
  });

  console.log(result.text);
  ```
</CodeGroup>

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

Each model is also available without the `pascal-` prefix, as `architect`,
`architect-high` and so on. Both names run the same model. `GET /v1/models` lists them
all.

If your plan doesn't include the effort you asked for, the request is refused with
`403 upgrade_required`.

## Cap the credits a turn spends

Set `metadata.pascal_max_credits` to the most credits the turn may spend. It's a string,
like every metadata value.

<CodeGroup>
  ```python Python theme={null}
  import os
  from openai import OpenAI

  client = OpenAI(
      api_key=os.environ["PASCAL_API_KEY"],
      base_url="https://editor.pascal.app/api/v1",
  )

  response = client.responses.create(
      model="pascal-architect",
      metadata={
          "project_id": os.environ["PASCAL_PROJECT_ID"],
          "pascal_max_credits": "50",
      },
      input="Furnish the living room.",
  )

  if response.status == "incomplete":
      print(response.incomplete_details.reason)  # "max_credits"
  ```

  ```ts TypeScript 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: "pascal-architect",
    metadata: { project_id: process.env.PASCAL_PROJECT_ID!, pascal_max_credits: "50" },
    input: "Furnish the living room.",
  });

  if (response.status === "incomplete") {
    console.log(response.incomplete_details?.reason); // "max_credits"
  }
  ```

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

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

  const result = await generateText({
    model: pascal("pascal-architect"),
    prompt: "Furnish the living room.",
    providerOptions: {
      openai: {
        metadata: { project_id: process.env.PASCAL_PROJECT_ID!, pascal_max_credits: "50" },
      },
    },
    maxRetries: 0,
  });

  console.log(result.rawFinishReason); // "max_credits" when the turn stopped at the cap
  ```
</CodeGroup>

A turn that reaches the cap stops starting new work, keeps what it finished, and saves
it as a version. A value that isn't a number above 0 is refused with a `400`, so a turn
never runs uncapped by mistake. See [Cap the credits a turn
spends](/docs/developers/api#cap-the-credits-a-turn-spends) for how close to the cap a turn
ends.

## Run a long turn in the background, and cancel it

Send `background: true` for work that may take a while, such as building a floor from a
plan. The request answers `queued` at once with the response id. Poll it with
`responses.retrieve`, and stop it with `responses.cancel`.

<CodeGroup>
  ```python Python theme={null}
  import os
  import time
  from openai import OpenAI

  client = OpenAI(
      api_key=os.environ["PASCAL_API_KEY"],
      base_url="https://editor.pascal.app/api/v1",
  )

  turn = client.responses.create(
      model="pascal-architect-high",
      background=True,
      metadata={"project_id": os.environ["PASCAL_PROJECT_ID"]},
      input="Build a two-bedroom ground floor with an open kitchen.",
  )

  while turn.status in ("queued", "in_progress"):
      time.sleep(10)
      turn = client.responses.retrieve(turn.id)

  print(turn.status, turn.output_text)

  # To stop it before it ends:
  # client.responses.cancel(turn.id)
  ```

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

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

  let turn = await client.responses.create({
    model: "pascal-architect-high",
    background: true,
    metadata: { project_id: process.env.PASCAL_PROJECT_ID! },
    input: "Build a two-bedroom ground floor with an open kitchen.",
  });

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

  console.log(turn.status, turn.output_text);

  // To stop it before it ends:
  // await client.responses.cancel(turn.id);
  ```

  ```ts AI SDK theme={null}
  // The AI SDK's OpenAI provider has no background option, so start, poll and
  // cancel a background turn over HTTP with the same key and base URL.
  const baseURL = "https://editor.pascal.app/api/v1";
  const headers = {
    Authorization: `Bearer ${process.env.PASCAL_API_KEY}`,
    "Content-Type": "application/json",
  };

  const queued = await fetch(`${baseURL}/responses`, {
    method: "POST",
    headers,
    body: JSON.stringify({
      model: "pascal-architect-high",
      background: true,
      metadata: { project_id: process.env.PASCAL_PROJECT_ID },
      input: "Build a two-bedroom ground floor with an open kitchen.",
    }),
  }).then((res) => res.json());

  const turn = await fetch(`${baseURL}/responses/${queued.id}`, { headers }).then((res) => res.json());
  console.log(turn.status);

  // To stop it before it ends:
  // await fetch(`${baseURL}/responses/${queued.id}/cancel`, { method: "POST", headers });
  ```
</CodeGroup>

A background turn runs to its end with nobody connected. Credits are charged and the
version is saved as for any other turn. It ends `completed`, `incomplete`, `failed` or
`cancelled`.

Cancelling stops the turn the way the editor's **Stop** does, and returns the response
as it ended. Cancelling a turn that already ended returns it as it ended. Any key that
can edit the project can read its turns, but you can only cancel turns started with your
account's keys. Cancelling someone else's turn returns `404 turn_not_found`, as for a
turn that doesn't exist.

Without `background`, a request follows its turn for up to 13 minutes. A turn still
running then is stopped, except a `pascal-architect-pro` turn, which keeps running and
answers `status: "in_progress"` for you to poll.

## Retry without starting a second turn

A turn edits the scene and spends credits. If a request times out and you don't know
whether its turn started, send it again with the same `Idempotency-Key`: you get back
the turn the first request started, as it stands now, instead of a second turn.

<CodeGroup>
  ```python Python theme={null}
  import os
  import uuid
  from openai import OpenAI

  client = OpenAI(
      api_key=os.environ["PASCAL_API_KEY"],
      base_url="https://editor.pascal.app/api/v1",
  )

  key = str(uuid.uuid4())  # keep it with the request, and reuse it only to retry that request
  turn = client.responses.create(
      model="pascal-architect-high",
      background=True,
      metadata={"project_id": os.environ["PASCAL_PROJECT_ID"]},
      input="Add a covered porch along the front of the house.",
      extra_headers={"Idempotency-Key": key},
  )
  print(turn.id, turn.status)
  ```

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

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

  const key = crypto.randomUUID(); // keep it with the request, and reuse it only to retry that request
  const turn = await client.responses.create(
    {
      model: "pascal-architect-high",
      background: true,
      metadata: { project_id: process.env.PASCAL_PROJECT_ID! },
      input: "Add a covered porch along the front of the house.",
    },
    { headers: { "Idempotency-Key": key } },
  );
  console.log(turn.id, turn.status);
  ```

  ```ts AI SDK theme={null}
  // Background turns go over HTTP (see above), so the key is one more header.
  const response = await fetch("https://editor.pascal.app/api/v1/responses", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.PASCAL_API_KEY}`,
      "Content-Type": "application/json",
      "Idempotency-Key": crypto.randomUUID(),
    },
    body: JSON.stringify({
      model: "pascal-architect-high",
      background: true,
      metadata: { project_id: process.env.PASCAL_PROJECT_ID },
      input: "Add a covered porch along the front of the house.",
    }),
  });
  console.log(response.headers.get("idempotent-replayed"), (await response.json()).id);
  ```
</CodeGroup>

A replayed answer carries `Idempotent-Replayed: true`. Pascal keeps a key for 24 hours,
for you and the project the request names. The same key with a different request returns
`409 idempotency_mismatch`, so use a new key for each new request.

## Handle errors

An error comes back with an HTTP status and an `error` object, which each SDK turns into
an exception. Read the `code` to decide what to do.

<CodeGroup>
  ```python Python theme={null}
  import os
  from openai import APIStatusError, OpenAI

  client = OpenAI(
      api_key=os.environ["PASCAL_API_KEY"],
      base_url="https://editor.pascal.app/api/v1",
  )

  try:
      client.responses.create(
          model="pascal-architect",
          metadata={"project_id": os.environ["PASCAL_PROJECT_ID"], "pascal_max_credits": "0"},
          input="Add a bedroom.",
      )
  except APIStatusError as error:
      print(error.status_code, error.code, error.param)  # 400 invalid_input metadata.pascal_max_credits
  ```

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

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

  try {
    await client.responses.create({
      model: "pascal-architect",
      metadata: { project_id: process.env.PASCAL_PROJECT_ID!, pascal_max_credits: "0" },
      input: "Add a bedroom.",
    });
  } catch (error) {
    if (!(error instanceof OpenAI.APIError)) throw error;
    console.log(error.status, error.code, error.param); // 400 invalid_input metadata.pascal_max_credits
  }
  ```

  ```ts AI SDK theme={null}
  import { createOpenAI } from "@ai-sdk/openai";
  import { APICallError, generateText } from "ai";

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

  try {
    await generateText({
      model: pascal("pascal-architect"),
      prompt: "Add a bedroom.",
      providerOptions: {
        openai: {
          metadata: { project_id: process.env.PASCAL_PROJECT_ID!, pascal_max_credits: "0" },
        },
      },
      maxRetries: 0,
    });
  } catch (error) {
    if (!APICallError.isInstance(error)) throw error;
    console.log(error.statusCode, error.responseBody); // 400 {"error":{...}}
  }
  ```
</CodeGroup>

Don't retry a failed turn automatically: it may already have edited the scene. Every
error tells the SDKs not to retry it, except `429 rate_limit_exceeded`: nothing ran, so
the OpenAI SDKs wait the `Retry-After` seconds and send it again on their own. With the
AI SDK and `maxRetries: 0`, wait `Retry-After` yourself. A request without enough credits
gets `402 insufficient_quota`. An unknown model is refused with `model_not_found`, and
the message lists the valid names. A bad `metadata.pascal_max_credits` gets
`400 invalid_input`. Each error's `doc_url` links to [Errors](/docs/developers/api#errors),
which lists every code.

## Get the request id for support

Every answer carries a `Request-Id` header. A turn's reply also carries `x-request-id`,
the same value as its response id; every other answer, each error included, carries the
`Request-Id` value there. Each SDK gives it to you, so you can include it when you
contact Pascal.

<CodeGroup>
  ```python Python theme={null}
  import os
  from openai import OpenAI

  client = OpenAI(
      api_key=os.environ["PASCAL_API_KEY"],
      base_url="https://editor.pascal.app/api/v1",
  )

  response = client.responses.create(
      model="pascal-architect",
      metadata={"project_id": os.environ["PASCAL_PROJECT_ID"]},
      input="Add a bedroom.",
  )

  print(response._request_id)  # the same as response.id
  ```

  ```ts TypeScript 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: "pascal-architect",
    metadata: { project_id: process.env.PASCAL_PROJECT_ID! },
    input: "Add a bedroom.",
  });

  console.log(response._request_id); // the same as response.id
  ```

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

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

  const result = await generateText({
    model: pascal("pascal-architect"),
    prompt: "Add a bedroom.",
    providerOptions: {
      openai: { metadata: { project_id: process.env.PASCAL_PROJECT_ID! } },
    },
    maxRetries: 0,
  });

  console.log(result.response.headers?.["x-request-id"]); // the same as result.response.id
  ```
</CodeGroup>

A refused request has a request id too: `error.request_id` in Python, `error.requestID`
in TypeScript, and the `x-request-id` header of the AI SDK's `APICallError.responseHeaders`.

## Next steps

* [Pascal API (v1)](/docs/developers/api): what a turn returns, questions, tool calls, and
  downloading the 3D file.
* [OpenAPI document](https://editor.pascal.app/api/openapi.json): every route and
  parameter.


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