Ir al contenido

Documentación de la API de Fileverity

Código de ejemplo

Programas completos en Node.js y Python para la API de Fileverity: comprobar una imagen, comprobar un PDF grande, detectar texto escrito con IA y verificar firmas de webhooks.

Ver como MarkdownActualizado el 2026-11-04

Esta página contiene cuatro programas completos para la API de Fileverity, cada uno en Node.js 18 o posterior y en Python 3.9 o posterior: comprobar una imagen y esperar el resultado, comprobar un PDF grande, detectar texto escrito con IA y recibir webhooks con verificación de firma. Copie uno, defina FILEVERITY_API_KEY y ejecútelo.

Los programas llaman directamente a la API HTTPS, con el fetch integrado en Node.js y el paquete requests en Python, así que solo necesita un cliente HTTP. Las llamadas, los campos y los estados son los descritos en Archivos, Análisis, Detecciones de texto y Webhooks.

ProgramaQué haceLlamadas
Comprobar una imagenSube una imagen, espera el resultado e imprime el resumen y la URL del informe.POST /files, POST /analyses, GET /analyses/{id}
Comprobar un PDF grandeSube un documento con una URL prefirmada, consulta periódicamente hasta que termina la comprobación y guarda el informe en PDF.POST /files, GET /files/{id}, POST /analyses, GET /analyses/{id}, GET /analyses/{id}/report.pdf
Detectar texto escrito con IAEnvía un archivo de texto e imprime el veredicto, la puntuación y cada pasaje escrito con IA.POST /text/detections
Recibir webhooksVerifica la firma de cada envío, ignora los duplicados y gestiona los eventos.Su endpoint, llamado con analysis.completed, analysis.failed y balance.low

Antes de ejecutar

  • Cree una clave Test en Claves de API de la Consola. Una clave de prueba devuelve resultados de ejemplo fijos y nunca se cobra, así que todos los programas se ejecutan gratis. Para comprobar archivos reales, cree una clave Live. Los programas no cambian.
  • Node.js: guarde cada programa como un archivo .mjs, lo que le permite usar import y await de nivel superior, y ejecútelo con node. No hay nada que instalar.
  • Python: ejecute pip install requests. El receptor de webhooks necesita además pip install flask.
export FILEVERITY_API_KEY=fv_test_...

Cómo gestionan los programas las claves, los errores y los reintentos

Los tres programas que llaman a la API comparten las mismas funciones request y api. Cópielas en su propio código tal cual.

AspectoQué hacen los programas
ClaveSe lee de FILEVERITY_API_KEY y nunca se escribe en el código. Llame a la API desde su servidor, no desde código del navegador.
IdempotenciaCada POST envía una Idempotency-Key, un UUID nuevo por solicitud, y lo reutiliza en cada reintento, de modo que un reintento nunca ejecuta ni cobra una comprobación dos veces. Para que sea seguro volver a ejecutar todo el script, derive en su lugar la clave del ID de su propio registro. Consulte Idempotencia.
ErroresUna respuesta que no es 2xx incluye error.type, error.code, error.message, error.param y error.request_id. Los programas se detienen con code: message (request request_id). Base su lógica en code e indique el ID de solicitud a soporte. Consulte Errores.
ReintentosSe reintentan los fallos de red, un estado 5xx (internal_error, analysis_failed, service_unavailable), rate_limited (429) e idempotency_in_progress (409), hasta 5 intentos. La espera es la del encabezado retry-after cuando la respuesta lo incluye; si no, 2, 4, 8 y 16 segundos. Cualquier otro 4xx se detiene de inmediato. pending_files_limit también es un 429, pero no tiene retry-after, así que no se reintenta.
Límite de frecuenciaCuando una respuesta indica x-ratelimit-remaining-requests: 0, el programa espera x-ratelimit-reset-requests segundos antes de la siguiente llamada, para no recibir un 429. Consulte Límites de frecuencia y encabezados.
Tiempos de esperaLos programas de Python agotan el tiempo de espera a los 90 segundos por solicitud, un plazo superior a los 60 segundos que Prefer: wait puede mantener abierta una solicitud.

