Interface Control Document (ICD): HRMS - EPC
| Abbreviation | Description |
|---|---|
| HRMS | Human Resource Management System |
| EPC | Emirates Platinum Card |
| IDCD | ID Card system (source of employee ID photos) |
| API | Application Programming Interface |
| SSM | AWS Systems Manager (Parameter Store) |
When an employee's photo changes in the HR system, HR will push the new photo to the Platinum Application. The photo replaces the member's ID photo and is approved automatically, so the member does not have to upload and wait for review.
Member/updateEmployeePhoto integration can migrate without changing the request bodyLegacy compatibility: The request body and the instanceId format are identical to the legacy integration POST /{projectId}/CALL/Member/updateEmployeePhoto/{instanceId}. Migrating clients change only the base URL, the path prefix and the API key.
/members/hr/* endpoints)| Risk | Mitigation |
|---|---|
| Network timeouts between HR and EPC | Configure appropriate timeout settings (recommended: 30s) |
| Employee not found in EPC | HR should handle 404 responses gracefully and log for review |
| Photo larger than the request size limit | Keep the decoded photo at or below 4.4 MB. Larger requests are rejected |
| Photo overwritten by another source | EPC also refreshes the photo from IDCD at registration, SSO login and admin HRMS resync. The most recent write is the current photo |
| Sr No. | Non-Functional Requirement | Value |
|---|---|---|
| 1 | PHI or Credit Card data? | No credit card data & no patient health information. The photo is personal data |
| 2 | Authentication Pattern | API Key (X-API-Key header) |
| 3 | Secure Channel / Encryption in Transit | TLS 1.2+ (HTTPS only) |
| 4 | Input validation | Yes - body validated via JSON Schema; photo content checked against the declared file type |
| 5 | Internet exposed? IP whitelisting? | Yes, exposed via CloudFront. IP whitelisting can be implemented via WAF rules |
| 6 | Logging | Request metadata logged (photo content is never logged) |
| 7 | Data exposure | Only the photo storage key and member status are returned |
| 8 | Data at rest | Photo stored in a private S3 bucket, not publicly accessible |
| Component | Value |
|---|---|
| Source | HRMS (Human Resource Management System) |
| Source Protocol | HTTPS REST API |
| Source Data Format | JSON (photo as base64) |
| Target | EPC (Emirates Platinum Card) |
| Target Protocol | HTTPS |
| Target Data Format | JSON |
┌─────────────┐ ┌─────────────┐
│ │ HTTPS / JSON │ │
│ HRMS │ ─────────────────────────▶ │ EPC API │
│ (Source) │ POST │ (Target) │
│ │ ◀───────────────────────── │ │
└─────────────┘ HTTPS / JSON └─────────────┘
Flow:
fileExtension)X-API-Key headerAPI Monitoring
Exception Handling
| HTTP Status | Scenario | HRMS Action |
|---|---|---|
| 200 | Success | Log success, continue |
| 400 | Validation error, or invalid photo (not base64, empty, too large, or content does not match fileExtension) |
Fix request data, retry |
| 401 | Invalid API key | Check API key, contact EPC team |
| 404 | Employee not found (no primary member matches instanceId) |
Log for manual review |
| 500 | Server error | Retry with backoff |
| 503 | Service unavailable | Retry later |
Actors: HRMS System (automated)
Trigger: Employee photo changes in HR
Flow:
instanceId = staffId!{staffId} (e.g. staffId!EK123456)approvedActors: HRMS System (automated)
Trigger: Employee photo changes in HR, and HR holds the EPC member ID
Flow:
instanceId = the member ID (e.g. 2155581665)Actors: HRMS System (automated)
Trigger: The member belongs to a company whose EPC setting "Disable integrations and self-registrations" is on
Flow:
200 with success: true, memberStatus and skipped: "company_hrms_sync_disabled". filePath is not returnedmessage is the same as for an applied photo, so HR reads skipped to tell the two apart| Field | Type | Required | Description | Example |
|---|---|---|---|---|
| instanceId | String | Yes | Employee identifier. staffId!{staffId} looks up by staff ID (! may be sent URL-encoded as %21). Any other value is treated as the member ID (with or without the non-prod stage suffix). |
"staffId!EK123456" |
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
| photo | String (base64) | Yes | Base64 encoded image. A data:image/...;base64, prefix and line breaks are tolerated. Decoded size must not exceed 4.4 MB. |
"iVBORw0KGgoAAAANSUhEUg..." |
| fileExtension | String | Yes | Image format of photo: "jpg", "jpeg", "png", "gif" or "webp". Must match the actual image content ("jpg" and "jpeg" are equivalent). |
"jpg" |
| Field | Type | Description |
|---|---|---|
| success | Boolean | True if operation succeeded |
| filePath | String | Storage key of the photo in EPC. Not returned when skipped is present |
| memberStatus | String | Member status after the update (unchanged by this call), e.g. "active" |
| skipped | String (optional) | company_hrms_sync_disabled when the photo was accepted but not applied because the member's company is managed by hand in EPC. Nothing was stored |
| message | String | Human-readable message |
Authentication: API Key via X-API-Key header
X-API-Key: <your-api-key>
Content-Type: application/json
Request:
POST /members/hr/photo/staffId!EK123456 HTTP/1.1
Host: uat.api.platinum.retter.io
X-API-Key: pk_live_xxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
{
"photo": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8U...",
"fileExtension": "jpg"
}
Response (200 OK):
{
"success": true,
"filePath": "members/550e8400-e29b-41d4-a716-446655440000/hr-photo.jpg",
"memberStatus": "active",
"message": "Employee photo updated successfully."
}
Request:
POST /members/hr/photo/2155581665 HTTP/1.1
Host: uat.api.platinum.retter.io
X-API-Key: pk_live_xxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
{
"photo": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
"fileExtension": "png"
}
Response (200 OK):
{
"success": true,
"filePath": "members/550e8400-e29b-41d4-a716-446655440000/hr-photo.png",
"memberStatus": "active",
"message": "Employee photo updated successfully."
}
| Scenario | Key Fields | Response Indicator |
|---|---|---|
| Lookup by Staff ID | instanceId starts with staffId! |
200 with filePath |
| Lookup by Member ID | instanceId without the staffId! prefix |
200 with filePath |
| Company managed by hand in EPC | Member's company has "Disable integrations and self-registrations" on | 200 with skipped, no filePath |
| Declared type does not match content | fileExtension differs from the actual image format |
400, code 10152, details.reason explains the mismatch |
| Dependant targeted | instanceId resolves to a dependant |
404, code 10073 |
{
"success": false,
"error": {
"code": 10001,
"message": "Validation error"
},
"details": {
"errors": {
"fileExtension": ["Invalid enum value. Expected 'jpg' | 'jpeg' | 'png' | 'gif' | 'webp'"]
}
}
}
Returned when the photo is not valid base64, is empty, exceeds 4.4 MB decoded, or its content does not match fileExtension. details.reason states which check failed.
{
"success": false,
"error": {
"code": 10152,
"message": "The employee photo is invalid. It must be a base64 encoded JPG, PNG, GIF or WebP image matching the declared file extension."
},
"details": {
"reason": "Photo content is image/jpeg, which does not match the declared fileExtension."
}
}
{
"success": false,
"error": {
"code": 10072,
"message": "Invalid or missing API key."
}
}
{
"success": false,
"error": {
"code": 10073,
"message": "Employee not found by employeeId or oldStaffId."
}
}
{
"success": false,
"error": {
"code": 10000,
"message": "Internal server error"
}
}
| Environment | URL |
|---|---|
| Staging | https://staging.api.platinum.retter.io/members/hr/photo/{instanceId} |
| UAT | https://uat.api.platinum.retter.io/members/hr/photo/{instanceId} |
| fileExtension | Content Type | Stored As |
|---|---|---|
| jpg | image/jpeg | hr-photo.jpg |
| jpeg | image/jpeg | hr-photo.jpg |
| png | image/png | hr-photo.png |
| gif | image/gif | hr-photo.gif |
| webp | image/webp | hr-photo.webp |
Failed HR photo pushes (except 401 responses) are logged with: