Wocha Docs

MFA API

Account API endpoints for TOTP enrolment and Customer API risk event queries

This page documents the MFA-related REST endpoints: TOTP enrolment and verification on the Account API, and risk assessment history on the Customer API.


Authentication

APITokenBase URL
Account APIUser access token (Authorization: Bearer eyJ...)https://TENANT.api.wocha.ai/v1/account
Customer APIManagement API key (wocha_mgmt_...) or OAuth token with risk:readhttps://TENANT.api.wocha.ai/v1/customer

TOTP enrolment flow

TOTP enrolment is a two-step process: initiate (POST), then verify and activate (PUT).

Step 1 — Initiate enrolment

POST /v1/account/mfa/totp
Authorization: Bearer {user_access_token}

Response 201 Created:

{
  "data": {
    "secret": "JBSWY3DPEHPK3PXP",
    "qr_uri": "otpauth://totp/Acme:user@example.com?secret=JBSWY3DPEHPK3PXP&issuer=Acme",
    "backup_codes": ["a1b2c3d4", "e5f6g7h8"]
  }
}
FieldDescription
secretBase32 TOTP secret — display once for manual entry
qr_uriotpauth:// URI — render as a QR code in your UI
backup_codesOne-time recovery codes — display once; stored hashed server-side on activation

The pending enrolment expires after 10 minutes. A new POST replaces any existing pending enrolment.

Errors:

StatusCodeWhen
409conflictTOTP is already enrolled
404not_foundUser identity not found
503service_unavailableIdentity service not configured

Step 2 — Verify and activate

Submit a 6-digit code from the user's authenticator app to activate the credential:

PUT /v1/account/mfa/totp
Authorization: Bearer {user_access_token}
Content-Type: application/json
 
{ "code": "123456" }

Response 200 OK:

{
  "data": {
    "enabled": true,
    "methods": ["totp"],
    "backup_codes_remaining": 10
  }
}

On success, the pending enrolment is cleared from identity metadata and the TOTP credential is activated in Kratos. Backup codes are stored as lookup secrets if none exist.

Errors:

StatusCodeWhen
422validation_errorInvalid or expired TOTP code, or code format invalid (must be 6 digits)
404not_foundNo pending TOTP enrolment — call POST first
409conflictTOTP is already enrolled
400validation_errorRequest body is not valid JSON

Remove TOTP

DELETE /v1/account/mfa/totp
Authorization: Bearer {user_access_token}

Returns 204 No Content on success. Also removes associated backup codes.


React SDK

The useMfa() hook wraps the Account API:

import { useMfa } from "@wocha/react";
 
function TotpSetup() {
  const { enrollTotp, verifyTotp, removeTotp } = useMfa();
 
  const start = async () => {
    const { secret, qr_uri, backup_codes } = await enrollTotp();
    // Render qr_uri and display backup_codes once
  };
 
  const confirm = async (code: string) => {
    const status = await verifyTotp(code);
    // status.enabled === true when active
  };
}

Risk events API

Query risk assessment audit events for your tenant. Requires the risk:read scope.

GET /v1/customer/risk/events
Authorization: Bearer wocha_mgmt_...

Query parameters

ParameterTypeDescription
limitintegerResults per page (1–100, default 50)
page_tokenstringCursor for pagination (event id from previous page)
verdictstringFilter by allow, step_up, or block
subject_idstringFilter by user identity ID
range_startISO 8601Events on or after this timestamp
range_endISO 8601Events on or before this timestamp

Response

{
  "data": [
    {
      "id": "evt_01HX...",
      "event_type": "risk.scored",
      "subject_id": "user-uuid",
      "outcome": "success",
      "score": 0.45,
      "verdict": "step_up",
      "rules_triggered": ["unknown_device", "geo_anomaly"],
      "assessments": {
        "unknown_device": {
          "confidence": "medium",
          "code": "unknown_device",
          "triggered": true,
          "weight": 0.2,
          "details": { "fingerprint_hash": "abc123" }
        },
        "geo_anomaly": {
          "confidence": "medium",
          "code": "geo_anomaly",
          "triggered": true,
          "weight": 0.3,
          "details": { "country": "US", "previous_country": "GB" }
        },
        "bot_detected": {
          "confidence": "high",
          "code": "bot_detected",
          "triggered": false,
          "weight": 0.8,
          "details": {}
        }
      },
      "evaluated_rules": 10,
      "total_rules": 10,
      "reasons": [
        { "code": "unknown_device", "message": "Device not previously seen", "weight": 0.2 }
      ],
      "metadata": {
        "source_ip": "203.0.113.42",
        "device_fingerprint": "fp_abc123",
        "confidence": 0.95
      },
      "event_time": "2026-06-14T10:30:00.000Z"
    }
  ],
  "list_metadata": { "page_token": "evt_01HX..." },
  "has_more": true
}

Assessor details

Each entry in assessments is keyed by assessor ID (bot_detected, velocity_anomaly, unknown_device, etc.) and includes:

FieldDescription
confidenceOrdinal level: low, medium, high, or neutral
codeAssessor identifier (same as the key)
triggeredWhether this assessor fired on this event
weightEffective weight (configured default or tenant override)
detailsAssessor-specific context (geo country, ASN, fingerprint hash, velocity counts, etc.)

Event types include risk.scored, risk.step_up_triggered, risk.blocked, and risk.bot_detected.

Example

curl -G "https://acme.api.wocha.ai/v1/customer/risk/events" \
  -H "Authorization: Bearer wocha_mgmt_..." \
  --data-urlencode "verdict=step_up" \
  --data-urlencode "limit=25" \
  --data-urlencode "range_start=2026-06-01T00:00:00Z"

On this page