Comprobar una imagen y esperar el resultado

El programa sube la imagen como multipart, crea un análisis con Prefer: wait=60 para que la solicitud se mantenga abierta hasta que el resultado esté listo, e imprime el resumen y la URL del informe. Si la espera termina antes, la API responde 202 con status: processing y el programa consulta GET /analyses/{id} cada 3 segundos. Consulte Esperar, consultar periódicamente o usar webhooks.

Guárdelo como check-image.mjs o check_image.py y ejecute node check-image.mjs photo.jpg o python check_image.py photo.jpg. Multipart admite imágenes de hasta 4 MB. Para una imagen más grande, de hasta 10 MB, use la subida prefirmada del programa siguiente con el tipo de contenido de la imagen, por ejemplo image/jpeg.

import { randomUUID } from "node:crypto";import { readFile } from "node:fs/promises";import { basename } from "node:path"; const base = "https://api.fileverity.com/v1";const key = process.env.FILEVERITY_API_KEY;const path = process.argv[2];if (!key || !path) throw new Error("Usage: FILEVERITY_API_KEY=... node check-image.mjs photo.jpg"); const retryable = ["rate_limited", "idempotency_in_progress"];const sleep = (seconds) => new Promise((resolve) => setTimeout(resolve, seconds * 1000)); async function request(url, init) {  for (let attempt = 1; ; attempt++) {    let response = null;    let problem;    try {      response = await fetch(url, init);      if (response.ok) {        const remaining = response.headers.get("x-ratelimit-remaining-requests");        const reset = Number(response.headers.get("x-ratelimit-reset-requests"));        if (remaining === "0") await sleep(reset);        return response;      }      problem = (await response.json().catch(() => ({}))).error ?? {};    } catch (error) {      problem = { code: "network_error", message: error.message };    }    const status = response?.status ?? 0;    const retry = status === 0 || status >= 500 || retryable.includes(problem.code);    if (!retry || attempt === 5) {      const { code = status, message = "Request failed", request_id: id = "unknown" } = problem;      throw new Error(`${code}: ${message} (request ${id})`);    }    await sleep(Number(response?.headers.get("retry-after")) || 2 ** attempt);  }} async function api(method, route, { headers, body } = {}) {  const auth = { Authorization: `Bearer ${key}` };  const response = await request(base + route, { method, headers: { ...auth, ...headers }, body });  return response.json();} const form = new FormData();form.append("file", new Blob([await readFile(path)]), basename(path));const file = await api("POST", "/files", {  headers: { "Idempotency-Key": randomUUID() },  body: form,}); let analysis = await api("POST", "/analyses", {  headers: {    "Content-Type": "application/json",    "Idempotency-Key": randomUUID(),    Prefer: "wait=60",  },  body: JSON.stringify({ model: "fvimg-2", file: file.id }),});while (analysis.status === "processing") {  await sleep(3);  analysis = await api("GET", `/analyses/${analysis.id}`);} if (analysis.status !== "succeeded") {  throw new Error(`The check ${analysis.status}: ${analysis.error?.message}`);}const { level, action, assessment, ai, edit } = analysis.summary;console.log(`${analysis.id} ${analysis.status}`);console.log(`level ${level}, action ${action}, assessment ${assessment}`);console.log(`AI generation: ${ai}, editing: ${edit}`);console.log(`Report: ${analysis.report_url}`);
an_8Hk2pQ4vN9sL1xR7cW3mY6zB succeededlevel L2, action accept, assessment authentic_untouchedAI generation: very_low, editing: noneReport: https://fileverity.com/verify/FV-7K2M-9QXD-4T8R

summary es el veredicto estable y legible por máquinas de una comprobación de imagen: level, action, assessment, ai y edit, descritos en Resumen de imagen. report_url es la página de verificación pública del informe. Con una clave de prueba el resultado es un ejemplo fijo, y su URL del informe no lleva a ninguna página.

