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 or 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
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.
- 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 writes into while you edit. It shares its
pal_idalias 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=liveto 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.
pal_idis always the id the caller passed in the URL — safe to persist on the client.edit_targetisdraftorlive.draft_pal_idis 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_urlis returned for draft-routed edits so the next API action is explicit.
Google Meet / layers.conferencing is live-owned
layers.conferencing (see Google Meet) 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.conferencingroutes to live even without?target=liveand syncs the conferencing layer into the open draft. Draft content in other fields is preserved. ?target=draftwith a conferencing op is rejected with400.- 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=draftto read the draft body without publishing it. The responsepal_idstill matches the id you queried, and the response addsis_draft_view: true,has_unpublished_changes,live_pal_id,draft_pal_id,published_view_url,publish_url, and arouting_messageexplaining that conversations still use the live version. - If you queried a draft id directly (rare — you usually address the shared live id), add
?source=liveto read the live parent’s body instead of the draft. - On any draft view,
layers.conferencingis still the live value, so a draft read never surfaces a stale conferencing layer.
When to use each mode
You want an API PATCH to behave the way the Builder does — edit safely, publish later
You want an API PATCH to behave the way the Builder does — edit safely, publish later
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).You want the pre-Builder behavior — the PATCH goes straight to live
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.You want to update Google Meet / conferencing config
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.You want to preview a PAL Builder draft over the API before publishing
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.You want to publish a Builder draft from the API
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.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.
