Skip to main content

Draft Workflow

Endpoints for safely editing an Assistant without affecting its live version. A draft is itself an Assistant row whose editingId points back at the original; its name mirrors the original. You create a draft, edit it with the ordinary Assistant routes, then commit the changes back onto the live Assistant or discard the draft.

Every draft route is gated by ai.verify (AI must be enabled on the tenant AND the caller must have AI access) and the write routes additionally require permission.assistant.write (the caller must be a data wizard on the owning principal). The {id} in each URL is the Assistant's primary-key UUID, resolved through the read_access scope so an Assistant the caller cannot see answers 404.

Best Practice

Use drafts when making significant changes to an Assistant that is actively used in production chats.

POST /api/assistants/{id}/_edit

Create a draft copy of an Assistant for editing. The {id} is the original (live) Assistant's id.

Authentication: Required (AI access via ai.verify)

Pre-blocks: assistant.lookup, permission.assistant.write

This route is idempotent: if a draft already exists it is returned as-is; otherwise the draft is created in a transaction, copying the original's datasets, secrets, skills, references, toolkits, and files. The draft's editingId points at the original.

Response:

Responds 200 with the draft Assistant (the handler returns the row directly; it does not use .created(), so the status is 200, not 201) and a Location header pointing at the draft.

Create a draftPOST /api/assistants/00000000-0000-4000-8000-000000000001/_edit
POST /api/assistants/{id}/_edit creates (or returns the existing) draft copy of an assistant for safe editing. Requires AI access (ai.verify) and permission.assistant.write. Idempotent: a second call returns the same draft. The draft is an Assistant row whose `editingId` points at the original; its `name` mirrors the original. The handler returns the draft directly with a 200 (it does NOT use .created()/201) and a Location header to the draft.
Response · 200
{
"_links": {
"self": {
"href": "https://informer.example.com/api/assistants/00000000-0000-4000-8000-000000000001/_edit"
},
"inf:commit-draft": {
"href": "https://informer.example.com/api/assistants/00000000-0000-4000-8000-000000000001/draft/_commit"
},
"inf:assistant-takeover": {
"href": "https://informer.example.com/api/assistants/00000000-0000-4000-8000-000000000002/_takeover"
},
"inf:assistant-share": {
"href": "https://informer.example.com/api/assistants/00000000-0000-4000-8000-000000000001/_edit/shares/{principalId}",
"templated": true
},
"inf:assistant-owner": {
"href": "https://informer.example.com/api/assistants/admin%3Asales-assistant-1/owner"
},
"inf:assistant-datasets": {
"href": "https://informer.example.com/api/assistants/admin%3Asales-assistant-1/datasets"
},
"inf:assistant-skills": {
"href": "https://informer.example.com/api/assistants/admin%3Asales-assistant-1/skills"
},
"inf:assistant-toolkits": {
"href": "https://informer.example.com/api/assistants/admin%3Asales-assistant-1/toolkits"
},
"inf:assistant-references": {
"href": "https://informer.example.com/api/assistants/admin%3Asales-assistant-1/references"
},
"inf:assistant-shares": {
"href": "https://informer.example.com/api/assistants/admin%3Asales-assistant-1/shares"
},
"inf:assistant-icon": {
"href": "https://informer.example.com/api/assistants/admin%3Asales-assistant-1/icon"
},
"inf:assistant-secrets": {
"href": "https://informer.example.com/api/assistants/admin%3Asales-assistant-1/secrets"
},
"inf:assistant-copy": {
"href": "https://informer.example.com/api/assistants/admin%3Asales-assistant-1/_copy"
},
"inf:favorite": {
"href": "https://informer.example.com/api/assistants/admin%3Asales-assistant-1/favorite"
},
"inf:assistant-files": {
"href": "https://informer.example.com/api/assistants/admin%3Asales-assistant-1/files"
},
"inf:assistant-file-upload": {
"href": "https://informer.example.com/api/assistants/admin%3Asales-assistant-1/files/_upload"
},
"inf:assistant-usage": {
"href": "https://informer.example.com/api/assistants/admin%3Asales-assistant-1/usage"
},
"inf:create-image": {
"href": "https://informer.example.com/api/assistants/admin%3Asales-assistant-1/_create-image"
},
"inf:chat-external": {
"href": "https://informer.example.com/assistants/admin%3Asales-assistant-1/chat"
}
},
"naturalId": "admin:sales-assistant-1",
"searchName": "sales assistant",
"permissions": {
"chat": true,
"edit": true,
"share": true,
"delete": true,
"write": true,
"changeOwner": true,
"copy": true,
"rename": true,
"assignTags": true
},
"id": "00000000-0000-4000-8000-000000000002",
"shared": false,
"embedded": false,
"tenant": "acme",
"name": "Sales Assistant",
"description": "Answers questions about the Order Desk pipeline.",
"token": null,
"welcome": null,
"instructions": "You are a helpful sales analytics assistant.",
"suggestions": null,
"data": null,
"modelId": "00000000-0000-4000-8000-000000000003",
"dispatcher": false,
"editingId": "00000000-0000-4000-8000-000000000001",
"updatedAt": "2026-01-15T16:20:00.000Z",
"createdAt": "2026-01-15T16:20:00.000Z",
"ownerId": "admin",
"slug": "sales-assistant-1",
"secrets": null,
"folderId": null,
"source": null,
"sourceId": null,
"libraryId": null
}
Captured from the API examples test suite; ids and timestamps are normalized.

