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

# Work with artifacts

> List, download, archive, and delete the files (artifacts) an agent produces.

When an HQ agent produces a deliverable — a chart, a PDF, a report — it publishes an **artifact**: the bytes live in object storage and a row records the metadata. Each artifact has a stable id and a download URL of the shape `/v1/api/artifacts/{id}/download`. This guide covers listing artifacts (workspace-wide and per-conversation), downloading an artifact's raw bytes, downloading a whole conversation as a ZIP archive, and deleting an artifact.

All requests go to `https://api.hq.zone` and authenticate with a personal access token:

```bash theme={null}
export HQ_TOKEN="hq_pat_..."
```

<Note>The endpoints in this guide carry these scopes: listing and downloading require `conversations:read`; deleting requires `conversations:write`. The single-artifact byte download (`/v1/api/artifacts/{id}/download`) requires no specific scope — any authenticated caller in the artifact's workspace may use it.</Note>

## List artifacts

There are two ways to list, depending on whether you want everything in the workspace or just one conversation's files.

<Steps>
  <Step title="List the whole workspace">
    [List artifacts](/api-reference/conversations/list-artifacts) returns the delivered artifacts visible to you across the workspace, newest first and capped at 500.

    ```bash theme={null}
    curl https://api.hq.zone/v1/api/artifacts \
      -H "Authorization: Bearer $HQ_TOKEN"
    ```

    Each entry carries `id`, `name`, `content_type`, `size_bytes`, `caption` (nullable), `sensitive`, `created_at`, `conversation_id` (null once detached), `conversation_title` (null once detached), and `download_url`:

    ```js theme={null}
    // → 200
    // {
    //   "artifacts": [
    //     {
    //       "id": "<ARTIFACT_ID>",
    //       "name": "q3-revenue.pdf",
    //       "content_type": "application/pdf",
    //       "size_bytes": 81234,
    //       "caption": null,
    //       "sensitive": false,
    //       "created_at": "2026-06-10T09:14:00Z",
    //       "conversation_id": "<CONVERSATION_ID>",
    //       "conversation_title": "Q3 numbers",
    //       "download_url": "/v1/api/artifacts/<ARTIFACT_ID>/download"
    //     }
    //   ]
    // }
    ```
  </Step>

  <Step title="List one conversation's artifacts">
    [List conversation artifacts](/api-reference/conversations/list-conversation-artifacts) returns just the files for a single conversation, in chronological order (oldest first).

    ```bash theme={null}
    curl https://api.hq.zone/v1/api/conversations/<CONVERSATION_ID>/artifacts \
      -H "Authorization: Bearer $HQ_TOKEN"
    ```

    Each entry is a trimmer shape — `id`, `name`, `content_type`, `size_bytes`, `sensitive`, `created_at`, and `download_url`:

    ```js theme={null}
    // → 200
    // {
    //   "artifacts": [
    //     {
    //       "id": "<ARTIFACT_ID>",
    //       "name": "chart.png",
    //       "content_type": "image/png",
    //       "size_bytes": 20481,
    //       "sensitive": false,
    //       "created_at": "2026-06-10T09:12:00Z",
    //       "download_url": "/v1/api/artifacts/<ARTIFACT_ID>/download"
    //     }
    //   ]
    // }
    ```
  </Step>
</Steps>

<Tip>If you are a workspace admin inspecting another member's conversation, add `?view=admin` to the per-conversation listing to read their artifacts. This admin read is audited.</Tip>

## Download an artifact's bytes

Both list responses give you a `download_url`. Resolve it against the base URL and call [Download an artifact](/api-reference/conversations/download-artifact). The response is **raw binary** — the artifact bytes streamed back with the original `Content-Type` and a `Content-Disposition: attachment` header carrying the filename — not JSON. Save it to a file rather than printing it.

<Steps>
  <Step title="Save the bytes with curl">
    Use `-o` to write the response body straight to a file:

    ```bash theme={null}
    curl https://api.hq.zone/v1/api/artifacts/<ARTIFACT_ID>/download \
      -H "Authorization: Bearer $HQ_TOKEN" \
      -o q3-revenue.pdf
    ```

    A 200 streams the bytes; a `404` means there is no such artifact in your workspace.
  </Step>

  <Step title="Save the bytes from JavaScript">
    Read the response as an `ArrayBuffer` (the body is binary, not JSON) and write it to disk:

    ```js theme={null}
    import { writeFile } from "node:fs/promises";

    const base = "https://api.hq.zone";
    const headers = { Authorization: `Bearer ${process.env.HQ_TOKEN}` };
    const artifactId = "<ARTIFACT_ID>";

    const res = await fetch(`${base}/v1/api/artifacts/${artifactId}/download`, { headers });
    if (!res.ok) throw new Error(`download failed: ${res.status}`);

    const bytes = Buffer.from(await res.arrayBuffer());
    await writeFile("q3-revenue.pdf", bytes);
    ```
  </Step>
</Steps>

<Note>The download is streamed, so large artifacts don't have to fit in memory on the server. The `Content-Type` you receive is whatever the artifact was published as (e.g. `application/pdf`, `image/png`).</Note>

## Download a conversation as a ZIP archive

To grab every artifact in a conversation at once, call [Download conversation artifacts](/api-reference/conversations/download-conversation-artifacts). The response is a **raw `application/zip` stream**, not JSON — save it to a file. Filenames that would collide inside the archive are de-duplicated automatically.

```bash theme={null}
curl https://api.hq.zone/v1/api/conversations/<CONVERSATION_ID>/artifacts/archive \
  -H "Authorization: Bearer $HQ_TOKEN" \
  -o conversation-artifacts.zip
```

A 200 returns the ZIP; a `404` means the conversation has no artifacts to download.

```js theme={null}
import { writeFile } from "node:fs/promises";

const base = "https://api.hq.zone";
const headers = { Authorization: `Bearer ${process.env.HQ_TOKEN}` };
const convId = "<CONVERSATION_ID>";

const res = await fetch(`${base}/v1/api/conversations/${convId}/artifacts/archive`, { headers });
if (res.status === 404) throw new Error("no artifacts to download");
if (!res.ok) throw new Error(`archive failed: ${res.status}`);

await writeFile("conversation-artifacts.zip", Buffer.from(await res.arrayBuffer()));
```

<Tip>The archive is a pure storage read — it works even when the conversation's workspace is asleep, since no agent VM has to be running.</Tip>

## Delete an artifact

[Delete an artifact](/api-reference/conversations/delete-artifact) permanently removes an artifact's row and its stored bytes.

```bash theme={null}
curl -X DELETE https://api.hq.zone/v1/api/artifacts/<ARTIFACT_ID> \
  -H "Authorization: Bearer $HQ_TOKEN"
```

Note the path: deletion targets `/v1/api/artifacts/{id}` (DELETE), while the byte download targets `/v1/api/artifacts/{id}/download` (GET). The delete response is JSON with the artifact `id` and a `deleted` flag:

```js theme={null}
// → 200
// { "id": "<ARTIFACT_ID>", "deleted": true }
```

You may delete an artifact if you participate in its backing conversation, or — once it has been detached from a deleted conversation — if you were its original producer. Otherwise the call returns `403`. A `404` means the artifact was not found in your workspace.

<Note>Deleting an artifact that is still attached to a conversation also removes it from that conversation's transcript. The bytes are gone and the existing download link will then 404.</Note>
