Overview
upLegal uses Chile’s Poder Judicial (PJUD) public registry to verify that lawyers are legitimately registered and authorized to practice law in Chile. This verification is mandatory for all lawyer accounts.
Why Verification is Required
Verification ensures:
✅ Client trust : Clients can book with confidence knowing lawyers are verified
✅ Platform integrity : Prevents fraudulent lawyer accounts
✅ Legal compliance : Meets Chilean regulations for legal service platforms
✅ Profile visibility : Verified profiles rank higher in search results
Unverified lawyer profiles are not shown to clients in the marketplace. Verification is required to receive bookings.
The RUT (Rol Único Tributario) is Chile’s national identification number in format:
12.345.678-9
│ │ │ └─ Verification digit (0-9 or K)
│ │ └─── Last 3 digits
│ └────── Middle 3 digits
└───────── First 2-3 digits
The RUT input field automatically formats as you type:
const formatRUT = ( rut : string ) : string => {
// Remove all non-digit and non-k/K characters
let cleanRut = rut . replace ( / [ ^ \dkK ] / g , '' )
if ( ! cleanRut ) return ''
if ( cleanRut . length === 1 ) return cleanRut
// Extract verification digit (last character)
const dv = cleanRut . slice ( - 1 ). toUpperCase ()
let number = cleanRut . slice ( 0 , - 1 )
// Add dots as thousand separators from right to left
let formatted = ''
let counter = 0
for ( let i = number . length - 1 ; i >= 0 ; i -- ) {
formatted = number [ i ] + formatted
counter ++
if ( counter === 3 && i > 0 ) {
formatted = '.' + formatted
counter = 0
}
}
// Add hyphen and verification digit
return ` ${ formatted } - ${ dv } `
}
// Example transformations:
// "12345678" → "1.234.567-8"
// "123456789" → "12.345.678-9"
// "12345678K" → "12.345.678-K"
Validation Algorithm
The system validates RUT using the official Chilean algorithm:
const validateRUT = ( rut : string ) : { isValid : boolean ; error ?: string } => {
if ( ! rut || typeof rut !== 'string' ) {
return { isValid: false , error: 'RUT no válido' }
}
// Clean RUT (remove dots and hyphen)
const cleanRut = rut . replace ( / \. / g , '' ). replace ( /-/ g , '' ). toUpperCase ()
// Validate format (7-8 digits + 1 check digit or 'K')
if ( ! / ^ \d {7,8} [ 0-9Kk ] $ / . test ( cleanRut )) {
return { isValid: false , error: 'Formato de RUT inválido' }
}
// Extract number and check digit
const rutNumber = cleanRut . slice ( 0 , - 1 )
const checkDigit = cleanRut . slice ( - 1 ). toUpperCase ()
// Calculate expected check digit using Modulo 11
let sum = 0
let multiplier = 2
for ( let i = rutNumber . length - 1 ; i >= 0 ; i -- ) {
sum += parseInt ( rutNumber . charAt ( i )) * multiplier
multiplier = multiplier === 7 ? 2 : multiplier + 1
}
const remainder = sum % 11
const expectedCheckDigit =
( 11 - remainder ) === 11 ? '0' :
( 11 - remainder ) === 10 ? 'K' :
String ( 11 - remainder )
// Compare calculated vs provided check digit
if ( expectedCheckDigit !== checkDigit ) {
return { isValid: false , error: 'RUT no válido' }
}
return { isValid: true }
}
PJUD Verification Process
Step-by-Step Flow
Enter RUT
Lawyer enters RUT in profile form: < Input
name = "rut"
value = {formData. rut }
onChange = { handleRutChange }
placeholder = "12.345.678-9"
/>
Input automatically formats to XX.XXX.XXX-X format as you type.
Client-Side Validation
RUT is validated before sending to PJUD: const { isValid , error } = validateRUT ( formData . rut )
if ( ! isValid ) {
setRutError ( error )
return
}
Click Verify Button
User clicks “Verificar” to initiate verification: const handleVerifyRUT = async () => {
setVerificationStatus ( 'verifying' )
setIsVerifying ( true )
const result = await verifyWithPJUD (
formData . rut ,
` ${ formData . first_name } ${ formData . last_name } `
)
if ( result ?. verified ) {
setFormData ( prev => ({
... prev ,
pjud_verified: true
}))
setVerificationStatus ( 'success' )
}
}
API Call to PJUD
Backend calls PJUD search endpoint: // lib/pjudScraper.ts
const searchUrl = 'https://www.pjud.cl/ajax/Lawyers/search'
// Split RUT into body and verifier
const cleanRut = rut . replace ( / \. / g , '' ). replace ( /-/ g , '' )
const rutBody = cleanRut . slice ( 0 , - 1 ) // e.g., "12345678"
const rutVerifier = cleanRut . slice ( - 1 ) // e.g., "9"
// Prepare form data
const formData = new URLSearchParams ()
formData . append ( 'dni' , rutBody )
formData . append ( 'digit' , rutVerifier )
// Submit POST request
const response = await fetch ( searchUrl , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/x-www-form-urlencoded' ,
'Accept' : 'text/html, */*; q=0.01'
},
body: formData . toString (),
mode: 'cors'
})
Parse PJUD Response
The response HTML is parsed using Cheerio: const resultHtml = await response . text ()
const $ = cheerio . load ( resultHtml )
// Check for "No results" alert
if ( $ ( '.alert-warning' ). length > 0 &&
$ ( '.alert-warning' ). text (). includes ( 'No se encontraron registros' )) {
return {
verified: false ,
message: 'No se encontró información del abogado en el Poder Judicial'
}
}
// Extract data from result table
const resultTable = $ ( 'table' )
const rows = resultTable . find ( 'tbody tr' )
if ( rows . length === 0 ) {
return {
verified: false ,
message: 'No se encontraron resultados válidos'
}
}
// If we found at least one row, RUT exists as a lawyer
const firstRow = rows . first ()
const cols = firstRow . find ( 'td' )
return {
verified: true ,
message: 'Abogado verificado exitosamente' ,
data: {
nombre: cols . eq ( 0 ). text (). trim (),
rut: rut ,
region: cols . eq ( 2 ). text (). trim (),
estado: 'Habilitado'
}
}
Update Profile
On successful verification, profile is updated: const { error } = await supabase
. from ( 'profiles' )
. update ({
rut: rut ,
pjud_verified: true ,
updated_at: new Date (). toISOString ()
})
. eq ( 'id' , user ?. id )
if ( ! error ) {
toast ({
title: 'RUT verificado' ,
description: 'El RUT ha sido verificado exitosamente como abogado.' ,
variant: 'default'
})
}
Verification States
The verification process has four states:
type VerificationStatus = 'idle' | 'verifying' | 'success' | 'error'
State Indicators
Idle State
Verifying State
Success State
Error State
// Default state before verification
< Button
type = "button"
variant = "outline"
onClick = { handleVerifyRUT }
disabled = { ! formData . rut || ! isEditing }
>
Verificar
</ Button >
CORS & Fallback Strategy
Direct browser requests to PJUD may fail due to CORS. The system includes a fallback:
try {
// Try direct request first
searchResponse = await fetch ( searchUrl , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/x-www-form-urlencoded' ,
},
body: formData . toString (),
mode: 'cors'
})
} catch ( corsError ) {
console . warn ( 'Direct request failed (CORS), trying proxy...' )
// Fallback: Use CORS proxy
const proxyUrl = `https://api.allorigins.win/raw?url= ${ encodeURIComponent ( searchUrl ) } `
searchResponse = await fetch ( proxyUrl , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/x-www-form-urlencoded' ,
},
body: formData . toString ()
})
}
In production, it’s recommended to route PJUD requests through your backend to avoid CORS issues and secure API access.
Verification Reset
If RUT is modified after verification, the status is reset:
const handleRutChange = ( e : React . ChangeEvent < HTMLInputElement >) => {
const formattedRut = formatRUT ( e . target . value )
// Check if RUT actually changed
const rutChanged = formattedRut !== formData . rut
if ( rutChanged ) {
setFormData ( prev => ({
... prev ,
rut: formattedRut ,
pjud_verified: false // Reset verification
}))
setVerificationStatus ( 'idle' )
setRutError ( null )
}
}
Changing your RUT after verification requires re-verification. The system automatically resets pjud_verified to false.
Error Handling
Comprehensive error messages for different failure scenarios:
interface PJUDVerificationResult {
verified : boolean
message ?: string
error ?: string
data ?: {
nombre ?: string
region ?: string
estado ?: string
}
}
// Error scenarios
const errors = {
invalidFormat: 'Formato de RUT inválido. Use el formato 12345678-9' ,
notFound: 'No se encontró información del abogado en el Poder Judicial' ,
networkError: 'Error de conexión. Verifica tu conexión a internet.' ,
timeout: 'La verificación está tomando más tiempo de lo esperado.' ,
unauthorized: 'Tu sesión ha expirado. Inicia sesión nuevamente.' ,
serverError: 'Error al conectar con el servicio de verificación'
}
Database Storage
Verification data is stored in the profiles table:
CREATE TABLE profiles (
id UUID PRIMARY KEY ,
user_id UUID REFERENCES auth . users (id),
rut VARCHAR ( 12 ), -- Format: "12.345.678-9"
pjud_verified BOOLEAN DEFAULT FALSE,
verification_date TIMESTAMP ,
-- ... other fields
);
And also synced to auth.users.user_metadata:
const { error } = await supabase . auth . updateUser ({
data: {
rut: formData . rut ,
pjud_verified: true ,
verification_date: new Date (). toISOString ()
}
})
Verification Badge Display
Verified lawyers display a badge on their public profile:
{ profile . pjud_verified && (
< Badge variant = "success" className = "flex items-center gap-1" >
< ShieldCheck className = "h-3 w-3" />
Verificado PJUD
</ Badge >
)}
Impact on Profile Visibility
Verification Status Profile Visibility Search Ranking Bookable Not verified Hidden N/A No Pending Limited (search only) Low No Verified Full High Yes
PJUD verification combined with 100% profile completion maximizes your visibility in client searches.
Troubleshooting
Verification fails with valid RUT
Ensure RUT is correctly formatted (XX.XXX.XXX-X)
Verify you’re registered with Colegio de Abogados
Check if your PJUD registration is active
Try again in a few minutes (PJUD API may be temporarily unavailable)
The PJUD API can be slow. If you see a timeout error:
Wait 1-2 minutes
Click “Verificar” again
If it persists, contact support with your RUT
The verification system checks that the RUT exists in PJUD’s lawyer registry. Name matching is informational only and doesn’t affect verification status.
Can't modify verified RUT
RUT fields are editable even after verification, but changing the RUT resets verification status. You’ll need to verify the new RUT.