Skip to main content

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.

RUT Format & Validation

Chilean RUT Format

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

Automatic Formatting

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

1

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.
2

Client-Side Validation

RUT is validated before sending to PJUD:
const { isValid, error } = validateRUT(formData.rut)

if (!isValid) {
  setRutError(error)
  return
}
3

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')
  }
}
4

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'
})
5

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'
  }
}
6

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

// 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 StatusProfile VisibilitySearch RankingBookable
Not verifiedHiddenN/ANo
PendingLimited (search only)LowNo
VerifiedFullHighYes
PJUD verification combined with 100% profile completion maximizes your visibility in client searches.

Troubleshooting

  1. Ensure RUT is correctly formatted (XX.XXX.XXX-X)
  2. Verify you’re registered with Colegio de Abogados
  3. Check if your PJUD registration is active
  4. Try again in a few minutes (PJUD API may be temporarily unavailable)
The PJUD API can be slow. If you see a timeout error:
  1. Wait 1-2 minutes
  2. Click “Verificar” again
  3. 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.
RUT fields are editable even after verification, but changing the RUT resets verification status. You’ll need to verify the new RUT.

Build docs developers (and LLMs) love