Studio turns
The Studio API doesn't take a template and fill it in. You send a Studio's director a message, the way you would in the app, and it answers — with the finished thing, or with a question first.
#A turn is a message
Each Studio has one endpoint: POST /v1/studio/{studio}/turns. Without artifact_id a turn starts something new; with one — or with the conversation_id a previous turn returned — it keeps shaping the same thing.
| Studio | Director | What a turn does |
|---|---|---|
website | Website Director | build or change a website |
document | Documents director | write or change a deck, proposal or document |
app | App Builder | build or change a workspace app |
image | Images director | make images on a board |
video | Video director | edit a video project |
Documents covers decks, proposals, résumés, invoices, one-pagers and reports.
Request bodyapplication/json
messagestringrequiredWhat you would type to this Studio's director, in plain words. Answers to its questions go here too.
max length 3800artifact_idstring · uuidoptionalWork on an existing artifact of this Studio (from a previous answer's
artifact.idor a list endpoint). Omit to start a new one.conversation_idstring · uuidoptionalContinue a conversation: the
conversation_idof a previous answer. Needed to answer a website director's questions before the site exists; otherwiseartifact_idalready implies it.autobooleanoptional"Decide for me": the director settles anything unclear itself and makes the artifact now instead of asking. Default false — the director may answer
needs_inputwith questions or an outline, exactly as in the app.DefaultfalsestreambooleanoptionalAnswer as server-sent events:
turn,plan,status, thenresult(the same body a waited-for call returns).DefaultfalsebackgroundbooleanoptionalAnswer 202 as soon as the turn is claimed and keep working; poll
GET /v1/turns/{id}.Defaultfalse
#What comes back
A turn always resolves to one of four statuses. Handle all four and you've handled everything a Studio can do.
POST /v1/studio/{studio}/turnsmessage · artifact_id · auto · stream · backgroundcompletedThe artifact and its urlDone. Keep shaping it with another turn on artifact_id.needs_inputQuestions, or an outline to approveReply on the same artifact_id or conversation_id — or send auto: true. loops back as a new turnbuildingThe turn id (and a job, for websites)Still working. Poll GET /v1/turns/{id} every 5–10 s.failederror — what went wrongThe turn started, then failed. Refusals before it starts are HTTP errors.
defaultOne request, one response with the finished turn. Simplest; the server holds it open for up to 800 s.
stream: trueServer-sent turn, plan and status events as the director works, then one result.
background: true202 as soon as the turn is claimed. Poll GET /v1/turns/{id} every 5–10 s.
Every answer carries the artifact (id, type, name, url, public_url), the conversation_id, and the director's own words in message.
#Questions and outlines
Like in the app, a director sometimes checks before it builds — who a deck is for, what a site sells — or proposes an outline to approve. The turn comes back needs_input:
{
"id": "1e0f…",
"object": "studio.turn",
"status": "needs_input",
"studio": "document",
"artifact": {
"id": "a3c2…",
"type": "document",
"name": "Untitled",
"url": "https://app.attria.io/studio/documents/a3c2…",
"public_url": null
},
"conversation_id": "c81b…",
"message": "One quick question before I write this.",
"questions": [
{
"text": "Who is this deck for?",
"options": [
"Investors",
"Customers",
"My team",
"Custom"
]
}
],
"created_at": "2026-09-27T18:02:11Z",
"completed_at": "2026-09-27T18:02:19Z"
}Reply in plain words with another turn on the same artifact_id (or conversation_id, for a website that doesn't exist yet). The options are suggestions, not a menu. To approve an outline, say so — “Build it” works.
curl -X POST "https://app.attria.io/api/v1/studio/document/turns" \
-H "Authorization: Bearer $ATTRIA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"artifact_id": "a3c2d1e0-5b4a-4c3d-8e2f-1a0b9c8d7e6f",
"message": "Investors. Build it."
}'const res = await fetch("https://app.attria.io/api/v1/studio/document/turns", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ATTRIA_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
artifact_id: "a3c2d1e0-5b4a-4c3d-8e2f-1a0b9c8d7e6f",
message: "Investors. Build it.",
}),
});
const turn = await res.json();import os, uuid, requests
res = requests.post(
"https://app.attria.io/api/v1/studio/document/turns",
headers={
"Authorization": f"Bearer {os.environ['ATTRIA_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"artifact_id": "a3c2d1e0-5b4a-4c3d-8e2f-1a0b9c8d7e6f",
"message": "Investors. Build it.",
},
timeout=900, # a turn that waits can take minutes
)
turn = res.json()#Decide for me
Send auto: true and the director settles anything unclear itself and makes the artifact now — no questions, no outline to confirm. Use it for scripts, batch jobs and agents with no person to answer.
curl -X POST "https://app.attria.io/api/v1/studio/website/turns" \
-H "Authorization: Bearer $ATTRIA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"message": "Build a landing page for Harbor Dental, a family dentist in Portland. Goal: book a first cleaning.",
"auto": true
}'const res = await fetch("https://app.attria.io/api/v1/studio/website/turns", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ATTRIA_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
message: "Build a landing page for Harbor Dental, a family dentist in Portland. Goal: book a first cleaning.",
auto: true,
}),
});
const turn = await res.json();import os, uuid, requests
res = requests.post(
"https://app.attria.io/api/v1/studio/website/turns",
headers={
"Authorization": f"Bearer {os.environ['ATTRIA_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"message": "Build a landing page for Harbor Dental, a family dentist in Portland. Goal: book a first cleaning.",
"auto": True,
},
timeout=900, # a turn that waits can take minutes
)
turn = res.json()Directors use only what you tell them and your workspace's brand. Put every fact, figure, name and date in message — auto fills gaps in judgement, not in facts.
#How long turns take
| Studio | Typical turn |
|---|---|
document | 20–60 s; longer for long decks. |
image | 20–60 s, with the finished images in the answer. |
app | 45–120 s. |
video | 10–40 s for edits. Filming a shot or a motion graphic is a content app. |
website | A few seconds to answer building; the site then builds in 2–5 minutes. |
A new website answers quickly with status: "building" and keeps building after the call returns. Poll GET /v1/turns/{id} — or GET /v1/websites/{id}, whose build.stage says which step it's on — every 5–10 seconds. For everything else, choose how to wait in Jobs & streaming.
#Endpoints
- POST
/v1/studio/website/turnsTalk to the Website Director - POST
/v1/studio/document/turnsTalk to the Documents director - POST
/v1/studio/app/turnsTalk to the App Builder - POST
/v1/studio/image/turnsTalk to the Images director - POST
/v1/studio/video/turnsTalk to the Video director - GET
/v1/turns/{id}Get a Studio turn
Reading what you made
- GET
/v1/websitesList websites - GET
/v1/websites/{id}Get a website and its build - GET
/v1/documentsList documents - GET
/v1/documents/{id}Get a document with its full content - GET
/v1/appsList apps - GET
/v1/apps/{id}Get an app - GET
/v1/images/boardsList image boards - GET
/v1/imagesList images - GET
/v1/videosList video projects