GET /api/assistants/{id}/draft

Retrieve the existing draft for an Assistant. The {id} is the original Assistant's id.

Authentication: Required (AI access via ai.verify)

Pre-blocks: assistant.draft (resolves the draft, 404 when none exists)

Response:

Responds 200 with the draft Assistant, a Location header to the draft, and an inf:original HAL link back to the Assistant being edited. When no draft exists the route answers 404.

Get the draftGET /api/assistants/00000000-0000-4000-8000-000000000001/draft
GET /api/assistants/{id}/draft returns the existing draft for the given (original) assistant id. Requires AI access. Responds 200 with a Location header to the draft and an `inf:original` HAL link back to the assistant being edited. 404s when no draft exists.
Response · 200
{
"_links": {
"self": {
"href": "https://informer.example.com/api/assistants/00000000-0000-4000-8000-000000000002"
},
"inf:commit-draft": {
"href": "https://informer.example.com/api/assistants/00000000-0000-4000-8000-000000000001/draft/_commit"
},
"inf:assistant-takeover": {
"href": "https://informer.example.com/api/assistants/00000000-0000-4000-8000-000000000002/_takeover"
},
"inf:assistant-share": {
"href": "https://informer.example.com/api/assistants/00000000-0000-4000-8000-000000000002/shares/{principalId}",
"templated": true
},
"inf:assistant-owner": {
"href": "https://informer.example.com/api/assistants/admin%3Asales-assistant-1/owner"
},
"inf:assistant-datasets": {
"href": "https://informer.example.com/api/assistants/admin%3Asales-assistant-1/datasets"
},
"inf:assistant-skills": {
"href": "https://informer.example.com/api/assistants/admin%3Asales-assistant-1/skills"
},
"inf:assistant-toolkits": {
"href": "https://informer.example.com/api/assistants/admin%3Asales-assistant-1/toolkits"
},
"inf:assistant-references": {
"href": "https://informer.example.com/api/assistants/admin%3Asales-assistant-1/references"
},
"inf:assistant-shares": {
"href": "https://informer.example.com/api/assistants/admin%3Asales-assistant-1/shares"
},
"inf:assistant-icon": {
"href": "https://informer.example.com/api/assistants/admin%3Asales-assistant-1/icon"
},
"inf:assistant-secrets": {
"href": "https://informer.example.com/api/assistants/admin%3Asales-assistant-1/secrets"
},
"inf:assistant-copy": {
"href": "https://informer.example.com/api/assistants/admin%3Asales-assistant-1/_copy"
},
"inf:favorite": {
"href": "https://informer.example.com/api/assistants/admin%3Asales-assistant-1/favorite"
},
"inf:assistant-files": {
"href": "https://informer.example.com/api/assistants/admin%3Asales-assistant-1/files"
},
"inf:assistant-file-upload": {
"href": "https://informer.example.com/api/assistants/admin%3Asales-assistant-1/files/_upload"
},
"inf:assistant-usage": {
"href": "https://informer.example.com/api/assistants/admin%3Asales-assistant-1/usage"
},
"inf:create-image": {
"href": "https://informer.example.com/api/assistants/admin%3Asales-assistant-1/_create-image"
},
"inf:chat-external": {
"href": "https://informer.example.com/assistants/admin%3Asales-assistant-1/chat"
},
"inf:original": {
"href": "https://informer.example.com/api/assistants/00000000-0000-4000-8000-000000000001"
}
},
"naturalId": "admin:sales-assistant-1",
"searchName": "sales assistant",
"permissions": {
"chat": true,
"edit": true,
"share": true,
"delete": true,
"write": true,
"changeOwner": true,
"copy": true,
"rename": true,
"assignTags": true
},
"id": "00000000-0000-4000-8000-000000000002",
"name": "Sales Assistant",
"description": "Answers questions about the Order Desk pipeline.",
"slug": "sales-assistant-1",
"ownerId": "admin",
"welcome": null,
"modelId": "00000000-0000-4000-8000-000000000003",
"instructions": "You are a helpful sales analytics assistant.",
"suggestions": null,
"token": null,
"data": null,
"source": null,
"sourceId": null,
"folderId": null,
"editingId": "00000000-0000-4000-8000-000000000001",
"shared": false,
"embedded": false,
"dispatcher": false,
"libraryId": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme",
"datasets": [],
"model": {
"naturalId": "go_everyday",
"permissions": {},
"promptPpm": null,
"completionPpm": null,
"cacheReadPpm": null,
"cacheWriteShortPpm": null,
"cacheWriteLongPpm": null,
"id": "00000000-0000-4000-8000-000000000003",
"model": null,
"slug": "go_everyday",
"provider": {
"permissions": {},
"tenant": "acme",
"id": "00000000-0000-4000-8000-000000000004",
"name": "Anthropic",
"slug": "anthropic",
"type": "anthropic",
"key": null,
"managed": true,
"internal": false,
"settings": null,
"data": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z"
}
},
"icon": null
}
Captured from the API examples test suite; ids and timestamps are normalized.

