Skip to main content

The Challenge Object

A challenge is a single verification a user has to complete — an OTP, a magic link, a passkey prompt, etc. One challenge tracks one attempt at proving something: that the user owns this email, that they’re really the one signing in, that they consent to a sensitive action.

Attributes

id
string
required
Unique identifier.
app_id
string
required
The app this challenge belongs to.
app_user_id
string
The user being challenged. May be empty for anonymous flows (e.g. a sign-up before the user record exists).
purpose
string
required
Why this challenge exists. One of:
  • authenticate — primary login
  • mfa — second factor after primary auth
  • step_up — re-verify before a sensitive action
  • verify_contact — confirm an email or phone number
  • verify_identity — KYC / identity verification
  • change_identifier — confirm a new email or phone before swapping
  • custom — triggered by you, for your own flow
challenge_method
string
required
How the user will prove themselves. One of:email_otp, sms_otp, magic_link, totp, backup_code, webauthn, oauth, push, plaid_idv
status
string
required
Current state. One of:
  • pending — issued, waiting on the user
  • completed — user passed
  • failed — too many wrong attempts
  • expiredexpires_at passed before completion
  • cancelled — voided by you or the user
  • denied — explicitly rejected (e.g. user said “this wasn’t me”)
identifier
string
The email or phone the challenge was sent to, when applicable.
intent
string
Free-form label for what action this challenge guards (e.g. login, delete_account, wire_transfer). Useful for filtering and audit.
intent_fields
object
Structured data describing the action. Echoed back when the challenge completes so you can act on it.
initiator_type
string
Who started the challenge: user, admin, system, or your own value.
initiator_id
string
ID of the initiator.
parent_challenge_id
string
If this challenge is a follow-up to another (e.g. MFA after primary auth), the parent’s ID.
attempts
integer
Number of guesses made so far.
max_attempts
integer
How many guesses are allowed before the challenge fails. Default 3.
remaining_attempts
integer
max_attempts - attempts. Convenience field.
timeout
integer
Lifetime in seconds from creation. Default 600.
callback_url
string
Where the user is sent after completing a magic link or OAuth challenge.
short_url
string
Shareable short URL for magic-link and similar flows. Only present once a short token has been minted.
metadata
object
Arbitrary key/value data you attached when creating the challenge.
context
object
Additional context captured by Scute (request origin, flow hints, etc.).
device_info
object
User agent / device fingerprint of the requester, when available.
ip_address
string
IP that initiated the challenge.
created_at
string
required
ISO 8601 timestamp.
expires_at
string
required
When the challenge stops accepting answers.
delivered_at
string
When the OTP / magic link was successfully sent.
opened_at
string
When the user first opened the magic link or OTP message, if tracked.
verified_at
string
When the user passed the challenge.
completed_at
string
When the challenge reached its final state (verified, failed, expired, cancelled, or denied).

Example

{
  "id": "ch_01H8X...",
  "app_id": "app_01H8X...",
  "app_user_id": "usr_01H8X...",
  "purpose": "authenticate",
  "challenge_method": "email_otp",
  "status": "pending",
  "identifier": "user@example.com",
  "intent": "login",
  "intent_fields": {},
  "initiator_type": "user",
  "initiator_id": "usr_01H8X...",
  "attempts": 0,
  "max_attempts": 3,
  "remaining_attempts": 3,
  "timeout": 600,
  "callback_url": null,
  "short_url": null,
  "metadata": {},
  "context": {},
  "ip_address": "203.0.113.42",
  "created_at": "2026-04-23T10:30:00Z",
  "expires_at": "2026-04-23T10:40:00Z",
  "delivered_at": "2026-04-23T10:30:01Z",
  "opened_at": null,
  "verified_at": null,
  "completed_at": null
}