### SCIM 2.0 Integration Guide

This guide documents Palats SCIM endpoints for customer user provisioning.

- Base path: `/v2/customer/scim`
- Media type: `application/scim+json`
- Authentication: `Authorization: Bearer <access_key>`

## Discovery

- `GET /v2/customer/scim/ServiceProviderConfig`

  - patch: supported
  - bulk: not supported
  - filter: supported (maxResults: 1000)
  - sort: not supported
  - etag: not supported
  - authentication: OAuth Bearer token

- `GET /v2/customer/scim/Schemas`
- `GET /v2/customer/scim/Schemas/{id}`

- `GET /v2/customer/scim/ResourceTypes`
- `GET /v2/customer/scim/ResourceTypes/{name}`

Notes:

- Resource types include `User` and `Group`. Group CRUD is not implemented; see Groups section.

## Resources

- Users: `GET /Users`, `GET /Users/{id}`, `POST /Users`, `PUT /Users/{id}`, `PATCH /Users/{id}`, `DELETE /Users/{id}`
- Groups: `GET /Groups` has not yet been implemented fully and currently responds with an empty list. Creating/updating/deleting groups is not supported.

## Users schema

Palats follows `urn:ietf:params:scim:schemas:core:2.0:User` with these commonly used attributes:

- `id` (readOnly): mirrors `externalId`
- `externalId` (readWrite): provisioning identifier; also used as the SCIM resource id in paths
- `userName` (readWrite): optional login identifier
- `name.givenName`, `name.familyName` (readWrite)
- `emails[0].value` (readWrite)
- `phoneNumbers[0].value` (readWrite)
- `active` (readWrite): `false` deprovisions; `true` re-activates (via PATCH)

Palats also supports the Enterprise User extension schema (`urn:ietf:params:scim:schemas:extension:enterprise:2.0:User`) with:

- `urn:ietf:params:scim:schemas:extension:enterprise:2.0:User.department` (readWrite): Department/organizational unit name (optional)
  - Type: string (optional, nullable)
  - Case-sensitive: Yes
  - Behavior: Auto-creates organizational unit if name doesn't exist within the organization
  - Normalization: Whitespace is trimmed; empty strings are treated as null (no department)
  - Organization-scoped: Always belongs to the requesting organization

Important conventions:

- SCIM `id` equals `externalId`. Treat `{id}` in paths as your `externalId`.
- `meta.location` is `/v2/customer/scim/Users/{externalId}`.
- Department names are case-sensitive and organization-scoped. If a department name doesn't exist, it will be automatically created.

Example representation:

```json
{
	"schemas": [
		"urn:ietf:params:scim:schemas:core:2.0:User",
		"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
	],
	"id": "john.doe@example.com",
	"externalId": "john.doe@example.com",
	"userName": "john.doe@example.com",
	"active": true,
	"name": { "givenName": "John", "familyName": "Doe", "formatted": "John Doe" },
	"emails": [
		{ "value": "john.doe@example.com", "type": "work", "primary": true }
	],
	"phoneNumbers": [{ "value": "+4612345678", "type": "work" }],
	"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User": {
		"department": "Engineering"
	},
	"meta": {
		"resourceType": "User",
		"location": "/v2/customer/scim/Users/john.doe@example.com"
	}
}
```

## Pagination and filtering

- Pagination query params on `GET /Users`:

  - `startIndex` (default 1)
  - `count` (1–1000)

- Supported filters on `GET /Users`:
  - `id eq "<value>"`
  - `externalId eq "<value>"`
  - `userName eq "<value>"` (matches the user email)
  - `email eq "<value>"`
  - `active eq true|false`

Responses use the SCIM `ListResponse` with `totalResults`, `startIndex`, `itemsPerPage`, and `Resources`.

---

## Endpoints

### GET /Users

List users with optional pagination and filter.

Headers:

- `Authorization: Bearer <access_key>`
- `Accept: application/scim+json`

Query params: `startIndex`, `count`, `filter`

Example:

```bash
curl -s -H "Authorization: Bearer $KEY" -H "Accept: application/scim+json" \
  "https://palats.app/api/v2/customer/scim/Users?filter=externalId%20eq%20%22john.doe%40example.com%22"
```

Response 200 (truncated):

```json
{
	"schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
	"Resources": [
		{ "id": "john.doe@example.com", "externalId": "john.doe@example.com" }
	],
	"totalResults": 1,
	"itemsPerPage": 1,
	"startIndex": 1
}
```

