Documentation Index
Fetch the complete documentation index at: https://docs.scute.io/llms.txt
Use this file to discover all available pages before exploring further.
Intent Verification
Verify a user’s identity by sending them a magic link or OTP code. Get the result via webhook or a signed JWT on redirect.
Flow
1. POST /workspaces/{id}/children → create org (once)
2. POST /apps/{app_id}/m2m/token → get server token (once)
3. POST /verify/{app_id}/verifications/intent → send verification
4. User clicks link or enters code
5. You get: webhook + redirect with signed JWT
Setup
Create a child workspace
One per organization. Returns API credentials.
curl -X POST https://api.scute.io/v1/workspaces/{workspace_id}/children \
-H "Authorization: Bearer {workspace_api_key}" \
-H "Content-Type: application/json" \
-d '{
"name": "Acme IT",
"branding_display_name": "Acme IT",
"branding_color": "#0c2340",
"branding_logo_url": "https://example.com/logo.png",
"countries": ["us", "ca"],
"magic_link_expiry": 900,
"webhook_url": "https://your-app.com/webhooks/scute"
}'
Returns app_id and api_key_token. Save both.
Skip this if you’re using a single app — just use your existing credentials.
Get an M2M token
curl -X POST https://api.scute.io/v1/apps/{app_id}/m2m/token \
-H "Authorization: Bearer {api_key_token}" \
-H "Content-Type: application/json" \
-d '{"client_name": "my-backend"}'
Use the returned short_token (scm2m_...) in X-Authorization for all verification requests.
Send a verification
POST /v1/verify/{app_id}/verifications/intent
X-Authorization: Bearer {short_token}
| Field | Required | Description |
|---|
intent_name | yes | e.g. “Password Reset”, “Approve Ticket” |
method | no | email (default) or sms |
verification_type | no | magic_link or otp |
redirect_url | no | Where to send the user after verification |
meta_data.contact_email | for email | Recipient email |
meta_data.contact_phones | for sms | [{ "phone_number": "+15551234567", "phone_type": "mobile" }] |
meta_data.contact_name | no | Display name |
meta_data.ticket_id | no | Your reference ID — returned in webhooks and JWT |
meta_data.contact_id | no | Your user ID — returned in webhooks and JWT |
Add any extra fields to meta_data — they’re all echoed back.
Email magic link
await fetch(`https://api.scute.io/v1/verify/${APP_ID}/verifications/intent`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Authorization": `Bearer ${M2M_TOKEN}`,
},
body: JSON.stringify({
intent_name: "Password Reset",
method: "email",
verification_type: "magic_link",
redirect_url: "https://your-app.com/verified",
meta_data: {
contact_email: "user@company.com",
contact_name: "Sarah",
ticket_id: "12345",
},
}),
});
SMS OTP
await fetch(`https://api.scute.io/v1/verify/${APP_ID}/verifications/intent`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Authorization": `Bearer ${M2M_TOKEN}`,
},
body: JSON.stringify({
intent_name: "Confirm Payment",
method: "sms",
verification_type: "otp",
meta_data: {
contact_email: "user@company.com",
contact_phones: [{ phone_number: "+15551234567", phone_type: "mobile" }],
},
}),
});
Getting the result
Option A: Redirect with JWT
After verification, the user is redirected to:
https://your-app.com/verified?scute_token=eyJ...
The scute_token is an RS256 JWT signed with your app’s key. Verify it against your JWKS:
GET /v1/auth/{app_id}/jwks
Payload:
{
"sub": "user@company.com",
"verification_id": "uuid",
"intent": "Password Reset",
"ticket_id": "12345",
"contact_id": "1991",
"verified_at": 1776316476,
"exp": 1776316536,
"iss": "scute",
"aud": "app_id"
}
60-second TTL. One-time use.
Redirect URL template — set a default in Settings with placeholders:
https://your-app.com/verified?ticket={ticket_id}&contact={contact_id}
The scute_token is appended automatically.
Option B: Webhook
Events fire to your registered webhook URL.
| Event | When |
|---|
verification.*.requested | Sent |
verification.*.verified | Confirmed |
verification.*.expired | Timed out |
verification.*.failed | OTP max attempts |
verification.*.denied | Contact denied (consent mode) |
verification.*.delivery_failed | SMS/email didn’t send |
Replace * with email or sms.
Payload:
{
"event_type": "verification.sms.verified",
"data": {
"verification_id": "uuid",
"intent": "Password Reset",
"meta_data": { "ticket_id": "12345", "contact_email": "user@company.com" },
"timing": {
"sent_at": "...",
"confirmed_at": "...",
"time_to_verify_seconds": 52
},
"device": { "os": "iOS", "browser": "Safari" },
"ip_address": "203.0.113.42"
}
}
Expiration events include was_opened: true/false so you know if the link was ever clicked.
Signature: X-Webhook-Signature: t={timestamp},v1={hmac} — verify with HMAC-SHA256("{timestamp}.{payload}", webhook_secret).
Verification modes
Set in Settings > Verification.
Dismiss (default) — confirm identity, redirect. Done.
Consent — confirm identity, then ask the user to approve or deny the action. Webhook includes consent_decision: "approved" or "denied".
Managing verifications
| Action | Endpoint |
|---|
| Check status | GET /v1/verify/{app_id}/verifications/{id} |
| Cancel | DELETE /v1/verify/{app_id}/verifications/{id} |
| Resend | POST /v1/verify/{app_id}/verifications/{id}/resend |
| Deny | POST /v1/verify/{app_id}/verifications/{id}/deny |
Settings
| Setting | Default |
|---|
| Tenant mode | full or verify_only |
| Verification mode | dismiss or consent |
| Redirect URL template | — |
| Redirect delay | 4s |
| Success message | ”Your identity has been successfully verified.” |
| Rate limit | 10/hour per contact |
Testing
Scute Testbench — connect with M2M token, search users, send verifications, watch webhooks live.
Local webhooks: ngrok http 3000