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

# Draft vs live PALs

> How the PAL Builder draft and the live PAL relate — and what that means for PATCH, GET, and publish calls against a PAL id.

Every PAL you edit in the [PAL Builder](https://maker.tavus.io/dev) has two rows behind a single `pal_id`: a **live** row that powers conversations, deployments, and public reads, and a **draft** row that the Builder writes into as you edit. Publishing copies the draft over the live row.

For most integrations you never need to think about this — you call [Create PAL](/api-reference/pals/create-pal) or [Patch PAL](/api-reference/pals/patch-pal) with the same `pal_id` and things work. This page exists because the API endpoints that read and write PALs expose the draft/live split when a Builder session is open, and you may want to opt into (or out of) that behavior explicitly.

## The model

<Info>
  A PAL created purely through the API — never opened in the PAL Builder — has no draft. All of the behavior on this page applies only once a Builder session has produced a draft row.
</Info>

* **Live PAL** — the version of the PAL that runs when a conversation starts, when a deployment answers a call, or when a Google Meet invite arrives. Public reads (`GET /v2/pals/{pal_id}`) return this row by default. This is the only row that is exposed to end users.
* **Builder draft** — a hidden copy of the PAL that the [PAL Builder](https://maker.tavus.io/dev) writes into while you edit. It shares its `pal_id` alias with live from the caller's perspective, but has its own internal row so the Builder can preview changes (including "Test" conversations) before publishing.
* **Publish** — copies the draft's content over the live row. Until you publish, live traffic keeps using the previously-published version.

## How PATCH picks a row

`PATCH /v2/pals/{pal_id}` **routes to the draft by default** when a PAL Builder draft exists for that PAL:

* If a draft exists, the patch lands on the draft. Live conversations continue to use the previously-published version until you publish.
* If no draft exists (the PAL has never been opened in the Builder), the patch lands on live, exactly as before.
* Pass `?target=live` to bypass the draft. Tavus writes to live **and resets the draft to match live**, so live becomes the single source of truth again. Any unpublished draft edits are discarded.

This default keeps API and UI edits consistent. The Builder reads the draft row, so a PATCH that skipped the draft would produce an editor showing the pre-patch state — the classic "my edit didn't show up" symptom. Routing to the draft by default means an API caller and a human editor never disagree about what "the current PAL" is.

Every PATCH response echoes the routing back so a client can log or branch on it:

```json theme={null}
{
  "pal_id": "pcb7a34da5fe",
  "edited_pal_id": "p5f1e8d2a934",
  "edit_target": "draft",
  "live_pal_id": "pcb7a34da5fe",
  "draft_pal_id": "p5f1e8d2a934",
  "publish_url": "/v2/pals/pcb7a34da5fe/publish",
  "routing_message": "Edited the Builder draft (p5f1e8d2a934). The live PAL (pcb7a34da5fe) is unchanged and will not reflect these edits until you publish. To edit the live PAL directly, re-send with ?target=live (this discards unpublished draft edits and resyncs the draft from live)."
}
```

* `pal_id` is always the id the caller passed in the URL — safe to persist on the client.
* `edit_target` is `draft` or `live`.
* `draft_pal_id` is the underlying draft row. You do not usually need it — patching or publishing the live id automatically routes to the draft — but it's returned for tooling that wants to address the draft directly.
* `publish_url` is returned for draft-routed edits so the next API action is explicit.

## Google Meet / `layers.conferencing` is live-owned

`layers.conferencing` (see [Google Meet](/sections/conversational-video-interface/pal/meetings)) is deployment config. It provisions the PAL's `@tavusinvite.com` email address, and calendar invites keep arriving while a Builder draft is open. Because of that, conferencing is **always owned by the live PAL**:

* A PATCH that touches `layers.conferencing` routes to live **even without `?target=live`** and syncs the conferencing layer into the open draft. Draft content in other fields is preserved.
* `?target=draft` with a conferencing op is rejected with `400`.
* A single request cannot mix conferencing ops with edits to other fields. Split the request in two if you need to change both.
* Publishing a stale draft (`POST /v2/pals/{pal_id}/publish`) — one that predates a conferencing change made against live — preserves the live conferencing config. Publishing never wipes deployed Google Meet integrations.

## How GET picks a row

`GET /v2/pals/{pal_id}` **returns the live PAL by default**, even when a draft exists. This is the public API contract customers depend on, and it is unchanged.

* Add `?source=draft` to read the draft body without publishing it. The response `pal_id` still matches the id you queried, and the response adds `is_draft_view: true`, `has_unpublished_changes`, `live_pal_id`, `draft_pal_id`, `published_view_url`, `publish_url`, and a `routing_message` explaining that conversations still use the live version.
* If you queried a draft id directly (rare — you usually address the shared live id), add `?source=live` to read the live parent's body instead of the draft.
* On any draft view, `layers.conferencing` is still the live value, so a draft read never surfaces a stale conferencing layer.

## When to use each mode

<AccordionGroup>
  <Accordion title="You want an API PATCH to behave the way the Builder does — edit safely, publish later" defaultOpen>
    Use the default (no `target`). Your edits land on the draft, the Builder shows them, and live traffic is unaffected until you publish (`POST /v2/pals/{pal_id}/publish`).
  </Accordion>

  <Accordion title="You want the pre-Builder behavior — the PATCH goes straight to live">
    Pass `?target=live`. Live is updated and the retained draft is reset to match, so nothing about the PAL diverges from what your API call just wrote. Any unpublished draft edits (including ones made by a teammate in PAL Maker) are discarded, but the draft row and its id remain available for future Builder edits.
  </Accordion>

  <Accordion title="You want to update Google Meet / conferencing config">
    PATCH `layers.conferencing` on its own and either omit `target` or pass `?target=live`. It is always routed to live and synced into the draft. Explicit `?target=draft` returns `400`. Do not combine it with other field edits in the same request.
  </Accordion>

  <Accordion title="You want to preview a PAL Builder draft over the API before publishing">
    `GET /v2/pals/{pal_id}?source=draft`. Response body is the draft; `is_draft_view` is `true`. Live conversations are unchanged.
  </Accordion>

  <Accordion title="You want to publish a Builder draft from the API">
    `POST /v2/pals/{pal_id}/publish`. The draft is copied onto the live row, and the response returns the published PAL with `status: "success"`. If the draft predates a conferencing change made against live, the live conferencing config is preserved.
  </Accordion>
</AccordionGroup>

## Migration notes

If you were patching PALs against the API before the PAL Builder draft was introduced, or your PAL has never been opened in the Builder, nothing changes: your patches still apply to live.

If your account uses PAL Maker **and** you patch the same PAL over the API, the default write target has changed — your edits now stack on top of any open Builder draft instead of jumping past it. To restore the previous behavior for a single call, pass `?target=live`.