### GET /Users/{id}

Fetch a single user by `{id}` (your `externalId`).

```bash
curl -s -H "Authorization: Bearer $KEY" -H "Accept: application/scim+json" \
  "https://palats.app/api/v2/customer/scim/Users/john.doe@example.com"
```

Responses:

- 200 with User
- 404 SCIM error if not found

### POST /Users

Create a user. `emails[0].value` is required. If `externalId` is omitted, it defaults to `userName` or email.

Request body:

```json
{
	"schemas": [
		"urn:ietf:params:scim:schemas:core:2.0:User",
		"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
	],
	"externalId": "john.doe@example.com",
	"userName": "john.doe@example.com",
	"name": { "givenName": "John", "familyName": "Doe" },
	"emails": [
		{ "value": "john.doe@example.com", "type": "work", "primary": true }
	],
	"phoneNumbers": [{ "value": "+4612345678", "type": "work" }],
	"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User": {
		"department": "Engineering"
	}
}
```

Example:

```bash
curl -s -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/scim+json" \
  "https://palats.app/api/v2/customer/scim/Users" \
  -d '{
    "schemas":[
      "urn:ietf:params:scim:schemas:core:2.0:User",
      "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
    ],
    "externalId":"john.doe@example.com",
    "userName":"john.doe@example.com",
    "emails":[{"value":"john.doe@example.com"}],
    "name":{"givenName":"John","familyName":"Doe"},
    "phoneNumbers":[{"value":"+4612345678"}],
    "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User":{
      "department":"Engineering"
    }
  }'
```

Responses:

- 201 with created User and `Location` header `/v2/customer/scim/Users/{externalId}`
- 400 SCIM error on invalid payload
- 409 SCIM error on duplicate

### PUT /Users/{id}

Replace user attributes. If `active: false`, the user is deprovisioned (204). Updating `externalId` will change the resource id.

Example body (replace):

```json
{
	"schemas": [
		"urn:ietf:params:scim:schemas:core:2.0:User",
		"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
	],
	"userName": "johnny@example.com",
	"emails": [{ "value": "john.doe@example.com" }],
	"name": { "givenName": "John", "familyName": "Doe" },
	"phoneNumbers": [{ "value": "+4612345678" }],
	"externalId": "john.doe@example.com",
	"active": true,
	"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User": {
		"department": "Product"
	}
}
```

Responses:

- 200 with updated User
- 204 when deprovisioning with `active: false`
- 404 if not found

### PATCH /Users/{id}

Supports `add`, `replace`, and `remove` operations on the following paths: `userName`, `name.givenName`, `name.familyName`, `phoneNumbers`, `externalId`, `active`, `department` (or `urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:department`).

- Setting `active` to `false` deprovisions the user (204).
- Setting `active` to `true` re-activates the user and returns 200 with the updated resource.
- Setting `department` to a string creates or assigns the user to that department (case-sensitive).
- Removing `department` (or setting to `null`) removes the user's department assignment.

Example (deprovision):

```json
{
	"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
	"Operations": [{ "op": "Replace", "path": "active", "value": false }]
}
```

Example (reactivate and update):

```json
{
	"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
	"Operations": [
		{ "op": "Replace", "path": "active", "value": true },
		{ "op": "Replace", "path": "userName", "value": "johnny@example.com" }
	]
}
```

Example (update department):

```json
{
	"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
	"Operations": [
		{ "op": "Replace", "path": "department", "value": "Engineering" }
	]
}
```

Example (remove department):

```json
{
	"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
	"Operations": [{ "op": "Remove", "path": "department" }]
}
```

Responses:

- 200 with updated User
- 204 on deprovision
- 400 on invalid PatchOp
- 404 if not found

### DELETE /Users/{id}

Deprovisions the user with data scrubbing. Returns 204.

---

## Errors

SCIM error format example:

```json
{
	"schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
	"status": "400",
	"detail": "Invalid SCIM request",
	"scimType": "invalidSyntax"
}
```

Common cases:

- 400 `invalidSyntax` for malformed payloads
- 400 `invalidFilter` for unsupported filters
- 404 when resource not found
- 409 `uniqueness` on duplicates
- 500 for unexpected server errors

## Security

- Use a Palats Customer API Access Key in the `Authorization: Bearer` header.
- Always send and expect `application/scim+json` where applicable.
- Rotate keys periodically and store them securely.
