The Attribute Profile (L2)
The Semantic Catalog (L1) defines the organization's world: entities, attributes, and relationships. The L2 Attribute Profile is the use-case layer above it. Its job is to say: "for this use case, these are the actors, these are the relationships between them, and these are the constraints that apply here, tightened beyond the catalog defaults if the use case demands it."
What a Profile Does
A profile is a use-case binding that composes one or more published catalogs into a named collection of roles and relationships.
Roles bind catalog entities to use-case actors. A role gives a catalog entity a use-case name (employee, employer) and optionally narrows it to one or more specialization subtypes. The employee role binds to the Person entity and specializes to the Employee subtype, so the role carries both the base Person attributes and the Employee-specific ones.
Relationships are selected, not redeclared. The profile selects which catalog relationships the use case includes, by name. Relationship semantics, cardinality, and localization are already defined on the catalog and are not repeated in the profile.
Overrides are narrow-only. A profile may tighten individual attribute constraints per role, but it cannot loosen anything already declared on the catalog. Making email MANDATORY for the employee role is a valid override; making a MANDATORY catalog attribute OPTIONAL is not. This ensures that governance constraints established at L1 can only be strengthened, never weakened, as they flow downstream.
Catalogs are pinned by version. The catalogRefs array pins exactly one version per source catalog. Resolution is deterministic: downstream layers that pin this profile version see a stable view of the catalog regardless of subsequent catalog revisions.
The profile does not select individual attributes. Attribute selection happens at the Attribute Set (L3) layer, which takes a published profile as its input. A profile is the governance boundary; a set is the selection surface.
These layers are authored over REST in this walkthrough. VDX presents a guided UI for the same layers; the concepts and the resulting model are identical.
Create, Publish, and Resolve the Profile
The "Acme Employee" profile composes the catalog published in Catalog (L1). It defines two roles:
employee, bound to thePersonentity, narrowed to theEmployeespecialization. Inherits all Person attributes plus the Employee-specific ones.employer, bound to theOrganizationentity, narrowed to theEmployerspecialization.
It selects both catalog relationships (residentialAddress and employment) and applies one override: email is tightened from OPTIONAL (its catalog default on Person) to MANDATORY for the employee role in this use case.
Create
- Request
- Response
POST /api/attributes/v1/profiles
{
"name": "Acme Employee Profile (layered)",
"description": "Use-case profile over the layered Acme catalog: employee (Person/Employee) + employer (Organization/Employer) roles, residentialAddress + employment relationships.",
"catalogRefs": [
{ "catalogId": "5b8c...catalogId", "catalogVersion": 1 }
],
"roles": [
{
"id": "employee",
"name": "Employee",
"entity": { "catalogId": "5b8c...catalogId", "entity": "Person" },
"specializes": ["Employee"]
},
{
"id": "employer",
"name": "Employer",
"entity": { "catalogId": "5b8c...catalogId", "entity": "Organization" },
"specializes": ["Employer"]
}
],
"relationships": [
{ "catalogId": "5b8c...catalogId", "relationship": "residentialAddress" },
{ "catalogId": "5b8c...catalogId", "relationship": "employment" }
],
"overrides": [
{ "roleRef": "employee", "path": ["email"], "conformance": "MANDATORY" }
]
}
201 Created returns the bare AttributeProfile. Capture .id and .version; they are the profileId and profileVersion the Attribute Set (L3) will pin.
{
"id": "a1f0...profileId",
"name": "Acme Employee Profile (layered)",
"version": 1,
"status": "DRAFT"
}
Field reference
| Field | Meaning |
|---|---|
catalogRefs | [{ catalogId, catalogVersion }]. Pins one version per source catalog. |
roles[].id | The use-case role identifier used throughout L3 traversal paths (e.g. "employee"). |
roles[].name | A human-readable name for the role. |
roles[].entity | { catalogId, entity: "<EntityName>" }. The catalog entity this role is bound to. |
roles[].specializes | Array of specialization entity names to narrow the role. The role carries the base entity's attributes plus the specialization's additions. |
relationships[] | { catalogId, relationship: "<relationshipName>" }. Selects a catalog relationship by name. The relationship's cardinality and localization are read from the catalog. |
overrides[] | { roleRef, path: [...], conformance?, sdPolicy?, ... }. Narrow-only per (role, attribute path). The allow-list fields are conformance, sdPolicy, entryCodesSubset, sensitive. |
The overrides entry { "roleRef": "employee", "path": ["email"], "conformance": "MANDATORY" } upgrades email from OPTIONAL (its value on the Person entity in the catalog) to MANDATORY for the employee role in this use case. Any attribute not listed in overrides reads through to the catalog value.
Publish
Publish the profile to freeze it for downstream consumption.
- Request
- Response
PUT /api/attributes/v1/profiles/{profileId}/status
{ "status": "PUBLISHED" }
200 OK returns the updated AttributeProfile with status: "PUBLISHED". Capture the published version; this is the profileVersion the attribute set will pin via profileRef.
{
"id": "a1f0...profileId",
"version": 1,
"status": "PUBLISHED"
}
After publishing, the profile is immutable. Any subsequent changes require a new draft.
Resolve
- Request
- Response
GET /api/attributes/v1/profiles/{profileId}/resolved
200 OK returns the resolved profile with all roles expanded against the pinned catalog snapshot. Each role carries its full attribute set including attributes inherited through the specialization chain. The employee role's resolved attributes include given_name, family_name, and email from Person, plus employee_id, job_title, and employment_status from the Employee specialization.
{
"id": "a1f0...profileId",
"version": 1,
"status": "PUBLISHED",
"roles": [
{
"id": "employee",
"name": "Employee",
"entity": { "catalogId": "5b8c...catalogId", "entity": "Person" },
"specializes": ["Employee"]
},
{
"id": "employer",
"name": "Employer",
"entity": { "catalogId": "5b8c...catalogId", "entity": "Organization" },
"specializes": ["Employer"]
}
]
}
Override and Version-Pinning Precedence
The profile is the second layer in the resolution order. When a downstream layer resolves an attribute's effective value:
- Catalog (L1) establishes the canonical value for
valueType,overlays.i18n,sdPolicy, and all governance overlays. - Profile override (L2) is the effective value for fields in the override allow-list (
conformance,sdPolicy,entryCodesSubset,sensitive). Anything outside the allow-list reads through to the catalog unchanged. - Set override (L3) may tighten further per selected traversal entry.
- VC channel (L4) is selection and claim mapping only; it does not override governance.
No layer widens what a layer above has declared. The version pin in catalogRefs means this profile always resolves the catalog at version 1, regardless of how the catalog evolves after publishing.
See Modeling Your World for the full precedence table.
The License Gate
Creating and publishing a profile is a license-gated enterprise write, governed by the semantic-attribute-binding feature. Reading (get, list, resolve) is never gated. See Modeling Your World for the full gate behaviour.
Every write on this page uses the operator's OIDC bearer token:
Authorization: Bearer <operator access token>
Content-Type: application/json
The tenant is resolved from the token's tenant_id claim.
Next Steps
- Attribute Set (L2): pin this published profile, select traversal paths (including cross-relationship paths), and apply set-level narrow overrides
- Modeling Your World: the full layer model, governance-overlay detail, and version-pinning precedence
- Catalog (L1): the entity, attribute, and relationship definitions this profile composes