Skip to main content

AI Instructions

AI instructions are reusable prompt-text presets, each addressed by a tenant-unique slug. Assistants and other AI surfaces splice an instruction's content in via a ${slug} reference, so a single preset can carry house voice, citation rules, or tone guidance into many AI conversations without copying the text.

Every instruction route is gated by the AI module. The route lifecycle runs ai.verify first, which asserts both that the tenant's AI master switch is on and that the caller carries the ai credential. When AI is off the route answers 400 ("AI is not enabled on the system"); when AI is on but the caller lacks AI access it answers 403.

AI must be enabled

All five routes below require AI to be enabled on the tenant. The read routes (GET list and single) add no further permission check, so any AI-enabled caller may read instructions within the tenant. The write routes layer per-action permissions on top, described in each section.

An instruction has these properties:

FieldTypeDescription
idstring (UUID)Instruction id
slugstringTenant-unique URL-safe name used in ${slug} references
descriptionstring | nullOptional human-readable description
contentstringThe prompt text spliced into AI surfaces
ownerIdstringUsername of the owner; defaults to the creator
createdAt / updatedAtdateTimestamps

The {id} path segment accepts either a UUID or a slug: AiInstruction.findById resolves by UUID when the value looks like one and falls back to slug otherwise.

GET /api/ai-instructions

List the tenant's AI instructions.

Authentication: AI enabled (ai.verify); no permission check

Response:

A HAL collection (rel inf:ai-instructions) with rows embedded under _embedded["inf:ai-instruction"]. A postQuery hook attaches a permissions object (req.permissions('aiInstruction')) to the collection so the client can render per-action affordances.