Comprobar un PDF grande

Los documentos de hasta 50 MB se suben en tres pasos: pida una URL de subida con POST /files y un cuerpo JSON, envíe los bytes a esa URL con PUT y espere hasta que el archivo muestre uploaded. Después, el programa crea el análisis y lo consulta cada 5 segundos, porque los documentos pueden quedar en cola y una comprobación puede tardar. Imprime el progreso, el resumen y la URL del informe, y guarda el informe en PDF junto al script.

Guárdelo como check-pdf.mjs o check_pdf.py y ejecute node check-pdf.mjs contract.pdf o python check_pdf.py contract.pdf. Para otro tipo de documento, cambie content_type por uno de los tipos de contenido admitidos. Para un documento cifrado, añada options: { password } a la solicitud de análisis.

import { randomUUID } from "node:crypto";import { readFile, writeFile } from "node:fs/promises";import { basename } from "node:path"; const base = "https://api.fileverity.com/v1";const key = process.env.FILEVERITY_API_KEY;const path = process.argv[2];if (!key || !path) throw new Error("Usage: FILEVERITY_API_KEY=... node check-pdf.mjs contract.pdf"); const retryable = ["rate_limited", "idempotency_in_progress"];const sleep = (seconds) => new Promise((resolve) => setTimeout(resolve, seconds * 1000)); async function request(url, init) {  for (let attempt = 1; ; attempt++) {    let response = null;    let problem;    try {      response = await fetch(url, init);      if (response.ok) {        const remaining = response.headers.get("x-ratelimit-remaining-requests");        const reset = Number(response.headers.get("x-ratelimit-reset-requests"));        if (remaining === "0") await sleep(reset);        return response;      }      problem = (await response.json().catch(() => ({}))).error ?? {};    } catch (error) {      problem = { code: "network_error", message: error.message };    }    const status = response?.status ?? 0;    const retry = status === 0 || status >= 500 || retryable.includes(problem.code);    if (!retry || attempt === 5) {      const { code = status, message = "Request failed", request_id: id = "unknown" } = problem;      throw new Error(`${code}: ${message} (request ${id})`);    }    await sleep(Number(response?.headers.get("retry-after")) || 2 ** attempt);  }} async function api(method, route, { headers, body } = {}) {  const auth = { Authorization: `Bearer ${key}` };  const response = await request(base + route, { method, headers: { ...auth, ...headers }, body });  return response.json();} const bytes = await readFile(path);const file = await api("POST", "/files", {  headers: { "Content-Type": "application/json", "Idempotency-Key": randomUUID() },  body: JSON.stringify({    filename: basename(path),    content_type: "application/pdf",    bytes: bytes.length,  }),}); if (file.status === "pending") {  const { url, method, headers } = file.upload;  await request(url, { method, headers, body: bytes });  let ready = false;  for (let attempt = 0; attempt < 15 && !ready; attempt++) {    ready = (await api("GET", `/files/${file.id}`)).status === "uploaded";    if (!ready) await sleep(2);  }  if (!ready) throw new Error("The upload was not confirmed.");}console.log(`${file.id} uploaded`); let analysis = await api("POST", "/analyses", {  headers: { "Content-Type": "application/json", "Idempotency-Key": randomUUID() },  body: JSON.stringify({ model: "fvdoc-3", file: file.id }),});const deadline = Date.now() + 15 * 60 * 1000;while (analysis.status === "processing") {  if (Date.now() > deadline) {    throw new Error(`Still processing. Fetch it later with GET /analyses/${analysis.id}`);  }  console.log(`${analysis.id} processing: ${analysis.progress?.stage ?? "starting"}`);  await sleep(5);  analysis = await api("GET", `/analyses/${analysis.id}`);} if (analysis.status !== "succeeded") {  throw new Error(`The check ${analysis.status}: ${analysis.error?.message}`);}const { level, action, reason, pages, images, text, findings } = analysis.summary;console.log(`${analysis.id} ${analysis.status}`);console.log(`level ${level}, action ${action}, reason ${reason ?? "none"}`);console.log(`${pages} pages, ${images} photos examined, text: ${text ?? "not evaluated"}`);console.log(`findings: ${findings.join(", ") || "none"}`);console.log(`Report: ${analysis.report_url}`); if (analysis.livemode) {  const pdf = await request(`${base}/analyses/${analysis.id}/report.pdf`, {    headers: { Authorization: `Bearer ${key}` },  });  const name = `Fileverity-${analysis.report_id}.pdf`;  await writeFile(name, Buffer.from(await pdf.arrayBuffer()));  console.log(`Saved ${name}`);}
file_Pw3mY6zB8Hk2pQ4vN9sL1xR7 uploadedan_Zc4vB7nP1sK8tL3wR6yQm2Xa processing: queuedan_Zc4vB7nP1sK8tL3wR6yQm2Xa processing: analyzingan_Zc4vB7nP1sK8tL3wR6yQm2Xa processing: reportingan_Zc4vB7nP1sK8tL3wR6yQm2Xa succeededlevel L2, action accept, reason clean12 pages, 3 photos examined, text: humanfindings: noneReport: https://fileverity.com/verify/FV-3PXH-6WQ2-8NDCSaved Fileverity-FV-3PXH-6WQ2-8NDC.pdf
  • El PUT va directamente al almacenamiento. No lleva encabezado Authorization, y los encabezados de upload.headers se envían exactamente como se devuelven. Consulte Subida prefirmada.
  • Con una clave de prueba el archivo se devuelve como uploaded y no hay nada que enviar, así que el programa omite la subida. El análisis devuelve un ejemplo fijo de inmediato y no hay un PDF de ejemplo, por lo que el programa guarda el informe solo con una clave Live.
  • Descargue el PDF en cuanto termine la comprobación. Las imágenes de páginas y los extractos de texto de un documento se conservan 3 horas, y un PDF descargado más tarde los omite. Consulte Informe en PDF.
  • Una comprobación que termina con failed o canceled no se cobró. El programa se detiene con el mensaje de error del análisis.

