Resource Schema Structure

kind: <string>                    # Required
apiVersion: <string>              # Required
metadata:                         # Optional
  description: <string>
  version: <string>
  version_in_path: <boolean>
methods:                          # Required
  resource: [<http-methods>]
  instance: [<http-methods>]
descriptions:                     # Optional
  resource: {<method>: <string>}
  instance: {<method>: <string>}
schema:                           # Required
  type: array                     # Must be "array"
  key:                            # Required for instance methods
    name: <string>
    description: <string>
    schema: <json-schema>
  query_params: [...]             # Optional
  items:                          # Required
    type: object                  # Usually "object"
    properties: {...}
    required: [...]
default_query_params: [...]       # Optional
security:                         # Optional
  scheme: {...}
  resource: [<methods>]
  instance: [<methods>]
asyncapi:                         # Optional
  publish: <boolean>
  subscribe: <boolean>
validations:                      # Optional
  rules: [...]

Top-Level Fields

kind (Required)

Type: String Description: Resource name used in API paths. By convention this should be the plural noun — it is used directly as the URL path segment. URL Format: /{kind} and /{kind}/{id}

Rules:

  • ✅ Lowercase
  • ✅ Plural nouns recommended
  • ✅ Alphanumeric + hyphens/underscores
  • ❌ No spaces or special characters

Examples:

kind: users              # → /users, /users/{user_id}
kind: blog-posts         # → /blog-posts, /blog-posts/{post_id}
kind: product_categories # → /product_categories, /product_categories/{category_id}

plural (Optional)

Type: String Description: Explicit plural form of the resource kind, used as the URL path when kind cannot be the plural noun itself (e.g. a composite word whose plural Firestone cannot infer).

Example:

kind: subclass
plural: subclasses   # → /subclasses, /subclasses/{id}

When omitted, kind is used as the URL path verbatim.


singular (Optional)

Type: String Description: Explicit singular form of the resource kind, used when deriving model type names (e.g. CreateProxy). Only needed when the automatic singularization produces the wrong result.

Example:

kind: proxies
singular: proxy      # model types: Proxy, CreateProxy, UpdateProxy

When omitted, the singular is inferred from kind (or plural) automatically.


apiVersion (Required)

Type: String Description: Resource definition version Current: Always "v1"

apiVersion: v1  # Required, always "v1" currently

metadata (Optional)

Type: Object Description: Resource-level metadata

Fields:

FieldTypeRequiredDescription
descriptionstringNoHuman-readable description
versionstringNoSemantic version (e.g., "1.0.0")
version_in_pathbooleanNoInclude version in URL path

Example:

metadata:
  description: User account management
  version: "2.1.0"
  version_in_path: false  # → /users (not /v2.1.0/users)

methods (Required)

Type: Object Description: HTTP methods to expose

Structure:

methods:
  resource: [<methods>]   # Collection endpoint: /{kind}
  instance: [<methods>]   # Instance endpoint: /{kind}/{id}

Valid HTTP Methods:

  • get - Retrieve resources
  • post - Create resource
  • put - Replace resource
  • patch - Update resource
  • delete - Delete resource

Examples:

# Full CRUD
methods:
  resource: [get, post]
  instance: [get, put, delete]

# Read-only
methods:
  resource: [get]
  instance: [get]

# Create-only
methods:
  resource: [post]
  instance: []

# Custom operations
methods:
  resource: [get, post]
  instance: [get, patch]  # PATCH instead of PUT

descriptions (Optional)

Type: Object Description: Human-readable operation descriptions

Structure:

descriptions:
  resource:
    get: "Description for GET /{kind}"
    post: "Description for POST /{kind}"
  instance:
    get: "Description for GET /{kind}/{id}"
    put: "Description for PUT /{kind}/{id}"
    delete: "Description for DELETE /{kind}/{id}"

Example:

descriptions:
  resource:
    get: List all users with optional filtering
    post: Create a new user account
  instance:
    get: Retrieve detailed user information
    put: Update user account
    delete: Permanently delete user account

schema (Required)

Type: Object Description: JSON Schema definition for the resource

schema.type (Required)

Must be: "array"

schema:
  type: array  # Always "array"

schema.key (Required for instance methods)

Type: Object Description: Identifier field for instance endpoints

Fields:

FieldTypeRequiredDescription
namestring✅ YesKey field name
descriptionstringNoKey description
schemaobject✅ YesJSON Schema for key

Examples:

# UUID key
key:
  name: user_id
  description: Unique user identifier
  schema:
    type: string
    format: uuid

# Integer key
key:
  name: id
  schema:
    type: integer
    minimum: 1

# String slug
key:
  name: slug
  schema:
    type: string
    pattern: '^[a-z0-9-]+$'
    minLength: 3
    maxLength: 50

schema.items (Required)

Type: Object Description: JSON Schema for resource objects

