Skip to main content

Files

Endpoints for managing the files attached to a Template: the TEMPLATE source file, ASSET files (CSS, JS, images), and DATA files.

Every route on this page gates on the templates license feature. The pre-block chain asserts that feature first (template.ensureTemplates calls req.assertFeature('templates')), then runs template.lookup(params.id) via the read_access scope, so a missing or unreadable Template answers 404 before the handler runs. Mutating routes add a permission check after the lookup: permission.template.write for POST/PUT, and permission.template.edit for DELETE.

A File row carries metadata only. Its bytes live separately and are read or written through the inf:file-contents link, never through these metadata endpoints.

GET /api/templates/{id}/files

List the files whose templateId is this Template.

Authentication: Required

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

Response:

A HAL collection with the standard start/count/total envelope, embedding inf:file. Each item adds an inf:file-contents sub-link to its byte stream. The collection covers all roles: TEMPLATE source files, ASSET files, and DATA files.

List a template’s filesGET /api/templates/00000000-0000-4000-8000-000000000001/files
Returns a HAL collection (start/count/total) of File rows whose templateId is this template, embedding `inf:file` with an `inf:file-contents` sub-link per item. Covers TEMPLATE source files, ASSET files (CSS/JS/images), and DATA files.
Response · 200
{
"_links": {
"self": {
"href": "https://informer.example.com/api/templates/00000000-0000-4000-8000-000000000001/files"
}
},
"start": 0,
"count": 3,
"total": 3,
"_embedded": {
"inf:file": [
{
"_links": {
"self": {
"href": "https://informer.example.com/api/templates/00000000-0000-4000-8000-000000000001/files/00000000-0000-4000-8000-000000000002"
},
"inf:file-contents": {
"href": "https://informer.example.com/api/templates/00000000-0000-4000-8000-000000000001/files/00000000-0000-4000-8000-000000000002/contents"
}
},
"extension": "txt",
"tenant": "acme",
"id": "00000000-0000-4000-8000-000000000002",
"filename": "scratch.txt",
"embedded": true,
"contentType": "text/plain",
"lobId": null,
"role": "ASSET",
"description": null,
"data": {},
"createdById": null,
"letterheadId": null,
"jobActionAttachmentId": null,
"jobActionId": null,
"oauthClientId": null,
"assistantId": null,
"integrationId": null,
"directory": false,
"indexed": false,
"indexingStartedAt": null,
"indexingErroredAt": null,
"indexingErrorMessage": null,
"indexedAt": null,
"parentId": null,
"libraryId": null,
"remoteId": null,
"remoteUrl": null,
"size": null,
"modifiedAt": null,
"messageId": null,
"chatId": null,
"expiresAt": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"ownerId": null,
"templateId": "00000000-0000-4000-8000-000000000001"
},
{
"_links": {
"self": {
"href": "https://informer.example.com/api/templates/00000000-0000-4000-8000-000000000001/files/00000000-0000-4000-8000-000000000003"
},
"inf:file-contents": {
"href": "https://informer.example.com/api/templates/00000000-0000-4000-8000-000000000001/files/00000000-0000-4000-8000-000000000003/contents"
}
},
"extension": "njk",
"tenant": "acme",
"id": "00000000-0000-4000-8000-000000000003",
"filename": "invoice.njk",
"embedded": true,
"contentType": "text/html",
"lobId": "16400",
"role": "TEMPLATE",
"description": null,
"data": {},
"createdById": null,
"letterheadId": null,
"jobActionAttachmentId": null,
"jobActionId": null,
"oauthClientId": null,
"assistantId": null,
"integrationId": null,
"directory": false,
"indexed": false,
"indexingStartedAt": null,
"indexingErroredAt": null,
"indexingErrorMessage": null,
"indexedAt": null,
"parentId": null,
"libraryId": null,
"remoteId": null,
"remoteUrl": null,
"size": "524288",
"modifiedAt": null,
"messageId": null,
"chatId": null,
"expiresAt": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"ownerId": null,
"templateId": "00000000-0000-4000-8000-000000000001"
},
{
"_links": {
"self": {
"href": "https://informer.example.com/api/templates/00000000-0000-4000-8000-000000000001/files/00000000-0000-4000-8000-000000000004"
},
"inf:file-contents": {
"href": "https://informer.example.com/api/templates/00000000-0000-4000-8000-000000000001/files/00000000-0000-4000-8000-000000000004/contents"
}
},
"extension": "css",
"tenant": "acme",
"id": "00000000-0000-4000-8000-000000000004",
"filename": "styles.css",
"embedded": true,
"contentType": "text/css",
"lobId": "16400",
"role": "ASSET",
"description": null,
"data": {},
"createdById": null,
"letterheadId": null,
"jobActionAttachmentId": null,
"jobActionId": null,
"oauthClientId": null,
"assistantId": null,
"integrationId": null,
"directory": false,
"indexed": false,
"indexingStartedAt": null,
"indexingErroredAt": null,
"indexingErrorMessage": null,
"indexedAt": null,
"parentId": null,
"libraryId": null,
"remoteId": null,
"remoteUrl": null,
"size": "524288",
"modifiedAt": null,
"messageId": null,
"chatId": null,
"expiresAt": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"ownerId": null,
"templateId": "00000000-0000-4000-8000-000000000001"
}
]
}
}
Captured from the API examples test suite; ids and timestamps are normalized.

