Summary
GET /api/openapi.json returns 500 {"error":"Unrecognized key: \"invoice_id\"","code":"INTERNAL"} whenever a table's meta.references declares a reference with apiSettable: false. Because sapporta endpoints list fetches /api/openapi.json, the CLI's endpoint discovery fails with the same error. Runtime table CRUD is unaffected — only OpenAPI / JSON-schema generation breaks.
Environment
@sapporta/server 0.3.0
zod 4.4.3
sapporta CLI 0.2.6
- Node 24, project scaffolded via
npx sapporta init
Reproduction
Define a sapportaTable whose meta marks a foreign-key reference as non-settable:
export const invoiceLines = sapportaTable({
drizzle: invoiceLinesTable,
meta: {
label: "Invoice Lines",
rowScope: "workspaceGlobal",
rowLabelColumns: ["description"],
references: {
invoice_id: { table: "invoices", column: "id", apiSettable: false },
product_id: { table: "products", column: "id" },
},
},
});
Boot the app, then:
GET /api/openapi.json -> 500 Unrecognized key: "invoice_id"
sapporta endpoints list -> Error: Unrecognized key: "invoice_id" (code INTERNAL)
Removing apiSettable: false from the reference makes /api/openapi.json return 200 (36 paths) and endpoints list work.
Root cause
OpenAPI generation converts the table's Zod schema with Zod v4 z.toJSONSchema, which lazily evaluates the .shape getter of an .omit(mask) result. The generated write schema already excludes the apiSettable: false FK column, and then .omit() is applied for that same key. In Zod v4 (unlike v3) .omit() throws Unrecognized key: "<k>" when a mask key is not present in the object's shape:
// zod/v4/core/util.js (get shape)
for (const key in mask) {
if (!(key in currDef.shape)) {
throw new Error(`Unrecognized key: "${key}"`);
}
...
}
Non-reference apiWritable: false columns do not trigger this (their key is still present when omitted); only apiSettable: false references do.
Stack (server)
Error: Unrecognized key: "invoice_id"
at get shape (zod/v4/core/util.js:376)
at Module.objectProcessor (zod/v4/core/json-schema-processors.js:280)
at process (zod/v4/core/to-json-schema.js:60)
... (arrayProcessor / objectProcessor recursion) ...
GET /api/openapi.json -> 500
Impact
Any project that uses apiSettable: false on a reference (a natural choice for server-managed FKs in master/detail tables) has a broken /api/openapi.json and non-functional sapporta endpoints / OpenAPI tooling.
Workaround
Remove apiSettable: false from reference metadata (which gives up generated-CRUD FK write-protection).
Suggested fix
Guard the omit in the schema-extraction path so it only omits keys actually present in the object's shape (or filter FK exclusions before building the omit mask), so it stays compatible with Zod v4's stricter .omit().
Summary
GET /api/openapi.jsonreturns500 {"error":"Unrecognized key: \"invoice_id\"","code":"INTERNAL"}whenever a table'smeta.referencesdeclares a reference withapiSettable: false. Becausesapporta endpoints listfetches/api/openapi.json, the CLI's endpoint discovery fails with the same error. Runtime table CRUD is unaffected — only OpenAPI / JSON-schema generation breaks.Environment
@sapporta/server0.3.0zod4.4.3sapportaCLI 0.2.6npx sapporta initReproduction
Define a
sapportaTablewhose meta marks a foreign-key reference as non-settable:Boot the app, then:
Removing
apiSettable: falsefrom the reference makes/api/openapi.jsonreturn 200 (36 paths) andendpoints listwork.Root cause
OpenAPI generation converts the table's Zod schema with Zod v4
z.toJSONSchema, which lazily evaluates the.shapegetter of an.omit(mask)result. The generated write schema already excludes theapiSettable: falseFK column, and then.omit()is applied for that same key. In Zod v4 (unlike v3).omit()throwsUnrecognized key: "<k>"when a mask key is not present in the object's shape:Non-reference
apiWritable: falsecolumns do not trigger this (their key is still present when omitted); onlyapiSettable: falsereferences do.Stack (server)
Impact
Any project that uses
apiSettable: falseon a reference (a natural choice for server-managed FKs in master/detail tables) has a broken/api/openapi.jsonand non-functionalsapporta endpoints/ OpenAPI tooling.Workaround
Remove
apiSettable: falsefrom reference metadata (which gives up generated-CRUD FK write-protection).Suggested fix
Guard the omit in the schema-extraction path so it only omits keys actually present in the object's shape (or filter FK exclusions before building the omit mask), so it stays compatible with Zod v4's stricter
.omit().