Skip to main content

Command Palette

Search for a command to run...

SharePoint REST API - Working with User Profiles

Updated
17 min readView as Markdown
SharePoint REST API - Working with User Profiles
R

I'm a .NET/M365 developer, trainer, author, MVP & MCT Alumni

SharePoint's User Profile Service stores and surfaces rich professional data about each user — job title, department, manager, skills, and more. The SP.UserProfiles.PeopleManager endpoint is the primary REST surface for reading and writing this data.


Introduction

SharePoint typically maintains a User Profile Service profile for Microsoft 365 users in the tenant. Profile data comes from Entra ID (for directory properties like name, title, and department), from Microsoft 365 (for profile photos, surfaced and cached by SharePoint), and from the User Profile Service itself (for SharePoint-specific and custom properties). The REST API exposes this through the SP.UserProfiles.PeopleManager class, which provides methods for:

  • Reading profile properties for any user
  • Reading your own profile
  • Writing single-value and multi-value profile properties
  • Following and unfollowing users
  • Querying follower and following relationships

PeopleManager also exposes SetMyProfilePicture, but modern Microsoft 365 solutions should generally use the Microsoft 365/Graph photo APIs for profile photo management — it isn't covered in this post.

The SharePoint schema also defines SP.UserProfiles.UserProfile (accessible via ProfileLoader.GetProfileLoader().GetUserProfile()), which exposes MySite-specific properties such as PersonalSiteCapabilities and PersonalSiteInstantiationState. That entity is not covered in this post.

User profile data is not scoped to any individual SharePoint site — the PeopleManager REST resource is available at <siteUri>/_api/SP.UserProfiles.PeopleManager from any SharePoint site context. The examples in this post use the tenant root site; the MySite host ({tenant}-my.sharepoint.com) also works (validated against SharePoint Online). In a Multi-Geo tenant, user profiles have a geo location and custom profile properties should be read and updated at the user's home geo — Microsoft recommends Microsoft Graph for default-property updates in that scenario because Graph is geo-aware.

Authentication note: The raw HTTP examples use a SharePoint access token. Operations whose meaning depends on a signed-in user — such as Follow — require delegated authentication. GetMyProperties can also be called with app-only authentication, but returns the application principal's pseudo-profile rather than a user's profile. App-only tokens can read any user's profile and write supported writable profile properties for other users without the ownership restriction observed with non-admin delegated authentication. For app-only authentication to SharePoint REST, Entra app-only requires certificate-based credentials — client secrets are not supported. In SPFx, use SPHttpClient instead of raw HTTP — it supplies the current user's authentication context and handles request digests for write operations.

Permission note: The token audience for all User Profile API operations is the SharePoint Online resource (00000003-0000-0ff1-ce00-000000000000), the same audience as site API calls. The base scopes are User.Read.All (SharePoint resource) for reads and User.ReadWrite.All (SharePoint resource) for writes. Some operations have additional requirements; in particular, SetMultiValuedProfileProperty required TermStore.ReadWrite.All in testing. These are SharePoint-specific permission scopes, distinct from the identically named Microsoft Graph scopes.


Before You Start: Headers and JSON Format

SharePoint's REST API defaults to OData v3 in most cases when the OData-Version header is not supplied. To use OData v4 — which is what SPFx's SPHttpClient uses by default — include the OData-Version: 4.0 header:

OData-Version: 4.0
Accept: application/json;odata.metadata=none
Content-Type: application/json;odata.metadata=none  (JSON-body requests only)

For a full explanation of OData versions, JSON format options, and how these headers interact, see Understanding SharePoint REST JSON Formats.


The PersonProperties Entity

Most PeopleManager read methods return SP.UserProfiles.PersonProperties, which is the standard profile response object.