POST /api/assistants/{id}/draft/_commit

Apply the draft back onto the live Assistant and delete the draft, all in one transaction.

Authentication: Required (AI access via ai.verify)

Pre-blocks: assistant.lookup, permission.assistant.write

The commit copies the draft's editable fields plus its files, skills, references, datasets, and secrets onto the original, logs an assistantUpdated activity, and deletes the draft. After a successful commit the draft no longer exists.

Response:

Responds 200 with the updated live Assistant. Runs in a database transaction.

Commit the draftPOST /api/assistants/00000000-0000-4000-8000-000000000001/draft/_commit
POST /api/assistants/{id}/draft/_commit applies the draft back onto the live assistant and deletes the draft, all in one transaction. Requires AI access and permission.assistant.write. Copies the editable fields plus files/skills/references/datasets/secrets onto the original, logs an `assistantUpdated` activity, and returns the updated LIVE assistant (200). After commit the draft no longer exists.
Response · 200
{
"_links": {
"self": {
"href": "https://informer.example.com/api/assistants/00000000-0000-4000-8000-000000000001/draft/_commit"
},
"inf:draft": {
"href": "https://informer.example.com/api/assistants/admin%3Asupport-assistant/draft"
},
"inf:edit": {
"href": "https://informer.example.com/api/assistants/admin%3Asupport-assistant/_edit"
},
"inf:comments": {
"href": "https://informer.example.com/api/assistants/admin%3Asupport-assistant/comments{?sort,limit}",
"templated": true
},
"inf:assistant-share": {
"href": "https://informer.example.com/api/assistants/00000000-0000-4000-8000-000000000001/draft/_commit/shares/{principalId}",
"templated": true
},
"inf:assistant-owner": {
"href": "https://informer.example.com/api/assistants/admin%3Asupport-assistant/owner"
},
"inf:assistant-datasets": {
"href": "https://informer.example.com/api/assistants/admin%3Asupport-assistant/datasets"
},
"inf:assistant-skills": {
"href": "https://informer.example.com/api/assistants/admin%3Asupport-assistant/skills"
},
"inf:assistant-toolkits": {
"href": "https://informer.example.com/api/assistants/admin%3Asupport-assistant/toolkits"
},
"inf:assistant-references": {
"href": "https://informer.example.com/api/assistants/admin%3Asupport-assistant/references"
},
"inf:assistant-shares": {
"href": "https://informer.example.com/api/assistants/admin%3Asupport-assistant/shares"
},
"inf:assistant-icon": {
"href": "https://informer.example.com/api/assistants/admin%3Asupport-assistant/icon"
},
"inf:assistant-secrets": {
"href": "https://informer.example.com/api/assistants/admin%3Asupport-assistant/secrets"
},
"inf:assistant-copy": {
"href": "https://informer.example.com/api/assistants/admin%3Asupport-assistant/_copy"
},
"inf:favorite": {
"href": "https://informer.example.com/api/assistants/admin%3Asupport-assistant/favorite"
},
"inf:assistant-files": {
"href": "https://informer.example.com/api/assistants/admin%3Asupport-assistant/files"
},
"inf:assistant-file-upload": {
"href": "https://informer.example.com/api/assistants/admin%3Asupport-assistant/files/_upload"
},
"inf:assistant-usage": {
"href": "https://informer.example.com/api/assistants/admin%3Asupport-assistant/usage"
},
"inf:create-image": {
"href": "https://informer.example.com/api/assistants/admin%3Asupport-assistant/_create-image"
},
"inf:chat-external": {
"href": "https://informer.example.com/assistants/admin%3Asupport-assistant/chat"
}
},
"naturalId": "admin:support-assistant",
"searchName": "support assistant",
"permissions": {
"chat": true,
"edit": true,
"share": true,
"delete": true,
"write": true,
"changeOwner": true,
"copy": true,
"rename": true,
"assignTags": true
},
"id": "00000000-0000-4000-8000-000000000001",
"name": "Support Assistant",
"description": "Triages inbound support questions.",
"slug": "support-assistant",
"ownerId": "admin",
"welcome": null,
"modelId": "00000000-0000-4000-8000-000000000002",
"instructions": "You are a helpful support assistant.",
"suggestions": null,
"token": null,
"data": null,
"source": null,
"sourceId": null,
"folderId": null,
"editingId": null,
"shared": false,
"embedded": false,
"dispatcher": false,
"libraryId": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme",
"model": {
"naturalId": "go_everyday",
"permissions": {},
"promptPpm": null,
"completionPpm": null,
"cacheReadPpm": null,
"cacheWriteShortPpm": null,
"cacheWriteLongPpm": null,
"id": "00000000-0000-4000-8000-000000000002",
"model": null,
"slug": "go_everyday",
"provider": {
"permissions": {},
"tenant": "acme",
"id": "00000000-0000-4000-8000-000000000003",
"name": "Anthropic",
"slug": "anthropic",
"type": "anthropic",
"key": null,
"managed": true,
"internal": false,
"settings": null,
"data": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z"
}
},
"icon": null
}
Captured from the API examples test suite; ids and timestamps are normalized.

