Entity metamodel API
The curation API works with entities: a table, a dashboard, a tag. This API works one level up, with the shapes those entities take — the entity types, and the attributes each type carries.
An entity type is a class; an entity is an instance of it. An attribute definition declares that instances of a type may carry a value of a given kind, under a given name. Nothing here writes values; that is the curation API.
All endpoints require authentication and return the standard DataDios response envelope:
{
"STATUS": "SUCCESS",
"MESSAGE": "",
"DATA": {}
}
A refused request has STATUS: "FAIL" and a machine-readable code in DATA.reason. Branch on
DATA.reason, never on the text in MESSAGE.
Endpoints
| Method and path | Purpose | Success status |
|---|---|---|
POST /entities/types | Create an entity type | 201 when created; 200 when the name already exists |
GET /entities/types | List entity types | 200 |
POST /entities/types/{guid}/attributes | Declare an attribute on an entity type | 201 |
GET /entities/types/{guid}/attributes | List the attributes that apply to a type | 200 |
Create an entity type
{
"name": "Data Product"
}
Creating a type is create-or-get, not create-or-update. Names are compared after trimming
spaces and ignoring letter case. If the name already resolves to a type, the API returns that type
with "created": false and writes nothing.
Two consequences follow from the identity rule, and both are easier to know than to discover:
- Creating "Data Product" twice converges on one type. There is no way to end up with two.
- A type's identity is derived from its name, so a type that is deleted and later recreated under the same name comes back with the same guid. Any curation that outlived the delete points at it again.
The response carries origin, which separates the types DataDios ships from the ones you create:
{
"entity_type": {
"guid": "...",
"name": "Data Product",
"origin": "user_defined",
"catalog_level": true
},
"created": true
}
catalog_level is one of the fields a type row carries to say how a client should treat the type.
List entity types describes them all.
List entity types
GET /entities/types returns both the seeded system types and your own, told apart by origin.
You need that distinction: only user-created types can be renamed or deleted.
The listing is paged with a cursor. Pass cursor and page_size as query parameters; the response
carries next_cursor, which is null on the last page.
{
"entity_types": [
{ "guid": "...", "name": "Data Product", "origin": "user_defined", "catalog_level": true }
],
"next_cursor": null
}
What a type row tells a client
From the DataDios release after v1.13.0, each row also says how to treat the type, so a client does
not need its own list of type names. A field that does not apply is left out, not sent as false or
null.
| Field | Sent on | Meaning |
|---|---|---|
catalog_level | Built-in levels, managed types, your types | true: the type's entities are rows in the Entity Catalog table |
vocabulary | domain, env, tag, classification, group | true: the type's entities are values picked in an attribute cell, not rows of the table |
label | Built-in levels, managed types | The name to show for the type |
icon | Built-in levels, managed types | A key the client maps to an icon |
parent_type | database, schema, table, column | The guid of the level one step above. Left out on datasource, the top level |
creatable | Managed types | false: creating an instance of the type is refused |
name_locked_hint | Managed types | Text to show where an instance's name can't be edited |
The built-in levels are datasource, database, schema, table and column, in that order from
the top:
{
"guid": "...",
"name": "table",
"origin": "system",
"label": "Object",
"icon": "table",
"catalog_level": true,
"parent_type": "<guid of the schema type>"
}
A vocabulary type carries only its mark:
{ "guid": "...", "name": "tag", "origin": "system", "vocabulary": true }
Every type you create is a catalog level, shown under its own name. A type that a connector reported, and that is none of the above, carries none of these fields.
From the DataDios release after v1.13.0,
a few types are managed by DataDios itself: today, dashboard. Their entries are added and kept in
step by the feature they belong to (see
Dashboards in the catalog), never by hand. A managed
type's row also carries how to show it:
{
"guid": "...",
"name": "dashboard",
"origin": "system",
"label": "Dashboard",
"icon": "dashboard",
"catalog_level": true,
"creatable": false,
"name_locked_hint": "The name comes from the dashboard. Rename it on the dashboard's page."
}
creatable: false means creating an instance is refused (see the
curation API). Creating a type with a managed type's name
returns that type with "created": false, the same as any existing name.
A type named dashboard that you created yourself before this release stays yours
(user_defined), and while it exists DataDios adds no dashboards to the catalog (see
Dashboards in the catalog). Since this release, you
can't create your own type with that name.
Declare an attribute
{
"name": "sla_tier",
"value_type": "string",
"cardinality": "multi",
"allowed_values": ["gold", "silver", "bronze"],
"required": false,
"default_value": "bronze",
"filterable": false
}
| Field | Required | Meaning |
|---|---|---|
name | yes | Attribute name, unique within the owning type |
value_type | yes | One of string, number, boolean, date, json, entity_ref |
cardinality | yes | single for one value, multi for a list |
allowed_values | no | Enumerated values, for scalar types only |
allowed_ref_type | for refs | Guid of the type an entity_ref attribute points at |
required | no | Whether instances must carry a value |
default_value | no | Applied when an instance is created without one; an entity guid for references |
filterable | no | Whether the attribute can be filtered on; entity_ref only |
Every malformed combination is refused with its own reason code rather than being quietly ignored. The rules that are easiest to get wrong:
allowed_valuesis for scalars only. A reference attribute is constrained byallowed_ref_typeinstead. Sending both is refused.- An
entity_refattribute must declareallowed_ref_type. Without it the attribute can never accept a value by name — only by guid — for the rest of its life, so this is refused at declaration rather than discovered later. filterableapplies toentity_refonly. Filtering is built on reference values, so a filterable string attribute would look filter-ready and match nothing, permanently.
Limits
| Limit | Value | Refusal reason |
|---|---|---|
| Attributes declared on one type | 200 | too_many_attribute_defs |
Entries in allowed_values | 1,000 | too_many_allowed_values |
| Request body, before it is parsed | 8 MB | request_too_large (413) |
The first two are not about the size of a single request. A type's attribute definitions are read on every curation write against that type, so an unbounded set is a cost paid by every later write rather than by the call that declared them.
Attribute names cannot be changed
An attribute's declaration is fixed once created. There is no rename and no edit: changing any
field means withdrawing the attribute and declaring a new one. filterable is the field callers
most often want to flip, and flipping it would rewrite the stored filter data of every instance of
the owning type — a background job, not a field edit.
To withdraw one, use DELETE /entities/attributes/{type_guid}/{name} from the curation API.
List a type's attributes
GET /entities/types/{guid}/attributes returns every attribute that applies to the type: the ones
declared on it, plus the five platform attributes every type carries — domain, env,
tags, classification, and groups.
A brand-new type with no attributes of its own still lists those five. They are owned by the
framework rather than by any one type, so they come back with "owner_type": null. That null is
deliberate and is not a missing value: naming an owning type would claim a per-type attribute where
the platform has a schema-wide one.
Withdrawn attributes are absent from this listing.
{
"attributes": [
{
"guid": "...",
"name": "tags",
"owner_type": null,
"value_type": "entity_ref",
"cardinality": "multi",
"allowed_values": null,
"allowed_ref_type": "...",
"required": false,
"default_value": null,
"filterable": true,
"origin": "system"
}
]
}
This listing is not paged. It returns the type's full applicable attribute set, which the 200-per-type limit above bounds.
Refusal reasons
| Reason | Status | When |
|---|---|---|
attribute_def_already_exists | 409 | An attribute of that name is already declared on the type |
attribute_def_withdrawn | 409 | An attribute of that name was withdrawn and its data is still being removed |
type_instances_managed | 409 | Creating a type: the name belongs to a type DataDios manages, and that type has not been set up for you yet (DataDios sets it up when it writes your first dashboard's catalog row) |
too_many_attribute_defs | 400 | The type is at the 200-attribute limit |
base_type_not_owner | 400 | An attribute was declared against the platform's own attribute owner |
unknown_entity_type | 404 | The type guid does not resolve |
unknown_value_type | 400 | value_type is outside the six |
invalid_cardinality | 400 | cardinality is neither single nor multi |
allowed_values_not_supported | 400 | allowed_values was sent on an entity_ref attribute |
allowed_ref_type_not_supported | 400 | allowed_ref_type was sent on a non-reference attribute |
allowed_ref_type_required | 400 | An entity_ref attribute omitted allowed_ref_type |
filterable_not_supported | 400 | filterable was set on a non-reference attribute |
referent_not_found | 404 | A reference default_value does not resolve |
ref_type_mismatch | 400 | An entity_ref default_value names an entity of the wrong type |
empty_value | 400 | name normalizes to nothing once spaces are trimmed |
invalid_page_size | 400 | page_size on the type listing is outside the permitted range |
invalid_cursor | 400 | cursor on the type listing is not one this API issued |
request_too_large | 413 | The body exceeds 8 MB |
The two attribute 409s are temporary in different ways
attribute_def_already_exists means the attribute is live. Declaring it again is refused rather
than treated as a no-op, because a declaration carries a shape: answering SUCCESS would report a
definition you may not have asked for. Withdraw it first if you need a different shape.
attribute_def_withdrawn means the attribute was withdrawn but its stored values have not finished
being removed. That refusal clears on its own — a daily job deletes the withdrawn definition once
its data is gone, after which the name can be declared afresh. It is a window, not a permanent
block.