Skip to main content

API Reference

All endpoints are under the base path /api/v1.

SaaS base URL: https://demo.humifortis.com/api/v1
Self-hosted: https://your-host:8080/api/v1


Authentication

All requests require an API key in the X-API-Key header.

X-API-Key: hf_your-api-key-here

API keys have scope-based access control:

ScopeAllowed operations
events:writePOST /events, POST /evaluate, MFA signal endpoints
risk:readGET /risk/:id, GET /decision/:id
read:auditRead audit logs
admin:risksReset risk scores, remediation actions

Rate Limiting

  • Enterprise plan: 9 999 req/s per key
  • Pro plan: 1 000 req/s per key
  • Free tier: 60 req/min (IP-based if unauthenticated)

Rate limit headers are included in every response. On limit: 429 Too Many Requests.


POST /events

Submit one or more security events for async risk scoring.

Scope required: events:write

Request

{
"event": {
"event_id": "550e8400-e29b-41d4-a716-446655440000",
"flow_id": "auth-session-id-or-trace-id",
"entity_id": "user:keycloak:myrealm:e4b96c3a-1234-5678-abcd-ef0123456789",
"entity_type": "user",
"event_type": "auth.login.failed",
"timestamp": "2025-12-16T10:00:00Z",
"source": "keycloak",
"metadata": {
"ip_address": "203.0.113.42",
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)...",
"device_id": "fp_abc123def456",
"country": "US",
"city": "Portland",
"failed_attempts": 5
}
}
}

For bulk ingestion, use the events array instead:

{
"events": [
{ "event_id": "...", "entity_id": "...", "event_type": "auth.login.failed", "..." },
{ "event_id": "...", "entity_id": "...", "event_type": "auth.login.success", "..." }
]
}
FieldTypeRequiredDescription
event_idstringyesUnique ID from your system (UUID recommended)
flow_idstringnoCorrelation ID for the auth session or trace
entity_idstringyesIdentity of the subject (see Entity IDs)
entity_typestringyesuser, session, token, api_key, device
event_typestringyesDot-namespaced event name (see Event Types)
timestampstringyesISO 8601 UTC
sourcestringnoSystem that generated the event (keycloak, nginx, etc.)
metadataobjectnoIP, UA, device_id, location, and any custom fields

Response — 202 Accepted

{
"accepted": 10,
"rejected": 2,
"errors": [
"Event #5: invalid event_type 'unknown.event'",
"Event #7: missing entity_id"
]
}

Events are processed asynchronously. 202 means the event was queued. Risk state in Redis is updated within milliseconds.


GET /risk/:entity_id

Retrieve the current risk state for an entity.

Scope required: risk:read

Request

GET /api/v1/risk/user%3Akeycloak%3Amyrealm%3Ae4b96c3a-1234-5678-abcd-ef0123456789
X-API-Key: hf_...

URL-encode the entity_id if it contains colons.

Response — 200 OK

{
"entity_id": "user:keycloak:myrealm:e4b96c3a-1234-5678-abcd-ef0123456789",
"entity_type": "user",
"risk_score": 71.2,
"risk_level": "HIGH",
"event_count": 15,
"contributing_factors": [
"AUTH_LOGIN_FAILED",
"AUTH_BRUTE_FORCE",
"IMPOSSIBLE_TRAVEL"
],
"last_updated": "2025-12-16T10:00:01Z"
}
Risk LevelScore Range
MINIMAL0 – 20
LOW20 – 40
MEDIUM40 – 60
HIGH60 – 80
CRITICAL80 – 100

POST /evaluate

Evaluate a login attempt synchronously and receive an enforcement decision. This is the primary endpoint called by the Keycloak connector at login time.

Scope required: events:write

Request

