Skip to content

Define your profile schema

The fields your organization actually collects. See Profile fields for why this is defined at runtime rather than shipped.

Start from a preset

IDEN ships no profile fields, but it does ship a catalogue of the ones most organizations end up defining — with the type, validators and permissions already decided.

curl http://localhost:8000/admin/profile-fields/presets \
  -H "Authorization: Bearer $TOKEN"

Nothing there exists until you create it. Each entry is shaped so it can be posted straight back to POST /admin/profile-fields, with anything you like changed first.

What they are actually for is userWritable. Who owns a piece of data is the question that is easy to get wrong, and each preset answers it and says why: preferred_name belongs to its owner, student_id belongs to the registrar.

A field the organization owns

A student number: set by the registrar, visible to the student, not editable by them, and unique.

curl -X POST http://localhost:8000/admin/profile-fields \
  -H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json" \
  -d '{
    "key": "student_id",
    "label": "Student number",
    "dataType": "string",
    "required": true,
    "unique": true,
    "userReadable": true,
    "userWritable": false,
    "validators": {"pattern": "^[0-9]{8}$"},
    "displayOrder": 10
  }'

A field the person owns

curl -X POST http://localhost:8000/admin/profile-fields \
  -H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json" \
  -d '{
    "key": "preferred_name",
    "label": "Preferred name",
    "userWritable": true,
    "displayOrder": 20
  }'

The only difference that matters is userWritable. It decides which endpoint can write the value:

PATCH /entity/profile PATCH /admin/users/{id}/profile
userWritable: true
userWritable: false 422

A field only some people have

Bind it to a group and it applies only to members:

-d '{"key": "enrollment_year", "label": "Year of enrolment",
     "dataType": "integer", "groupId": "<Students group id>",
     "validators": {"min": 1900, "max": 2100}}'

Staff never see it. Students see it in GET /entity/profile/schema, and a dashboard renders the form from that response rather than hardcoding your fields into a general-purpose identity provider.

Releasing a field to applications

By default, no application ever sees a custom field. To release one:

-d '{"key": "department", "label": "Department",
     "claimName": "department", "claimScope": "profile"}'

Now department appears in ID tokens and /oauth2/userinfo — but only for applications granted the profile permission. Both claimName and claimScope are required together: a claim with no permission attached would go to everyone.

Reserved OIDC names (sub, iss, aud, acr, sid, …) are refused, so a custom field cannot shadow a claim a token's meaning depends on.

Setting values

curl -X PATCH http://localhost:8000/entity/profile \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"fields": {"preferred_name": "Sam"}}'
curl -X PATCH http://localhost:8000/admin/users/{id}/profile \
  -H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json" \
  -d '{"fields": {"student_id": "12345678"}}'

Writing a field you may not write is refused with 422, not ignored — an application should never believe it saved something it did not.

What cannot be changed later

key, dataType, unique and groupId are absent from the update endpoint on purpose. Each would rewrite the meaning of values already stored: changing a type cannot recast them, and adding unique to a field that already has duplicates cannot succeed.

Define a new field instead.

Deleting a field deletes every answer to it

That is the point — an organization that stops collecting something should stop holding it — but there is no undo.