sayhii Organization API
The sayhii Organization API helps you keep your employee data and shared dictionaries in sync—so your people always have access to the latest information, without manual uploads or spreadsheet wrangling.
Base URL:
https://connect.sayhii.io
Authorization Header:Authorization: ApiKey <key>
Org Context: The API key itself determines your organization.
Only customer admins can create API keys. Share keys only with trusted developers or services, and scope them appropriately. API Keys can be managed in the sayhii Portal, for more information see API Keys
The sayhii Organization API is not available to all Organizations by default. Get in touch with us if you are interested in using the API to keep your employee data up to date in sayhii.
Quick Start
Sync your user list in just a few lines of code:
- curl
- python
- node.js
curl -H "Authorization: ApiKey $API_KEY" https://connect.sayhii.io/users
import os, requests
API_KEY = os.environ.get("SAYHII_API_KEY", "YOUR_KEY_HERE")
resp = requests.get(
"https://connect.sayhii.io/users",
headers={"Authorization": f"ApiKey {API_KEY}"}
)
resp.raise_for_status()
print(resp.json())
// Node 18+ has global fetch
const API_KEY = process.env.SAYHII_API_KEY || 'YOUR_KEY_HERE';
const res = await fetch('https://connect.sayhii.io/users', {
headers: { Authorization: `ApiKey ${API_KEY}` }
});
if (!res.ok) throw new Error(`Request failed: ${res.status}`);
console.log(await res.json());
Users
The Users endpoints let you retrieve employee information from sayhii, so you can see who is already loaded and configured in sayhii.
GET /users
Get a paginated list of everyone in your organization.
Query Parameters
- limit (
number, optional): Maximum number of users to return in this response. - nextToken (
string, optional): Token for retrieving the next page of results (returned by a previous call).
Response
Returns a JSON object:
- users (
UserSchema[], required): Array of user objects (length ≤limit). - nextToken (
string, optional): Token for retrieving the next page (pass as a parameter to the next API call). Omitted if there are no more results.
Notes
- Sort Order: The order of users is not guaranteed—do not rely on array order.
- Use
limitandnextTokenfor efficient pagination through large user lists. - If you don’t provide a
limit, the API will return as many users as possible in a single response (up to a maximum payload size of 300MB). If there are more users, you’ll get anextTokento fetch the next batch.
For large organizations, always use pagination to avoid timeouts and keep your integration reliable.
Example
- curl
- python
- node.js
curl -H "Authorization: ApiKey $API_KEY" "https://connect.sayhii.io/users?limit=100"
import os, requests
API_KEY = os.environ.get("SAYHII_API_KEY", "YOUR_KEY_HERE")
params = {"limit": 100}
resp = requests.get("https://connect.sayhii.io/users", params=params, headers={"Authorization": f"ApiKey {API_KEY}"})
resp.raise_for_status()
users = resp.json().get("users", [])
print(len(users), "users returned")
const API_KEY = process.env.SAYHII_API_KEY || 'YOUR_KEY_HERE';
const url = new URL('https://connect.sayhii.io/users');
url.searchParams.set('limit', '100');
const res = await fetch(url, { headers: { Authorization: `ApiKey ${API_KEY}` }});
if (!res.ok) throw new Error(`Request failed: ${res.status}`);
const { users = [] } = await res.json();
console.log(`${users.length} users returned`);
POST /users
Add or update users in batch upsert mode—perfect for syncing your HRIS or directory.
- Body: Array of
UserSchemaobjects - Matching Logic:
- Match on email first → update attributes.
- If email doesn’t match but id does → treat as a new record with the new email (old record replaced).
- Batch Size: Recommend ≤ 100,000 users (~200 MB) for reliability.
- Responses:
200– All records processed successfully210– Partial success (checkerrors[])400,401,500,502– Error
Example
- curl
- python
- node.js
curl -X POST -H "Authorization: ApiKey $API_KEY" \
-H "Content-Type: application/json" \
-d '[{
"id": "123",
"email": "jane@example.com",
"firstName": "Jane",
"lastName": "Doe",
"department": "Marketing"
}]' \
https://connect.sayhii.io/users
import os, requests, json
API_KEY = os.environ.get("SAYHII_API_KEY", "YOUR_KEY_HERE")
payload = [{
"id": "123",
"email": "jane@example.com",
"firstName": "Jane",
"lastName": "Doe",
"department": "Marketing"
}]
resp = requests.post(
"https://connect.sayhii.io/users",
headers={
"Authorization": f"ApiKey {API_KEY}",
"Content-Type": "application/json"
},
data=json.dumps(payload)
)
print(resp.status_code, resp.json())
const API_KEY = process.env.SAYHII_API_KEY || 'YOUR_KEY_HERE';
const payload = [{
id: '123',
email: 'jane@example.com',
firstName: 'Jane',
lastName: 'Doe',
department: 'Marketing'
}];
const res = await fetch('https://connect.sayhii.io/users', {
method: 'POST',
headers: {
'Authorization': `ApiKey ${API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
console.log(res.status, await res.json());
Partial Failure Response
{
"successCount": 9,
"failureCount": 1,
"message": "Processed with some errors",
"errors": [
{
"userId": "456",
"error": "Invalid department value"
}
]
}
Values for dictionary‑controlled user attributes (department, jobFunction, workType, role, gender, race, ethnicity) must match an existing dictionary entry. If any value is not recognized, that user record is rejected and an error is returned. To introduce a new value, update the appropriate dictionary first (via PUT /dictionaries or PATCH /dictionaries). See the UserSchema and DictionarySchema sections for definitions and examples.
GET /users/{id}
Look up a single user by their id (not email).
- Path Parameter:
id– The unique identifier you provided asid. - Response: Single user object (
UserSchema)
- curl
- python
- node.js
curl -H "Authorization: ApiKey $API_KEY" https://connect.sayhii.io/users/123
import os, requests
API_KEY = os.environ.get("SAYHII_API_KEY", "YOUR_KEY_HERE")
user_id = "123"
resp = requests.get(
f"https://connect.sayhii.io/users/{user_id}",
headers={"Authorization": f"ApiKey {API_KEY}"}
)
print(resp.status_code, resp.json())
const API_KEY = process.env.SAYHII_API_KEY || 'YOUR_KEY_HERE';
const userId = '123';
const res = await fetch(`https://connect.sayhii.io/users/${userId}`, {
headers: { Authorization: `ApiKey ${API_KEY}` }
});
console.log(res.status, await res.json());
Dictionaries
Dictionaries define the valid values for job functions, departments, work types, and similar fields—helping you keep your data clean and consistent. They also control which options users see when registering through the sayhii app, if your organization has enabled self-registration.
GET /dictionaries
Retrieve the full DictionarySchema. All keys are always present—missing values are filled with defaults.
- curl
- python
- node.js
curl -H "Authorization: ApiKey $API_KEY" https://connect.sayhii.io/dictionaries
import os, requests
API_KEY = os.environ.get("SAYHII_API_KEY", "YOUR_KEY_HERE")
resp = requests.get(
"https://connect.sayhii.io/dictionaries",
headers={"Authorization": f"ApiKey {API_KEY}"}
)
print(resp.json().keys())
const API_KEY = process.env.SAYHII_API_KEY || 'YOUR_KEY_HERE';
const res = await fetch('https://connect.sayhii.io/dictionaries', {
headers: { Authorization: `ApiKey ${API_KEY}` }
});
console.log(Object.keys(await res.json()));
GET /dictionaries/{dictionaryType}
Retrieve values for a single dictionary.
- Valid Types:
jobFunction,role,gender,race,ethnicity,workType,department
- curl
- python
- node.js
curl -H "Authorization: ApiKey $API_KEY" https://connect.sayhii.io/dictionaries/department
import os, requests
API_KEY = os.environ.get("SAYHII_API_KEY", "YOUR_KEY_HERE")
dict_type = "department"
resp = requests.get(
f"https://connect.sayhii.io/dictionaries/{dict_type}",
headers={"Authorization": f"ApiKey {API_KEY}"}
)
print(resp.json())
const API_KEY = process.env.SAYHII_API_KEY || 'YOUR_KEY_HERE';
const dictType = 'department';
const res = await fetch(`https://connect.sayhii.io/dictionaries/${dictType}`, {
headers: { Authorization: `ApiKey ${API_KEY}` }
});
console.log(await res.json());
PUT /dictionaries
Replace dictionaries with a new set of values.
- curl
- python
- node.js
curl -X PUT -H "Authorization: ApiKey $API_KEY" \
-H "Content-Type: application/json" \
-d '{ "department": ["Marketing","Sales","Engineering"] }' \
https://connect.sayhii.io/dictionaries
import os, requests, json
API_KEY = os.environ.get("SAYHII_API_KEY", "YOUR_KEY_HERE")
payload = {"department": ["Marketing","Sales","Engineering"]}
resp = requests.put(
"https://connect.sayhii.io/dictionaries",
headers={
"Authorization": f"ApiKey {API_KEY}",
"Content-Type": "application/json"
},
data=json.dumps(payload)
)
print(resp.status_code, resp.json())
const API_KEY = process.env.SAYHII_API_KEY || 'YOUR_KEY_HERE';
const payload = { department: ['Marketing','Sales','Engineering'] };
const res = await fetch('https://connect.sayhii.io/dictionaries', {
method: 'PUT',
headers: {
'Authorization': `ApiKey ${API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
console.log(res.status, await res.json());
This example replaces every dictionary: because only department values are provided, all other dictionaries revert to their defaults while department is set to the custom list you supplied.
PATCH /dictionaries
Merge new values by replacing only the provided keys.
- curl
- python
- node.js
curl -X PATCH -H "Authorization: ApiKey $API_KEY" \
-H "Content-Type: application/json" \
-d '{ "role": ["User","Admin","Manager"] }' \
https://connect.sayhii.io/dictionaries
import os, requests, json
API_KEY = os.environ.get("SAYHII_API_KEY", "YOUR_KEY_HERE")
payload = {"role": ["User","Admin","Manager"]}
resp = requests.patch(
"https://connect.sayhii.io/dictionaries",
headers={
"Authorization": f"ApiKey {API_KEY}",
"Content-Type": "application/json"
},
data=json.dumps(payload)
)
print(resp.status_code, resp.json())
const API_KEY = process.env.SAYHII_API_KEY || 'YOUR_KEY_HERE';
const payload = { role: ['User','Admin','Manager'] };
const res = await fetch('https://connect.sayhii.io/dictionaries', {
method: 'PATCH',
headers: {
'Authorization': `ApiKey ${API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
console.log(res.status, await res.json());
This PATCH updates only the role dictionary; every other dictionary keeps its previously defined values (whether default or custom) unchanged.
Only admins with appropriately scoped API keys can modify dictionaries or users.
Schemas
UserSchema
| Field | Type | Required | Notes |
|---|---|---|---|
| id | string | ✅ | Unique per org. Use your backend’s stable identifier. |
| string | ✅ | Case-insensitive. Must be unique. | |
| firstName | string | Optional. | |
| lastName | string | Optional. | |
| department | string | Must match a dictionary value. | |
| jobFunction | string | Must match a dictionary value. | |
| workType | string | Must match a dictionary value. | |
| role | string | Must match a dictionary value. | |
| managerEmail | string | Authoritative for hierarchy. | |
| managerGuid | string | Optional reference if manager email changes. | |
| guid | string | Optional reference for your backend system. | |
| hireYearAndMonth | string | Format YYYY.MM. | |
| yearOfBirth | string | Four-digit year. | |
| terminated | string | 'Y' or 'N' (default 'N'). | |
| disabled | boolean | Can be toggled by POST. Default false. | |
| portalQuestions | boolean | Whether user can answer in portal. Default false. | |
| workingZipCode | string | Optional. | |
| race/ethnicity/gender | string | Must match dictionary values. |
DictionarySchema
Each key contains an array of valid strings:
| Key | Example Values |
|---|---|
| jobFunction | ["Engineering","Marketing"] |
| role | ["User","Admin","Manager"] |
| gender | ["Male","Female","Non-binary"] |
| race | ["Asian","White","Black"] |
| ethnicity | ["Hispanic","Non-Hispanic"] |
| workType | ["Remote","Hybrid","Onsite"] |
| department | ["Sales","HR","Product"] |
Errors
RequestError
| Field | Type | Description |
|---|---|---|
| message | string | Human-readable description. |
| details | string | Additional error details (optional) |
| stack | string | Debug information (optional) |
Common Status Codes
| Code | Meaning |
|---|---|
| 200 | Success |
| 210 | Partial success (check errors) |
| 400 | Bad request |
| 401 | Unauthorized (invalid API key) |
| 404 | Not found |
| 500 | Internal server error |
| 502 | Upstream error |
Best Practices
- Batch Size: Keep requests ≤ 100,000 users (~200 MB) for reliability.
- Avoid PHI: Do not send protected health information.
- Case-Insensitive Emails: Emails are lowercased for matching.
- Retries: Use exponential backoff for 5xx/502 responses.
- Security: Provide keys only to trusted parties. Keys are org-scoped.
Changelog & Support
The API does not use versioned paths today. Breaking changes will be announced with advance notice.
For help or questions: support@sayhii.io