Kuali GraphQL Documentation

Guides

Bulk import an HR feed

Bulk-import your institution's people and organizational units from a Classic HR feed (or a JSON groups batch).


Overview

POST /api/v0/imports/hr-feed/people accepts an unmodified Classic hrmanifest document (schema version 2.0) and treats it as the authoritative active roster: people are created or updated, and (once a later run omits them) deactivated. The feed carries 90 attributes. The 31 under Applied fields are read; every other attribute is accepted but never stored, logged, or reported on.

Before your first import

  • The Research User Attributes page must be installed. Every roster is rejected without it — including one that carries only names, emails, and phones — with 422 user_attributes_page_required. The check runs before the payload is read, so it cannot depend on what the roster contains.
  • Enforce Unique Person Institutional IDs must be enabled in Identity Settings.

Applied fields

Attribute Element Destination
principalId record institutional ID
principalName record username
active record active
affiliationType affiliation affiliation
firstName name first name
middleName name middle name
lastName name last name
prefix name prefix
suffix name suffix
title name title
emailAddress email email
phoneNumber phone phone
primaryDepartment employment Research User Attributes — Primary Department
unitNumber appointment Research User Attributes — Appointments → Unit
appointmentType appointment Research User Attributes — Appointments → Appointment Type
jobCode appointment Research User Attributes — Appointments → Job Code
salary appointment Research User Attributes — Appointments → Salary
startDate appointment Research User Attributes — Appointments → Start Date
endDate appointment Research User Attributes — Appointments → End Date
addressLine1 address Research User Attributes — Office Address 1
addressLine2 address Research User Attributes — Office Address 2
city address Research User Attributes — Office City
stateOrProvince address Research User Attributes — Office State
postalCode address Research User Attributes — Office Postal Code
country address Research User Attributes — Office Country
addressTypeCode address not stored — reported when the applied address is not WRK
citizenshipType kcExtendedAttributes Research User Attributes — Citizenship Type
primaryTitle kcExtendedAttributes title, when the name carries none
directoryTitle kcExtendedAttributes title, when the name and primaryTitle carry none
phoneType phone not stored — selects the WRK row for phone
country phone phone, prepended when it is a dialing code

On the Research User Attributes page, the import changes only the fields listed above. The page's other fields — ORCID, NSF ID, eRA Commons username, office phone, and the rest — keep whatever value they have.

Which row is applied

A person can carry more than one <name>, <email>, <phone>, <address>, or <employment>. At most one row per element is applied.

  • Name and email: the first row in the document. nameCode, emailType, and the default and active flags are not imported and do not affect the choice, so put the row you want applied first.
  • Phone: the first row with phoneType="WRK". The number is stored as the person's phone. A record whose only numbers are FAX, HM, MBL, or OTH leaves the phone unset — those numbers have no destination on the person record.
  • Phone country: the schema does not constrain phone/@country — it is a plain string with no length, pattern, or list of allowed values. When it is one to three digits, with or without a leading +, it is prepended to the number as +<code> <number>. Any other value leaves the number unchanged, and the run reports the first such value once as phone_country_not_dialing_code.
  • Office address: the first <address> in the document, whatever its addressTypeCode. If that address is not WRK, it is still applied and the record carries a non_work_address_applied warning. The six office fields are written together: an attribute the applied address omits clears that field. An address with none of the six attributes writes nothing and is reported as office_address_empty; a record with no <address> leaves the office fields as they are.
  • Primary department: primaryDepartment of the first <employment> in the document, across all affiliations. primaryEmployment is not imported and does not affect the choice.
  • Title: the <name> row's title, then primaryTitle, then directoryTitle — the first one present is applied.
  • Appointments: every <appointment> becomes a row, in document order, replacing the rows the person had. A record with no <appointments> element leaves the stored rows untouched.
  • No qualifying row leaves the destination unset. It is never an error.

Limits

  • 250 MB and 100,000 records per submission
  • one roster import per institution at a time
  • results are retained 90 days

Retired fields

The Classic migration mapping marks these "do not migrate". They are accepted but never stored, logged, or reported.

