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 |
|
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 thedefaultandactiveflags 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 areFAX,HM,MBL, orOTHleaves 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 asphone_country_not_dialing_code. -
Office address:
the first
<address>in the document, whatever itsaddressTypeCode. If that address is notWRK, it is still applied and the record carries anon_work_address_appliedwarning. 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 asoffice_address_empty; a record with no<address>leaves the office fields as they are. -
Primary department:
primaryDepartmentof the first<employment>in the document, across all affiliations.primaryEmploymentis not imported and does not affect the choice. -
Title:
the
<name>row'stitle, thenprimaryTitle, thendirectoryTitle— 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.errorandfailure.messagesay 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. Withincounts.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'sfailure.messageand 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