{
"event": {
"event_id": "550e8400-e29b-41d4-a716-446655440000",
"flow_id": "auth-session-abc123",
"entity_id": "user:keycloak:myrealm:e4b96c3a-1234-5678-abcd-ef0123456789",
"entity_type": "user",
"event_type": "auth.login.attempt",
"timestamp": "2025-12-16T10:00:00Z",
"source": "keycloak",
"metadata": {
"ip_address": "203.0.113.42",
"user_agent": "Mozilla/5.0...",
"device_id": "fp_abc123def456",
"country": "US",
"city": "Portland"
}
},
"user_mfa_profile": {
"enrolled_methods": ["TOTP", "WEBAUTHN"],
"email_verified": true,
"has_email": true
},
"available_methods": ["TOTP"]
}
FieldTypeRequiredDescription
eventobjectyesThe login event (same shape as POST /events)
user_mfa_profileobjectnoUser's enrolled MFA methods and email status
user_mfa_profile.enrolled_methodsstring[]noAvailable methods: TOTP, WEBAUTHN, EMAIL_OTP
user_mfa_profile.email_verifiedboolnoWhether the user's email is verified in Keycloak
user_mfa_profile.has_emailboolnoWhether the user has an email address
available_methodsstring[]noMethods available at this moment (fallback if no profile)

Response — 200 OK

{
"entity_id": "user:keycloak:myrealm:e4b96c3a-1234-5678-abcd-ef0123456789",
"risk_score": 71.2,
"risk_level": "HIGH",
"event_count": 15,
"action": "CHALLENGE_MFA",
"enforced_action": "CHALLENGE_MFA",
"playbook_rule": "rule_brute_force_step_up",
"contributing_factors": ["AUTH_LOGIN_FAILED", "AUTH_BRUTE_FORCE"],
"derived_signals": ["brute_force_detected", "suspicious_location"],
"device_is_new": false,
"device_is_trusted": true,
"recommended_mfa_method": "TOTP",
"otp_policy": {
"validity_seconds": 120,
"max_attempts": 5
},
"mode": "enforce",
"enforcement_mode": "enforce",
"processed_at": "2025-12-16T10:00:01Z",
"latency_ms": 42,
"confidence": 0.95,
"signal_traces": [
{
"detector": "velocity_analyzer",
"signal": "AUTH_BRUTE_FORCE",
"weight": 0.45,
"value": "5 failures in 60s"
}
]
}

Action values

ActionMeaning
ALLOWLogin permitted — proceed without additional challenge
CHALLENGE_MFAMFA step-up required before access is granted
BLOCKLogin rejected — user is not permitted to proceed

The action is the raw engine decision. enforced_action reflects the actual enforcement after mode (enforce / dry_run / shadow) is applied.


Entity IDs

Entity IDs are free-form strings. The Keycloak connector uses:

user:keycloak:{realm_id}:{user_id}

For custom integrations, you can use any stable identifier:

user:alice@example.com
user:uuid:550e8400-e29b-41d4-a716-446655440000
service-account:my-service
device:fp_abc123

Event Types

Common event types:

Event typeDescription
auth.login.attemptUsed with /evaluate for inline RBA
auth.login.successSuccessful login
auth.login.failedFailed login attempt
auth.mfa.successMFA challenge passed
auth.mfa.failedMFA challenge failed
auth.logoutSession terminated
auth.password.changePassword updated
auth.account.lockedAccount disabled
waf.attack.blockedWAF blocked attack
edr.malware.detectedEDR malware alert
dlp.exfiltrationData exfiltration attempt

Use dot-namespaced names. Unknown event types are scored with neutral weight and logged.


Connector Headers (Keycloak connector)

The Keycloak connector sends these additional headers on every request:

X-Connector-Type: keycloak
X-Connector-Version: 1.0.0
X-Tenant-ID: {tenant_id} (when MULTI_TENANT=true)

You don't need to set these from custom integrations.


Error Responses

{
"error": "invalid_request",
"message": "missing required field: entity_id",
"request_id": "req_abc123"
}
StatusMeaning
400Malformed JSON or missing required fields
401Invalid or missing API key
403API key valid but insufficient scope
404Entity not found (risk/decision queries)
429Rate limit exceeded
500Internal server error

gRPC

The Humifortis engine also exposes a gRPC interface on the same port (8080) via cmux. Proto definitions are in the api/proto/ directory of the engine repository.


See Also