Project & API contract
Tracks changes shared across all packages: OpenAPI schema (contract/openapi.yaml) and project-level documentation.
For package-specific changes see:
- node/CHANGELOG.md —
@eluvia/habarico(JS) - python/CHANGELOG.md —
habarico(Python) - ruby/CHANGELOG.md —
habarico(Ruby)
Format follows Keep a Changelog.
[Unreleased]
Changed
-
The
filterquery parameter is written down, on the list endpoint that has always taken it and on the new export endpoint: thefield=<JSON value>terms it carries as one percent-encoded value, thatfieldis a filter the resource schema advertises (lookup suffix included), that an unknown field and a malformed term are both refused with422, and that asearch=term is the fulltext query rather than a field filter. The export endpoint takesorder_byas well, so an export matches the list it was started from; both parameters were served by the backends and sent by the frontend without the contract naming them.info.version3.2.4 -> 3.3.0, shared with the export endpoint entry below; documentation only, no wire change. (HAB-532) -
The
order_byquery parameter of a list endpoint is described: its notation, what a client may put infield(asort_bythe resource schema advertises, which is not always the field's own name), and that a resource forcing its ordering ignores the parameter rather than refusing it. The422examples now match what the backends answer —Unknown field name.carries no field value — andValidationErrorResponsesays outright thatstatusandsourceare the contract whilemessageis human text that differs between the two backends.info.version3.2.3 → 3.2.4; both backends implement it. (HAB-915) -
The documentation pages moved next to the code they document:
docs/node/,docs/python/anddocs/ruby/are nownode/docs/,python/docs/,ruby/docs/, the paid pages moved tohabarico-enterpriseand the site itself - the Docusaurus project that was indocs/- is its own repository,eluvia/habarico/hab-docs, which clones the pages from both monorepos and publishes one site out of them. A page is therefore part of its package: it is released with it, read out of its release tag, and a change to a page alone bumps that package. Two root CI jobs connect the repositories -trigger-docspublishes after a stable tag,docs-checkbuilds the whole site on a merge request that touches*/docs/**.contract/schema.svgis committed beside its.drawiosource with the script that exports it, anddocs/CHANGELOG.mdis gone: a page is described by the entry of the change it documents. (HAB-873) -
The
targetquery parameter of the chunked-upload endpoints (uploads/initiate/complete/abort) no longer lists aSTORAGESalias among its plaintext values: an alias is accepted only inside a signed token, and a plaintext one now answers404. The values that remain are a signed token and the name of a target registered throughregister_upload_target.info.version3.2.1 → 3.2.2; the paid implementation ships inhabarico-enterprise. (HAB-910) -
LinkActionParameters.urldocuments which URLs a client honours: onlyhttp:,https:and relative ones, always opened withnoopener,noreferrer. The field is templated with record values, so any other scheme —javascript:,data:,blob:, and by exhaustionmailto:andtel:— is refused rather than opened.info.version3.2.1 → 3.2.2; documentation only, no wire change. (HAB-908) -
GlobalResourceSchema:aggregate_enabled,dashboard_enabledandglobal_fulltext_search_enabledare marked Premium. All three are contributed by the paidhabarico-enterprisepackage — neither free backend emits any of them — but the contract described them as ordinary resource-schema keys, which is what led the JavaScript library to declare the first two on its free resource types.info.version3.2.0 → 3.2.1; documentation only, no wire change. (HAB-857) -
BoxSchema.cardisnullableand carries three states instead of two:truealways renders the box as a card,falsenever does - not even for a box with a label or toolbar actions, whose label then becomes a plain section heading - andnull(or an absent key) leaves the decision to the frontend, which also gives a view's first box a card on a page surface.info.versionbumped to3.2.0. (HAB-827) -
YouTrack and GitLab URLs are gone from this repository's changelogs and READMEs: an issue is referenced by its key alone (
(HAB-123)), the way code comments already do it, and another repository is named rather than linked. These files are published — the packages are MIT and the repository is mirrored to GitHub — so an internal URL in them is a link no outside reader can follow. TheCLAUDE.mdfiles keep theirs: they are internal developer documentation, kept off the site. (HAB-923)
Added
- The aggregate endpoint describes
min/maxover a date.AggregateFieldSchemagains an optionalformat(FieldFormatType) derived from the model column -date,date-time,time,textoruuid, absent when the column type has none - so a client can tell a date from a text field that istype: stringtoo.AggregateBucket.valueis a number or a string:min/maxover adate/date-timefield returns an ISO 8601 value ("2026-01-01","2026-01-01T00:00:00Z"), whose fraction digits and offset notation differ between the backends.AggregateFunctionTypeandWidgetTypesay which fields the widgets offermin/maxfor - numbers and dates inaggregate/simple-aggregate, numbers only in the charts - and that the backend keeps accepting any whitelisted field, other types being an unsupported target.info.version3.3.4 -> 3.4.0; additive, the paid implementation ships inhabarico-enterprise. (HAB-1012) - Resource export endpoint
GET /{api_prefix}/{service_name}/admin/{resource_name}/exportreturning the resource rows in a downloadable file (CSV/XLSX), signalled to the frontend through theexportaction in the list viewlist_actionsslot. The endpoint is free but off by default — the backend switches it on per resource — and it serves the synchronous export only: exports above a fixed row limit are refused with HTTP 422, and answering them in the background is ahabarico-enterprisefeature.info.version3.2.4 -> 3.3.0. (HAB-532) max_sizeandacceptonFieldOptions- the file size and file types the frontend checks a picked file against before an upload starts, so a file the backend would refuse anyway never leaves the browser. Both are optional and additive: the frontend already read them (FieldOptions.maxSize/.acceptin@eluvia/habarico, consumed byUploadInput), the spec had never described them, andhabarico-enterprisenow fills them from a field's own declared upload validations. Neither replaces backend validation, and an absentacceptdoes not mean the server accepts everything - a restriction that cannot be expressed as an allowlist entry is omitted rather than approximated.info.version3.3.0 -> 3.3.1. (HAB-942)- Clarified that
max_size/acceptare no longer anhabarico-enterprise-only fill: the Python package now derives both itself (MaxFileSizeValidator,FileExtensionValidator/validate_image_file_extension) forAdminFileSerializer/AdminImageFileSerializer, withhabarico-enterpriseadditionally folding the project-wide upload size ceiling intomax_sizefor a field a chunked-upload provider serves. No wire change.info.version3.3.3 -> 3.3.4. (HAB-966) x-habarico-tier: enterpriseon every part of the contract that the paidhabarico-enterprisepackage provides: the six endpoints (uploads/initiate/complete/abort,search,{resource_name}/aggregateand its/schema), thesearchquery parameter, and the 25Aggregate*/Search*/Upload*schemas together with their responses and request bodies.DashboardPreference,WidgetConfig,WidgetLayoutandChartOptionsare marked too: the dashboard is a paid feature, and their note records that a free-only install still stores the payload underpreferences.dashboardbut has no widgets to render it. The rest of the preferences store stays free and unmarked. Each also opens its description with a Premium line, becausedocusaurus-plugin-openapi-docsignores vendor extensions it does not know. The theme renders operation and response descriptions, so the note is what a reader of the six endpoint pages sees; it does not surface schema, request-body or parameter descriptions, and there the marking travels in the downloadableopenapi.yamland the diagram rather than on the page.contract/schema.drawiomarks the same split: thesearch,uploadsandaggregations & dashboardgroups are labelled(premium), and so are the two paid classes that sit inside otherwise free groups —UploadFieldOptionsincoreandDashboardPreferenceinpreferences— so all 25 paid schemas are covered rather than only the ones a paid group happens to enclose. A legend explains the suffix and points atx-habarico-tier. The base64upload-imagesaction (UploadImagesActionParameters) stays free and is not marked.info.version3.0.0 -> 3.1.0. (HAB-846)- Root README documents the licence boundary of the monorepo:
node/,python/andruby/are MIT, the paidhabarico-enterprisepackages are commercial and proprietary, and the repository as a whole carries no single licence. The package table gained a licence column. (HAB-811) docs/AI.md— the procedure for opening the documentation website to AI tools: publishing anllms.txtindex over the Markdown mirror the site already generates, the candidates for a search assistant (the local index plus an Ask AI widget, Algolia DocSearch, Orama, kapa.ai, Inkeep, Typesense, Meilisearch) with the criteria to pick between them, and the options for MCP indexing of a public site whose source repository is private. Described, not implemented — the acceptance criterion of HAB-727 asks for the procedure. (HAB-765)- Documentation website scaffold in
docs/— Docusaurus 3 (classic preset, TypeScript, v4 future flags), Habarico brand theming modelled on the reactnative.dev documentation site, three docs instances (python/ruby/node) plus shared concept pages, thepremium: truefront matter convention for labelling paid sections, and a build/deploy pipeline todocs.habarico.com(static nginx in DOKS). The website is a versioned unit of the monorepo like the packages (released asdocs@X.Y.Z), so its own changes are recorded in docs/CHANGELOG.md from now on (the site moved to thehab-docsrepository in HAB-870, and the changelog with it). Content migration from the library READMEs follows separately. (HAB-761) ViewModeTypecomponent schema (redirect/dialog/sidepanel), replacing the enum that was inlined at three places (ActionDescriptorMenuViewParameters.mode,ActionViewParameters.mode,GlobalViewSchema.mode). Matches theViewModeTypealready defined in the Python implementation andViewModein the JS one. (HAB-705)FieldFormatTypegaineddecimalandtree-relation, andActionTypegainedai-assistant. All three are implemented in Python and/or JS but were absent from the contract, so a generated client could not express them.tree-relationwas in fact already referenced by the spec, in the description ofRelationFieldOptions.parent. (HAB-708)DynamicRenderOptions,SortableParametersandConfirmDialogPropertiescomponent schemas, replacing structurally identical objects that were inlined twice each (FieldOptions.dynamic_render/BoxOptions.dynamic_render,ListBoxParameters.sortable/ListViewParameters.sortable,ActionSchema.confirm_dialog/ResourceActionSchema.confirm_dialog). Names follow the existing implementations. No change to the wire format. (HAB-705)ChoicesOptions,ImageOptions,AiOptions,RelationOptions,PhoneOptions,UploadOptions,ListedOptions,ActionViewChoice,SearchResultObject,SavedQueryValue,TruncBucket,WidthBucketandResourceObjectListcomponent schemas, extracted from objects that were defined inline insideFieldOptions,ArrayBoxParameters,ActionViewParameters,ActionDescriptorMenuViewParameters,SearchResourceObjects,SavedQueryPreference,GroupByBucketand theResourceObjectListResponseresponse. Names follow the existing implementations where one already had a name. No change to the wire format. (HAB-705)discriminatoronGroupByBucket, selectingTruncBucket/WidthBucketby thetypeproperty. The union was already discriminated in practice and in the Python implementation, but the spec did not declare it. (HAB-705)ValidatorRulecomponent schema (and the matching box indoc/schema.drawio) plus theFieldOptions.validatorsarray that references it — the frontend validation rules enforced before submit ({type, value, message},typeone ofmax_length/min_length/max_value/min_value/regex). Backend validation is always performed regardless; at most one rule pertypeis expected. Implemented in Python and consumed by the JS client. (HAB-445)
Fixed
- The
targetquery parameter of the chunked-upload endpoints was described as never sending internal model/field identifiers in plaintext. It is signed, not encrypted: the payload is base64-encoded JSON that anyone holding the URL can decode, so whatever it names is readable - only unalterable. The description now says so, and a token no longer names a model field at all: a storage is identified by itsSTORAGESalias or a registered target name, both of which the project chose itself. Previously a field's token carriedapp_label.model.field, which revealed more than the schema does - a serializersource=makes the schema's field name and the model's differ. No wire change;info.version3.3.1 -> 3.3.3. (HAB-942) AppDensityPreferencelistsstandard, notnormal, as the middle UI density - the value the frontend has sent sincenode@6.7.0, where it was renamed so a settings density can be passed straight toDataTable/ MUIDataGridwithout translation. Both backends rejected it, soPUT /user-preferences/preferences.layout.app.density/answered422for the only value the frontend produces. A backend on this version rejectsnormalin turn: a value an older frontend stored fails validation on the next read, is deleted, and the registered default takes its place. The endpoint description no longer promises amigratecallback either - no implementation has one, and both delete the offending record instead.info.version3.2.2 -> 3.2.3; the enum is renamed incontract/schema.drawioandcontract/schema.svgas well. (HAB-939)
Added
-
Properties the implementations already send but the spec never described, found by diffing the generated schemas against this file. All are additive - the wire format does not change, the spec catches up with it. Added to
doc/schema.drawioas well. (HAB-732)property type sent by BoxSchema.collapsibleopen/closed, nullableRuby, Python; rendered by the JS client ArrayBoxParameters.inlineboolean Ruby, Python, JS FieldSchema.short_labelstring Ruby, Python, JS — the spec had it on FieldDescriptoronlyMenuGroupSchema.colorColorTypePython, JS MenuSchema.colorColorTypePython, JS ResourceActionSchema.labelstring Ruby, Python; the spec had it on ActionSchemaonlyRelationFieldOptions.dialog_widthDialogWidthTypeRuby, JS
Changed
- 2026-08-25 — the three library READMEs were reduced to a quick start — what the library is,
installation, one minimal working example and a map of the documentation website — after their
content was migrated to docs.habarico.com. Together they went from
roughly 7 990 lines to 256. The website is the source of truth for user-facing documentation
from now on; there is no sync back into the READMEs. The root README points at the site instead
of the library READMEs, and every
CLAUDE.md(plus a newdocs/CLAUDE.md) states where documentation belongs, which templates it follows and what is generated and must not be edited by hand. Internal developer documentation — the fournode/src/*/README.mdlayer references andpython/src/habarico/contrib/uploads/README.md— stays in the repository. (HAB-764) - The OpenAPI contract directory
doc/renamed tocontract/(contract/openapi.yaml,contract/schema.drawio). The old name collided with the newdocs/documentation website and did not say what the directory holds — the file is the contract between backend and frontend, not documentation. No change to the contract content itself. - 2026-08-21 —
node/README.mdbrought in line with the schema names the contract and the JS client now use:useGetResourceSchema→useGetLocalResourceSchema,BaseResourceSchema/BaseViewSchema→Global*,ResourceSchema→LocalResourceSchema(pkName→pkFieldName,endpoint→endpoints),HttpMethod→RequestMethodType,SortPayload→SortDescriptor,FieldFormat→FieldFormatType, theFieldOptionsslots →*FieldOptions(withnumberFormat→numberandtarget→LinkTarget),BoxSchema.isReadonly→readOnly, andSchemaWidget*→Widget*. TheViewSchema/BoxSchema/ActionSchemalistings in § 13.3 were regenerated fromsrc/schema/types.ts— they had drifted beyond the renames (list.fieldsislist.boxes,detail.formActionsisdetail.bottomActions,BoxSchema.fieldsis folded intoboxes). (HAB-731) - 2026-08-21 — two further
node/README.mdcorrections found alongside the rename: § 11.3 documented auseCallActionprop that does not exist (onPostTrigger; the hook callsonDone, and only when the response carries nometa.triggerAction), and §§ 8.2 / 8.3 / 8.10 / 9.2 / 9.3 passed a schemaoptionsobject toTextInput,NumberInput,PhoneNumberInput,TextFieldandNumberField, which take explicit props (prefix/postfix,numberFormat,defaultRegion/allowedRegions) — theFieldOptionsslot is mapped onto them by the schema layer, not passed through. (HAB-731) - BREAKING:
BoxSchema.is_readonlyrenamed toread_only, so the same flag is spelled the same way everywhere in the spec. It was the odd one out:FieldSchemaandFieldDescriptorboth call itread_only, and Ruby already sentread_onlyon the box too. Renamed indoc/schema.drawioas well. Python and the JS client both usedis_readonly/isReadonlyand are aligned in the same batch. (HAB-732, HAB-708) - BREAKING:
AggregateFunctionrenamed toAggregateFunctionType. It was the last enum outside the preference values (LocalePreference,ThemePreference, …, which are named after the preference they store) without the*Typesuffix that the other 20 enums and scalar schemas all carry; the Python implementation already called itAggregateFunctionType. The property that references it (WidgetConfig.aggregateFunction) is unchanged. Renamed indoc/schema.drawioas well. (HAB-730) - BREAKING:
FieldOptions.number_formatrenamed tonumberand its schemaNumberFormatTypetoNumberFieldOptions. It was the onlyFieldOptionsslot not following the<slot>FieldOptionspattern of its six siblings, and the only object schema carrying the*Typesuffix, which everywhere else in this spec marks a scalar or enum (19 of the 21*Typeschemas). The slot is also now named after what it applies to (number), likeimage,phoneandchoices, rather than after what it contains. (HAB-708) - BREAKING:
KeyValueTyperenamed toKeyValue. It was the last object schema carrying the*Typesuffix, which everywhere else in this spec marks a scalar or enum. (HAB-708) FieldOptions.text_alignand the duplicateddialog_width(defined twice, identically, inViewActionParametersandViewActionDescriptorParameters) were extracted intoTextAlignTypeandDialogWidthType.TextAlignTypematches the name the Python implementation already uses. (HAB-708)- BREAKING:
FieldOptions.targetchanged its values from_blank/_self/_parent/_toptoself/blank, and both it andLinkActionParameters.targetnow reference the newLinkTargetType. The two properties described the same thing — where a link opens — in two incompatible spellings: one stored the bare value and had the underscore prepended at render time (LinkButton), the other stored the full HTML value and passed it straight to the anchor. The dropped_parentand_topdiffer from_selfonly inside an iframe, and the frontend never renders in one.TargetTypeis unrelated (it names the field a nested list is filtered by) and keeps its meaning; its description now says so. (HAB-708) - BREAKING:
ResourceSchemarenamed toLocalResourceSchema. It is returned only by the per-resource schema routes (/admin/{resource_name}/**/schema) and is the counterpart ofGlobalResourceSchema, which/admin/schemareturns; the spec previously named one half of the pair and left the other unmarked. The Ruby implementation already usesLocalResourceSchema, so this aligns the spec with the code. Renamed indoc/schema.drawioas well. (HAB-708) - BREAKING: The
FieldOptionsslot schemas gained the owner in their name, matching the<slot><Owner>pattern used by the parameter schemas:ChoicesOptions→ChoicesFieldOptions,ImageOptions→ImageFieldOptions,AiOptions→AiFieldOptions,RelationOptions→RelationFieldOptions,PhoneOptions→PhoneFieldOptions,UploadOptions→UploadFieldOptions. The container schemas (FieldOptions,BoxOptions,FilterDescriptorOptions) and the slots shared by two owners (DynamicRenderOptions,ConfirmDialogProperties) keep their names. (HAB-708) - BREAKING: The two action parameter schemas that did not follow the discriminator-first naming
pattern were renamed:
ActionViewParameters→ViewActionParameters,ActionLinkParameters→LinkActionParameters, and consequentlyActionViewEndpointParameters→ViewActionEndpointParameters.ActionSchemahas six structurally identical slots, one perActionType; four of them (ResourceActionParameters,SaveActionParameters,UploadImagesActionParameters,ExportActionParameters) already named the discriminator value first, as do all seven box slots and both view slots. Renamed indoc/schema.drawioas well. (HAB-708) - BREAKING:
ActionDescriptorMenuViewParametersrenamed toViewActionDescriptorParameters, in two steps: HAB-705 renamed theMenu*parameter schemas toAction*but leftMenuinside this one name, and the result still put the owner first. Renamed indoc/schema.drawioas well. The genuine menu entities (MenuSchema,MenuGroupSchema,MenuType) keep their names — they describe the navigation menu, not an action. (HAB-708) FieldOptions.relation.link_modenow referencesViewModeTypeand therefore acceptssidepanelin addition toredirectanddialog, and is now nullable (ViewModeTypecarriesnullable: true). Python and JS already typed it this way; the spec was the outlier. (HAB-705)WidgetConfig.colornow referencesColorTypeinstead of repeating its seven values inline. Same values, but the field is now nullable (ColorTypecarriesnullable: true). (HAB-705)
Removed
node-enterprise/,python-enterprise/andruby-enterprise/— the three paid packages are developed and released from a repository of their own (habarico-enterprise), so their code, changelogs and.pydevconfigs left this monorepo. Nothing here built or published them — they never had a child.gitlab-ci.ymlor a trigger in the root one — so the release jobs are unchanged and still covernode/,python/,ruby/anddocs/. The root README and the changelog list above no longer link into the removed directories.contract/openapi.yamlstays here and keeps describing the paid endpoints (/search,/{resource}/aggregate, the upload endpoints): the contract is shared between the free and the paid halves. (HAB-846)
Fixed
- The spec did not validate as OpenAPI 3.0.3.
ObjectResourceSchemaandLocalResourceSchemarequired a property namedpk_name, but the property they define — and the one both backends emit and the frontend reads — ispk_field_name. Fixed in both schemas. (HAB-708) ObjectResourceSchemarequiredviews, which it does not define and which no implementation sends: the schema is also used stand-alone asResourceObjectMeta.schema, where a per-record schema has no views. Removed fromrequired. (HAB-708)ResourceActionParametersrequiredmethod, which it does not define —methodbelongs toResourceActionEndpointParameters, where it is already required. Removed fromrequired. (HAB-708)- Twelve operations documented only their success response. They now declare the error responses
they can actually return, following the conventions the other operations already use:
404for the schema and global endpoints (unknown service, resource, record or action) and for theuser-preferencesgroup, which clients may find missing entirely;400+422for the three upload endpoints and422for the search endpoint (request body / query validation); and400+405+422forGET /{resource_name}/{action_name}, matching its POST/PUT/PATCH siblings. (HAB-708) extra_filters(onListBoxParameters,ListViewParameters,GalleryBoxParameters) andSortableParameters.sort_for_filterswere typed as a single{key, value}pair, so the contract allowed exactly one filter with a string value. Both backends send a map keyed by field name whose values may also be integers or booleans (Pythondict[str, str | int | bool | None], Rubyany_attr). All four now reference the newFilterValuesschema, an object withadditionalProperties— the field name is the key, so there is nokey/valuepair to describe.RelationFieldOptions.filtersis unaffected — there the code really does send a list ofKeyValuepairs. (HAB-708)ActionResulthad itsadditionalPropertiesnested one level too deep, insideproperties. The spec therefore declared a payload key literally namedadditionalProperties— which nothing sends — while forbidding the arbitrary keys an action result actually consists of. Its siblingResourceObjectalready had it right. (HAB-708)EndpointsSchemadescribed itself as "Methods schema … describing methods that resource supports", so the spec used two words for one thing. The description now matches the name and says what an absent method means. (HAB-708)OffsetPaginationMeta.cursordeclared three mutually inconsistent things:type: stringwithformat: int64(an integer format) and an integerexample. It is nowoneOfinteger / string, documented as an opaque token to be passed back verbatim. (HAB-708)- Removed every
nullable: truethat had no effect. In OpenAPI 3.0nullableonly applies whentypeis present in the same schema object, so anullablesitting next to a$ref/oneOf/allOf— or on an untyped schema, which already permits null — did nothing. All of the affected properties describe schema output, which both backends serialize with nulls stripped (Pythonremove_empty, Rubydeep_compact), so optionality is expressed byrequiredalone.UserPreferenceValueis the one genuine nullable: preference values are returned by a DRF serializer, bypassing that stripping, and can be stored asnull. ItsanyOfhad no branch that acceptsnull, so the object branch is nownullable: true— valid, because it has atype. (HAB-708) ActionDescriptor.viewwas a bare$refwith sibling keys, so itsdescriptionwas being dropped; it is now wrapped inoneOfand the description applies. (HAB-708)FieldFormatTypecontradicted itself: its description documentedphone,czech-bank-account,czech-ico,czech-dic,czech-birth-numberandczech-id-card-number, but the enum did not allow them, so a spec-conformant client had to reject formats the spec itself described. The six values were added to the enum. In the other direction,timewas allowed but undocumented and now has a description entry. The enum is also reordered to match the description, so the two can be compared at a glance. (HAB-708)- The choice objects of
ActionDescriptorMenuViewParameters.choicescarried their view parameters under a key namedproperties, whileActionViewParameters.choicesusedparameters. Both now useparameters, which is what all three implementations actually emit — the spec was simply wrong. (HAB-705) ViewActionDescriptorParameterscarried the view parameters under a key namedproperties, while the schema it overrides (ViewActionParameters) usedparameters. Both now useparameters. The frontend deep-merges the descriptor'sviewonto the action'sview(generateActionSchema), so mismatched keys would have made the override silently do nothing; all three implementations emitparameters. (HAB-708)- The list response schema (
ResourceObjectListResponse) declaredpropertieswithouttype: object; the extractedResourceObjectListnow declares it. (HAB-705)