Common Structure:

items:
  type: object
  properties:
    field_name:
      type: <type>
      description: <description>
      # ... validation rules
  required: [list, of, required, fields]

Example:

items:
  type: object
  properties:
    name:
      type: string
      minLength: 1
      maxLength: 100
      description: User's full name

    email:
      type: string
      format: email
      maxLength: 254
      description: Email address

    age:
      type: integer
      minimum: 0
      maximum: 150
      description: User's age

    is_active:
      type: boolean
      default: true
      description: Account status

  required: [name, email]

schema.query_params (Optional)

Type: Array of objects Description: Query parameters for filtering/pagination

Structure:

query_params:
  - name: <param_name>
    description: <description>
    required: <boolean>
    schema: <json-schema>
    methods: [<http-methods>]

Example:

query_params:
  - name: status
    description: Filter by status
    required: false
    schema:
      type: string
      enum: [active, inactive, pending]
    methods: [get]

  - name: limit
    description: Number of results
    required: false
    schema:
      type: integer
      minimum: 1
      maximum: 100
      default: 20
    methods: [get]

  - name: offset
    description: Pagination offset
    required: false
    schema:
      type: integer
      minimum: 0
      default: 0
    methods: [get]

default_query_params (Optional)

Type: Array of objects Description: Query parameters added to all GET requests

Example:

default_query_params:
  - name: limit
    schema:
      type: integer
      default: 20

  - name: offset
    schema:
      type: integer
      default: 0

security (Optional)

Type: Object Description: Security scheme definitions and requirements

Structure:

security:
  scheme:
    <scheme_name>:
      type: <type>
      # ... scheme-specific fields
  resource: [<methods>]   # Methods requiring auth on /{kind}
  instance: [<methods>]   # Methods requiring auth on /{kind}/{id}

Security Scheme Types:

HTTP Bearer (JWT)

security:
  scheme:
    bearer_auth:
      type: http
      scheme: bearer
      bearerFormat: JWT
  resource: [post]
  instance: [put, delete]

API Key

security:
  scheme:
    api_key:
      type: apiKey
      in: header
      name: X-API-Key
  resource: [get, post]
  instance: [get, put, delete]

OAuth2

security:
  scheme:
    oauth2:
      type: oauth2
      flows:
        authorizationCode:
          authorizationUrl: https://auth.example.com/oauth/authorize
          tokenUrl: https://auth.example.com/oauth/token
          scopes:
            read: Read access
            write: Write access
  resource: [get, post]
  instance: [get, put, delete]

Multiple Schemes

security:
  scheme:
    bearer_auth:
      type: http
      scheme: bearer
    api_key:
      type: apiKey
      in: header
      name: X-API-Key
  resource: [post]  # Either bearer OR api_key required
  instance: [put, delete]

asyncapi (Optional)

Type: Object Description: AsyncAPI-specific configuration

Fields:

FieldTypeDefaultDescription
publishbooleanfalseEnable publish operations
subscribebooleanfalseEnable subscribe operations

Example:

asyncapi:
  publish: true    # Clients can publish to this resource
  subscribe: true  # Clients can subscribe to updates

validations (Optional)

Server side business rules. Absent, nothing changes anywhere.

# A relationship, declared on the property that holds it
schema:
  items:
    properties:
      person:
        schema:
          $ref: "person.yaml#/schema"
        references:
          kind: persons              # Required: the resource referenced
          key: first_name            # Optional: defaults to the target's schema key
          value: person.first_name   # Optional: defaults to the property name
          on_missing: reject         # Optional: reject (default) or ignore
          immutable: false           # Optional: can the reference be repointed

# Anything conditional, as a CEL expression
validations:
  rules:
    - name: postal_code_matches_city   # Required, unique within the resource
      description: <string>            # Optional
      methods: [post, put]             # Optional: defaults to [post, put, patch]
      refs:                            # Optional: the lookups the rule needs
        postal:
          kind: postal_codes
          key: name                    # A path into the target, not a column name
          value: postal_code
          optional: false              # Default: the rule fails if this finds nothing
      expr: refs.postal.city == self.city   # Required
      error:                           # Optional
        status: 422                    # Defaults to 422
        message: "{self.postal_code} is not in {self.city}."
      examples:                        # Optional, generates tests
        - self: {postal_code: K1A, city: Ottawa}
          refs: {postal: {city: Ottawa}}
          expect: pass

An expression sees self (the resource as it will be), old (as it currently is), refs (what the resolver found) and ctx (whatever the caller passes). Use methods, not on: YAML parses a bare on: key as the boolean true.

Lookups are required by default, so a rule whose lookup finds nothing fails rather than blowing up in the expression. Mark a lookup optional: true for a rule that is about absence, and guard it with has().

See Validations for the full reference.


JSON Schema Types