File properties:

FieldTypeDescription
idstring (UUID)File id
filenamestringFilename with extension
extensionstringDerived file extension
rolestringTEMPLATE, ASSET, or DATA
contentTypestringMIME type
sizestring | nullByte size as a string, or null when no contents are stored
templateIdstring (UUID)Owning Template id
embeddedbooleanAlways true for Template files
lobIdstring | nullInternal large-object id backing the contents, or null
dataobjectDriver-specific metadata
createdAt / updatedAtdateTimestamps

POST /api/templates/{id}/files

Create a new file for the Template.

Authentication: Required

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

Request Body:

The payload validates only the three fields below. The handler forces embedded: true and takes templateId from the URL, so neither can be set by the caller.

FieldTypeRequiredDescription
filenamestringNoFilename with extension
contentTypestringNoMIME type
rolestringNoASSET, DATA, or TEMPLATE

Example Request:

{
"filename": "footer.html",
"contentType": "text/html",
"role": "ASSET"
}

Response:

Responds 201 Created with the new File and a Location header pointing at the file lookup. This creates metadata only; no contents are written. Upload the bytes separately through the contents PUT.

Create a template filePOST /api/templates/00000000-0000-4000-8000-000000000001/files
Payload validates only { filename?, contentType?, role? } where role is ASSET|DATA|TEMPLATE. The handler forces embedded:true and templateId from the URL. Creates metadata only (no contents — write those via the contents PUT). Responds 201 with a Location header.
Request body
{
"filename": "footer.html",
"contentType": "text/html",
"role": "ASSET"
}
Response · 201
{
"_links": {
"self": {
"href": "https://informer.example.com/api/templates/00000000-0000-4000-8000-000000000001/files/00000000-0000-4000-8000-000000000002"
}
},
"extension": "html",
"tenant": "acme",
"id": "00000000-0000-4000-8000-000000000002",
"directory": false,
"filename": "footer.html",
"contentType": "text/html",
"role": "ASSET",
"templateId": "00000000-0000-4000-8000-000000000001",
"embedded": true,
"updatedAt": "2026-01-15T16:20:00.000Z",
"createdAt": "2026-01-15T16:20:00.000Z",
"lobId": null,
"ownerId": null,
"data": {},
"createdById": null,
"letterheadId": null,
"jobActionAttachmentId": null,
"jobActionId": null,
"oauthClientId": null,
"description": null,
"assistantId": null,
"integrationId": null,
"parentId": null,
"libraryId": null,
"remoteId": null,
"remoteUrl": null,
"size": null,
"modifiedAt": null,
"indexed": false,
"indexingStartedAt": null,
"indexedAt": null,
"indexingErroredAt": null,
"indexingErrorMessage": null,
"messageId": null,
"chatId": null,
"expiresAt": null
}
Captured from the API examples test suite; ids and timestamps are normalized.

POST /api/templates/{id}/files/_upload

Upload a file to the Template as multipart form data.

Authentication: Required

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

Request:

Multipart form data carrying the file bytes.

Response:

Responds 201 Created with the stored File.

Not shown

The request body is raw multipart bytes, so this endpoint is not captured here.


GET /api/templates/{id}/files/{file}

Get the metadata for a single Template file.

Authentication: Required

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

Response:

The File's metadata plus an inf:file-contents link. A file id that does not belong to this Template answers 404.

