Donutwork Docs

Identity Provider (IDP) & MFA

Orchestrate user identities, implement multi-factor authentication (TOTP), and manage security audit trails.

Identity Provider (IDP) API

The Identity Provider (IDP) service centralizes user identity management and provides robust secondary authentication layers. It allows organizations to link external user identifiers to a secure, managed identity capable of Multi-Factor Authentication (MFA) via Time-based One-Time Passwords (TOTP) and advanced risk assessment (Sentinel).


Identity Management

Provision External User

Provision a new managed identity within the IDP ecosystem. This creates a security context linked to your platform's internal external_id.

POST
/2026-02-01/idp/user.json
Required permissionidp:writeApiAccessPermission::IDP_WRITE

Query Parameters

No query parameters required.

Request Body

JSON
{
  "user": {
    "external_id": "ORG_USR_9921_AFG"
  }
}
user.external_idstringRequired
Your system's unique identifier for the user (e.g., UUID or DB ID).

Responses

User identity successfully provisioned.

{
  "user": {
    "id": "6908bffd...",
    "external_id": "ORG_USR_9921_AFG",
    "status": "active"
  }
}

Identity already exists for the specified external_id.

{
  "error": "User with this external_id already exists for this company"
}

Retrieve Identity Profile

Fetch the security profile for a specific user. This includes authentication capabilities (MFA status), operational status (locked/active), and audit metadata.

GET
/2026-02-01/idp/{externalUserId}.json
Required permissionidp:readApiAccessPermission::IDP_READ

Query Parameters

externalUserIdstringRequired
The identifier provided during provisioning.

Responses

Identity profile retrieved.

{
  "id": "6908...",
  "external_id": "ORG_USR_9921_AFG",
  "status": "active",
  "has_otp": true,
  "has_passkeys": false,
  "is_locked": false,
  "failed_attempts": 0,
  "saas_client_id": "67c9..."
}

Multi-Factor Authentication (MFA)

Configuration of TOTP Secret

Generate a new TOTP secret and QR code payload to enable secondary authentication. Scanning this payload into an authenticator application (e.g., Microsoft/Google Authenticator) completes the setup.

POST
/2026-02-01/idp/{externalUserId}/otp.json
Required permissionidp:writeApiAccessPermission::IDP_WRITE

Query Parameters

externalUserIdstringRequired
The user identifier.

Request Body

JSON
{
  "otp": {
    "issuer_name": "Acme Enterprise",
    "account_name": "alexander@enterprise.com"
  }
}
otp.issuer_namestringRequired
The organization name displayed in the authenticator app.
otp.account_namestringRequired
The specific account identifier displayed in the app.

Responses

New MFA secret successfully generated.

{
  "user": {
    "external_id": "ORG_USR_9921_AFG",
    "new_otp_secret": "REDACTED_OTP_SECRET",
    "qrcode_payload": "otpauth://totp/Acme+Enterprise:alexander%40enterprise.com?secret=REDACTED_OTP_SECRET&issuer=Acme+Enterprise",
    "is_setup_complete": true
  }
}

Verify Authentication Code

Validate a 6-digit TOTP code (or a one-time backup code) to finalize the authentication process.

POST
/2026-02-01/idp/otp/verify.json
Required permissionidp:writeApiAccessPermission::IDP_WRITE

Query Parameters

No query parameters required.

Request Body

JSON
{
  "otp": {
    "id": "ORG_USR_9921_AFG",
    "code": "552192"
  }
}
otp.idstringRequired
The unique external user identifier.
otp.codestringRequired
The 6-digit code or numeric backup code.

Responses

Authentication successful.

{
  "status": "success",
  "id": "ORG_USR_9921_AFG",
  "message": "OTP verified successfully."
}

Invalid credentials or code expired.

{
  "error": "Invalid credentials or access denied"
}

Provision Backup Codes

Generate a collection of single-use backup codes. These allow users to regain access if the primary MFA device is unavailable.

POST
/2026-02-01/idp/{externalUserId}/backupcodes.json
Required permissionidp:writeApiAccessPermission::IDP_WRITE

Query Parameters

externalUserIdstringRequired
The user identifier.

Responses

Set of 10 backup codes generated.

{
  "user": {
    "id": "ORG_USR_9921_AFG",
    "backupCodes": [
      "ABCD-1234",
      "EFGH-5678",
      "..."
    ]
  }
}

Passkey (WebAuthn) Delegation

For architecture, benefits and production integration pattern (backend relay S2S), see:

Create Registration Options

Generate challenge and WebAuthn options used by the client to create a passkey credential.

POST
/2026-02-01/idp/{externalUserId}/passkey/options.json
Required permissionidp:writeApiAccessPermission::IDP_WRITE

Query Parameters

externalUserIdstringRequired
The user identifier.

Request Body

JSON
{
  "passkey": {
    "display_name": "Alexander",
    "account_name": "alexander@enterprise.com"
  }
}
passkey.display_namestring
Display name shown during registration.
passkey.account_namestring
Account name bound to the credential.

Responses

Registration options generated.