List AI instructionsGET /api/ai-instructions
GET /api/ai-instructions is a HAL collection (rel inf:ai-instructions) of the tenant’s instruction presets, rows embedded under inf:ai-instruction. It has no permission pre — any AI-enabled caller may read it (the route’s only gate is ai.verify). A postQuery hook attaches a `permissions` object (req.permissions('aiInstruction')) so the client can render per-action affordances.
Response · 200
{
"_links": {
"self": {
"href": "https://informer.example.com/api/ai-instructions"
}
},
"start": 0,
"count": 3,
"total": 3,
"permissions": {
"edit": false,
"delete": false,
"write": false,
"changeOwner": true,
"copy": true
},
"_embedded": {
"inf:ai-instruction": [
{
"_links": {
"self": {
"href": "https://informer.example.com/api/ai-instructions/00000000-0000-4000-8000-000000000001"
}
},
"permissions": {},
"id": "00000000-0000-4000-8000-000000000001",
"slug": "order-desk-tone",
"description": "House voice for Order Desk replies",
"ownerId": "admin",
"content": "You are the Order Desk assistant. Answer concisely, cite the order number, and never speculate about delivery dates you cannot confirm.",
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme"
},
{
"_links": {
"self": {
"href": "https://informer.example.com/api/ai-instructions/00000000-0000-4000-8000-000000000002"
}
},
"permissions": {},
"id": "00000000-0000-4000-8000-000000000002",
"slug": "cite-sources",
"description": "Require source citations in answers",
"ownerId": "admin",
"content": "When you state a fact drawn from a dataset, cite the dataset and field it came from.",
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme"
},
{
"_links": {
"self": {
"href": "https://informer.example.com/api/ai-instructions/00000000-0000-4000-8000-000000000003"
}
},
"permissions": {},
"id": "00000000-0000-4000-8000-000000000003",
"slug": "scratch-instruction",
"description": null,
"ownerId": "admin",
"content": "Temporary instruction used only to demonstrate deletion.",
"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.

POST /api/ai-instructions

Create an AI instruction.

Authentication: AI enabled (ai.verify) + permission.aiInstructions.create

The create permission is satisfied by any caller carrying the ai credential. It is not Superuser-gated.

Request Body:

FieldTypeRequiredDescription
slugstringYesTenant-unique slug for ${slug} references
descriptionstringNoDescription. A blank string or null is dropped
contentstringYesThe prompt text

The payload is stripUnknown, so unrecognized fields are dropped. The new row's ownerId defaults to the caller's username.

Example Request:

{
"slug": "quarterly-report-voice",
"description": "Tone and structure for quarterly report narratives",
"content": "Write the quarterly summary in plain business English. Lead with the headline number, then explain the drivers in at most three sentences."
}

Response:

Responds 201 Created with the instruction and a Location header. The Location points at the instruction by slug, not UUID.

Create an AI instructionPOST /api/ai-instructions
POST /api/ai-instructions requires AI access (ai.verify) and permission.aiInstructions.create, which is satisfied by any caller carrying the `ai` credential (NOT superuser-gated). Payload is stripUnknown: { slug (required), description?, content (required) }. The new row’s ownerId defaults to the caller’s username. Responds 201 with a Location header pointing at the instruction by SLUG (not uuid). slug must be unique per tenant; a missing slug or content 400s at validation.
Request body
{
"slug": "quarterly-report-voice",
"description": "Tone and structure for quarterly report narratives",
"content": "Write the quarterly summary in plain business English. Lead with the headline number, then explain the drivers in at most three sentences."
}
Response · 201
{
"_links": {
"self": {
"href": "https://informer.example.com/api/ai-instructions/quarterly-report-voice"
}
},
"permissions": {},
"id": "00000000-0000-4000-8000-000000000001",
"tenant": "acme",
"slug": "quarterly-report-voice",
"description": "Tone and structure for quarterly report narratives",
"content": "Write the quarterly summary in plain business English. Lead with the headline number, then explain the drivers in at most three sentences.",
"updatedAt": "2026-01-15T16:20:00.000Z",
"createdAt": "2026-01-15T16:20:00.000Z",
"ownerId": "admin"
}
Captured from the API examples test suite; ids and timestamps are normalized.
Slug must be unique per tenant

A slug is unique within the tenant. A missing slug or content answers 400 at validation.


GET /api/ai-instructions/{id}

Retrieve a single AI instruction.

Authentication: AI enabled (ai.verify); no permission check

Path Parameters:

ParameterTypeDescription
idstringInstruction UUID or slug

Response:

The single instruction (HAL rel inf:ai-instruction). There is no per-row permission check on read; the only gate is ai.verify.

Get an AI instructionGET /api/ai-instructions/00000000-0000-4000-8000-000000000001
GET /api/ai-instructions/{id} returns the single instruction (HAL rel inf:ai-instruction). The {id} resolves by uuid OR by slug (AiInstruction.findById branches on isUuid). Its only gate is ai.verify; there is no per-row permission check on read.
Response · 200
{
"_links": {
"self": {
"href": "https://informer.example.com/api/ai-instructions/00000000-0000-4000-8000-000000000001"
}
},
"permissions": {},
"id": "00000000-0000-4000-8000-000000000001",
"slug": "order-desk-tone",
"description": "House voice for Order Desk replies",
"ownerId": "admin",
"content": "You are the Order Desk assistant. Answer concisely, cite the order number, and never speculate about delivery dates you cannot confirm.",
"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/ai-instructions/{id}

Update an AI instruction.

Authentication: AI enabled (ai.verify) + permission.aiInstruction.edit

The edit permission is granted to the owner or a Superuser, both of which also need AI access.

Path Parameters:

ParameterTypeDescription
idstringInstruction UUID or slug

Request Body:

FieldTypeRequiredDescription
descriptionstringNoDescription. A blank string or null is dropped
contentstringYesThe replacement prompt text

The payload is stripUnknown. content is required on every update because it is a full replacement of the content, not a patch.

Slug is not editable

slug is stripped from the payload, so a PUT that tries to rename the slug silently keeps the original.

Example Request:

{
"description": "House voice for Order Desk replies (refined)",
"content": "You are the Order Desk assistant. Answer concisely, cite the order number, and escalate to a human when a refund is requested."
}

Response:

Responds 200 with the updated instruction.

Update an AI instructionPUT /api/ai-instructions/00000000-0000-4000-8000-000000000001
PUT /api/ai-instructions/{id} requires ai.verify and permission.aiInstruction.edit (the owner OR a superuser, both also needing AI access). Payload is stripUnknown: { description?, content (required) }. `slug` is NOT editable — it is stripped, so attempting to rename the slug silently keeps the original. Returns the updated instruction (200).
Request body
{
"description": "House voice for Order Desk replies (refined)",
"content": "You are the Order Desk assistant. Answer concisely, cite the order number, and escalate to a human when a refund is requested."
}
Response · 200
{
"_links": {
"self": {
"href": "https://informer.example.com/api/ai-instructions/00000000-0000-4000-8000-000000000001"
}
},
"permissions": {},
"id": "00000000-0000-4000-8000-000000000001",
"slug": "order-desk-tone",
"description": "House voice for Order Desk replies (refined)",
"ownerId": "admin",
"content": "You are the Order Desk assistant. Answer concisely, cite the order number, and escalate to a human when a refund is requested.",
"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.

DELETE /api/ai-instructions/{id}

Delete an AI instruction.

Authentication: AI enabled (ai.verify) + permission.aiInstruction.delete

The delete permission is granted to the owner or a Superuser. Unlike create and edit, the delete permission driver does not itself require the ai credential, but the route's ai.verify pre still does, so AI must be enabled.

Path Parameters:

ParameterTypeDescription
idstringInstruction UUID or slug

Response:

Responds 200 with an empty body (Informer's db.remove convention), not 204.

Delete an AI instructionDELETE /api/ai-instructions/00000000-0000-4000-8000-000000000001
DELETE /api/ai-instructions/{id} requires ai.verify (AI enabled) and permission.aiInstruction.delete (the owner OR a superuser). Unlike create/edit, the delete permission driver does NOT itself require the `ai` credential, but the route’s ai.verify pre still does. Returns 200 with an EMPTY body (Informer db.remove convention), NOT 204.
Response · 200 · no body
Captured from the API examples test suite; ids and timestamps are normalized.
Permanent deletion

Deleting an instruction is permanent. Any AI surface that referenced it via ${slug} will no longer resolve that reference.