Detectar texto escrito con IA

La detección de texto requiere una sola llamada síncrona. El programa lee un archivo de texto, lo envía a POST /text/detections e imprime el veredicto, la puntuación, la proporción de texto escrito con IA y cada pasaje escrito con IA con sus posiciones. La detección de texto escrito con IA admite inglés, francés, alemán, español y chino (en, fr, de, es, zh). El idioma se detecta automáticamente; pase un código como segundo argumento para fijarlo. El texto debe tener al menos 100 palabras y como máximo 100 000 caracteres.

Guárdelo como detect-text.mjs o detect_text.py y ejecute node detect-text.mjs essay.txt o python detect_text.py essay.txt fr. El Inicio rápido incluye un comando que escribe un essay.txt de ejemplo. Con una clave de prueba la respuesta es un resultado fijo para un texto escrito por una persona, por lo que el programa no imprime ningún pasaje.

import { randomUUID } from "node:crypto";import { readFile } from "node:fs/promises"; const base = "https://api.fileverity.com/v1";const key = process.env.FILEVERITY_API_KEY;const [, , path, language] = process.argv;if (!key || !path) {  throw new Error("Usage: FILEVERITY_API_KEY=... node detect-text.mjs essay.txt [en|fr|de|es|zh]");} const retryable = ["rate_limited", "idempotency_in_progress"];const sleep = (seconds) => new Promise((resolve) => setTimeout(resolve, seconds * 1000)); async function request(url, init) {  for (let attempt = 1; ; attempt++) {    let response = null;    let problem;    try {      response = await fetch(url, init);      if (response.ok) {        const remaining = response.headers.get("x-ratelimit-remaining-requests");        const reset = Number(response.headers.get("x-ratelimit-reset-requests"));        if (remaining === "0") await sleep(reset);        return response;      }      problem = (await response.json().catch(() => ({}))).error ?? {};    } catch (error) {      problem = { code: "network_error", message: error.message };    }    const status = response?.status ?? 0;    const retry = status === 0 || status >= 500 || retryable.includes(problem.code);    if (!retry || attempt === 5) {      const { code = status, message = "Request failed", request_id: id = "unknown" } = problem;      throw new Error(`${code}: ${message} (request ${id})`);    }    await sleep(Number(response?.headers.get("retry-after")) || 2 ** attempt);  }} async function api(method, route, { headers, body } = {}) {  const auth = { Authorization: `Bearer ${key}` };  const response = await request(base + route, { method, headers: { ...auth, ...headers }, body });  return response.json();} const text = await readFile(path, "utf8");const detection = await api("POST", "/text/detections", {  headers: { "Content-Type": "application/json", "Idempotency-Key": randomUUID() },  body: JSON.stringify({ text, language }),}); const { verdict, score, ai_fraction: share, usage, segments } = detection;console.log(`verdict: ${verdict} (score ${score.toFixed(2)})`);console.log(`AI-written share: ${Math.round(share * 100)}% of ${usage.characters} characters`);console.log(`language: ${detection.language}, billed units: ${usage.billed_units}`);const points = Array.from(text);for (const { start, end, score: passage } of segments) {  const excerpt = points.slice(start, Math.min(end, start + 80)).join("");  console.log(`${start}-${end} score ${passage.toFixed(2)}: ${excerpt}`);}
verdict: mixed (score 0.97)AI-written share: 50% of 889 characterslanguage: en, billed units: 1267-495 score 0.97: In today's fast-paced logistics landscape, it is essential for businesses to pri590-809 score 0.93: Moving forward, it is crucial to implement robust quality assurance measures and

