diff --git a/src/api-reference/user-provisioning/v4.user-provisioning.md b/src/api-reference/user-provisioning/v4.user-provisioning.md index 05b90c1f49..63478afcc5 100644 --- a/src/api-reference/user-provisioning/v4.user-provisioning.md +++ b/src/api-reference/user-provisioning/v4.user-provisioning.md @@ -55,6 +55,241 @@ SAP Concur users must purchase either Concur Expense or Concur Travel or both in This API supports company-level access tokens. + +## UPS API Best Practices + +The following guidelines outline the best practices for working with the SAP Concur User Provisioning APIs. + + +### Key Recommendations + +* Use the `/Bulk/` endpoint for batch operations (up to 100 operations per request) and `/Users/` for single-user operations. +* Use `PATCH` operations instead of `PUT` for regular user updates to minimize error rates. +* Track provision completion using the `provisionId` returned in the response, either via polling (with exponential backoff) or webhook notifications. +* Pre-validate data before submission — most failures are caused by data validation issues. +* When identity extensions fail, retry the entire operation after fixing the issue. When spend/travel extensions fail, retry only those extensions via a subsequent `PATCH` request. +* There is a 240-minute provision request timeout; please reach out to us with the `provisionId` if requests remain pending for an extended period. + +### When to Use Each Endpoint + +| Scenario | Recommended Endpoint | # of Operations per request | Processing Type | Tracking | +|----------|---------------------|----------------------------|-----------------|----------| +| Creating/updating multiple users, Initial data load, Real-time HR Sync | `POST /Bulk/` | 1 - 100 | Asynchronous | provisionId | +| Single user operations | `POST /Users/`, `[PUT or PATCH or DELETE] /Users/{userId}/` | 1 | Asynchronous | provisionId | + +> **Note:** The `/Bulk/` endpoint is invoked via the `POST` HTTP method. However, each operation within the request payload can specify its own method (`POST`, `PUT`, `PATCH`, or `DELETE`) as per the [SCIM RFC](https://datatracker.ietf.org/doc/html/rfc7644#section-3.7). + +### Operations Best Practices + +For regular user updates, use `PATCH` operations rather than `PUT` (full replace), whether for single user requests or bulk operations. This is the approach used by our large enterprise customers handling thousands of daily updates. + +#### For bulk updates (HR sync, daily changes) + +```http +POST /Bulk/ +``` + +Payload: + +```json +{ + "schemas": ["urn:ietf:params:scim:api:messages:2.0:BulkRequest"], + "Operations": [ + { + "method": "PATCH", + "path": "/Users/user-uuid-1", + "bulkId": "update-emp001", + "data": { + "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], + "Operations": [ + {"op": "replace", "path": "userName", "value": "user0001@example.com"}, + {"op": "replace", "path": "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:department", "value": "Engineering"} + ] + } + }, + { + "method": "PATCH", + "path": "/Users/user-uuid-2", + "bulkId": "update-emp002", + "data": { + "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], + "Operations": [ + {"op": "replace", "path": "active", "value": false} + ] + } + } + ] +} +``` + +#### For single user updates + +```http +PATCH /Users/{userId}/ +``` + +Payload: + +```json +{ + "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], + "Operations": [ + {"op": "replace", "path": "name.givenName", "value": "Jonathan"}, + {"op": "replace", "path": "title", "value": "Director"} + ] +} +``` + +### Tracking Completion + +After submitting a provision request (via `/Users/` or `/Bulk/`), you receive a `provisionId` in the response. Use one of the following approaches to know when the operation completes: + +* **Polling:** Call `GET /provisions/{provisionId}/status/` at regular intervals until the operation completes. + * Use intervals of **5+ seconds** with exponential backoff. + * Always check the `completed` field to track completion, then `status` field to check the outcome. +* **Webhook Notifications** _(alternative to polling)_: Subscribe to provisioning events via the [Event Subscription Service](https://developer.concur.com/api-reference/ess/v4.event-subscription.html). + * When a provision completes, you receive a notification with the `provisionId` and status. + * Allows you to fetch results immediately **without polling**. + * Refer [this page](/event-topics/user-provisioning/v4.user-provisioning-events.html) for the event schema. + +### Handling Failures + +#### Getting Provision Request Status + +Track completion using the status endpoint to determine if the request completed successfully or if there were any failures (polling strategy): + +```http +GET /provisions/{provisionId}/status/ +``` + +To retrieve detailed failure information (whether detected via polling or webhook notifications), request the operation-level results: + +```http +GET /provisions/{provisionId}/status/?attributes=operations&count=100 +``` + +If using **webhooks**, once you receive a completion notification, check the detailed status if any failures occurred. In case of **polling strategy**, if the provision status indicates the failure, call the status endpoint with `?attributes=operations` to get operation level failure details. + +#### Understanding Failures + +Check for failures by examining the `status` and `operationsCount` fields in the status response: + +When `status.completed` is `true` but `status.success` is `false`, it indicates a failure. Compare the `operationsCount` fields to determine how many operations succeeded and how many failed: + +```json +{ + "id": "2f3ed639-a1d4-5083-c966-17d2e2b746e6", + "status": { + "completed": true, + "success": false + }, + ... + "operationsCount": { + "total": 10, + "success": 8, + "failed": 2 + } + ... +} +``` + +#### Getting Failure Details + +```http +GET /provisions/{provisionId}/status/?attributes=operations +``` + +#### Operation-Level Failures + +Each operation in the provision status response includes its own `status` object. If an operation failed, the `status.success` will be `false` and an `error` object will provide details: + +```json +{ + "id": "2f3ed639-a1d4-5083-c966-17d2e2b746e6", + ... + "operations": [ + { + "id": "1", + "status": { + "completed": true, + "success": false, + "code": "400" + }, + "resource": { + "id": "a51882ef-f002-4c0f-960d-e3e1d23c851b", + "type": "User" + }, + "extensions": [ + ... + ] + } + ] +} +``` + +#### Extension-Level Failures + +If an extension failed, the `extensions` object in the operation response will include details for each extension. Each extension reports its own `status` and any associated `error` information if it failed: + +```json +{ + "id": "2f3ed639-a1d4-5083-c966-17d2e2b746e6", + ... + "operations": [ + { + ... + "extensions": [ + { + "name": "urn:ietf:params:scim:schemas:extension:spend:2.0:User", + "status": { + "completed": true, + "success": false, + "code": "400", + "result": "error" + }, + "messages": [ + { + "code": "INVALID_VALUE", + "message": "Invalid 'urn:ietf:params:scim:schemas:extension:spend:2.0:User:reimbursementCurrency' Size", + "schemaPath": "urn:ietf:params:scim:schemas:extension:spend:2.0:User:reimbursementCurrency", + "type": "error" + } + ] + } + ] + } + ] +} +``` + +Use `/swagger` endpoint to retrieve the schema for error codes and messages at each level (provision, operation, extension). + +#### Extension Failure Retry Strategy + +**Identity extensions failed:** +* Other extensions DO NOT process if identity extension fails. +* The same operation can be retried after fixing the identity issue. + +**Spend / Travel extensions failed:** +* Identity extensions would have been processed successfully. +* The spend/travel extensions can be retried independently after fixing the issue with a `PATCH` operation in subsequent requests. + +### Reducing Failure Rates + +1. Pre-validate data before submission. You can use `/Schemas` endpoint to retrieve the schemas for validation. +2. Classify errors as either retryable (transient) or permanent (data-related) and retry accordingly. +3. Correct data issues when encountering validation errors. Most failures are caused by data validation issues. +4. Retry transient failures using exponential backoff. + +### Handling Long Pending Provision Requests + +Currently the timeout for each provision request is 240 minutes. The provision request is marked as failure by the system if it exceeds the timeout. + +If you have a provision request that is pending for a long time, please reach out to us with the `provisionId` for investigation. The operation can be retried after the timeout based on the failure scenarios mentioned above. + +> **Note:** We have plans to reduce this timeout under 60 minutes. + + ## Events UPS supports post event notification when the provisioning process is complete. Using the [Event Subscription Service](https://developer.concur.com/api-reference/ess/v4.event-subscription.html) allows users to be notified through web services when certain actions take place in connected companies with the `provisionCompleted` event.