Artifacts
This content is not available in your language yet.
An artifact is a file that an agent produced for your workspace, for example a PDF brochure written by a workflow’s pdf_export node. Most artifacts are written by the platform while a workflow runs; you read them and download their files. The one way to create an artifact yourself is createTextArtifact, for a workspace owner. There is no update or delete operation.
| Operation | Kind | What it does |
|---|---|---|
artifact(id) | query | One artifact: kind, title, status, size. |
artifacts(...) | query | Your artifacts, newest first, optionally for one session. |
artifactDownloadUrl(id) | mutation | A fresh download link, valid for 5 minutes. |
createTextArtifact(...) | mutation | Owner only: store an HTML page or a Markdown document. |
Credentials
Section titled “Credentials”Each operation accepts either of two credentials, with the x-huat-platform: customer header:
- A dashboard JWT (
Authorization: Bearer <jwt>, fromlogin): any artifact of your workspace. - The
avatarTokenfromsdkStart(Authorization: Bearer <avatarToken>): only the artifacts of the session that token belongs to. This is what lets an end user in your chat UI download a document their own conversation produced, without a dashboard login.
An API key is not accepted. With an avatarToken, an artifact of another session answers exactly like one that does not exist (NOT_FOUND), and passing another session’s id to artifacts(sessionId:) is FORBIDDEN. See Authentication for the three credential types.
The ArtifactDto type
Section titled “The ArtifactDto type”| Field | Type | Description |
|---|---|---|
id | Int! | Artifact id. |
kind | String! | What produced it, a lower-case slug: pdf_document (a pdf_export node), or pdf_document_draft (the same node in a dashboard draft test run), an image, a voice-over (audio_speech, WAV), a piece of music (audio_music, MP3), a narrated explainer (video_script, a JSON manifest played by the app) or a document from a generate_artifact node (off by default), html_page or markdown (from createTextArtifact). An open set: new kinds can appear without a schema change. |
status | ArtifactStatus! | PENDING, READY or FAILED (below). |
title | String! | Display title, usable as a file name (for a PDF, the node’s rendered titleTemplate). |
mimeType | String! | Media type of the file, for example application/pdf. |
sizeBytes | Float | File size in bytes once READY; null before. |
errorCode | String | Why it FAILED: one of RENDER_FAILED, UPLOAD_FAILED, TIMEOUT, TOO_LARGE, INVALID_INPUT, BLOCKED (the model declined the request), INTERNAL. null otherwise. Never raw error text. |
sessionId | Int | The conversation (session) that produced it; null for runs with no session. |
messageId | Int | The message it belongs to, when the producer recorded one. |
workflowRunId | Int | The workflow run that produced it, readable with workflowRun. |
agentId | Int | The AI employee (agent) that generated it; null for owner-written files and older rows. |
generator | String | The model that generated it; null when a person wrote it or it was rendered from a template. |
hasThumbnail | Boolean! | true when a small preview image exists: call artifactDownloadUrl(id, thumbnail: true) for its link. |
isTest | Boolean! | true for output of a draft or test run. Hidden from artifacts unless a workspace owner passes includeTests. |
visibility | String | Reserved (PRIVATE or TENANT) and not enforced yet: every artifact follows the session and AI employee that made it, and no value makes a file public. null for every row today. |
createdAt | DateTime! | When it was requested. |
updatedAt | DateTime! | When it last changed, for example when it became READY. |
The storage location of the file is deliberately not a field. To download, mint a link with artifactDownloadUrl.
Statuses
Section titled “Statuses”| Status | Meaning |
|---|---|
PENDING | Being produced (rendering or uploading). Poll artifact(id) until it is READY or FAILED. |
READY | Stored. Call artifactDownloadUrl(id) for a link. |
FAILED | Not produced; errorCode says why. There is no file. |
The pdf_export node explains each errorCode. A pdf_export node finishes its artifact before the workflow moves on, so by the time a later node (or your app) has the artifactId, the artifact is normally READY.
artifact
Section titled “artifact”artifact(id: Int!): ArtifactDto!One artifact. NOT_FOUND for an id that does not exist, belongs to another workspace, or (with an avatarToken) belongs to another session.
query GetArtifact($id: Int!) { artifact(id: $id) { id kind status title mimeType sizeBytes errorCode sessionId }}{ "id": 123 }{ "data": { "artifact": { "id": 123, "kind": "pdf_document", "status": "READY", "title": "5-day plan for Lisbon", "mimeType": "application/pdf", "sizeBytes": 48211, "errorCode": null, "sessionId": 9001 } }}artifacts
Section titled “artifacts”artifacts(sessionId: Int, kind: String, includeTests: Boolean, first: Int, after: String): ArtifactConnection!Your artifacts, newest first, any status. Download links are not included: call artifactDownloadUrl per file.
| Argument | Type | Description |
|---|---|---|
sessionId | Int | Only artifacts of this session. With an avatarToken, omit it or pass the token’s own session; any other session is FORBIDDEN. |
kind | String | Only this kind, for example pdf_document (which leaves out draft-test output). A malformed slug is BAD_REQUEST. |
includeTests | Boolean | Workspace owners only: also list output of draft or test runs (hidden by default). Ignored for members. Default false. |
first | Int | Page size, default 20, at most 100. |
after | String | A previous page’s pageInfo.endCursor; walks to older artifacts. A malformed cursor is BAD_REQUEST. |
With an avatarToken, the list always holds only that session’s artifacts, whether or not you pass sessionId.
Returns ArtifactConnection: edges { cursor node { ...ArtifactDto } } and pageInfo { hasNextPage endCursor } (endCursor is null for an empty page).
query SessionFiles($sessionId: Int, $first: Int, $after: String) { artifacts( sessionId: $sessionId kind: "pdf_document" first: $first after: $after ) { edges { node { id title status createdAt } } pageInfo { hasNextPage endCursor } }}{ "sessionId": 9001, "first": 10 }artifactDownloadUrl
Section titled “artifactDownloadUrl”artifactDownloadUrl(id: Int!, thumbnail: Boolean): ArtifactDownloadUrlDto!Mints a fresh signed HTTPS link to a READY artifact’s file, valid for 5 minutes. It is a mutation, not a query, so no client cache can replay an expired link: call it when the user clicks download. Pass thumbnail: true to get the link of the small JPEG preview instead of the full file; it works only for an artifact with hasThumbnail: true.
| Field | Type | Description |
|---|---|---|
artifactId | Int! | The artifact the link is for. |
url | String! | The signed link. Valid until expiresAt. |
expiresAt | DateTime! | When the link stops working. |
mimeType | String! | Media type of the file. |
title | String! | Display title, usable as a file name. |
mutation DownloadArtifact($id: Int!) { artifactDownloadUrl(id: $id) { url expiresAt mimeType title }}{ "id": 123 }Errors
Section titled “Errors”| Code | When |
|---|---|
CONFLICT | The artifact is PENDING (not finished yet) or FAILED (there is no file). Only READY has a link. |
NOT_FOUND | Same scoping as artifact(id): no such artifact in your workspace, or (with an avatarToken) not in that token’s session. |
INTERNAL_SERVER_ERROR | The link could not be signed; try again. |
The mutation is rate-limited to 60 calls per minute per client IP address.
createTextArtifact
Section titled “createTextArtifact”Available in production since 2026-10-29.
createTextArtifact(kind: String!, title: String!, content: String!): ArtifactDto!Stores a page or document you wrote as a READY artifact in one call. Workspace owner only (dashboard JWT): a member and an SDK avatarToken get FORBIDDEN.
| Argument | Notes |
|---|---|
kind | html_page (a web page as HTML) or markdown. Any other value is BAD_REQUEST. |
title | 1 to 200 characters. |
content | The text, at most 262144 bytes of UTF-8 (the limit counts bytes, not characters). Empty or larger content is BAD_REQUEST. |
mutation CreateText($kind: String!, $title: String!, $content: String!) { createTextArtifact(kind: $kind, title: $title, content: $content) { id kind status mimeType sizeBytes }}- Stored as plain text. The file is never stored as a web page, so a storage or download link can never render it as one. Do not write the content into your own page. Show it through a sanitising, sandboxed preview, and remove scripts, forms, frames, external resources and navigation: none of those are supported in a stored page.
- Rate limit. Throttled per client IP.
- No delete yet. There is no mutation to delete or edit an artifact. An unfinished artifact is cleaned up automatically after 7 days, and others follow the workspace’s retention rules.
- Read it back with
artifactand download it withartifactDownloadUrl, like any other artifact.