Sharing & Ownership
Endpoints for managing Assistant ownership, the shares that grant other Principals access, and per-user favorites.
Every route on this page requires AI access via ai.verify (AI enabled on the
tenant AND the caller has AI access). The Assistant is resolved through
assistant.lookup (the read_access model scope), so a caller only reaches
these routes for an Assistant they may see. A Principal is a User or a Team: its
id is the username for a User owner/share and the team id for a Team
owner/share.
GET /api/assistants/{id}/owner
Get the owner of an Assistant.
Authentication: Required (AI access)
Pre-blocks: assistant.lookup(params.id)
Response:
Returns the owner Principal HAL-shaped (the inf:assistant-owner resource),
embedding inf:user for a User owner or inf:team for a Team owner. The
principal id is the username (User owner) or team id (Team owner). This is not
a flat { ownerId, ownerType } object.
{
"_links": {
"self": {
"href": "https://informer.example.com/api/assistants/00000000-0000-4000-8000-000000000001/owner"
}
},
"id": "admin",
"userId": "admin",
"teamId": null,
"tenant": "acme",
"_embedded": {
"inf:user": {
"_links": {
"self": {
"href": "https://informer.example.com/api/users/admin"
},
"inf:memberships": {
"href": "https://informer.example.com/api/users/admin/memberships"
},
"inf:avatar-upload": {
"href": "https://informer.example.com/api/users/admin/_upload/{uploadId}/avatar-upload",
"templated": true
},
"inf:password": {
"href": "https://informer.example.com/api/users/admin/password"
},
"inf:feed": {
"href": "https://informer.example.com/api/users/admin/feed"
},
"inf:viewed-feed": {
"href": "https://informer.example.com/api/users/admin/_markFeedViewed"
},
"inf:superuser": {
"href": "https://informer.example.com/api/users/admin/superuser"
}
},
"permissions": {
"changeDomain": false,
"changeProfile": true,
"changePassword": true,
"delete": false,
"edit": true,
"reassign": true,
"superuser": true,
"enable": true,
"changeTheme": true,
"lock": true,
"setGlobalPermissions": true,
"manageLogin": true
},
"domain": "local",
"username": "admin",
"displayName": "acme Administrator",
"familyName": "Administrator",
"givenName": "acme",
"middleName": null,
"email": null,
"data": null,
"superuser": true,
"timezone": "America/New_York",
"settings": {
"log": {
"log": false,
"info": false,
"warn": false,
"debug": false,
"error": true,
"remote": false
}
},
"enabled": true,
"source": null,
"sourceId": null,
"lastViewedFeed": null,
"tenant": "acme",
"globalPermissions": {
"ai": false,
"jobCreation": true,
"viewAllTeams": true,
"viewAllUsers": true,
"painlessScriptCreation": true
},
"userFields": {},
"locked": false,
"lockedAt": null,
"loginAttempts": 0,
"passwordSetAt": "2026-01-15T16:20:00.000Z",
"passwordExpiresAt": "2026-01-15T16:20:00.000Z",
"forgotPasswordRequestTime": null,
"mfa": null,
"mfaConfig": {},
"aiMessage": null,
"aiConsent": false,
"hasSharedDatasources": false,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"initials": "EA",
"colorIndex": 4,
"avatarColor": {
"background": "#81C784",
"text": "#1B5E20"
},
"managesPassword": true
}
}
}
PUT /api/assistants/{id}/owner
Transfer ownership of an Assistant to another Principal.
Authentication: Required (AI access)
Permission: assistant:write
Pre-blocks: assistant.lookup(params.id), permission.assistant.write
Request Body:
The route identifies the new owner with Principal.find({ where: payload }), so
the payload IS the Principal where. Send the principal id under id. There is
no Joi schema on this route.
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Target Principal id: a username (User) or team id (Team) |
There is no ownerId field. A payload matching no Principal answers 400.
Example Request:
{
"id": "marketing"
}
Response:
Responds 200 with the updated Assistant row (its ownerId set to the new
principal id) and a Location header. The handler uses
h.response(assistant).location(...), so the status is 200, not 201.
{
"id": "marketing"
}
{
"_links": {
"self": {
"href": "https://informer.example.com/api/assistants/00000000-0000-4000-8000-000000000001"
},
"inf:draft": {
"href": "https://informer.example.com/api/assistants/marketing%3Asales-assistant/draft"
},
"inf:edit": {
"href": "https://informer.example.com/api/assistants/marketing%3Asales-assistant/_edit"
},
"inf:comments": {
"href": "https://informer.example.com/api/assistants/marketing%3Asales-assistant/comments{?sort,limit}",
"templated": true
},
"inf:assistant-share": {
"href": "https://informer.example.com/api/assistants/00000000-0000-4000-8000-000000000001/shares/{principalId}",
"templated": true
},
"inf:assistant-owner": {
"href": "https://informer.example.com/api/assistants/marketing%3Asales-assistant/owner"
},
"inf:assistant-datasets": {
"href": "https://informer.example.com/api/assistants/marketing%3Asales-assistant/datasets"
},
"inf:assistant-skills": {
"href": "https://informer.example.com/api/assistants/marketing%3Asales-assistant/skills"
},
"inf:assistant-toolkits": {
"href": "https://informer.example.com/api/assistants/marketing%3Asales-assistant/toolkits"
},
"inf:assistant-references": {
"href": "https://informer.example.com/api/assistants/marketing%3Asales-assistant/references"
},
"inf:assistant-shares": {
"href": "https://informer.example.com/api/assistants/marketing%3Asales-assistant/shares"
},
"inf:assistant-icon": {
"href": "https://informer.example.com/api/assistants/marketing%3Asales-assistant/icon"
},
"inf:assistant-secrets": {
"href": "https://informer.example.com/api/assistants/marketing%3Asales-assistant/secrets"
},
"inf:assistant-copy": {
"href": "https://informer.example.com/api/assistants/marketing%3Asales-assistant/_copy"
},
"inf:favorite": {
"href": "https://informer.example.com/api/assistants/marketing%3Asales-assistant/favorite"
},
"inf:assistant-files": {
"href": "https://informer.example.com/api/assistants/marketing%3Asales-assistant/files"
},
"inf:assistant-file-upload": {
"href": "https://informer.example.com/api/assistants/marketing%3Asales-assistant/files/_upload"
},
"inf:assistant-usage": {
"href": "https://informer.example.com/api/assistants/marketing%3Asales-assistant/usage"
},
"inf:create-image": {
"href": "https://informer.example.com/api/assistants/marketing%3Asales-assistant/_create-image"
},
"inf:chat-external": {
"href": "https://informer.example.com/assistants/marketing%3Asales-assistant/chat"
}
},
"naturalId": "marketing:sales-assistant",
"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-000000000001",
"name": "Sales Assistant",
"description": "Answers questions about the Order Desk pipeline.",
"slug": "sales-assistant",
"ownerId": "marketing",
"welcome": null,
"modelId": "00000000-0000-4000-8000-000000000002",
"instructions": null,
"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",
"datasets": [],
"editing": null,
"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
}
Transferring ownership to a Team requires Data Designer or higher in that Team. Superusers bypass this check.
Shares
A share grants a Principal access to an Assistant at a numeric accessLevel.
GET /api/assistants/{id}/shares
List all shares for an Assistant.
Authentication: Required (AI access)
Pre-blocks: assistant.lookup(params.id)
Response:
A HAL collection (the inf:assistant-all-shares resource) embedding
inf:assistant-share. Each row is built from a raw SQL join across the share,
principal, team, user, and avatar tables, then display-mapped (a type of
User or Team, the principal name, and avatar data), and filtered to
accessLevel > 0. These are not the raw share columns. With no shares the
collection is empty.
[
{
"assistantId": "00000000-0000-4000-8000-000000000001",
"principalId": "marketing",
"accessLevel": 2,
"id": "marketing",
"teamId": "marketing",
"name": "Marketing",
"materialIcon": null,
"icon": null,
"color": null,
"username": null,
"displayName": null,
"email": null,
"avatarUpdatedAt": null,
"type": "Team"
}
]
With no shares yet:
GET /api/assistants/{id}/shares/{principalId}
Get a single share.
Authentication: Required (AI access)
Pre-blocks: assistant.lookup(params.id)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | Assistant id |
principalId | string | A username (User) or team id (Team) |
Response:
Returns the single AssistantShare row
({ tenant, id, assistantId, principalId, accessLevel, createdAt, updatedAt }).
A principal with no share for this Assistant answers 404.
{
"_links": {
"self": {
"href": "https://informer.example.com/api/assistants/00000000-0000-4000-8000-000000000001/shares/marketing"
}
},
"id": "00000000-0000-4000-8000-000000000002",
"tenant": "acme",
"assistantId": "00000000-0000-4000-8000-000000000001",
"principalId": "marketing",
"accessLevel": 2,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z"
}
PUT /api/assistants/{id}/shares/{principalId}
Create or update (upsert) a share.
Authentication: Required (AI access)
Permission: assistant:share
Pre-blocks: assistant.lookup(params.id), permission.assistant.share
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | Assistant id |
principalId | string | A username (User) or team id (Team) |
Request Body:
There is no Joi schema on this route, so accessLevel comes straight from the
payload. When omitted it falls back to the model default of 1.
| Field | Type | Required | Description |
|---|---|---|---|
accessLevel | integer | No | Access level (1=read, 2=write, 3=admin). Defaults to 1. |
Repeat the PUT with a different accessLevel to change an existing share.
Example Request:
{
"accessLevel": 2
}
Response:
Responds 200 with the upserted AssistantShare row. The upsert runs in a
database transaction and records an assistantShared Activity.
{
"accessLevel": 2
}
{
"_links": {
"self": {
"href": "https://informer.example.com/api/assistants/00000000-0000-4000-8000-000000000001/shares/marketing"
}
},
"tenant": "acme",
"id": "00000000-0000-4000-8000-000000000002",
"assistantId": "00000000-0000-4000-8000-000000000001",
"principalId": "marketing",
"accessLevel": 2,
"updatedAt": "2026-01-15T16:20:00.000Z",
"createdAt": "2026-01-15T16:20:00.000Z"
}
DELETE /api/assistants/{id}/shares/{principalId}
Revoke a share.
Authentication: Required (AI access)
Permission: assistant:share
Pre-blocks: assistant.lookup(params.id), permission.assistant.share
Response:
Responds 200 with an empty body (Informer convention), not 204. The removal
runs in a database transaction.
Access Levels
| Level | Name | Permissions |
|---|---|---|
| 1 | Read | View Assistant, use in chats |
| 2 | Write | Edit Assistant, manage Skills/Datasets/Secrets |
| 3 | Admin | Full control, change ownership, manage shares |
Favorites
Favorites are per-user. Each is keyed on the calling user's username, so the routes always act on the caller's own favorite.
GET /api/assistants/{id}/favorite
Check whether the calling user has favorited the Assistant.
Authentication: Required (AI access)
Pre-blocks: assistant.lookup(params.id)
Response:
Returns the Favorite row for the calling user. The caller has not favorited the
Assistant answers 404. This does not return a { favorited: boolean } object.
{
"_links": {
"self": {
"href": "https://informer.example.com/api/assistants/00000000-0000-4000-8000-000000000001/favorite"
}
},
"permissions": {},
"id": "00000000-0000-4000-8000-000000000002",
"principalId": "admin",
"reportId": null,
"datasetId": null,
"queryId": null,
"datasourceId": null,
"jobId": null,
"assistantId": "00000000-0000-4000-8000-000000000001",
"templateId": null,
"libraryId": null,
"appId": null,
"integrationId": null,
"toolkitId": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme"
}
PUT /api/assistants/{id}/favorite
Add the Assistant to the calling user's favorites.
Authentication: Required (AI access)
Pre-blocks: assistant.lookup(params.id)
Response:
Responds 200 with the upserted Favorite row. The operation is idempotent.
{
"_links": {
"self": {
"href": "https://informer.example.com/api/assistants/00000000-0000-4000-8000-000000000001/favorite"
}
},
"permissions": {},
"id": "00000000-0000-4000-8000-000000000002",
"tenant": "acme",
"assistantId": "00000000-0000-4000-8000-000000000001",
"principalId": "admin",
"reportId": null,
"datasetId": null,
"queryId": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"datasourceId": null,
"jobId": null,
"templateId": null,
"libraryId": null,
"appId": null,
"integrationId": null,
"toolkitId": null
}
DELETE /api/assistants/{id}/favorite
Remove the Assistant from the calling user's favorites.
Authentication: Required (AI access)
Pre-blocks: assistant.lookup(params.id)
Response:
Responds 200 with an empty body (Informer convention), not 204.