DELETE /api/assistants/{id}/draft

Discard the draft, leaving the live Assistant unchanged.

Authentication: Required (AI access via ai.verify)

Pre-blocks: permission.assistant.write

The draft is removed via the read_access scope; the original Assistant is untouched.

Response:

Responds 200 with an empty body (Informer convention), not 204.

Discard the draftDELETE /api/assistants/00000000-0000-4000-8000-000000000001/draft
DELETE /api/assistants/{id}/draft discards the draft (db.remove on the read_access scope), leaving the live assistant unchanged. Requires AI access and permission.assistant.write. Returns 200 with an EMPTY body (Informer convention), NOT 204.
Response · 200 · no body
Captured from the API examples test suite; ids and timestamps are normalized.

Worked Example

The draft lifecycle combines these draft routes with the ordinary Assistant routes documented elsewhere. The PUT and skills steps below are not draft-specific endpoints; they are the standard Assistant update and skills routes applied to the draft's id.

1. Create the draft

POST /api/assistants/{id}/_edit

Returns the draft Assistant (its editingId points at the original).

2. Edit the draft

Use the standard Assistant update route against the draft's id:

PUT /api/assistants/{draftId}
{
"instructions": "Updated instructions...",
"suggestions": ["New suggestion"]
}

3. Add skills to the draft

Use the standard skills route against the draft's id:

POST /api/assistants/{draftId}/skills
{
"type": "web-search",
"name": "Web Search"
}

4. Commit or discard

Commit the changes back onto the live Assistant:

POST /api/assistants/{id}/draft/_commit

Or discard the draft:

DELETE /api/assistants/{id}/draft