Marvin SCIM 2.0 — Users API
SCIM 2.0 (RFC 7643 / 7644) · Last updated July 2026
Marvin implements SCIM 2.0 to let identity providers provision, deprovision, and update users automatically.
All endpoints are under:
/sso/scim/v2/{scim_key}/
Your scim_key and secret will be shared with you separately
Authentication#
Every request must include an Authorization header with a Bearer token configured in your SCIM integration settings.
Authorization: Bearer <your-scim-token>
Content-Type: application/scim+json ← required for POST / PATCH
Accept: application/scim+json
⚠️ Sending
Content-Type: application/jsoninstead ofapplication/scim+jsonwill return 415 Unsupported Media Type. Most identity providers set the correct type automatically when SCIM is configured properly.
Users#
GET /sso/scim/v2/{scim_key}/Users#
Returns all active, non-suspended users on the team. Supports optional filter by userName to look up a single user.
Query parameters
| Parameter | Type | Description |
|---|---|---|
filter | string | SCIM filter expression, e.g. userName eq "user@example.com" |
Response 200
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
"totalResults": 2,
"Resources": [
{
"id": "alice@example.com",
"userName": "alice@example.com",
"name": { "givenName": "Alice", "familyName": "Smith" }
},
{
"id": "bob@example.com",
"userName": "bob@example.com",
"name": { "givenName": "Bob", "familyName": "Jones" }
}
]
}
GET /sso/scim/v2/{scim_key}/Users/{email}#
Returns the full SCIM representation of a single user, including their current Marvin role.
Response 200
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"id": "alice@example.com",
"externalId": "alice@example.com",
"userName": "alice@example.com",
"name": {
"givenName": "Alice",
"familyName": "Smith"
},
"roles": [
{
"value": "Full",
"display": "Full",
"primary": true
}
],
"meta": {
"resourceType": "User",
"created": "2024-01-15T09:00:00+00:00",
"lastModified": "2024-01-15T09:00:00+00:00",
"location": "https://app.heymarvin.com/sso/scim/v2/Users/alice@example.com"
}
}
POST /sso/scim/v2/{scim_key}/Users#
Creates a new Marvin user. If a user with the same userName already exists, their profile and role are updated instead — this is an upsert.
The optional roles field sets the user's Marvin role. Omitting it leaves the team's default role in place (typically Viewer).
Request body
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"userName": "alice@example.com",
"name": {
"givenName": "Alice",
"familyName": "Smith"
},
"active": true,
"roles": [
{
"value": "Full",
"primary": true
}
]
}
| Field | Required | Description |
|---|---|---|
userName | ✅ Yes | User's email address |
name.givenName | ✅ Yes | First name |
name.familyName | ✅ Yes | Last name |
active | No | true to activate, false to suspend |
roles | No | Array with one role object. If omitted, team default role is used. |
roles[].value | No | Role name — see Role Reference for valid values |
roles[].primary | No | Should be true |
Response 201 — full User object (same shape as GET /Users/{email})
PATCH /sso/scim/v2/{scim_key}/Users/{email}#
Updates one or more fields on an existing user. Only fields present in the body are modified — omitted fields are left unchanged.
This is the primary endpoint for role changes triggered by your identity provider.
✅ Role update: send
roles[0].valuewith the new role name. Marvin updates the user's role and marks it as SSO-managed so it is not overwritten by future team default changes.
Request body — role change
{
"roles": [
{
"value": "Full",
"primary": true
}
]
}
Request body — name and/or active status
{
"name": {
"familyName": "Johnson"
},
"active": false
}
Setting active: false suspends the user.
Response 200 — full User object with updated values
DELETE /sso/scim/v2/{scim_key}/Users/{email}#
Suspends the user — they lose access to Marvin but their data is preserved. This is a soft delete; the user can be reactivated via a subsequent POST or PATCH with "active": true.
Response 200 — User object with suspended state reflected
Roles#
GET /sso/scim/v2/{scim_key}/Roles#
Returns all valid role names. Use this endpoint when configuring attribute mappings in your identity provider — the value strings here are exactly what must be sent in roles[0].value on POST / PATCH requests.
Response 200
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
"totalResults": 4,
"Resources": [
{ "value": "Full", "id": 0 },
{ "value": "Viewer", "id": 2 },
{ "value": "Admin", "id": 3 },
{ "value": "Collaborator", "id": 4 }
]
}
Role Reference#
| SCIM value | Marvin role | Permissions |
|---|---|---|
Full | Contributor | Can view, tag, annotate, and create insights |
Viewer | Viewer | Read-only access to shared projects |
Admin | Team Admin | Full access including team settings and billing |
Collaborator | Collaborator | Can comment and react; limited editing |
The value field is case-insensitive when received — full, Full, and FULL are all accepted. Use the canonical casing above in attribute mappings.
⚠️ Sending an unrecognised value (e.g.
SuperAdmin) is silently ignored — the request succeeds and the user's current role is left unchanged. No error is returned.
Error Responses#
| Status | Cause |
|---|---|
400 | Email domain not allowed for this team |
403 | Email address not permitted (blocked domain) |
404 | Invalid or unknown scim_key, or user not found |
415 | Content-Type is not application/scim+json |
401 / 403 | Bearer token missing or incorrect |