Attribute Element
campus affiliation
employeeStatus employment
employeeType employment
baseSalaryAmount employment
employeeId employment
primaryEmployment employment
addressLine3 address
default address
active address
nameCode name
default name
active name
extension phone
default phone
active phone
emailType email
default email
active email
jobTitle appointment
preferedJobTitle appointment

Not yet mapped

The migration mapping does not mention these, so no decision has been made about them. Until one is, they are handled exactly like retired fields: accepted, and never stored, logged, or reported.

Attribute Element
statusEmailRecipient hrmanifest
reportDate hrmanifest
entityId record
default affiliation
active affiliation
county kcExtendedAttributes
ageByFiscalYear kcExtendedAttributes
race kcExtendedAttributes
educationLevel kcExtendedAttributes
degree kcExtendedAttributes
major kcExtendedAttributes
handicapped kcExtendedAttributes
handicapType kcExtendedAttributes
veteran kcExtendedAttributes
veteranType kcExtendedAttributes
visa kcExtendedAttributes
visaType kcExtendedAttributes
visaCode kcExtendedAttributes
visaRenewalDate kcExtendedAttributes
officeLocation kcExtendedAttributes
secondaryOfficeLocation kcExtendedAttributes
school kcExtendedAttributes
yearGraduated kcExtendedAttributes
directoryDepartment kcExtendedAttributes
vacationAccrual kcExtendedAttributes
onSabbatical kcExtendedAttributes
idProvided kcExtendedAttributes
idVerified kcExtendedAttributes
multiCampusPrincipalId kcExtendedAttributes
multiCampusPrincipalName kcExtendedAttributes
salaryAnniversaryDate kcExtendedAttributes
degreeCode degree
degree degree
graduationYear degree
fieldOfStudy degree
specialization degree
school degree
schoolId degree
schoolIdCode degree

Run statuses

A run reports accepted, validating, applying, or sweeping while it is still working, and finishes in one of:

  • completed — every record applied and the sweep ran clean
  • completed_with_failures — the run finished, but at least one record failed or a deactivation did
  • completed_deactivations_withheld — applied, but the deactivation sweep exceeded its threshold and is waiting on your confirmation
  • completed_deactivations_refused, completed_deactivations_stale — the sweep was refused outright, or the approved candidate set had drifted by the time you confirmed it
  • failed — the run stopped and will not resume; failure.error and failure.message say why. How much applied depends on the cause: a rejected payload means nothing did, but a sweep failure happens only after every record already landed — the deactivation pass is what failed, not the roster
  • failed_dependency_unavailable — attribute writes failed consecutively past the configured threshold, so the run halted instead of failing every remaining record one at a time. Within counts.processed, records from chunks completed before the halt applied fully and resubmitting re-applies them as no-ops; the halting chunk's own failing records had their person data applied but not their attributes, so resubmitting re-attempts just those writes. The streak can come from either of two different causes — the destination page (or its reference data) being unreachable, or a value the destination keeps rejecting on every record — so check the run's failure.message and the per-record results to see which one it was before resubmitting

Per-record failure reasons

GET /api/v0/imports/:runId/results streams one outcome line per submitted record. Every line also carries attributes — written, unchanged, skipped, or failed, this record's own attribute-write outcome — and a warnings list of field/value/reason entries for this record's own attributes; a run-level warning is never on a per-record line, only on the run itself. A failed record names one of:

  • missing_required_field — a required imported attribute was blank or absent
  • invalid_value — a supplied value didn't match its required pattern
  • duplicate_in_payload — the same institutional ID appeared twice in this submission
  • held_by_other_person — the username is already assigned to a different person
  • ambiguous_institutional_id — two existing people already hold this institutional ID
  • identity_rejected — the record failed a save-time validation not listed above
  • dependency_unavailable — the person was applied but their attribute write failed after retries
  • section_create_rejected — the person's attribute section document could not be created
  • validation_rejected — the attribute write failed a save-time field validation on the section document
  • write_rejected — the attribute section document write failed for another reason
  • unit_number_field_disabled, parent_not_found, parent_cycle — group-batch specific