Skip to main content

Sharing

Endpoints for managing a Template's shares: who can see and work with the Template, and at what access level.

A share binds a Principal (a User or a Team) to a Template at an access level. The principalId in the share routes is the Principal id itself: for a User that is the username (for example annie.larson), and for a Team it is the team id (for example order-desk). There is no separate UUID principal key.

Access level is an integer:

Access LevelMeaning
0None (effectively removes the share; the collection hides level-0 rows)
1Read
2Write
3Share

Listing and reading shares need only an authenticated user. Granting (PUT) and revoking (DELETE) a share require permission.template.share. The Template owner always keeps full access, independent of any share row.

GET /api/templates/{id}/shares

List a Template's shares as a HAL collection. Rows are filtered to accessLevel > 0, so a level-0 (removed) share is not returned.

Authentication: Required (session)

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

Response:

A HAL collection under _embedded["inf:template-share"]. Each row is mapped from the Principal and carries templateId, principalId, accessLevel, and a derived type ("User" or "Team") plus name. User rows add username/displayName/email and avatar fields; Team rows add materialIcon/icon/color. The underlying query has no ORDER BY, so row order is not guaranteed.

List a template’s sharesGET /api/templates/00000000-0000-4000-8000-000000000001/shares
HAL collection of the template’s shares (accessLevel > 0 only). Each row is mapped from the principal: a derived `type` ("User" or "Team"), `name`, accessLevel, and for users username/email/avatar fields, for teams materialIcon/icon/color.
Response · 200
[
{
"templateId": "00000000-0000-4000-8000-000000000001",
"principalId": "order-desk",
"accessLevel": 2,
"id": "order-desk",
"teamId": "order-desk",
"name": "Order Desk",
"materialIcon": "storefront",
"icon": null,
"color": "blue",
"username": null,
"displayName": null,
"email": null,
"avatarUpdatedAt": null,
"type": "Team"
},
{
"templateId": "00000000-0000-4000-8000-000000000001",
"principalId": "annie.larson",
"accessLevel": 1,
"id": "annie.larson",
"teamId": null,
"name": "Annie Larson",
"materialIcon": null,
"icon": null,
"color": null,
"username": "annie.larson",
"displayName": "Annie Larson",
"email": "annie.larson@example.com",
"avatarUpdatedAt": null,
"type": "User",
"avatarColor": {
"background": "#81C784",
"text": "#1B5E20"
},
"colorIndex": 4,
"initials": "AL"
}
]
Captured from the API examples test suite; ids and timestamps are normalized.

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

Get a single share row for one Principal.

Authentication: Required (session)

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

Response:

A db.lookup on the TemplateShare model: the raw row, with tenant, templateId, principalId, accessLevel, createdAt, and updatedAt.

Get a single shareGET /api/templates/00000000-0000-4000-8000-000000000001/shares/order-desk
db.lookup on the TemplateShare row: returns tenant, templateId, principalId, accessLevel, createdAt, updatedAt. 404s when the principal has no share on this template.
Response · 200
{
"_links": {
"self": {
"href": "https://informer.example.com/api/templates/00000000-0000-4000-8000-000000000001/shares/order-desk"
}
},
"id": "00000000-0000-4000-8000-000000000002",
"tenant": "acme",
"templateId": "00000000-0000-4000-8000-000000000001",
"principalId": "order-desk",
"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.

A Principal with no share on this Template answers 404.

Get a share that does not existGET /api/templates/00000000-0000-4000-8000-000000000001/shares/no-such-principal
A principal with no share on this template 404s from the db.lookup handler.
Response · 404
{
"statusCode": 404,
"error": "Not Found",
"message": "Not Found"
}
Captured from the API examples test suite; ids and timestamps are normalized.

PUT /api/templates/{id}/shares/{principalId}

Create or update (upsert) a Principal's share on a Template.

Authentication: Required (session)

Pre-blocks: template.lookup(params.id), permission.template.share(pre.template)

Request Body:

FieldTypeRequiredDescription
accessLevelintegerYes1 read, 2 write, 3 share. 0 removes the share from the list.
No payload validation

This route has no Joi payload schema, so accessLevel is not validated at the route level. The body is written straight onto the share row. Send a valid integer access level.

Example Request:

{
"accessLevel": 1
}

Response:

Responds 200 with the upserted share for both create and update (the handler does not call .created(), so the status is 200, not 201).

Share a template with a user or teamPUT /api/templates/00000000-0000-4000-8000-000000000001/shares/annie.larson
PUT to .../shares/{principalId} upserts a share. principalId is a Principal id: a username for a user, a team id for a team. Body is { accessLevel }: 1 read, 2 write, 3 share (0 removes it from the list). The route has no joi payload schema, so accessLevel is not validated by the route. Requires permission.template.share. Returns the upserted share with status 200 (create AND update) — NOT 201.
Request body
{
"accessLevel": 1
}
Response · 200
{
"_links": {
"self": {
"href": "https://informer.example.com/api/templates/00000000-0000-4000-8000-000000000001/shares/annie.larson"
}
},
"tenant": "acme",
"id": "00000000-0000-4000-8000-000000000002",
"templateId": "00000000-0000-4000-8000-000000000001",
"principalId": "annie.larson",
"accessLevel": 1,
"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/templates/{id}/shares/{principalId}

Revoke a Principal's share, removing their access.

Authentication: Required (session)

Pre-blocks: template.lookup(params.id), permission.template.share(pre.template)

Response:

Responds 200 with an empty body (Informer convention), not 204. The Template owner's own access is unaffected by removing a share.

Remove a shareDELETE /api/templates/00000000-0000-4000-8000-000000000001/shares/annie.larson
db.remove revokes the share. Requires permission.template.share. Returns 200 with an EMPTY body (Informer convention) — NOT 204. The template owner’s own access is unaffected.
Response · 200 · no body
Captured from the API examples test suite; ids and timestamps are normalized.

Permission Mapping

Share access levels grant the matching abilities. A higher level includes the lower ones:

Access LevelReadWriteShare
0 - Nonenonono
1 - Readyesnono
2 - Writeyesyesno
3 - Shareyesyesyes

Template ownership:

  • The Template owner always has full (share-level) access, regardless of share rows.
  • Change the owner via PUT /api/templates/{id}/owner.
  • Granting or revoking a share requires permission.template.share.

Global sharing:

  • Set shared: true via PUT /api/templates/{id} to make the Template accessible to all users in the tenant.
  • Global sharing requires permission.template.write.