Skip to main content
When an HQ agent builds a live app, the platform records it as a surface: a persistent, browser-reachable application backed by the conversation’s VM. Each surface has a stable id, a slug, a lifecycle state, a declared_port the in-VM server listens on, a run_mode, an auth_mode, and a ready-to-open url. This guide covers listing a conversation’s surfaces, listing the whole workspace’s app catalog, minting a time-limited share link, switching a surface between private and public, and the delete / restore lifecycle. All requests go to https://api.hq.zone and authenticate with a personal access token:
The endpoints in this guide carry these scopes: reads (listing a conversation’s surfaces, listing apps) require conversations:read; every mutation (share, visibility, restore, delete) requires conversations:write.

What a surface is

A surface is the live app row attached to a conversation. The list endpoints return these fields per surface:
  • id — the surface id used by the share / visibility / restore / delete endpoints.
  • slug — the URL slug for the app.
  • state — the lifecycle state. Non-destroyed surfaces are listed; the states you will see include booting, live, paused, archived. A destroyed surface is filtered out of every listing.
  • declared_port — the port the app’s server is bound to inside the VM.
  • run_modedev or prod.
  • auth_modesigned_url (private) or public. This is the underlying column; the Apps list also exposes it as a derived visibility of private or public.
  • url — a ready-to-open, visitor-facing URL: the bare public URL when auth_mode is public, or a signed plain-host URL when it is signed_url.
There is no visibility column on a surface. Visibility is derived from auth_mode: public maps to visibility public, and signed_url maps to visibility private.

List a conversation’s surfaces

List conversation surfaces returns the live application surfaces attached to one conversation, excluding destroyed ones, ordered by most recent activity.
1

Fetch the surfaces for a conversation

The response wraps a surfaces array. Each entry carries id, slug, state, declared_port, run_mode, auth_mode, the visitor-facing url, and a separate owner-only dev_url:
The url is the visitor-facing view that the Share modal hands out and “open in new tab” points at. The dev_url is a separate owner-only developer-embed URL (always signed) that loads the in-browser devtools runtime; it is an empty string when no signing secret is configured. If you are a workspace admin inspecting another member’s conversation, add ?view=admin — this admin read is audited.

List the workspace app catalog

List apps returns the workspace-wide catalog of persistent app surfaces, visible to any member of the workspace, excluding destroyed apps and ordered by most recent activity.
The response carries an apps array plus the workspace quota (max_persistent) and how many apps currently count against it (used). Each app includes id, slug, state, a derived visibility (public or private), run_mode, declared_port, backing_conversation_id (null if that conversation was deleted), a visitor-facing url, and created_at / last_activity_at as Unix-seconds timestamps:
The conversation-surfaces list is conversation-scoped (one thread’s apps); the Apps catalog is workspace-scoped (every member sees the same apps). Use the conversation list to embed the app a thread just built; use the catalog for a workspace-wide management view.
Share a surface mints a time-limited signed URL you can paste into chat, email, or a doc without making the surface publicly visible. The surface stays private; the URL carries the token that lets a recipient in.
1

Mint a link with the default TTL

Omit the body (or send an empty one) to get the default validity window of 7 days:
The response is the surface id, the tokenized url, and exp (the expiry as a Unix-seconds timestamp):
2

Mint a link with a custom TTL

Set ttl_secs (seconds) to control the window. It is clamped to the inclusive range 60 seconds (1 minute) to 2,592,000 seconds (30 days); anything outside that range is rejected with a 400:
The share link keeps the surface private — it does not flip auth_mode. To open a surface to anyone with the bare URL, change its visibility instead (next section). A 404 from this endpoint means the surface was not found in your workspace.

Change visibility

Set surface visibility flips a surface between private and public. The only accepted values are private and public; anything else returns a 400.
The response echoes the surface id, the resulting visibility, and the underlying auth_mode it maps to (private to signed_url, public to public):
Visibility can only be changed on a surface in a non-terminal state (live, paused, or booting). If the surface is not found or is in a terminal state, the call returns 404.

Delete and restore

An app surface can be put to sleep and woken back up, or torn down for good.
1

Restore a paused app

Restore a surface wakes a paused app back to live. Restore is app-only.
The response carries the surface id and an outcome describing what happened — already_live, restored, or in_progress:
A 404 means the surface was not found in your workspace.
2

Delete an app

Delete a surface stops the app and releases its resources, flipping the surface to a destroyed state.
The response carries the surface id and an outcomedestroyed (this call did the teardown) or already_destroyed (it was already gone, so the call is a no-op):
A 404 means the surface was not found in your workspace.
Delete is permanent: a destroyed surface is filtered out of every listing and cannot be restored — restore rejects surfaces in a terminal state (publish a fresh app instead). A non-admin caller must participate in the surface’s backing conversation; a workspace admin may delete any app in the workspace.