Category¶
Three facets a caller needs before it picks a model name: what the model produces, whether its weights are published, and which of the operator's groupings it belongs to. The first two are read from the public catalogue. The third is the operator's declaration, because no public catalogue carries it.
Kind: what the model produces¶
Every catalogue model carries kind, decided from the output modalities its source declares and from nothing else: text, image, video or audio. A model that emits video is a video model even when it also emits text — the heaviest artifact decides, because that is the endpoint shape the caller has to post to. A text-to-video model whose vendor description reads "Image model for prompt-driven generation" is a video model here, because modalities.output says so.
The catalogue's outputModalities answered ["text"] for every id until this facet existed, including the image and video models it had always carried, so no console could tell a renderer from a chat model.
Weights: open or closed¶
openWeights (open_weights in the OpenAI-shaped view) is the catalogue's own open_weights flag: true where the weights are published, false where they are withheld, and null where the source said nothing. null is a third answer, not a hedge — a model whose source never stated it matches neither weights=open nor weights=closed, and no filter treats absence as a claim.
tools, reasoning and price follow the same rule in the Jeden-shaped catalogue: an id the route registry holds no metadata for answers null for each, never false or a price of zero, so no budget or filter reads an unpriced model as free or an undeclared one as incapable.
Declared categories¶
A category is a name the operator declares and the membership rule that decides which models carry it. Brama infers none: a gateway that guessed would be publishing an opinion as metadata, and a caller could not tell the two apart.
The declaration lives in the route registry, beside the routes and the registry's own schema member, under categories:
{
"routes": {"image-model": "openai/gpt-image-1"},
"categories": {
"uncensored": {
"providers": ["abliteration-ai"],
"routes": ["venice/venice-uncensored"],
"terms": ["uncensored", "abliterated"]
}
}
}
A model is in the category when any of the three matches:
| Member | Meaning |
|---|---|
providers | Every model of that provider, for a provider whose whole catalogue is the category. |
routes | One exact provider/model route. |
terms | A string the publisher put in the model's route id or display name. |
The match is on what the publisher named the model, never on its description. A description is prose about a model and mentions its neighbours: the catalogue's cybersecurity variants read "based on the uncensored model, with provider moderation for illegal activities", which is the opposite claim in a sentence containing the word.
The terms live in the operator's document rather than in the gateway binary, so adding one is an edit to a file and never a release. A category name is lowercase letters, digits and hyphens, at most thirty-two bytes; a document declares at most thirty-two categories, and each carries at most five hundred and twelve members.
The uncensored category¶
uncensored is the first category anybody asked for, and it is a declaration like any other. Declaring it takes one command:
$ brama categories set uncensored --term uncensored --term abliterated --provider abliteration-ai
declared model category uncensored
$ brama categories ls
uncensored (24 models)
providers: abliteration-ai
routes: -
terms: uncensored, abliterated
The model count beside the name is the check on the declaration: a rule that matches nothing reads zero here instead of looking like a working facet in two consoles. When the model catalogue itself cannot be read, no count is invented: GET /v1/categories answers "models": null with the read's cause in catalog_failure, and brama catalogue categories list prints models unknown: the catalogue could not be read after saying the cause on stderr (its --json form carries the same catalog_failure). What a provider filters at serving time is the provider's business and no catalogue states it; this category says only that the publisher named the model that way.
Reading the facets¶
GET /v1/models narrows on all four members, and a value the gateway has no meaning for is refused by name rather than answering the whole catalogue:
| Query | Accepts | Refusal |
|---|---|---|
kind | text, image, video, audio | kind must be text, image, video or audio |
weights | open, closed | weights must be open or closed |
category | a declared name | category must be lowercase letters, digits and hyphens |
provider | a provider id | provider must be a provider identifier |
Any other query member is refused as `limit` is not a model filter; narrow by kind, weights, category or provider. GET /v1/categories reports every declaration with its rule and its live model count, and brama models prints the same facets from a shell.
An alias carries the categories of the route it points at: asking for the uncensored models and being shown the alias that reaches one is the answer the caller wanted.
Writing a category¶
Two surfaces, one validated, staged, owner-only write: brama categories set from a shell and PUT /v1/admin/categories from the console. A document that would not load is refused before it replaces the one that does, and the refusal names the category, the member and what was wrong with it.