start y end cuentan puntos de código Unicode, no bytes ni unidades UTF-16. Las cadenas de Python ya se indexan por punto de código; el programa de Node.js divide antes el texto con Array.from. Consulte Posiciones de los segmentos.

La detección de texto devuelve una estimación estadística, no una prueba de autoría. Úsela como un elemento más de una revisión humana y no se base solo en ella para tomar decisiones que afecten a una persona.

Recibir webhooks y verificar firmas

Un webhook avisa a su servidor cuando termina una comprobación, para que no tenga que consultar la API periódicamente. Este receptor es un servidor HTTP mínimo: el módulo http de Node.js y Flask para Python. Verifica la firma de cada envío, ignora un envío que ya ha gestionado e imprime el evento. Sigue lo descrito en la página Webhooks.

  1. Haga que el puerto 3000 sea accesible en una dirección HTTPS pública, por ejemplo con un túnel hacia su máquina. Las URL de los endpoints deben usar HTTPS en el puerto predeterminado 443 y apuntar a una dirección pública.
  2. En la Consola, abra Webhooks, añada un endpoint con la URL https://your-host/webhooks/fileverity y elija sus eventos. Copie el secreto de firma, que empieza por whsec_. Se muestra una sola vez.
  3. Guarde el programa como webhook-receiver.mjs o webhook_receiver.py e inícielo con el secreto: FILEVERITY_WEBHOOK_SECRET=whsec_... node webhook-receiver.mjs o FILEVERITY_WEBHOOK_SECRET=whsec_... python webhook_receiver.py.
  4. Elija Enviar prueba en el endpoint. Las claves de prueba nunca envían eventos; Enviar prueba envía en su lugar un evento de ejemplo firmado.
