ThorAPI OpenAPI Spec Agent Guide
Use this guide when an AI agent, developer, or automation workflow needs to enhance a ThorAPI/ValkyrAI OpenAPI spec while staying compatible with ThorAPI code generation.
Authoritative rules source
Before changing a spec, load the latest ThorAPI rules in this order:
-
If ValkyrAI is running, fetch the live rules endpoint:
curl -sS -H "Accept: application/json" http://localhost:8080/v1/abi/rules | jq '.' -
If the endpoint is unavailable, read the repository fallback:
docs/thorapi-openapi-rules.json -
Use the governance README for endpoint and ruleset notes:
docs/thorapi-openapi-rules.README.md
The checked-in fallback is ThorAPI OpenAPI Canonical Ruleset (v1), version 1.0.0.
Edit targets
Edit source inputs only:
valkyrai/src/main/resources/openapi/api.yamlvalkyrai/src/main/resources/openapi/api.hbs.yaml- included OpenAPI bundle fragments under
valkyrai/src/main/resources/openapi/ - ThorAPI platform templates/fragments under
thorapi/src/main/resources/openapi/when the platform source itself is the target
Do not edit derived specs or generated code:
valkyrai/src/main/resources/openapi/api-out.yaml- generated Java, TypeScript, controller, delegate, repository, or model files under
generated/ortarget/
Run ThorAPI through ./vaix; it assembles api.yaml + api.hbs.yaml, emits api-out.yaml, then drives code generation.
Absolute rules
No complex additionalProperties
Do not use free-form or complex additionalProperties in persisted component schemas.
- Invalid:
additionalProperties: true - Invalid:
additionalPropertieswithtype: objector inline properties - Valid primitive maps:
additionalPropertieswithtype: string,integer,number, orboolean - Prefer scalar JSON strings for map-like persisted fields when the shape is intentionally flexible.
No inline complex objects
Any type: object with properties must be moved to #/components/schemas/<Name> and referenced with $ref.
This applies to:
- object properties
- array item schemas
allOf,oneOf, andanyOfbranches- command request and response bodies
Typed shared scalar enum components
Shared enum components are supported when they declare a scalar type, usually type: string, and enum values.
ThorAPI marks typed top-level enum components as non-CRUD during enhancement so they generate as Java enums, not model beans, repositories, services, or database tables.
Bare enum-only schemas without a scalar type are invalid because they cannot be classified safely.
Persisted record schemas still need type: object; put enum values on a property or reference a typed shared enum component.
No ThorAPI-managed audit fields
Do not declare ThorAPI-managed fields in component schemas:
id
modified_date
created_date
owner_id
key_hash
last_modified_by_id
last_modified_date
last_accessed_by_id
last_accessed_date
trashed
Spec serialization code also filters camelCase variants such as createdDate, ownerId, keyHash, and lastModifiedDate.
Use x-thorapi-suppress-audit: true only when intentionally opting out of audit injection and the persistence behavior is understood.
Tenant bundle scope
For tenant-scoped application bundles, every persisted component schema must declare its generated table scope in the source OpenAPI:
components:
schemas:
Teeth:
type: object
x-valkyr-scope: app_private
properties:
toothNumber:
type: integer
ClinicLookup:
type: object
x-valkyr-scope: shared
properties:
label:
type: string
Allowed values:
app_private: domain data owned by one generated application.shared: explicitly shared tenant business data.
Optional table-name overrides must be declared on the schema with
x-valkyr-table-name. In org_schema placement, app-private tables are
manifested as <appid8>_<table_name> and shared business tables as
shared_<table_name>. In app_schema, app_database, and byoc_database
placements, app-private tables keep their generated base table name because the
physical schema/database boundary already provides app isolation.
Tenant bundle registration fails closed when scope is missing, derived table names collide, a generated table name exceeds the identifier limit, or a model tries to expose a Valkyr platform-owned table.
Relationship scope is also validated from component $ref fields:
app_privatemodels may reference same-bundleapp_privatemodels.app_privatemodels may referencesharedmodels.sharedmodels must not referenceapp_privatemodels.- External or cross-app model references must declare
x-valkyr-relationship-contract: sharedon the relationship field.
Example:
components:
schemas:
Teeth:
type: object
x-valkyr-scope: app_private
properties:
remoteAppointment:
$ref: '#/components/schemas/RemoteAppointment'
x-valkyr-relationship-contract: shared
Avoid reserved identifiers
Do not use reserved words or URL-special characters as schema or property names.
Current reserved groups include:
meta
class
%
?
*
@
!
(
)
String
Float
Integer
Object
Long
Array
Boolean
UNION
JOIN
SELECT
INSERT
UPDATE
DELETE
ApiUtil
MediaType
Mono
Content
Rename risky concepts, for example Content to ContentData or Mono to MonoResource.
Use documented ThorAPI extensions only
Use only documented x-thorapi-* extensions with the right value types:
| Extension | Type | Use |
|---|---|---|
x-thorapi-nonCrud | boolean | Marks a command or non-CRUD operation. |
x-thorapi-disable-stats | boolean | Prevents stats endpoint generation. |
x-thorapi-handler-class | string | Fully qualified handler class. |
x-thorapi-handler-bean | string | Handler bean name. |
x-thorapi-handler-method | string | Handler method name. |
x-thorapi-secureField | boolean | Marks a field for encryption. |
x-thorapi-dataField | string or object | PII, grouping, uniqueness, and indexing metadata. |
x-thorapi-suppress-audit | boolean | Suppresses audit field injection. |
x-thorapi-hidden | boolean | Marks a property hidden/read-only in generated API surfaces. |
x-thorapi-generateService | boolean | Forces service generation. |
x-thorapi-generateRepository | boolean | Forces repository generation. |
Mark sensitive fields
Use x-thorapi-secureField: true for encrypted fields.
For hashed password-style fields, use x-field-extra-annotation with @SecureField(...).
Builds and runtime that depend on secure fields must provide THORAPI_SECRET_KEY. Losing or changing it without migration makes encrypted data unrecoverable.
Keep x-thorapi-dataField parseable
String form must be comma-separated key=value pairs. Object form is preferred for clarity.
Allowed keys:
uniquefieldGroupadvancedindexhidden
Boolean keys must use boolean values. fieldGroup must be a string.
Keep operations stable
Command endpoints should use:
- stable
operationId - named request schema
- named response schema
x-thorapi-nonCrud: truewhen the route is not a standard generated CRUD surface
Good patterns
Named object reference:
components:
schemas:
CustomerProfile:
type: object
properties:
mailingAddress:
$ref: '#/components/schemas/MailingAddress'
MailingAddress:
type: object
properties:
street:
type: string
city:
type: string
Primitive map:
metadata:
type: object
additionalProperties:
type: string
Scalar JSON string for flexible persisted data:
environmentVariables:
type: string
description: JSON object encoded as a string for ThorAPI-safe persistence.
Secure and indexed field:
email:
type: string
format: email
x-thorapi-secureField: true
x-thorapi-dataField:
unique: true
fieldGroup: personal-details
index: true
Non-CRUD command endpoint:
paths:
/trust/keys/{keyId}/rotate:
post:
operationId: rotateTrustKey
x-thorapi-nonCrud: true
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/RotateTrustKeyRequest'
responses:
'200':
description: Trust key rotated.
content:
application/json:
schema:
$ref: '#/components/schemas/RotateTrustKeyResponse'
Agent workflow
-
Read the latest rules from
/v1/abi/rulesordocs/thorapi-openapi-rules.json. -
Locate the source OpenAPI file or bundle fragment that owns the schema or path.
-
Make the smallest source edit that improves the contract.
-
Normalize complex shapes:
- replace inline objects with named schemas
- replace complex maps with named schemas, primitive maps, or scalar JSON strings
- add scalar
typeto shared enum components, or move persisted enum values into object properties
-
Check all names against reserved words and generated audit fields.
-
Check every
x-thorapi-*key and value type. -
Run generation:
./vaix generate -
For broader changes, run tests:
./vaix test -
If canonical generator inputs were intentionally changed, update and validate the generator input manifest:
./scripts/update-generator-inputs-manifest.sh
./scripts/validate-generator-inputs.sh
Pre-commit checklist
- I edited source OpenAPI inputs or bundle fragments, not generated outputs.
- The latest ThorAPI rules were checked.
- No persisted component schema uses free-form or complex
additionalProperties. - No inline object schemas remain in properties, array items, or composition branches.
- Shared top-level enum schemas declare an explicit scalar
type; no bare enum-only schemas were added. - No ThorAPI-managed audit fields were added.
- No reserved schema or property names were added.
- All
x-thorapi-*extensions are documented and typed correctly. - Sensitive fields use
x-thorapi-secureField: trueor an explicit@SecureField(...)annotation. - Command endpoints use stable
operationIds, named request/response schemas, andx-thorapi-nonCrud: truewhen appropriate. -
./vaix generatepasses. -
./vaix testpasses or any skipped test scope is documented.
Delegation prompt
Enhance the ThorAPI OpenAPI spec using the latest rules. First fetch /v1/abi/rules if ValkyrAI is running; otherwise read docs/thorapi-openapi-rules.json. Edit only source OpenAPI inputs or bundle fragments, never api-out.yaml or generated code. Enforce ThorAPI rules: no complex additionalProperties, no inline complex objects, shared enum components must declare an explicit scalar type, no bare enum-only schemas, no generated audit fields, no reserved identifiers, documented x-thorapi-* extensions only, secureField for sensitive fields, and named request/response schemas for command endpoints. Run ./vaix generate before reporting completion, and run ./vaix test for broad contract changes.