Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
235 changes: 235 additions & 0 deletions src/api-reference/user-provisioning/v4.user-provisioning.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.


## <a name="best-practices"></a>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": "[email protected]"},
{"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.


## <a name="event-usage"></a>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.
Expand Down