Skip to main content

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.

Get an assistant ownerGET /api/assistants/00000000-0000-4000-8000-000000000001/owner
GET /api/assistants/{id}/owner returns the owner Principal HAL-shaped (api inf:assistant-owner), embedding inf:user OR inf:team depending on the owner type. The principal id is the username (user owner) or team id (team owner) — NOT the flat { ownerId, ownerType } shape the .md guessed. Requires AI access (ai.verify).
Response · 200
{
"_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
}
}
}
Captured from the API examples test suite; ids and timestamps are normalized.

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.

FieldTypeRequiredDescription
idstringYesTarget 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.

Transfer assistant ownershipPUT /api/assistants/00000000-0000-4000-8000-000000000001/owner
PUT /api/assistants/{id}/owner identifies the new owner by a Principal `where` taken from the WHOLE payload (the route does Principal.find({ where: payload })), so the body is { id: '<principal>' } — a username or team id — NOT { ownerId } as the .md shows. There is no joi schema; a payload matching no principal 400s. Returns 200 with a Location header (NOT .created()/201) and the updated assistant row (ownerId set to the new principal id). Transferring to a team requires Data Designer+ in that team (superusers bypass). Requires permission.assistant.write.
Request body
{
"id": "marketing"
}
Response · 200
{
"_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
}
Captured from the API examples test suite; ids and timestamps are normalized.
Transferring to a Team

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.

List sharesGET /api/assistants/00000000-0000-4000-8000-000000000001/shares
A HAL collection (api inf:assistant-all-shares) embedding inf:assistant-share. Each row is built from a raw SQL join and display-mapped (type User/Team, name, avatar data), filtered to accessLevel > 0 — NOT the raw share columns the .md lists. Each embedded share links to its inf:assistant-share self.
Response · 200
[
{
"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"
}
]
Captured from the API examples test suite; ids and timestamps are normalized.

With no shares yet:

List shares (none yet)GET /api/assistants/00000000-0000-4000-8000-000000000001/shares
GET /api/assistants/{id}/shares is a HAL collection (api inf:assistant-all-shares) embedding inf:assistant-share. With no shares it is an empty collection. Requires AI access (ai.verify).
Response · 200 · empty list (no items in this example)
Captured from the API examples test suite; ids and timestamps are normalized.

GET /api/assistants/{id}/shares/{principalId}

Get a single share.

Authentication: Required (AI access)

Pre-blocks: assistant.lookup(params.id)

Path Parameters:

ParameterTypeDescription
idstringAssistant id
principalIdstringA 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.

Get a single shareGET /api/assistants/00000000-0000-4000-8000-000000000001/shares/marketing
GET /api/assistants/{id}/shares/{principalId} returns one AssistantShare (db.lookup) — the share columns { tenant, assistantId, principalId, accessLevel, createdAt, updatedAt }. 404 when no share exists for that principal. Requires AI access (ai.verify).
Response · 200
{
"_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"
}
Captured from the API examples test suite; ids and timestamps are normalized.

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:

ParameterTypeDescription
idstringAssistant id
principalIdstringA 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.

FieldTypeRequiredDescription
accessLevelintegerNoAccess 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.

Share an assistant with a principalPUT /api/assistants/00000000-0000-4000-8000-000000000001/shares/marketing
PUT /api/assistants/{id}/shares/{principalId} upserts an AssistantShare (db.upsert in a transaction) and records an "assistantShared" Activity. principalId is a username or team id. accessLevel comes straight from the payload (no joi schema; defaults to the model default 1 when omitted): 1=read, 2=write, 3=admin. Repeat the PUT to change the level. Requires permission.assistant.share. Returns 200 with the AssistantShare row.
Request body
{
"accessLevel": 2
}
Response · 200
{
"_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"
}
Captured from the API examples test suite; ids and timestamps are normalized.

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.

Revoke a shareDELETE /api/assistants/00000000-0000-4000-8000-000000000001/shares/marketing
DELETE /api/assistants/{id}/shares/{principalId} removes the AssistantShare (db.remove in a transaction). Returns 200 with an EMPTY body (Informer convention), NOT 204 as the .md claims. Requires permission.assistant.share.
Response · 200 · no body
Captured from the API examples test suite; ids and timestamps are normalized.

Access Levels

LevelNamePermissions
1ReadView Assistant, use in chats
2WriteEdit Assistant, manage Skills/Datasets/Secrets
3AdminFull 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.

Check a favoriteGET /api/assistants/00000000-0000-4000-8000-000000000001/favorite
GET /api/assistants/{id}/favorite returns the Favorite row for the calling user, or 404 when the caller has not favorited the assistant (it does NOT return { favorited: bool } as the .md shows).
Response · 200
{
"_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"
}
Captured from the API examples test suite; ids and timestamps are normalized.

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.

Favorite an assistantPUT /api/assistants/00000000-0000-4000-8000-000000000001/favorite
PUT /api/assistants/{id}/favorite upserts a per-user Favorite (keyed on the caller username). Idempotent. Returns 200 with the Favorite row { id, principalId, assistantId, ... }. Requires AI access (ai.verify).
Response · 200
{
"_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
}
Captured from the API examples test suite; ids and timestamps are normalized.

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.

Unfavorite an assistantDELETE /api/assistants/00000000-0000-4000-8000-000000000001/favorite
DELETE /api/assistants/{id}/favorite removes the caller's Favorite. Returns 200 with an EMPTY body (Informer convention), NOT 204 as the .md claims.
Response · 200 · no body
Captured from the API examples test suite; ids and timestamps are normalized.