Create an artifact
Create an artifact together with its first HTML version. The new artifact starts at current_version: 1 and is unpublished until you call Publish an artifact.
Requires the Applets feature and the workspace admin or owner role. See Artifacts overview.
Path parameters
slug:requiredstringThe workspace slug. It appears in your Plane URLs. In https://app.plane.so/my-team/projects/, the slug is my-team.
Body parameters
html:requiredstringThe rendered HTML for version 1. This is the only required field. An empty or missing value returns 400.
name:optionalstringDisplay name. Truncated to 255 characters. Falls back to Untitled dashboard when omitted, empty, or whitespace, so a missing name is never rejected.
description:optionalstringFree-form description. Truncated to 2000 characters rather than rejected.
prompt:optionalstringThe prompt that produced this HTML, stored against the version for provenance. Not returned by any read endpoint.
project:optionalstring (uuid)Optionally scope the artifact to a project. The field is project, not project_id. The *_id naming used elsewhere in v2 does not apply to these endpoints.
data_mode:optionalstringHow the artifact's data is sourced. One of snapshot (frozen at generation time) or live (re-read on view). Defaults to snapshot. Any other value returns 400.
A project id from another workspace is dropped without an error
The API checks that project belongs to the calling workspace. A value that fails the check is set to null instead of rejected, and the artifact is created at workspace scope. If project scoping matters, read project back to confirm it was set.
Scopes
workspaces.artifacts:write
Errors
| Status | Code | Cause |
|---|---|---|
400 | None | html is missing or empty, or data_mode is outside the enum. See the note below. |
401 | unauthorized | Missing or invalid credentials. |
402 | payment_required | The Applets feature isn't enabled on your plan. |
403 | forbidden | You are not a workspace admin or owner, or your token lacks the scope. |
404 | not_found | No such workspace, or it's outside your tenant. |
406 | not_acceptable | The Accept header asks for a representation the API can't produce. |
409 | conflict | Declared by the schema; no current condition produces it on this route. |
413 | payload_too_large | The request body is over the size limit. Large HTML can hit this. |
415 | unsupported_media_type | The Content-Type isn't one this endpoint accepts. |
429 | rate_limited | Throttled. Honor the Retry-After header before retrying. |
The 400 here is not problem+json
Unlike the rest of v2, the two validation failures on this route return a bare {"detail": "…"} body with no type or code member. Code that branches on problem.code needs a fallback for this shape. The schema declares the standard ValidationProblemDetail for 400, so this is a known inconsistency rather than intended behavior.
curl -X POST \
"https://api.plane.so/api/v2/workspaces/my-team/artifacts/" \
-H "X-Api-Key: $PLANE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Q1 velocity by squad",
"description": "Throughput and cycle time, split by squad.",
"html": "<section><h1>Q1 velocity by squad</h1></section>",
"data_mode": "snapshot"
}'import requests
response = requests.post(
"https://api.plane.so/api/v2/workspaces/my-team/artifacts/",
headers={"X-Api-Key": "your-api-key"},
json={
"name": "Q1 velocity by squad",
"description": "Throughput and cycle time, split by squad.",
"html": "<section><h1>Q1 velocity by squad</h1></section>",
"data_mode": "snapshot",
},
)
print(response.json())const response = await fetch("https://api.plane.so/api/v2/workspaces/my-team/artifacts/", {
method: "POST",
headers: {
"X-Api-Key": "your-api-key",
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Q1 velocity by squad",
description: "Throughput and cycle time, split by squad.",
html: "<section><h1>Q1 velocity by squad</h1></section>",
data_mode: "snapshot",
}),
});
const data = await response.json();{
"id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53",
"name": "Q1 velocity by squad",
"current_version": 1,
"is_published": false,
"anchor": null,
"data_mode": "snapshot"
}{
"detail": "`html` is required."
}The response status is 201, not 200
A successful create returns 201 Created. The OpenAPI document declares 200 for this operation, but that is a gap in the schema annotation, not the actual behavior. Accept any 2xx instead of matching 200 exactly.
The response body contains the artifact's identity and publish state, but not its HTML. To get the HTML, call Get an artifact.
Next steps
- Publish an artifact to get a shareable anchor
- Append a new version when the content is regenerated

