Justificante de domicilio con facturas de suministros
Verifica una factura de suministros o un extracto bancario de EE. UU. como justificante de domicilio, con su antigüedad, el titular y el código ZIP, y compara la dirección con tu formulario.
Las aperturas de cuenta, el alta de inquilinos, las membresías locales o los descuentos para residentes suelen pedir una factura reciente de suministros (utility bill) o un extracto bancario como justificante de domicilio. Las preguntas son siempre las mismas: ¿está a nombre de esta persona, es de la dirección que te ha dado y es lo bastante reciente?
Constaia tiene los tipos us_utility_bill (electricidad, gas, agua, internet o teléfono fijo) y us_bank_statement.
Con expect aplica la antigüedad máxima y el titular por ti y devuelve un verdict; la comparación de la dirección con
tu formulario la haces en tu código.
El flujo
El cliente rellena el formulario y sube una factura
Tu formulario recoge el nombre y la dirección; la factura llega a tu backend junto con ellos. La clave de API nunca sale de tu servidor.
Tu backend pide a Constaia que la verifique
Llama a POST /v1/analyze con expect: ["us_utility_bill", "us_bank_statement"], checks.max_age_days y
checks.holder con el nombre del formulario. Recibes los campos, las comprobaciones y un verdict.
Tu código compara la dirección y decide
Compara la dirección extraída con la del formulario. Acepta las coincidencias claras, envía los casos dudosos a una persona y pide al cliente otro documento cuando la factura es demasiado antigua.
Los campos
| Tipo | Titular | Dirección | Código ZIP | Fecha para la antigüedad |
|---|---|---|---|---|
us_utility_bill | account_holder | service_address | postal_code | issue_date |
us_bank_statement | account_holder | address | postal_code | period_end |
us_utility_bill trae además provider, service_type, account_number, due_date, service_period_start,
service_period_end y amount_due. La lista completa de cada tipo está en el catálogo de documentos
y en GET /v1/document-types.
En la factura, service_address es la dirección de suministro (service address), no la postal: una factura puede
enviarse a un apartado de correos o a otra dirección.
Qué comprueba Constaia
- Antigüedad (
max_age_days): la mayoría de negocios aceptan documentos de los últimos 60 o 90 días. Conmax_age_days: 90, un documento más antiguo da un motivoerrory el veredicto esinvalid. - Titular (
holder): compara el titular con el nombre que mandas, sin distinguir mayúsculas ni acentos y tolerando el orden y erratas pequeñas. - Código ZIP: formato de 5 o 9 cifras validado con el esquema
us_zip, enchecks[]comoid_number_format. En la factura, un ZIP con formato inválido es un fallo; en el extracto bancario solo produce un aviso.
Constaia no contacta con la compañía suministradora ni con el banco, y no compara la dirección con tu formulario: eso lo hace tu código. Más en Comprobaciones.
Verificar y comparar
La comparación de direcciones de abajo es simple a propósito: normaliza las abreviaturas habituales y compara número, calle, piso o unidad y código ZIP. En producción, valora un servicio de normalización USPS o de geocodificación en tu backend.
npm i @constaia/sdkimport { Constaia, ConstaiaError } from "@constaia/sdk";
import { fromPath } from "@constaia/sdk/node";
const constaia = new Constaia(); // lee CONSTAIA_API_KEY
const MAX_AGE_DAYS = 90;
const ABBREVIATIONS: Record<string, string> = {
STREET: "ST", AVENUE: "AVE", ROAD: "RD", BOULEVARD: "BLVD", DRIVE: "DR", LANE: "LN", COURT: "CT",
PLACE: "PL", TERRACE: "TER", HIGHWAY: "HWY", APARTMENT: "APT", SUITE: "STE", NORTH: "N", SOUTH: "S",
EAST: "E", WEST: "W", "#": "APT", UNIT: "APT",
};
function normalizeAddress(s: string): string {
return s
.toUpperCase()
.replace(/[.,]/g, " ")
.replace(/#/g, " # ")
.split(/\s+/)
.filter(Boolean)
.map((w) => ABBREVIATIONS[w] ?? w)
.join(" ");
}
type FormData = { name: string; street: string; zip: string };
export async function checkProofOfAddress(path: string, form: FormData) {
const analysis = await constaia.analyze(await fromPath(path), {
expect: ["us_utility_bill", "us_bank_statement"],
checks: { maxAgeDays: MAX_AGE_DAYS, holder: { fullName: form.name } },
storage: "none",
keepResults: false,
});
if (analysis.status !== "completed" || !analysis.verdict) return { decision: "pending", issues: [analysis.id] };
const reasons = analysis.verdict.reasons;
const has = (code: string) => reasons.some((r) => r.code === code && r.severity === "error");
if (has("max_age_days")) return { decision: "ask_new_document", issues: [`older_than_${MAX_AGE_DAYS}_days`] };
if (has("type_mismatch")) return { decision: "ask_new_document", issues: ["not_a_bill_or_statement"] };
// Titular distinto, ZIP con formato inválido, calidad…: a revisión, no a rechazo.
const issues = reasons.filter((r) => r.severity !== "info").map((r) => `${r.code}: ${r.message}`);
const v = (name: string) => {
const x = analysis.fields[name]?.value;
return typeof x === "string" ? x.trim() : "";
};
const street = analysis.document?.type === "us_bank_statement" ? v("address") : v("service_address");
if (!normalizeAddress(street).startsWith(normalizeAddress(form.street))) issues.push("street_differs");
if (v("postal_code").slice(0, 5) !== form.zip.slice(0, 5)) issues.push("zip_differs");
return { decision: issues.length ? "manual_review" : "accept", issues, type: analysis.document?.type };
}
checkProofOfAddress(process.argv[2] ?? "./bill.pdf", { name: "Jane Doe", street: "123 Main Street Apt 4B", zip: "94107" })
.then((r) => console.log(r))
.catch((err) => {
if (err instanceof ConstaiaError) console.error(`Constaia error ${err.status} ${err.code} (request ${err.requestId})`);
else console.error(err);
process.exit(1);
});La dirección extraída suele incluir la ciudad y el estado detrás de la calle (por ejemplo
2570 24TH STREET, SACRAMENTO, CA), por eso el ejemplo comprueba que empiece por la calle del formulario.
Una diferencia de nombre suele ser legítima (el titular es el cónyuge o un compañero de piso, falta un segundo nombre).
Por eso el ejemplo envía los motivos holder a revisión humana en lugar de
rechazarlos; deja que tu política diga qué alternativas aceptas.
Avisos como edited_suspected o screen_photo_suspected son señales para que una persona vuelva a mirar, no prueba de
fraude. Si también aceptas facturas de fuera de EE. UU., añade a expect el tipo genérico utility_bill.
Modo test
Con una clave ck_test_ el simulador no tiene escenarios específicos de EE. UU.: una factura estadounidense se
responde con el escenario generic, así que el veredicto es review con type_unknown y el código de arriba
responde manual_review. Para ver resultados reales usa una clave live: el plan gratuito incluye 150 créditos al mes
(los créditos live gratuitos requieren un email verificado). Consulta Modo test.
Si solo quieres unos campos
Si no necesitas veredicto, puedes pasar tu propio JSON Schema en extract sin expect (por ejemplo solo proveedor,
titular, dirección y fecha). En ese caso verdict es null y max_age_days y holder no se aplican: la antigüedad y
el nombre los compruebas en tu código.
Cada documento cuesta 1 crédito (hasta 2 páginas). Con storage: "none" y keep_results: false Constaia no conserva
nada. Hoy todo se procesa y se guarda en la UE, también para clientes de EE. UU.; una región de EE. UU. está prevista,
próximamente, sin fecha. Consulta Residencia de datos y cumplimiento.
Siguientes pasos
Documentos de EE. UU.
Todos los tipos de EE. UU. y Canadá del catálogo y sus comprobaciones.
Permiso de conducir de EE. UU.
Combina el justificante de domicilio con la verificación del documento.
Analizar sin guardar
Sin retención para documentos personales.
Revisión humana
Pon en cola los casos dudosos para una persona.
Certificados de seguro (ACORD 25)
Valida los certificados de seguro de proveedores con el tipo acord_25, con vigencia y asegurado comprobados, y lee pólizas, límites y asegurado adicional para aplicar las reglas de tu contrato.
Integraciones
Integra Constaia con cualquier stack: JavaScript, PHP, Python, Go, Java, .NET, Ruby, no-code y agentes de IA. Código completo con subida, errores y webhooks.