{
  "status": "success",
  "passkey": {
    "challenge_id": "ch_01J...",
    "options": {
      "challenge": "BASE64_CHALLENGE",
      "rp": {
        "id": "example.com",
        "name": "Donutwork"
      }
    }
  }
}

Confirm Registration

Confirm passkey registration by submitting the credential payload generated by the WebAuthn client.

POST
/2026-02-01/idp/{externalUserId}/passkey/confirm.json
Required permissionidp:writeApiAccessPermission::IDP_WRITE

Query Parameters

externalUserIdstringRequired
The user identifier.

Request Body

JSON
{
  "passkey": {
    "challenge_id": "ch_01J...",
    "payload": "{\"id\":\"...\",\"rawId\":\"...\",\"response\":{...}}",
    "device": "Chrome 125 / macOS"
  }
}
passkey.challenge_idstringRequired
Challenge reference from options endpoint.
passkey.payloadstring|objectRequired
Serialized PublicKeyCredential payload.
passkey.devicestring
Client device fingerprint for audit.

Responses

Passkey registered successfully.

{
  "status": "success",
  "passkey": {
    "id": "pk_01J...",
    "external_id": "ORG_USR_9921_AFG",
    "registered": true
  }
}

Create Authentication Challenge

Generate a passkey challenge for login verification.

POST
/2026-02-01/idp/passkey/challenge.json
Required permissionidp:writeApiAccessPermission::IDP_WRITE

Query Parameters

No query parameters required.

Request Body

JSON
{
  "passkey": {
    "id": "ORG_USR_9921_AFG"
  }
}
passkey.idstringRequired
External user identifier.

Responses

Authentication challenge generated.

{
  "status": "success",
  "passkey": {
    "challenge_id": "ch_01J...",
    "options": {
      "challenge": "BASE64_CHALLENGE",
      "rpId": "example.com"
    }
  }
}

Verify Passkey Authentication

Validate the assertion payload generated by the authenticator.

POST
/2026-02-01/idp/passkey/verify.json
Required permissionidp:writeApiAccessPermission::IDP_WRITE

Query Parameters

No query parameters required.

Request Body

JSON
{
  "passkey": {
    "id": "ORG_USR_9921_AFG",
    "challenge_id": "ch_01J...",
    "payload": "{\"id\":\"...\",\"rawId\":\"...\",\"response\":{...}}"
  }
}
passkey.idstringRequired
External user identifier.
passkey.challenge_idstringRequired
Challenge reference from challenge endpoint.
passkey.payloadstring|objectRequired
Serialized PublicKeyCredential assertion payload.

Responses

Passkey authentication successful.

{
  "status": "success",
  "id": "ORG_USR_9921_AFG",
  "message": "Passkey verified successfully."
}

Revoke Passkey Credential

Delete one registered passkey credential for the user.

DELETE
/2026-02-01/idp/{externalUserId}/passkey/{credentialId}.json
Required permissionidp:writeApiAccessPermission::IDP_WRITE

Query Parameters

externalUserIdstringRequired
The user identifier.
credentialIdstringRequired
Passkey credential identifier.

Responses

Credential removed successfully.

{
  "status": "success",
  "id": "ORG_USR_9921_AFG",
  "message": "Passkey removed successfully."
}

Sentinel Risk Assessment

Sentinel provides a sophisticated analytical layer for evaluating authentication risk in real-time.

Analyze Session Integrity (Discover)

Submit session metadata to Sentinel to perform a risk assessment based on behavioral patterns, network reputation, and environmental context.

POST
/2026-02-01/sentinel/verify.json
Required permissionsentinel:writeApiAccessPermission::SENTINEL_WRITE

Query Parameters

No query parameters required.

Request Body

JSON
{
  "sentinel": {
    "id": "ORG_USR_9921_AFG",
    "context": {
      "ip": "1.1.1.1",
      "ua": "EnterpriseAgent/1.0"
    }
  }
}
sentinel.idstringRequired
External user identifier for the risk evaluation.
sentinel.context.ipstringRequired
Source IP address of the session.
sentinel.context.uastringRequired
User-Agent value for the session.
sentinel.context.user_idstring
Optional explicit user identifier override.

Responses

Risk assessment completed.

{
  "success": true,
  "event_id": "67f4...",
  "user_id": "ORG_USR_9921_AFG",
  "ip": "1.1.1.1",
  "geo_country": "US",
  "risk_score": 0.62,
  "risk_band": "high",
  "decision_advice": "challenge_mfa",
  "decision": "challenge_mfa",
  "recommended_factor": "passkey_or_otp",
  "reasons": [
    "High source IP velocity detected."
  ],
  "verification_cost_ms": 18
}

Missing input fields.

{
  "success": false,
  "message": "Missing required parameters: user_id, ip, or user_agent."
}

Sentinel also accepts the legacy envelope:

{
  "event": {
    "user_id": "ORG_USR_9921_AFG",
    "ip": "1.1.1.1",
    "user_agent": "EnterpriseAgent/1.0",
    "sentinel_session": "sess_123"
  }
}

On this page