Building a custom SCIM integration to manage Perk users
By the end of this guide, you will have built a custom integration that provisions and manages your Perk user base automatically through the System for Cross-domain Identity Management (SCIM) API.
Perk exposes a user management API so you can connect Perk to the tools you already use and keep your user base clean and simple to manage. That API uses the SCIM 2.0 protocol, which is widely supported across identity providers such as Microsoft Azure and Okta. If those providers can read your user data, they can be connected to Perk directly. If you store your user information somewhere else, you can build your own integration by following this guide.
This tutorial uses Python with the requests library to make HTTP requests. For the underlying protocol, see the IETF SCIM standard (RFC 7644) and the SCIM API reference.
Time to complete: ~30 minutes
Prerequisites
Before you start, make sure you have:
- A Perk account with account admin access
- An API key (see Step 2: Authenticate)
- Python 3 with the
requestslibrary installed - A source of user data you can read programmatically (your identity provider or HR tool)
Step 1: Get your user data
Your first step depends on where your user information lives. Find the API your identity provider or HR tool offers for retrieving your user list. Here are the documentation pages for some commonly used tools:
For this tutorial, retrieve three attributes for each user: their work email (used as the Perk username), their first name, and their last name. You can extend the integration with more fields from there. For the full list of attributes the API supports, see the SCIM user model reference.
Step 2: Authenticate
You need an API key to call the Perk API. To create one, see Create an API key.
NoteIf you can't create an API key, ask one of your Perk account admins to either make you an admin temporarily or create a key and share it with you securely.
Add your key to every request in an Authorization header:
Authorization: apikey <your_api_key>This tutorial defaults to the sandbox environment so you can create and update test data safely. To get a sandbox account, sign up at the Perk sandbox sign-up page and generate an API key there. The sandbox base URL is https://app.sandbox-perk.com/api/v2/scim. The same SCIM integration is created automatically the first time you call any endpoint — you don't need to set anything up in the Perk web app first.
Test your key by listing the users in your sandbox account:
curl --get \
--url "https://app.sandbox-perk.com/api/v2/scim/Users" \
--header "Authorization: apikey <your_api_key>"The same request in Python:
import requests
base_url = "https://app.sandbox-perk.com/api/v2/scim/"
def get_all_users(api_key):
response = requests.get(
base_url + "Users",
headers={"Authorization": f"apikey {api_key}"},
)
return response.json()The sandbox provides a couple of users by default, so you should see them in the response.
NoteThe same API key works in both sandbox and production. To switch to production, change the base URL to
https://app.perk.com/api/v2/scim. See Verify your integration.
Step 3: Provision users
Now that your data is ready and your key works, you can start provisioning users. At a high level, you will:
- Store a mapping of your users to their Perk user IDs (Perk uses these IDs to identify them).
- Create the users that don't yet have a Perk ID.
- Fetch your Perk users in batches and update any that need it.
Map your users to their Perk profile
Assuming you identify your users by their work email, use that email to match them in Perk:
user_map = dict()
def map_all_users(all_users):
for user in all_users: # Your source user data
if user.email not in user_map:
user_map[user.email] = {
"first_name": user.first_name,
"last_name": user.last_name,
"email": user.email,
"perk_id": None,
}Create users that don't exist
Next, go through your mapping and check whether each user already exists. Perk's user filtering endpoint filters by the userName attribute, which is the work email:
def get_user_by_email(api_key, user_email):
response = requests.get(
base_url + f"Users?filter=userName eq USER_EMAIL",
headers={"Authorization": f"apikey {api_key}"},
)
payload = response.json()
if payload["totalResults"] == 0:
return None
return payload["Resources"][0]Two small helpers generate the user payload and decode Perk's response:
def generate_user_payload(user):
payload = {
"name": {"givenName": user["first_name"], "familyName": user["last_name"]},
"userName": user["email"],
"active": True,
}
if user.get("perk_id"):
payload["id"] = user["perk_id"]
return payload
def decode_user_payload(payload):
return {
"first_name": payload["name"]["givenName"],
"last_name": payload["name"]["familyName"],
"perk_id": payload["id"],
}
NoteA user marked as
activecan sign in, book trips, and be booked for by admins. An inactive user disappears from the platform until reactivated. You can't deactivate a user who has pending trips.
Add a method to create a user in Perk from your stored data:
def create_user(api_key, payload):
response = requests.post(
base_url + "Users",
headers={"Authorization": f"apikey {api_key}"},
json=payload,
)
return response.json()With those helpers in place, you can create the missing users:
def create_missing_users(api_key):
for user_email, user_info in user_map.items():
if not user_info.get("perk_id"):
perk_user = get_user_by_email(api_key, user_email)
if not perk_user:
payload = generate_user_payload(user_info)
perk_user = create_user(api_key, payload)
user_map[user_email]["perk_id"] = perk_user["id"]Update existing users
Now that your map is fully populated, make sure the data stored in Perk is up to date. Start with a method that compares a user to the data Perk returns:
def compare_users(user, perk_user):
# Returns True if there's a difference.
if user["first_name"] != perk_user["name"]["givenName"]:
return True
if user["last_name"] != perk_user["name"]["familyName"]:
return True
if user["email"] != perk_user["userName"]:
return True
return FalseIf your mapping doesn't contain one of the users Perk returns, that user shouldn't be there, so add a method to deactivate them:
def deactivate_user(api_key, user_id):
patch_payload = {
"Operations": [
{"op": "replace", "path": "active", "value": False}
]
}
requests.patch(
base_url + f"Users/USER_ID",
headers={"Authorization": f"apikey {api_key}"},
json=patch_payload,
)If you find any other difference, update the user in Perk:
def update_user(api_key, user):
payload = generate_user_payload(user)
requests.put(
base_url + f"Users/{payload['id']}",
headers={"Authorization": f"apikey {api_key}"},
json=payload,
)Finally, put it all together. Fetch your existing Perk users in batches and sync each one:
def compare_and_update_users(api_key, page_size=10):
start_index = 1
total_results = page_size
while start_index < total_results:
response_payload = get_users(api_key, page_size, start_index)
for perk_user in response_payload["Resources"]:
user = user_map.get(perk_user["userName"])
if not user:
deactivate_user(api_key, perk_user["id"])
elif compare_users(user, perk_user):
update_user(api_key, user)
total_results = response_payload["totalResults"]
start_index += page_sizeYour basic SCIM integration with Perk is now in place.
Handle errors and rate limits
Real-world integrations need to handle failures. The SCIM troubleshooting guide lists the error details Perk returns for client errors, so you can debug your application.
Each of the five methods that call the Perk API — get_users, create_user, get_user_by_email, deactivate_user, and update_user — should check the response status and surface any error:
if response.status_code != 200: # 201 for create_user
# Insert your preferred way of reporting errors (email, chat message, log).
print(response.json()["detail"])Perk also applies request throttling (see Rate limits), so we recommend implementing exponential backoff. Trigger a retry when the API returns a 429 or 500 response. For a simple example, see Retry with exponential backoff.
Verify your integration
Before you manage real users, run your own tests to confirm you're confident in the implementation. We recommend a read-only first pass: instead of creating and updating users, log the data you would write and check that it's correct.
To switch to production:
- Change
base_urltohttps://app.perk.com/api/v2/scim. - Generate a production API key from Create an API key.