Common JSON Schema types used in schema.items.properties:

String

field_name:
  type: string
  minLength: 1           # Optional
  maxLength: 100         # Optional
  pattern: '^[a-z]+$'    # Optional regex
  format: email          # Optional format
  enum: [val1, val2]     # Optional enumeration
  default: "value"       # Optional default

Common Formats:

  • email - Email address
  • uri - URI/URL
  • uuid - UUID
  • date - ISO 8601 date (YYYY-MM-DD)
  • date-time - ISO 8601 datetime
  • ipv4 - IPv4 address
  • ipv6 - IPv6 address

Number / Integer

field_name:
  type: integer       # or "number" for floats
  minimum: 0          # Optional
  maximum: 100        # Optional
  multipleOf: 5       # Optional
  exclusiveMinimum: 0 # Optional
  default: 10         # Optional

Boolean

field_name:
  type: boolean
  default: false  # Optional

Array

field_name:
  type: array
  items:
    type: string       # or object, number, etc.
  minItems: 0          # Optional
  maxItems: 10         # Optional
  uniqueItems: true    # Optional

Object

field_name:
  type: object
  properties:
    nested_field:
      type: string
  required: [nested_field]
  additionalProperties: false  # Optional

Null / Optional

# Nullable field
field_name:
  type: [string, "null"]

# Optional field (not in required array)
properties:
  optional_field:
    type: string
required: []  # optional_field not listed

Validation Keywords

Common JSON Schema validation keywords:

KeywordTypesDescriptionExample
minLengthstringMinimum lengthminLength: 1
maxLengthstringMaximum lengthmaxLength: 100
patternstringRegex patternpattern: '^[A-Z]'
formatstringString formatformat: email
enumanyFixed valuesenum: [a, b, c]
minimumnumberMinimum valueminimum: 0
maximumnumberMaximum valuemaximum: 100
exclusiveMinimumnumberExclusive minexclusiveMinimum: 0
exclusiveMaximumnumberExclusive maxexclusiveMaximum: 100
multipleOfnumberMultiple ofmultipleOf: 10
minItemsarrayMin array lengthminItems: 1
maxItemsarrayMax array lengthmaxItems: 10
uniqueItemsarrayUnique itemsuniqueItems: true
requiredobjectRequired fieldsrequired: [name]
additionalPropertiesobjectAllow extra propsadditionalProperties: false

Complete Example

kind: users
apiVersion: v1

metadata:
  description: User account management API
  version: "1.0.0"
  version_in_path: false

methods:
  resource: [get, post]
  instance: [get, put, delete]

descriptions:
  resource:
    get: List all users with optional filtering
    post: Create a new user account
  instance:
    get: Get user details
    put: Update user account
    delete: Delete user account

schema:
  type: array

  key:
    name: user_id
    description: Unique user identifier
    schema:
      type: string
      format: uuid

  query_params:
    - name: status
      description: Filter by account status
      required: false
      schema:
        type: string
        enum: [active, inactive, pending]
      methods: [get]

    - name: limit
      description: Number of results to return
      required: false
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 20
      methods: [get]

  items:
    type: object
    properties:
      name:
        type: string
        minLength: 1
        maxLength: 100
        description: User's full name

      email:
        type: string
        format: email
        maxLength: 254
        description: Email address

      status:
        type: string
        enum: [active, inactive, pending]
        default: pending
        description: Account status

      age:
        type: integer
        minimum: 0
        maximum: 150
        description: User's age in years

      tags:
        type: array
        items:
          type: string
          minLength: 1
          maxLength: 50
        maxItems: 10
        uniqueItems: true
        description: User tags

    required: [name, email, status]

security:
  scheme:
    bearer_auth:
      type: http
      scheme: bearer
      bearerFormat: JWT
  resource: [post]
  instance: [put, delete]

asyncapi:
  publish: false
  subscribe: true

Common Patterns

UUID Primary Key

key:
  name: id
  schema:
    type: string
    format: uuid

Integer Auto-Increment Key

key:
  name: id
  schema:
    type: integer
    minimum: 1

Slug/Handle Key

key:
  name: slug
  schema:
    type: string
    pattern: '^[a-z0-9-]+$'
    minLength: 3
    maxLength: 50

Timestamps

created_at:
  type: string
  format: date-time
  description: Creation timestamp

updated_at:
  type: string
  format: date-time
  description: Last update timestamp

Nested Address

address:
  type: object
  properties:
    street: {type: string}
    city: {type: string}
    state: {type: string, pattern: '^[A-Z]{2}$'}
    postal_code: {type: string, pattern: '^\d{5}(-\d{4})?$'}
  required: [street, city, postal_code]

Enum with Description

status:
  type: string
  enum: [draft, published, archived]
  description: |
    Publication status:
    - draft: Not yet published
    - published: Publicly visible
    - archived: No longer active

See Also