Property Type Description
AccountName string The claims-format login name: i:0#.f|membership|{upn}.
DisplayName string The user's display name.
Title string Job title.
Email string Email address.
PictureUrl string URL to the profile picture. As observed in SharePoint Online, includes a ?t=<timestamp> cache-busting query parameter.
PersonalUrl string URL to the user's personal (OneDrive) site. Populated only when the user's personal site has been provisioned.
PersonalSiteHostUrl string URL to the MySite host. As observed in SharePoint Online, includes the :443 port suffix (e.g., https://contoso-my.sharepoint.com:443/). Populated even before the user's personal site has been provisioned.
UserUrl string URL to the user's SharePoint profile page.
LatestPost string The user's most recent microblog post.
IsFollowed bool Whether the calling user follows this person. Reflects the signed-in user's follow list in delegated calls; not meaningful for app-only callers.
DirectReports Collection(string) Claims-format login names of direct reports.
ExtendedManagers Collection(string) Claims-format login names of the management chain above the user.
ExtendedReports Collection(string) Claims-format login names in the user's extended-report hierarchy. In SharePoint Online testing, the user's own account appeared as the first entry.
Peers Collection(string) Claims-format login names of colleagues with the same direct manager.
UserProfileProperties Collection(SP.KeyValue) Flat collection of the profile properties exposed to the caller as key-value pairs. Each entry has Key, Value, and ValueType fields. In SharePoint Online testing, ValueType was "Edm.String" for all returned entries, regardless of the underlying property type — type information is not preserved in this representation. Multi-value properties are returned as pipe-delimited strings (e.g., "Golf|Baseball|Hiking").

Profile property policy and privacy settings can affect which properties are visible to a particular caller.

Shape difference in collection contexts: When PersonProperties objects appear inside GetFollowersFor or GetPeopleFollowedBy responses, the shape changes. DirectReports, ExtendedManagers, and Peers become empty strings rather than arrays. ExtendedReports becomes a plain string containing just the person's own login name (not an array, and not empty). UserProfileProperties becomes a whitespace-only string rather than a key-value collection. Use GetPropertiesFor to get the full shape for a specific user (validated against SharePoint Online).

SP.KeyValue is a simple complex type with three string properties: Key, Value, and ValueType.


Reading Profile Data

Get Properties for a Specific User

Returns the full PersonProperties for a given user, identified by their claims-format account name:

GET https://contoso.sharepoint.com/_api/SP.UserProfiles.PeopleManager/GetPropertiesFor(accountName=@v)?@v='i:0%23.f%7Cmembership%7Cadele@contoso.com'
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Authorization: Bearer <token>

The accountName parameter uses the @v URL alias pattern because the claims-format login name contains characters that must be percent-encoded in a URL. Percent-encode characters that require encoding (#%23; these examples also encode | as %7C), then wrap the encoded login name in the single quotes required by the OData alias.

Response — 200 OK (abridged)

{
  "AccountName": "i:0#.f|membership|adele@contoso.com",
  "DirectReports": [],
  "DisplayName": "Adele Vance",
  "Email": "adele@contoso.com",
  "ExtendedManagers": [
    "i:0#.f|membership|admin@contoso.com"
  ],
  "ExtendedReports": [
    "i:0#.f|membership|adele@contoso.com"
  ],
  "IsFollowed": false,
  "LatestPost": null,
  "Peers": [],
  "PersonalSiteHostUrl": "https://contoso-my.sharepoint.com:443/",
  "PersonalUrl": "https://contoso-my.sharepoint.com/personal/adele_contoso_com/",
  "PictureUrl": "https://contoso-my.sharepoint.com/User%20Photos/Profile%20Pictures/adele_contoso_com_MThumb.jpg?t=63871234567",
  "Title": "Sales Representative",
  "UserProfileProperties": [
    { "Key": "UserName", "Value": "adele@contoso.com", "ValueType": "Edm.String" },
    { "Key": "FirstName", "Value": "Adele", "ValueType": "Edm.String" },
    { "Key": "LastName", "Value": "Vance", "ValueType": "Edm.String" },
    { "Key": "WorkPhone", "Value": "+1 425 555 0100", "ValueType": "Edm.String" },
    { "Key": "Department", "Value": "Sales", "ValueType": "Edm.String" },
    { "Key": "SPS-Skills", "Value": "Sales|CRM|Negotiation", "ValueType": "Edm.String" }
  ],
  "UserUrl": "https://contoso-my.sharepoint.com/Person.aspx?accountname=i%3A0%23.f%7Cmembership%7Cadele%40contoso.com"
}

The UserProfileProperties collection shown above is abridged — in the tenant used for testing, the response contained roughly 90 entries, one per profile property defined in that tenant. Byte-array properties such as ADGuid and SPS-SavedSID serialize as the string "System.Byte[]" — the actual binary data is not available through this representation.

Both app-only (User.Read.All) and delegated (User.Read.All) tokens return 200 (validated against SharePoint Online).


Get a Single Profile Property for a User

Returns the value of one named profile property as a plain string. There is also a GetUserProfilePropertiesFor method (plural) that accepts an array of property names, but it is not implemented in the SharePoint REST API — calling it returns 400 with the message "The method GetUserProfilePropertiesFor cannot be invoked as its parameter propertiesForUser is not supported." Use GetPropertiesFor and filter the UserProfileProperties collection client-side when you need multiple specific properties.

GET https://contoso.sharepoint.com/_api/SP.UserProfiles.PeopleManager/GetUserProfilePropertyFor(accountName=@v,propertyName='Department')?@v='i:0%23.f%7Cmembership%7Cadele@contoso.com'
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Authorization: Bearer <token>

Response — 200 OK

{
  "value": "Sales"
}

The property name is the internal profile property name — the same Key value that appears in the UserProfileProperties collection. Both app-only and delegated tokens return 200 (validated against SharePoint Online).


Get Your Own Properties

Returns the PersonProperties for the caller. With delegated authentication, this returns the signed-in user's real profile. With app-only authentication, it returns the application service principal's pseudo-profile — AccountName is "i:0i.t|00000003-0000-0ff1-ce00-000000000000|app@sharepoint", Email is null, and the remaining fields reflect the app identity rather than any user. GetMyProperties with an app-only token does not error (returns 200), but the data is not meaningful for user profile work (validated against SharePoint Online):

GET https://contoso.sharepoint.com/_api/SP.UserProfiles.PeopleManager/GetMyProperties
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Authorization: Bearer <token>

Minimum scope: User.Read.All (SharePoint resource). Both app-only and delegated tokens return 200. With app-only, the response reflects the app service principal rather than any user — not useful for profile data.

Response — 200 OK — same shape as GetPropertiesFor (validated against SharePoint Online).


Writing Profile Data

Profile write operations are performed through SetSingleValueProfileProperty and SetMultiValuedProfileProperty. Both are POST actions on the PeopleManager endpoint.

Documentation discrepancy: Microsoft's current general user-profile programming documentation states that changing profile properties through REST/client APIs is not implemented and that client profiles are read-only except for the profile picture. However, SharePoint Online accepted both SetSingleValueProfileProperty and SetMultiValuedProfileProperty through REST in live testing. Microsoft's more recent troubleshooting guidance also explicitly documents Entra app-only user-profile updates using User.ReadWrite.All.

Before writing profile properties, consider the source of the data. Properties synchronized from Entra ID — such as display name, job title, and department — are periodically overwritten by the User Profile Service sync job; updating them in SharePoint will not persist, and those properties should be changed at their source in Entra ID. SharePoint-specific and custom profile properties are the appropriate targets for these write operations. Note that the synchronization is one-directional: custom SharePoint profile properties do not sync back to Entra ID.

Delegated write restriction: With delegated authentication, an ordinary user can update their own profile. In testing, attempting to update another user's profile with a non-admin delegated user returned 401 with the message "This operation requires you to be managing your own data or have administrator privileges." Delegated administrator behavior was not tested. App-only authentication successfully updated other users' profiles (both validated against SharePoint Online).

Set a Single-Value Profile Property

Updates the value of one profile property for a user:

POST https://contoso.sharepoint.com/_api/SP.UserProfiles.PeopleManager/SetSingleValueProfileProperty
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Content-Type: application/json;odata.metadata=none
Authorization: Bearer <token>

{
  "accountName": "i:0#.f|membership|adele@contoso.com",
  "propertyName": "AboutMe",
  "propertyValue": "Sales professional based in Seattle."
}

Minimum scope: User.ReadWrite.All (SharePoint resource). Both app-only and delegated tokens work at this scope. With the non-admin delegated user tested, accountName had to match the signed-in user. Delegated administrator cross-user behavior was not tested (validated against SharePoint Online).

Response — 204 No Content


Set a Multi-Value Profile Property

Updates the value of a multi-value profile property. The values are provided as a JSON string array:

POST https://contoso.sharepoint.com/_api/SP.UserProfiles.PeopleManager/SetMultiValuedProfileProperty
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Content-Type: application/json;odata.metadata=none
Authorization: Bearer <token>

{
  "accountName": "i:0#.f|membership|adele@contoso.com",
  "propertyName": "SPS-Skills",
  "propertyValues": ["Sales", "CRM", "Negotiation"]
}

In SharePoint Online testing, SetMultiValuedProfileProperty required both User.ReadWrite.All and TermStore.ReadWrite.All on the SharePoint resource — a token with User.ReadWrite.All alone returned 401 regardless of token type, the caller's SharePoint permission level, or whether the caller is a tenant global admin. Microsoft's troubleshooting documentation associates the TermStore.ReadWrite.All requirement specifically with taxonomy-backed properties, but live testing found that the REST method required it even for a plain-text multi-value property with no taxonomy backing (validated against SharePoint Online). That discrepancy is worth noting if you encounter an unexpected 401 in this scenario.

Response — 204 No Content

When read back through GetPropertiesFor, multi-value properties appear in UserProfileProperties as a pipe-delimited string (e.g., "Sales|CRM|Negotiation").


Following Users

Deprecation notice: Following People is a deprecated classic SharePoint social capability. The endpoints documented in this section remain callable in SharePoint Online, as confirmed by live testing, but this is a legacy system — these APIs are not recommended as the basis for new modern SharePoint solutions.

PeopleManager exposes follow/unfollow operations and follower/following queries.

Follow a User

POST https://contoso.sharepoint.com/_api/SP.UserProfiles.PeopleManager/Follow
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Content-Type: application/json;odata.metadata=none
Authorization: Bearer <delegated token>

{
  "accountName": "i:0#.f|membership|adele@contoso.com"
}

Follow is inherently a delegated operation — it represents a signed-in user choosing to follow another. App-only tokens return 500 with the message "Sorry, you don't have a license to use Newsfeed. Please contact your help desk." (validated against SharePoint Online). Minimum delegated scope: User.ReadWrite.All (SharePoint resource).

Response — 204 No Content

Stop Following a User

POST https://contoso.sharepoint.com/_api/SP.UserProfiles.PeopleManager/StopFollowing
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Content-Type: application/json;odata.metadata=none
Authorization: Bearer <delegated token>

{
  "accountName": "i:0#.f|membership|adele@contoso.com"
}

Same permission requirements as Follow: delegated only, minimum scope User.ReadWrite.All (SharePoint resource). App-only returned 500 in testing with the message "You are not following this person." — the app principal follows no one, so there is nothing to unfollow. This differs from Follow app-only, which returns a Newsfeed license error before the operation is attempted (validated against SharePoint Online).

Response — 204 No Content (validated against SharePoint Online).


Check If You Are Followed By a User

Returns whether a specific user follows the caller:

GET https://contoso.sharepoint.com/_api/SP.UserProfiles.PeopleManager/AmIFollowedBy(accountName=@v)?@v='i:0%23.f%7Cmembership%7Cadele@contoso.com'
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Authorization: Bearer <delegated token>

Minimum scope: User.Read.All (SharePoint resource). "I" refers to the signed-in user. App-only tokens return 200 with false — the application service principal is not followed by anyone (validated against SharePoint Online).

Response — 200 OK (validated against SharePoint Online)

{
  "value": true
}

Check If You Are Following a User

Returns whether the caller follows a specific user:

GET https://contoso.sharepoint.com/_api/SP.UserProfiles.PeopleManager/AmIFollowing(accountName=@v)?@v='i:0%23.f%7Cmembership%7Cadele@contoso.com'
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Authorization: Bearer <delegated token>

Minimum scope: User.Read.All (SharePoint resource). "I" refers to the signed-in user in delegated calls. App-only tokens return 200 with false — the application service principal follows no one (validated against SharePoint Online).

Response — 200 OK (validated against SharePoint Online)

{
  "value": true
}

Get Followers for a User

Returns the list of users who follow a given account:

GET https://contoso.sharepoint.com/_api/SP.UserProfiles.PeopleManager/GetFollowersFor(accountName=@v)?@v='i:0%23.f%7Cmembership%7Cadele@contoso.com'
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Authorization: Bearer <token>

Response — 200 OK (abridged)

{
  "value": [
    {
      "AccountName": "i:0#.f|membership|alex@contoso.com",
      "DisplayName": "Alex Wilber",
      "Email": "alex@contoso.com",
      "DirectReports": "",
      "ExtendedManagers": "",
      "ExtendedReports": "i:0#.f|membership|alex@contoso.com",
      "Peers": "",
      "UserProfileProperties": " ",
      "UserUrl": "https://contoso-my.sharepoint.com/Person.aspx?accountname=..."
    }
  ]
}

Note the truncated shape: DirectReports, ExtendedManagers, and Peers are empty strings (not arrays); ExtendedReports is a plain string containing just the follower's own login name (not an array); and UserProfileProperties is a whitespace-only string (not a key-value collection). This is the collection context shape difference described in the PersonProperties section. Use GetPropertiesFor if you need the full profile data for each follower.

Both app-only and delegated tokens return 200 (validated against SharePoint Online).


Get Users Followed By a User

Returns the list of users that a given account follows:

GET https://contoso.sharepoint.com/_api/SP.UserProfiles.PeopleManager/GetPeopleFollowedBy(accountName=@v)?@v='i:0%23.f%7Cmembership%7Cadele@contoso.com'
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Authorization: Bearer <token>

Response — 200 OK — same shape as GetFollowersFor (validated against SharePoint Online).


Quick Reference

Headers

Header Notes
Authorization: Bearer <token> Required for the raw HTTP OAuth examples in this post. In SPFx, SPHttpClient handles authentication automatically.
Accept: application/json;odata.metadata=none Required for a JSON response. Omitting it causes SharePoint to return Atom XML.
Content-Type: application/json;odata.metadata=none Required on requests with a JSON body (POST write operations).
OData-Version: 4.0 Activates OData v4 behavior. Used throughout the examples in this post.

Response Status Codes

Operation Status Body
GetPropertiesFor 200 OK PersonProperties entity
GetUserProfilePropertyFor 200 OK { "value": "..." } scalar
GetMyProperties 200 OK PersonProperties entity
GetFollowersFor / GetPeopleFollowedBy 200 OK { "value": [...] } collection
AmIFollowedBy / AmIFollowing 200 OK { "value": true/false } scalar
SetSingleValueProfileProperty 204 No Content Empty
SetMultiValuedProfileProperty 204 No Content Empty
Follow / StopFollowing 204 No Content Empty
Follow (app-only) 500 "Sorry, you don't have a license to use Newsfeed. Please contact your help desk."
StopFollowing (app-only) 500 "You are not following this person."
SetMultiValuedProfileProperty without TermStore.ReadWrite.All 401 "The current user has insufficient permissions to perform this operation."
Cross-user write with non-admin delegated token 401 "This operation requires you to be managing your own data..."

Permission Requirements

All User Profile API calls use the SharePoint Online resource (00000003-0000-0ff1-ce00-000000000000) — the same resource as site-level API calls. The scopes below are SharePoint-specific and are distinct from identically named Microsoft Graph scopes.

Operation Minimum delegated scope Notes Minimum app-only scope
GetPropertiesFor User.Read.All (SharePoint) Any user's profile User.Read.All (SharePoint)
GetUserProfilePropertyFor User.Read.All (SharePoint) Any user's profile property User.Read.All (SharePoint)
GetMyProperties User.Read.All (SharePoint) Returns caller's profile User.Read.All (SharePoint) — returns app pseudo-profile, not useful
GetFollowersFor / GetPeopleFollowedBy User.Read.All (SharePoint) User.Read.All (SharePoint)
AmIFollowedBy User.Read.All (SharePoint) "I" = signed-in user User.Read.All (SharePoint) — app-only returns 200/false (app principal is followed by no one)
AmIFollowing User.Read.All (SharePoint) "I" = signed-in user User.Read.All (SharePoint) — app-only returns 200/false (app principal follows no one)
SetSingleValueProfileProperty User.ReadWrite.All (SharePoint) Non-admin: own profile only in testing; delegated administrator behavior not tested User.ReadWrite.All (SharePoint) — any user
SetMultiValuedProfileProperty User.ReadWrite.All + TermStore.ReadWrite.All (SharePoint) Non-admin: own profile only in testing; delegated administrator behavior not tested; TermStore.ReadWrite.All required in testing even for non-taxonomy properties User.ReadWrite.All + TermStore.ReadWrite.All (SharePoint) — any user
Follow / StopFollowing User.ReadWrite.All (SharePoint) Delegated only Not applicable — app-only returns 500 (Follow: Newsfeed license error; StopFollowing: "You are not following this person")

Note: All scopes listed are on the Office 365 SharePoint Online API (resource 00000003-0000-0ff1-ce00-000000000000), not Microsoft Graph. An SPFx solution using SPHttpClient does not require explicit scope grants — it calls SharePoint as the current user, whose profile access is governed by SharePoint's own rules.


Wrapping Up

SP.UserProfiles.PeopleManager provides a unified endpoint for reading and writing SharePoint user profile data. A few things worth keeping in mind:

  • Profile operations are not scoped to the SharePoint site used to make the request. The resource is available as <siteUri>/_api/SP.UserProfiles.PeopleManager; the tenant root and MySite host both worked in SharePoint Online testing.
  • The accountName parameter accepts the claims-format login name (i:0#.f|membership|user@contoso.com). When used with the @v alias pattern, percent-encode characters that require encoding (#%23; these examples also encode | as %7C), then wrap the encoded login name in the single quotes required by the OData alias.
  • UserProfileProperties in the PersonProperties response is a flat collection of all profile properties as key-value pairs. All ValueType entries read "Edm.String" regardless of the actual property type, and byte-array properties like ADGuid serialize as the string "System.Byte[]". Multi-value properties appear as pipe-delimited strings.
  • The PersonProperties shape changes when entries appear inside follower/following collection responses — DirectReports, ExtendedManagers, and Peers become empty strings; ExtendedReports becomes a plain string of just the person's own login name; and UserProfileProperties becomes a whitespace string. Use GetPropertiesFor when you need the full shape for a specific user.
  • SetMultiValuedProfileProperty requires TermStore.ReadWrite.All in addition to User.ReadWrite.All. In SharePoint Online testing, this was required even for a plain-text multi-value property with no taxonomy backing, despite Microsoft's documentation associating that permission with taxonomy updates.
  • Follow, StopFollowing, AmIFollowedBy, AmIFollowing, GetFollowersFor, and GetPeopleFollowedBy are part of the classic SharePoint Following People feature, which is deprecated in SharePoint Online. The endpoints remain callable, but these APIs are not recommended for new modern solutions.
  • Follow and StopFollowing are inherently delegated operations. App-only calls return 500 in both cases, but with different errors: Follow returns a Newsfeed license error, while StopFollowing returns "You are not following this person." — consistent with the app principal having no follow relationship (both validated against SharePoint Online).
  • With delegated authentication, an ordinary user can update their own profile. In testing, a non-admin delegated user attempting to update another user's profile returned 401. Delegated administrator behavior was not tested. App-only authentication successfully updated other users' profiles.

Happy coding!