DonutworkHelp Center
Donutwork documentation

IDP User Metadata

Create and replace application-owned JSON metadata on company-scoped Sentinel identities.

IDP User Metadata

Each Sentinel identity can carry an application-owned JSON object. Metadata is returned by the full user profile and after successful magic-link verification, but it never grants Donutwork backoffice roles or API permissions.

All operations require a bearer API key with idp:write and remain scoped to its company.

Supply metadata during provisioning

Add the optional metadata object to the normal user provisioning request:

{
  "user": {
    "external_id": "ORG_USR_9921_AFG",
    "metadata": {
      "plan": "enterprise",
      "region": "eu",
      "features": ["sso", "audit"]
    }
  }
}

Replace metadata

PUT
/2026-02-01/idp/{externalUserId}/metadata.json
Required permissionidp:write

Path, query, and header parameters

externalUserIdstring· pathRequired
The external_id supplied at provisioning.

Request Body

JSON
{
  "user": {
    "metadata": {
      "plan": "enterprise",
      "region": "eu",
      "features": [
        "sso",
        "audit"
      ]
    }
  }
}
user.metadataobjectRequired
Complete replacement object. Send an empty object to clear metadata.

Responses

Metadata replaced.

{
  "user": {
    "id": "6908...",
    "external_id": "ORG_USR_9921_AFG",
    "metadata": {
      "plan": "enterprise",
      "region": "eu",
      "features": [
        "sso",
        "audit"
      ]
    }
  }
}

The identity does not exist in the authenticated company.

The metadata is not a valid object or exceeds a structural limit.

Request examples

curl --request PUT 'https://api.hub.donutwork.com/2026-02-01/idp/EXTERNALUSERID/metadata.json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "user": {
    "metadata": {
      "plan": "enterprise",
      "region": "eu",
      "features": [
        "sso",
        "audit"
      ]
    }
  }
}'
const response = await fetch('https://api.hub.donutwork.com/2026-02-01/idp/EXTERNALUSERID/metadata.json', {
  method: 'PUT',
  headers: {
    Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "user": {
      "metadata": {
        "plan": "enterprise",
        "region": "eu",
        "features": [
          "sso",
          "audit"
        ]
      }
    }
  }),
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
  const error = await response.text();
  throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
  : contentType.includes('application/json') ? await response.json()
  : await response.text();
$request = curl_init('https://api.hub.donutwork.com/2026-02-01/idp/EXTERNALUSERID/metadata.json');
curl_setopt_array($request, [
    CURLOPT_CUSTOMREQUEST => 'PUT',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => '{
  "user": {
    "metadata": {
      "plan": "enterprise",
      "region": "eu",
      "features": [
        "sso",
        "audit"
      ]
    }
  }
}',
]);
$response = curl_exec($request);
if ($response === false) {
    $error = curl_error($request);
    curl_close($request);
    throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
    : (str_contains($contentType, 'application/json')
        ? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);

Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.

This endpoint replaces the full object; it does not merge fields. The object may contain at most 32 KB, 6 nested levels, and 500 entries. Keys must be 1–128 characters, cannot contain ., and cannot start with $. Values must be JSON-compatible.

The Public API logger redacts the complete metadata field from request and response content.

On this page