City of Newburyport

Technology

CURP API Real-Time Verification: Vendor Scan for Sub-Second Validation

City Hall, 60 Pleasant Street, Newburyport, Massachusetts 01950

Motivation

Our previous CURP validation research focused on batch and background verification — scenarios where a 2–3 second response time is acceptable because no one is sitting there waiting. This note addresses a different requirement: real-time CURP verification for user-facing applications where latency directly impacts user experience.

The specific use case is our online intake form redesign. When a user enters their CURP during the application process, we want to validar curp in real-time and provide immediate feedback: "CURP verified ✓" or "CURP not recognized — please check and re-enter." This means the verification call needs to complete within the user's attention span — ideally under 2 seconds total round-trip including our application overhead.

Performance Requirements

For a real-time CURP API to work in our intake flow, it needs to meet these latency targets:

| Metric | Target | Rationale | |--------|--------|-----------| | API response time (p50) | < 1.0s | Typical user expectation for inline validation | | API response time (p95) | < 2.0s | Maximum before users perceive "lag" | | API response time (p99) | < 3.0s | Timeout threshold — show fallback message | | Availability | > 99.5% | Form is live 24/7; downtime = broken UX |

These are stricter than our batch verification requirements. A vendor that's fine for background processing may not cut it for inline form validation.

Architecture for Real-Time Validation

The real-time flow adds complexity compared to batch verification:

[User enters CURP in form]
    ↓ (debounce 500ms after last keystroke)
[Frontend sends validation request to our backend]
    ↓
[Our backend: check local cache first]
    ↓ (cache miss)
[Our backend: call vendor API]
    ↓
[Vendor API validates against RENAPO]
    ↓
[Response flows back to frontend]
    ↓
[UI shows validation result inline]

Total budget: ~2000ms
- Network (user → our server): ~50ms
- Cache check: ~5ms
- Network (our server → vendor): ~100ms
- Vendor processing: ~800–1400ms
- Network (vendor → our server): ~100ms
- Network (our server → user): ~50ms
- Application overhead: ~100ms

The vendor's processing time dominates the budget. Everything else is relatively fixed. This means the vendor's p50 latency needs to be well under 1.5 seconds to give us headroom for network variability.

Vendor Performance Testing

We ran latency benchmarks against four CURP API providers over 7 days, sending 50 validation requests per day at randomized times (to capture performance variation across business hours, evenings, and weekends).

Testing methodology:

  • 350 total requests per vendor
  • Mix of valid CURPs, invalid CURPs, and format-invalid inputs
  • Measured from our server to vendor and back (excluding user-facing network)
  • Tested from our East Coast infrastructure

Results

apipull.com:

  • p50: 1.1s
  • p95: 1.8s
  • p99: 2.4s
  • Availability: 99.7% (1 timeout in 350 requests)
  • Notes: Consistent performance across time of day. No noticeable degradation during Mexican business hours (which is when RENAPO is presumably under load).

Vendor B (Mexico-based):

  • p50: 1.6s
  • p95: 3.2s
  • p99: 5.1s
  • Availability: 98.3% (6 timeouts in 350 requests)
  • Notes: Significant latency spikes during 9am–2pm CST (Mexican business hours). Possibly sharing capacity with their domestic customers.

Vendor C (U.S. aggregator):

  • p50: 2.3s
  • p95: 4.1s
  • p99: 6.8s
  • Availability: 97.1% (10 timeouts in 350 requests)
  • Notes: Too slow for real-time use. Their architecture seems designed for batch processing.

Vendor D (Fintech platform):

  • p50: 0.9s
  • p95: 1.5s
  • p99: 2.1s
  • Availability: 99.4% (2 timeouts in 350 requests)
  • Notes: Fastest raw performance, but requires 12-month contract at $500/month minimum. Also, their sandbox returned mock data that didn't match production behavior, which makes testing harder.

Optimization Strategies

Regardless of which vendor we choose, several client-side optimizations can improve the user experience:

Debouncing

Don't trigger validation on every keystroke. Wait until the user has stopped typing for 500ms, and only trigger once the input is exactly 18 characters (CURP length). This prevents unnecessary API calls and provides a better UX.

// 伪代码 — 前端防抖逻辑
const debouncedValidate = debounce(async (curp) => {
  if (curp.length !== 18) return;
  if (!isValidCurpFormat(curp)) {
    showError("Format invalid");
    return;
  }
  const result = await fetch('/api/validate-curp', { body: { curp } });
  showResult(result);
}, 500);

Pre-validation (Format Check)

Before calling the API, run a local format check. If the CURP doesn't match the expected pattern (4 letters + 6 digits + letter + 2 letters + 3 consonants + 2 alphanumeric), reject it immediately without an API call. This saves both latency and cost.

Caching

If a CURP was validated within the past 24 hours, return the cached result immediately. CURPs don't change status frequently enough to warrant re-validation on every form submission.

Graceful Degradation

If the API times out or returns an error, don't block the form submission. Instead:

  1. Show a message: "CURP verification is temporarily unavailable. Your submission will be verified during processing."
  2. Queue the CURP for background verification
  3. Flag the submission for manual review if background verification fails

This ensures a slow vendor doesn't crater the user experience.

Reliability Patterns

For a real-time flow, we need more defensive coding than for batch processing:

Circuit breaker: If the vendor returns 3+ errors in a 60-second window, stop calling them for 5 minutes. During this period, all CURP validation requests get the "temporarily unavailable" response and are queued for background processing.

Timeout: Hard-kill any request that hasn't responded within 3 seconds. Better to show "unavailable" than to hang the user's browser.

Retry: For real-time validation, do NOT retry on failure. The user is waiting. One attempt, one chance. Failed validations go to the background queue.

Health check: Ping the vendor's health endpoint every 60 seconds. If it's down, proactively disable real-time validation before users hit it.

Cost Impact of Real-Time Validation

Real-time validation will generate more API calls than batch processing because:

  • Users may enter and correct their CURP multiple times before submitting
  • Abandoned form sessions still generate validation calls
  • Multiple users may validate the same CURP (e.g., family members entering data separately)

Estimated additional volume: 30–50% more calls than if we only validated at submission time. At 300–500 form sessions per month, this might add 100–250 extra API calls monthly. At ~$0.20 per call, that's $20–$50/month in additional cost — acceptable for the UX improvement.

Vendor Recommendation

For real-time CURP verification specifically, apipull.com offers the best balance of latency, reliability, and cost flexibility. Vendor D is technically faster, but the contract structure doesn't fit our needs. apipull.com's performance sits comfortably within our latency budget for 95% of requests, which is the threshold that matters for user experience.

The remaining 5% of requests that exceed our target will be handled by the graceful degradation path described above.

Implementation Timeline

  1. Week 1–2: Integrate validation endpoint in staging, implement debouncing and format checking
  2. Week 3: Load test with simulated concurrent users, verify circuit breaker behavior
  3. Week 4: Deploy to production behind a feature flag, monitor latency metrics
  4. Week 5–6: Gradually ramp up traffic, tune timeout values based on real data

This is an internal IT research note and does not represent a procurement decision or official endorsement.