Skip to main content

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:

  1. If ValkyrAI is running, fetch the live rules endpoint:

    curl -sS -H "Accept: application/json" http://localhost:8080/v1/abi/rules | jq '.'
  2. If the endpoint is unavailable, read the repository fallback:

    docs/thorapi-openapi-rules.json
  3. 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.yaml
  • valkyrai/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/ or target/

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: additionalProperties with type: object or inline properties
  • Valid primitive maps: additionalProperties with type: string, integer, number, or boolean
  • 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, and anyOf branches
  • 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_private models may reference same-bundle app_private models.
  • app_private models may reference shared models.
  • shared models must not reference app_private models.
  • External or cross-app model references must declare x-valkyr-relationship-contract: shared on 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:

ExtensionTypeUse
x-thorapi-nonCrudbooleanMarks a command or non-CRUD operation.
x-thorapi-disable-statsbooleanPrevents stats endpoint generation.
x-thorapi-handler-classstringFully qualified handler class.
x-thorapi-handler-beanstringHandler bean name.
x-thorapi-handler-methodstringHandler method name.
x-thorapi-secureFieldbooleanMarks a field for encryption.
x-thorapi-dataFieldstring or objectPII, grouping, uniqueness, and indexing metadata.
x-thorapi-suppress-auditbooleanSuppresses audit field injection.
x-thorapi-hiddenbooleanMarks a property hidden/read-only in generated API surfaces.
x-thorapi-generateServicebooleanForces service generation.
x-thorapi-generateRepositorybooleanForces 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:

  • unique
  • fieldGroup
  • advanced
  • index
  • hidden

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: true when 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

  1. Read the latest rules from /v1/abi/rules or docs/thorapi-openapi-rules.json.

  2. Locate the source OpenAPI file or bundle fragment that owns the schema or path.

  3. Make the smallest source edit that improves the contract.

  4. Normalize complex shapes:

    • replace inline objects with named schemas
    • replace complex maps with named schemas, primitive maps, or scalar JSON strings
    • add scalar type to shared enum components, or move persisted enum values into object properties
  5. Check all names against reserved words and generated audit fields.

  6. Check every x-thorapi-* key and value type.

  7. Run generation:

    ./vaix generate
  8. For broader changes, run tests:

    ./vaix test
  9. 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: true or an explicit @SecureField(...) annotation.
  • Command endpoints use stable operationIds, named request/response schemas, and x-thorapi-nonCrud: true when appropriate.
  • ./vaix generate passes.
  • ./vaix test passes 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.