Design notes
These are choices we made on purpose, with reasons. Some look weird at first. If something in the API surprises you and you find yourself thinking "wait, why would they do that?" — chances are it's listed here with the explanation. If your eyebrow is raising about something not listed, that might be a real bug. File it.
HEAD method not declared in the OpenAPI spec
HEAD is supported on every resource per RFC 7231 §4.3.2 — every GET endpoint also accepts HEAD requests, returning identical headers and an empty body.
We don't declare head: operations in the OpenAPI spec. Declaring HEAD on 22 paths would balloon the spec for nearly zero codegen benefit — major typed generators (openapi-typescript, openapi-generator-cli python, oapi-codegen) don't expose HEAD on generated client classes regardless of whether it's declared.
For runtime discoverability, the Allow header on 405 Method Not Allowed responses lists every supported method including HEAD. See HTTP method coverage → HEAD wherever GET is declared for usage and the runtime discovery pattern.
Tag schema uses three single-value enums for the discriminator
The Tag polymorphic schema uses three single-value enum classes — RfidTagRequest.tag_type: enum: [rfid], BleTagRequest.tag_type: enum: [ble], BarcodeTagRequest.tag_type: enum: [barcode] — rather than a single shared discriminator field.
This is an OpenAPI 3.0 workaround for the allOf-with-siblings limitation: tag_type with allOf references plus sibling JSON Schema keywords breaks Pydantic-strict generators. The current three-enum pattern is the workaround that keeps the discriminator legible across all generators.
How different generators surface this:
openapi-typescript: clean discriminated union.openapi-generator-clipython target: one model per variant — usable.datamodel-codegen: produces three separate enum classes (TagType,TagType2,TagType4), which reads confusingly — generator-specific quirk.
Nullable fields use OpenAPI 3.0 nullable: true
We're on OpenAPI 3.0.3. Nullable response fields use nullable: true rather than the OpenAPI 3.1 type-union syntax (type: ["string", "null"]).
Generator behavior varies:
- Verified-working:
[email protected](emitsstring | null) andopenapi-generator-clipython target (emitsOptional[StrictStr]). Both round-trip CRUD against null-bearing responses unmodified. - Known-broken:
[email protected]emits nullable fields as non-Optional required types. Pydantic validation fails on every nullable field that's actuallynull.
For integrators using datamodel-codegen, switch to one of the verified-working targets, or apply --use-annotated --use-union-operator flags with custom post-processing.
We'll migrate to OpenAPI 3.1 type-union syntax when the generator ecosystem stabilizes 3.1 support across all targets we care about.