Get a template fileGET /api/templates/00000000-0000-4000-8000-000000000001/files/00000000-0000-4000-8000-000000000002
Returns one File’s metadata (db.lookup) plus an `inf:file-contents` link. A file id that does not belong to the template 404s.
Response · 200
{
"_links": {
"self": {
"href": "https://informer.example.com/api/templates/00000000-0000-4000-8000-000000000001/files/00000000-0000-4000-8000-000000000002"
},
"inf:file-contents": {
"href": "https://informer.example.com/api/templates/00000000-0000-4000-8000-000000000001/files/00000000-0000-4000-8000-000000000002/contents"
}
},
"extension": "html",
"tenant": "acme",
"id": "00000000-0000-4000-8000-000000000002",
"filename": "footer.html",
"embedded": true,
"contentType": "text/html",
"lobId": null,
"role": "ASSET",
"description": null,
"data": {},
"createdById": null,
"letterheadId": null,
"jobActionAttachmentId": null,
"jobActionId": null,
"oauthClientId": null,
"assistantId": null,
"directory": false,
"indexed": false,
"indexingStartedAt": null,
"indexingErroredAt": null,
"indexingErrorMessage": null,
"indexedAt": null,
"parentId": null,
"libraryId": null,
"remoteId": null,
"remoteUrl": null,
"size": null,
"modifiedAt": null,
"messageId": null,
"chatId": null,
"expiresAt": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"ownerId": null,
"templateId": "00000000-0000-4000-8000-000000000001",
"integrationId": null
}
Captured from the API examples test suite; ids and timestamps are normalized.

PUT /api/templates/{id}/files/{file}

Update a Template file's metadata.

Authentication: Required

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

Request Body:

This route has no Joi payload schema. The body is whatever File columns you want to change, typically filename and contentType.

FieldTypeDescription
filenamestringNew filename
contentTypestringMIME type

Example Request:

{
"filename": "page-footer.html",
"contentType": "text/html"
}

Response:

Responds 200 with the updated File.

Update a template filePUT /api/templates/00000000-0000-4000-8000-000000000001/files/00000000-0000-4000-8000-000000000002
PUT has no joi payload schema: the body is the File columns to change (filename, contentType). Returns the updated File (200). A pre-block returns 403 if you try to rename a file whose role is TEMPLATE.
Request body
{
"filename": "page-footer.html",
"contentType": "text/html"
}
Response · 200
{
"_links": {
"self": {
"href": "https://informer.example.com/api/templates/00000000-0000-4000-8000-000000000001/files/00000000-0000-4000-8000-000000000002"
},
"inf:file-contents": {
"href": "https://informer.example.com/api/templates/00000000-0000-4000-8000-000000000001/files/00000000-0000-4000-8000-000000000002/contents"
}
},
"extension": "html",
"tenant": "acme",
"id": "00000000-0000-4000-8000-000000000002",
"filename": "page-footer.html",
"embedded": true,
"contentType": "text/html",
"lobId": null,
"role": "ASSET",
"description": null,
"data": {},
"createdById": null,
"letterheadId": null,
"jobActionAttachmentId": null,
"jobActionId": null,
"oauthClientId": null,
"assistantId": null,
"directory": false,
"indexed": false,
"indexingStartedAt": null,
"indexingErroredAt": null,
"indexingErrorMessage": null,
"indexedAt": null,
"parentId": null,
"libraryId": null,
"remoteId": null,
"remoteUrl": null,
"size": null,
"modifiedAt": null,
"messageId": null,
"chatId": null,
"expiresAt": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"ownerId": null,
"templateId": "00000000-0000-4000-8000-000000000001",
"integrationId": null
}
Captured from the API examples test suite; ids and timestamps are normalized.
Renaming a TEMPLATE file

A pre-block (internals.checkRename) returns 403 if you try to set filename on a file whose role is TEMPLATE. Other roles rename freely.


DELETE /api/templates/{id}/files/{file}

Delete a file from the Template.

Authentication: Required

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

Response:

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

Delete a template fileDELETE /api/templates/00000000-0000-4000-8000-000000000001/files/00000000-0000-4000-8000-000000000002
Removes the file and returns 200 with an EMPTY body (Informer convention), NOT 204. Deleting the file that is currently the primary also nullifies the template’s templateFileId.
Response · 200 · no body
Captured from the API examples test suite; ids and timestamps are normalized.
Deleting the primary file

Deleting the file that is currently the primary TEMPLATE file also nullifies the Template's templateFileId.


GET /api/templates/{id}/files/{file}/contents

Get the byte contents of a Template file.

Authentication: Required

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

Response:

The file bytes with an appropriate Content-Type header.

Not shown

This endpoint streams raw file bytes rather than JSON, so it is not captured here.


