# Archivos

> Suba imágenes y documentos a Fileverity: multipart para archivos de hasta 4 MB y subida prefirmada para archivos de hasta 50 MB. Objeto de archivo, límites y conservación.

Fuente: https://fileverity.com/es/docs/files

Las imágenes y los documentos se comprueban en dos pasos. Primero suba el archivo con `POST /files` y reciba un ID de archivo. Después cree un [análisis](https://fileverity.com/es/docs/analyses.md) que haga referencia a ese ID. El texto no necesita archivo; use [Detecciones de texto](https://fileverity.com/es/docs/text-detections.md).

## Archivos admitidos

|  | Imágenes (fvimg-2) | Documentos (fvdoc-3) |
| --- | --- | --- |
| Formatos | `jpeg`, `png`, `webp` | `pdf`, `docx`, `doc`, `rtf`, `odt`, `pptx`, `txt` |
| Tamaño máximo | 10 MB | 50 MB |
| Otros límites | 40 megapíxeles | 100 páginas |

## Dos formas de subir un archivo

| Método | Solicitud | Tamaño | Estado del archivo en la respuesta |
| --- | --- | --- | --- |
| Multipart | `multipart/form-data` con el campo `file` | Hasta 4 MB | `uploaded` |
| URL prefirmada | JSON con `filename`, `content_type` y `bytes`, y luego un `PUT` de los bytes | Hasta 50 MB | `pending` hasta que llegan los bytes |

Las imágenes están limitadas al tamaño de imagen indicado arriba, con cualquiera de los dos métodos.

## Subida multipart

Envíe archivos de hasta 4 MB como `multipart/form-data` en el campo `file`. El formato se detecta a partir del contenido del archivo, no de su nombre ni del tipo declarado.

**Subida multipart: cURL**

```bash
curl https://api.fileverity.com/v1/files \
  -H "Authorization: Bearer $FILEVERITY_API_KEY" \
  -F file=@claim-photo.jpg
```

**Subida multipart: JavaScript**

```javascript
import { readFile } from "node:fs/promises";

const form = new FormData();
form.append("file", new Blob([await readFile("claim-photo.jpg")]), "claim-photo.jpg");
const response = await fetch("https://api.fileverity.com/v1/files", {
  method: "POST",
  headers: { Authorization: "Bearer " + process.env.FILEVERITY_API_KEY },
  body: form,
});
const file = await response.json();
```

**Subida multipart: Python**

```python
import os
import requests

response = requests.post(
    "https://api.fileverity.com/v1/files",
    headers={"Authorization": "Bearer " + os.environ["FILEVERITY_API_KEY"]},
    files={"file": open("claim-photo.jpg", "rb")},
)
file = response.json()
```

**201 Created**

```json
{
  "id": "file_8Hk2pQ4vN9sL1xR7cW3mY6zB",
  "object": "file",
  "filename": "claim-photo.jpg",
  "bytes": 2481920,
  "content_type": "image/jpeg",
  "status": "uploaded",
  "livemode": true,
  "created": 1793700000,
  "expires_at": 1793786400
}
```

## Subida prefirmada

Para archivos de más de 4 MB y hasta 50 MB, solicite una URL de subida y envíe los bytes directamente al almacenamiento.

1. `POST /files` con un cuerpo JSON: `filename` (opcional), `content_type` y el tamaño exacto en `bytes`. La respuesta tiene `status: pending` y un objeto `upload`.
2. Envíe los bytes del archivo con un `PUT` a `upload.url`, con los encabezados de `upload.headers`, antes de `upload.expires_at`. Esta solicitud no necesita el encabezado `Authorization`.
3. Si lo desea, consulte `GET /files/{id}` periódicamente hasta que `status` sea `uploaded`.
4. Cree el análisis con el ID del archivo.

**Subida prefirmada: cURL**

```bash
FILE=$(curl -s https://api.fileverity.com/v1/files \
  -H "Authorization: Bearer $FILEVERITY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"filename": "contract.pdf", "content_type": "application/pdf", "bytes": 18874368}')

UPLOAD_URL=$(echo "$FILE" | jq -r .upload.url)
FILE_ID=$(echo "$FILE" | jq -r .id)

curl -X PUT "$UPLOAD_URL" \
  -H "content-type: application/pdf" \
  --data-binary @contract.pdf
```

**Subida prefirmada: JavaScript**

```javascript
import { readFile } from "node:fs/promises";

const bytes = await readFile("contract.pdf");
const response = await fetch("https://api.fileverity.com/v1/files", {
  method: "POST",
  headers: {
    Authorization: "Bearer " + process.env.FILEVERITY_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ filename: "contract.pdf", content_type: "application/pdf", bytes: bytes.length }),
});
const file = await response.json();

await fetch(file.upload.url, { method: "PUT", headers: file.upload.headers, body: bytes });
```

**Subida prefirmada: Python**

```python
import os
import requests

data = open("contract.pdf", "rb").read()
response = requests.post(
    "https://api.fileverity.com/v1/files",
    headers={"Authorization": "Bearer " + os.environ["FILEVERITY_API_KEY"]},
    json={"filename": "contract.pdf", "content_type": "application/pdf", "bytes": len(data)},
)
file = response.json()

requests.put(file["upload"]["url"], headers=file["upload"]["headers"], data=data)
```

**201 Created**

```json
{
  "id": "file_Pw3mY6zB8Hk2pQ4vN9sL1xR7",
  "object": "file",
  "filename": "contract.pdf",
  "bytes": 18874368,
  "content_type": "application/pdf",
  "status": "pending",
  "livemode": true,
  "created": 1793700000,
  "expires_at": 1793786400,
  "upload": {
    "url": "https://example.private.blob.vercel-storage.com/f/0d6f1c2e-7b1a-4c53-9a0e-5e8a41b3c7d2?signature=...",
    "method": "PUT",
    "headers": {
      "content-type": "application/pdf"
    },
    "expires_at": 1793703600
  }
}
```

La URL que se muestra está abreviada. Use tal cual la de su respuesta. Envíe el encabezado `content-type` exactamente como se devuelve en `upload.headers`.

### Tipos de contenido admitidos

| Tipo | Tipos de contenido |
| --- | --- |
| Imágenes | `image/jpeg`, `image/png`, `image/webp` |
| Documentos | `application/pdf`, `application/vnd.openxmlformats-officedocument.wordprocessingml.document`, `application/msword`, `application/rtf`, `application/vnd.oasis.opendocument.text`, `application/vnd.openxmlformats-officedocument.presentationml.presentation`, `text/plain` |

## El objeto de archivo

| Campo | Tipo | Descripción |
| --- | --- | --- |
| `id` | cadena | ID del archivo, con el prefijo `file_`. |
| `object` | cadena | Siempre `file`. |
| `filename` | cadena o null | Nombre de archivo saneado. |
| `bytes` | entero o null | Tamaño en bytes. |
| `content_type` | cadena | Tipo de contenido detectado o declarado. |
| `status` | cadena | `pending` hasta que llegan los bytes subidos y después `uploaded`. |
| `livemode` | booleano | `false` para los archivos creados con una clave de prueba. Los archivos están separados por modo. |
| `created` | entero | Marca de tiempo Unix. |
| `expires_at` | entero | Marca de tiempo Unix a partir de la cual se elimina el archivo. |
| `upload` | objeto | Solo en la respuesta a una solicitud de subida JSON: `url`, `method` (`PUT`), `headers` y `expires_at`. |

## Conservación

- Un archivo se conserva como máximo 24 horas (consulte `expires_at`) y después se elimina.
- Un archivo se elimina en cuanto se inicia un análisis a partir de él. Cada archivo subido se usa en un solo análisis; vuelva a subirlo para comprobarlo de nuevo.
- `DELETE /files/{id}` elimina un archivo y sus bytes almacenados antes de tiempo.
- Un espacio de trabajo puede tener hasta 50 archivos sin caducar y 200 MB por modo a la vez. Por encima de ese límite, las subidas fallan con `pending_files_limit` (HTTP 429, sin `retry-after`) hasta que se usen, se eliminen o caduquen archivos.
- Un ID de archivo desconocido, caducado o ya usado devuelve `file_not_found` al crear un análisis y `not_found` al recuperarlo o eliminarlo. También se devuelve `file_not_found` cuando la subida a `upload.url` aún no ha terminado, así que espere hasta que `GET /files/{id}` muestre `uploaded`.

## Errores

| Código | Estado | Causa |
| --- | --- | --- |
| `missing_parameter` | 400 | Falta el campo `file` en el cuerpo multipart. |
| `unsupported_media_type` | 415 | El cuerpo no es `multipart/form-data` ni JSON. |
| `unsupported_file` | 415 | El formato no está admitido. |
| `invalid_file` | 422 | El archivo está vacío, dañado o no se puede leer. |
| `file_too_large` | 413 | Un archivo multipart de más de 4 MB, o `bytes` por encima de 50 MB. |
| `invalid_parameter` | 400 | Falta un campo JSON, tiene un tipo incorrecto o es desconocido. |
| `invalid_json` | 400 | El cuerpo JSON no es un objeto. |
| `pending_files_limit` | 429 | Demasiadas subidas sin caducar en el espacio de trabajo. No se envía con un encabezado `retry-after`. |
| `idempotency_mismatch`, `idempotency_in_progress` | 409 | La `Idempotency-Key` se usó con una solicitud distinta, o su primera solicitud sigue en curso. |

> **Nota.** Las claves de prueba validan las subidas, pero no almacenan los bytes. Una solicitud de subida JSON con una clave de prueba devuelve un objeto de subida de prueba (`upload.test` es `true` y el archivo ya está `uploaded`): no hay nada que subir con PUT.