import { createHmac, timingSafeEqual } from "node:crypto";import { createServer } from "node:http"; const secret = process.env.FILEVERITY_WEBHOOK_SECRET;if (!secret) throw new Error("Set FILEVERITY_WEBHOOK_SECRET to the endpoint's signing secret.");const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");const port = Number(process.env.PORT) || 3000;const seen = new Set(); function verify(headers, body) {  const id = headers["webhook-id"];  const timestamp = headers["webhook-timestamp"];  const signatures = headers["webhook-signature"];  if (!id || !timestamp || !signatures) return false;  if (!(Math.abs(Date.now() / 1000 - Number(timestamp)) <= 300)) return false;  const expected = createHmac("sha256", key).update(`${id}.${timestamp}.`).update(body).digest();  return signatures.split(" ").some((entry) => {    const [version, value = ""] = entry.split(",");    const given = Buffer.from(value, "base64");    return version === "v1" && given.length === expected.length && timingSafeEqual(given, expected);  });} function handleEvent(event) {  const { type, data } = event;  if (data.test) {    console.log(`Test event ${event.id}`);  } else if (type === "analysis.completed") {    const { level, action } = data.summary;    console.log(`${data.id} succeeded: level ${level}, action ${action}, ${data.report_url}`);  } else if (type === "analysis.failed") {    console.log(`${data.id} ${data.status}: ${data.error.code}`);  } else if (type === "balance.low") {    const money = (cents) => `US$${(cents / 100).toFixed(2)}`;    console.log(`Balance ${money(data.balance)} is below your ${money(data.threshold)} alert`);  }} async function receive(req, res) {  if (req.method !== "POST" || req.url !== "/webhooks/fileverity") return res.writeHead(404).end();  const chunks = [];  let size = 0;  for await (const chunk of req) {    size += chunk.length;    if (size > 1_000_000) return res.writeHead(413).end();    chunks.push(chunk);  }  const body = Buffer.concat(chunks);  if (!verify(req.headers, body)) return res.writeHead(400).end();  const event = JSON.parse(body.toString("utf8"));  try {    if (!seen.has(event.id)) {      handleEvent(event);      seen.add(event.id);    }  } catch (error) {    console.error(`Could not handle ${event.id}`, error);    return res.writeHead(500).end();  }  res.writeHead(200).end();} createServer((req, res) => receive(req, res).catch(() => res.destroy())).listen(port, () => console.log(`Listening on port ${port}`));
Listening on port 3000Test event evt_Qm4vB7nP1sK8tL3wR6yZ2cXaan_8Hk2pQ4vN9sL1xR7cW3mY6zB succeeded: level L2, action accept, https://fileverity.com/verify/FV-7K2M-9QXD-4T8R
  • Cuerpo sin procesar. La firma cubre los bytes exactos que se enviaron. Los programas leen el cuerpo como bytes, calculan un HMAC-SHA256 sobre webhook-id, un punto, webhook-timestamp, un punto y esos bytes, con el secreto decodificado desde base64 como clave, y analizan el JSON solo cuando la firma es válida.
  • Comparación en tiempo constante. timingSafeEqual en Node.js y hmac.compare_digest en Python comparan el resultado con cada firma v1 del encabezado webhook-signature.
  • Tolerancia de la marca de tiempo. Se rechaza un webhook-timestamp que difiera en más de cinco minutos del reloj, o que no sea un número.
  • Respuestas rápidas. Un envío válido recibe 200 en cuanto se gestiona el evento. Una firma incorrecta recibe 400. Si la gestión falla, el programa responde 500 y Fileverity reintenta más tarde con el mismo ID de evento. Consulte Envío y reintentos.
  • Duplicados. Los eventos se envían al menos una vez y un reintento conserva el ID del evento. Los programas recuerdan los ID que ya han gestionado y responden 200 sin gestionar un evento dos veces. En producción, sustituya el conjunto en memoria por una restricción de unicidad sobre el ID del evento en su base de datos y ponga el trabajo en una cola para que la respuesta siga siendo rápida.
  • Datos del evento. analysis.completed y analysis.failed incluyen el análisis sin result; llame a GET /analyses/{id} cuando necesite el informe completo. balance y threshold en balance.low están en centavos de dólar estadounidense.

Siguientes pasos