PUT /api/templates/{id}/files/{file}/contents

Replace the byte contents of a Template file.

Authentication: Required

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

Request Body:

Raw file contents (text or binary, depending on the content type).

Response:

Confirms the contents were written.

Not shown

The request body is a raw byte stream, so this endpoint is not captured here.


GET /api/templates/{id}/template-file

Get the primary Template file's metadata.

Authentication: Required

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

Response:

The File whose id equals the Template's templateFileId (the primary TEMPLATE source), plus an inf:file-contents link.

Get the primary template fileGET /api/templates/00000000-0000-4000-8000-000000000001/template-file
Returns the File whose id equals the template’s templateFileId (the primary TEMPLATE source). db.lookup (200), with an `inf:file-contents` link.
Response · 200
{
"_links": {
"self": {
"href": "https://informer.example.com/api/templates/00000000-0000-4000-8000-000000000001/template-file"
},
"inf:file-contents": {
"href": "https://informer.example.com/api/templates/00000000-0000-4000-8000-000000000001/template-file/contents"
}
},
"extension": "njk",
"tenant": "acme",
"id": "00000000-0000-4000-8000-000000000002",
"filename": "invoice.njk",
"embedded": true,
"contentType": "text/html",
"lobId": "16400",
"role": "TEMPLATE",
"description": null,
"data": {},
"createdById": null,
"letterheadId": null,
"jobActionAttachmentId": null,
"jobActionId": null,
"oauthClientId": null,
"assistantId": null,
"directory": false,
"indexed": false,
"indexingStartedAt": null,
"indexingErroredAt": null,
"indexingErrorMessage": null,
"indexedAt": null,
"parentId": null,
"libraryId": null,
"remoteId": null,
"remoteUrl": null,
"size": "524288",
"modifiedAt": null,
"messageId": null,
"chatId": null,
"expiresAt": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"ownerId": null,
"templateId": "00000000-0000-4000-8000-000000000001",
"integrationId": null
}
Captured from the API examples test suite; ids and timestamps are normalized.

PUT /api/templates/{id}/template-file

Set the primary Template file.

Authentication: Required

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

Request Body:

This route has no Joi payload schema. It does not accept a { templateFileId } selector. Instead, it creates a new File from the request body (forced to role: 'TEMPLATE' and embedded: true) and repoints the Template's templateFileId at it.

FieldTypeDescription
filenamestringFilename for the new primary file
contentTypestringMIME type

Example Request:

{
"filename": "invoice-v2.njk",
"contentType": "text/html"
}

Response:

Responds 200 with the newly created File. The Template's templateFileId now points at it.

Set the primary template filePUT /api/templates/00000000-0000-4000-8000-000000000001/template-file
Despite the field table in older docs, this does NOT take a { templateFileId } body. It has no joi schema: it CREATES a new File from the request body (forced to role:’TEMPLATE’, embedded:true) and repoints the template’s templateFileId at it. Returns the newly-created File (200).
Request body
{
"filename": "invoice-v2.njk",
"contentType": "text/html"
}
Response · 200
{
"_links": {
"self": {
"href": "https://informer.example.com/api/templates/00000000-0000-4000-8000-000000000001/template-file"
},
"inf:file-contents": {
"href": "https://informer.example.com/api/templates/00000000-0000-4000-8000-000000000001/files/00000000-0000-4000-8000-000000000002/contents"
}
},
"extension": "njk",
"tenant": "acme",
"id": "00000000-0000-4000-8000-000000000002",
"directory": false,
"filename": "invoice-v2.njk",
"contentType": "text/html",
"templateId": "00000000-0000-4000-8000-000000000001",
"embedded": true,
"role": "TEMPLATE",
"updatedAt": "2026-01-15T16:20:00.000Z",
"createdAt": "2026-01-15T16:20:00.000Z",
"lobId": null,
"ownerId": null,
"data": {},
"createdById": null,
"letterheadId": null,
"jobActionAttachmentId": null,
"jobActionId": null,
"oauthClientId": null,
"description": null,
"assistantId": null,
"integrationId": null,
"parentId": null,
"libraryId": null,
"remoteId": null,
"remoteUrl": null,
"size": null,
"modifiedAt": null,
"indexed": false,
"indexingStartedAt": null,
"indexedAt": null,
"indexingErroredAt": null,
"indexingErrorMessage": null,
"messageId": null,
"chatId": null,
"expiresAt": null
}
Captured from the API examples test suite; ids and timestamps are normalized.