Tags | Private teams | API | automation |
ADMIN PRIVILEGES REQUIRED
This documentation is for Stack Overflow for Teams Enterprise. Free, Basic, and Business users can access their documentation here. Find your plan.
Overview
The introduction of Private Teams in Stack Overflow for Teams Enterprise (SOE) allows site administrators to restrict the sharing of sensitive information to only those who should have access. To streamline and automate management of Private Team rosters, SOE offers team membership sync with SOE's API v2.
NOTE: These instructions are for Stack Overflow API v2, not API v3. API v3 does not support team membership sync.
Syncing team membership via API push
To enable API team membership sync:
Click Admin settings in the left-hand menu, then Teams sync.
Click Push data via API.
Click Use API push.
With team membership sync enabled, your site will update Private Teams membership each time you push a properly-formatted JSON file to https:[your_site]/api/2.3/enterprise/usersync. This is your site's user sync API endpoint.
NOTE: This special endpoint is not documented on your site at https://[your_site]/api/docs or in the API v2 support article.
Team membership sync authorization
To use the API to sync team memberships, you need an OAuth access token owned by a site admininstrator. The API team sync endpoint will reject access tokens owned by regular users, or community service keys. Learn more about acquiring an OAuth token at https://[your_site]/api/docs/authentication.
To call the user sync API, make a form-type POST request (encoded "application/x-www-form-urlencoded") to your site's usersync URL (https:[your_site]/api/2.3/enterprise/usersync). Use the following form fields.
Form field | Definition |
| The OAuth access token (see "Write-enabled API" in the API Docs) |
| The key from your API Access Keys page |
| A JSON array of user sync requests, with one entry for each team. |
| (boolean) If true, will do a preview of the sync process. |
Example JSON request
[{"Team":"TEAM-NAME","Members":[{"UserIdentifier":"USER-ID-1","Level":"USER-LEVEL-1"},{"UserIdentifier":"USER-ID-2,"Level":"USER-LEVEL-2"}]}]
Include the following fields in your JSON request:
TEAM-NAME The team to sync membership with
USER-ID The user ID of the user
USER-LEVEL The level (role) of the user in the team ("Member", "Admin", or "Moderator")
NOTE: The team sync API call is a full update operation of the membership roster (not an append operation). You must include all the members of the team with each API call. Team sync will remove any existing team members not included in the JSON data file.
Example HTTP request
POST https://stackoverflowenterprise.example.com/api/2.3/enterprise/usersync HTTP/1.1 Connection: Keep-Alive Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded Content-Length: 964 Host: stackoverflowenterprise.example.comaccess_token=c9GJIziqUtqx0uBr7t4mdw))&key=iyGkn*C5yqRXRzinhFwkdw((&requestsJson=%5b%0d%0a++%7b%0d%0a++++%22Team%22%3a+%22productone%22%2c%0d%0a++++%22Members%22%3a+%5b%0d%0a++++++%7b%0d%0a++++++++%22UserIdentifier%22%3a+%22S-1-5-21-927147286-1022559569-3540382430-1105%22%2c%0d%0a++++++++%22Level%22%3a+%22Admin%22%0d%0a++++++%7d%2c%0d%0a++++++%7b%0d%0a++++++++%22UserIdentifier%22%3a+%22S-1-5-21-927147286-1022559569-3540382430-1107%22%2c%0d%0a++++++++%22Level%22%3a+%22Member%22%0d%0a++++++%7d%0d%0a++++%5d%0d%0a++%7d%2c%0d%0a++%7b%0d%0a++++%22Team%22%3a+%22internalchat%22%2c%0d%0a++++%22Members%22%3a+%5b%0d%0a++++++%7b%0d%0a++++++++%22UserIdentifier%22%3a+%2202580093-e2e7-4ba8-abf6-c9ae355c745b%22%2c%0d%0a++++++++%22Level%22%3a+%22Admin%22%0d%0a++++++%7d%2c%0d%0a++++++%7b%0d%0a++++++++%22UserIdentifier%22%3a+%22d17c2397-2eb5-4a22-959b-4935727390d0%22%2c%0d%0a++++++++%22Level%22%3a+%22Member%22%0d%0a++++++%7d%0d%0a++++%5d%0d%0a++%7d%0d%0a%5d&dryRun=False
Example HTTP response
{ "items": [ { "HasErrors": true, "Results": [ { "StatusCode": "TeamNotFound", "Team": "notexisting" }, { "SyncResult": { "IntendedChanges": [], "ActualChanges": [], "Status": "Success", "SiteName": "productone", "Log": "[PushAsync] START User Sync from Push for Team productone (6/17/2019 3:00:22 PM -04:00)\r\nSwitched Sites: 10026 => 10026\r\nDesired Site Membership (Total Count: 0):\r\n +----------------------------------------------------+----------------------+\r\n | Credential | Access Level |\r\n +----------------------------------------------------+----------------------+\r\n +----------------------------------------------------+----------------------+\r\n\r\nLooking up Accounts...\r\n Credential Type to look up: SAML\r\n \r\n +-----------+----------------------------------------------------+------------+--------------+\r\n | Resolved? | Credential | AccountId | Access Level |\r\n +-----------+----------------------------------------------------+------------+--------------+\r\n +-----------+----------------------------------------------------+------------+--------------+\r\n\r\nDetermining intended changes...\r\n\r\nApplying User Changes...\r\n No changes needed.\r\n\r\nFinal Sync Result: Success\r\n[PushAsync] END User Sync (6/17/2019 3:00:22 PM -04:00)\r\n" }, "StatusCode": "Success", "Team": "productone" }, { "SyncResult": { "IntendedChanges": [ { "IsDeactivated": false, "AccountId": 13645930, "Change": "NoChange", "NewUserType": "Admin", "CurrentUserType": "Admin", "SiteUserId": 1 }, { "IsDeactivated": false, "AccountId": 13645931, "Change": "NoChange", "NewUserType": "Registered", "CurrentUserType": "Registered", "SiteUserId": 2 } ], "ActualChanges": [], "Status": "Success", "SiteName": "internalchat", "Log": "[PushAsync] START User Sync from Push for Team internalchat (6/17/2019 3:00:22 PM -04:00)\r\nSwitched Sites: 10029 => 10029\r\nDesired Site Membership (Total Count: 3):\r\n +----------------------------------------------------+----------------------+\r\n | Credential | Access Level |\r\n +----------------------------------------------------+----------------------+\r\n | 02580093-e2e7-4ba8-abf6-c9ae355c745b | Admin |\r\n | d17c2397-2eb5-4a22-959b-4935727390d0 | Registered |\r\n | gfbiuweiuviopugw | Registered |\r\n +----------------------------------------------------+----------------------+\r\n\r\nLooking up Accounts...\r\n Credential Type to look up: SAML\r\n Trying to find Account with Credential 02580093-e2e7-4ba8-abf6-c9ae355c745b\r\n [02580093-e2e7-4ba8-abf6-c9ae355c745b] Found Stack Overflow Enterprise Account, ID '13645930' for Credential '02580093-e2e7-4ba8-abf6-c9ae355c745b'.\r\n Trying to find Account with Credential d17c2397-2eb5-4a22-959b-4935727390d0\r\n [d17c2397-2eb5-4a22-959b-4935727390d0] Found Stack Overflow Enterprise Account, ID '13645931' for Credential 'd17c2397-2eb5-4a22-959b-4935727390d0'.\r\n Trying to find Account with Credential gfbiuweiuviopugw\r\n [gfbiuweiuviopugw] No Account with that SAML credential found, trying to find by email instead...\r\n [gfbiuweiuviopugw] No Stack Overflow Enterprise account found for Credential gfbiuweiuviopugw\r\n \r\n +-----------+----------------------------------------------------+------------+--------------+\r\n | Resolved? | Credential | AccountId | Access Level |\r\n +-----------+----------------------------------------------------+------------+--------------+\r\n | yes | 02580093-e2e7-4ba8-abf6-c9ae355c745b | 13645930 | Admin |\r\n | yes | d17c2397-2eb5-4a22-959b-4935727390d0 | 13645931 | Registered |\r\n | no | gfbiuweiuviopugw | | Registered |\r\n +-----------+----------------------------------------------------+------------+--------------+\r\n\r\nDetermining intended changes...\r\n +----------------+------------+---------------+---------------+--------------+\r\n | Change | AccountId | CurrentAccess | NewAccess | Deactivated? |\r\n +----------------+------------+---------------+---------------+--------------+\r\n | NoChange | 13645930 | Admin | Admin | no |\r\n | NoChange | 13645931 | Registered | Registered | no |\r\n +----------------+------------+---------------+---------------+--------------+\r\n\r\nApplying User Changes...\r\n No changes needed.\r\n\r\nFinal Sync Result: Success\r\n[PushAsync] END User Sync (6/17/2019 3:00:22 PM -04:00)\r\n" }, "StatusCode": "Success", "Team": "productone" } ] } ], "has_more": false, "quota_max": 10000, "quota_remaining": 9999, "page": 1, "page_size": 1, "total": 1, "type": "user_sync_api_response" }
Status codes—overall sync process
The HTTP response will include a status code for each Private Team updated.
Status code | Definition |
Success | The entire sync process succeeded |
PartialSyncFailure | The sync process started, achieved partial success, then failed |
SuccessfulDryRun | The sync process completed as a dry run (preview mode) |
OtherError | An unspecified error occurred |
UserSyncNotEnabled | The Private Team is not set to be managed by API user sync |
FailedToGetMembersFromSource | There was some problem with parsing the JSON |
ErrorTryingToResolveUsers | There was a system error when trying to find users for the given identifiers |
FailedToDetermineChanges | There was a system error when trying to determine intended changes |
ErrorApplyingChanges | There was a system error when trying to make the actual changes |
TeamNotFound | No team with the given URL could be found |
The basic API response matches other API methods. It will always be a single page with a single item:
{ "items": [{...}], "has_more": false, "quota_max": 10000, "quota_remaining": 9999, "page": 1, "page_size": 1, "total": 1, "type": "user_sync_api_response" }
The data item itself is a single element of type items-array
, a root object with two properties:
Results
is an array containing one item per individual team that you requested.HasErrors
is a boolean that indicates if any result has an error. This is a quick way to see if the entire bulk operation/batch succeeded, thus saving having to dig deep into individual results.
{ "HasErrors": true, "Results": [...] }
Each result item contains information about a single Private Team:
Team
is the URL slug of the team (https://[your_site]/c/[team_name])SyncResult
is information about the actual result of the team sync. See below for a full list.
SyncResult values
SyncResult value | Definition |
Status | Same as |
SiteName | The display name of the Private Team |
IntendedChanges and | Shows which changes the sync process determined should be done, and which ones were actually done |
Change | May return "NoChange", "AddToSite", "RemoveFromSite", or "ChangeUserType" |
CurrentUserType and | Returns either "Admin" for team owners, or "Registered" for regular members |
AccountId | The AccountId of the user |
SiteUserId | The User Id within that team |
IsDeactivated | Returns "true" if the account is currently deactivated and will reject login |
ActualChanges | Indicates which changes were made (blank on a dry run) |
Log | A human-readable log file that contains verbose information about the sync operation. Log format may change, and is not meant to be machine-parseable. |
NOTE: SyncResult
may be absent for some status codes.
Log example
{ "SyncResult": { "IntendedChanges": [ { "IsDeactivated": false, "AccountId": 13645930, "Change": "NoChange", "NewUserType": "Admin", "CurrentUserType": "Admin", "SiteUserId": 1 }, { "IsDeactivated": false, "AccountId": 13645931, "Change": "NoChange", "NewUserType": "Registered", "CurrentUserType": "Registered", "SiteUserId": 2 } ], "ActualChanges": [], "Status": "Success", "SiteName": "internalchat", "Log": "[PushAsync] START User Sync from Push for Team internalchat (6/17/2019 3:00:22 PM -04:00)\r\nSwitched Sites: 10029 => 10029\r\nDesired Site Membership (Total Count: 3):\r\n +----------------------------------------------------+----------------------+\r\n | Credential | Access Level |\r\n +----------------------------------------------------+----------------------+\r\n | 02580093-e2e7-4ba8-abf6-c9ae355c745b | Admin |\r\n | d17c2397-2eb5-4a22-959b-4935727390d0 | Registered |\r\n | gfbiuweiuviopugw | Registered |\r\n +----------------------------------------------------+----------------------+\r\n\r\nLooking up Accounts...\r\n Credential Type to look up: SAML\r\n Trying to find Account with Credential 02580093-e2e7-4ba8-abf6-c9ae355c745b\r\n [02580093-e2e7-4ba8-abf6-c9ae355c745b] Found Stack Overflow Enterprise Account, ID '13645930' for Credential '02580093-e2e7-4ba8-abf6-c9ae355c745b'.\r\n Trying to find Account with Credential d17c2397-2eb5-4a22-959b-4935727390d0\r\n [d17c2397-2eb5-4a22-959b-4935727390d0] Found Stack Overflow Enterprise Account, ID '13645931' for Credential 'd17c2397-2eb5-4a22-959b-4935727390d0'.\r\n Trying to find Account with Credential gfbiuweiuviopugw\r\n [gfbiuweiuviopugw] No Account with that SAML credential found, trying to find by email instead...\r\n [gfbiuweiuviopugw] No Stack Overflow Enterprise account found for Credential gfbiuweiuviopugw\r\n \r\n +-----------+----------------------------------------------------+------------+--------------+\r\n | Resolved? | Credential | AccountId | Access Level |\r\n +-----------+----------------------------------------------------+------------+--------------+\r\n | yes | 02580093-e2e7-4ba8-abf6-c9ae355c745b | 13645930 | Admin |\r\n | yes | d17c2397-2eb5-4a22-959b-4935727390d0 | 13645931 | Registered |\r\n | no | gfbiuweiuviopugw | | Registered |\r\n +-----------+----------------------------------------------------+------------+--------------+\r\n\r\nDetermining intended changes...\r\n +----------------+------------+---------------+---------------+--------------+\r\n | Change | AccountId | CurrentAccess | NewAccess | Deactivated? |\r\n +----------------+------------+---------------+---------------+--------------+\r\n | NoChange | 13645930 | Admin | Admin | no |\r\n | NoChange | 13645931 | Registered | Registered | no |\r\n +----------------+------------+---------------+---------------+--------------+\r\n\r\nApplying User Changes...\r\n No changes needed.\r\n\r\nFinal Sync Result: Success\r\n[PushAsync] END User Sync (6/17/2019 3:00:22 PM -04:00)\r\n" }, "StatusCode": "Success", "Team": "productone" }